1 %include {
2 /*
3 ** Zero-Clause BSD license:
4 **
5 ** Copyright (C) 2020-09-01 by D. Richard Hipp <drh@sqlite.org>
6 **
7 ** Permission to use, copy, modify, and/or distribute this software for
8 ** any purpose with or without fee is hereby granted.
9 **
10 ****************************************************************************
11 **
12 ** This software translates a PIC-inspired diagram language into SVG.
13 **
14 ** PIKCHR (pronounced like "picture") is *mostly* backwards compatible
15 ** with legacy PIC, though some features of legacy PIC are removed
16 ** (for example, the "sh" command is removed for security) and
17 ** many enhancements are added.
18 **
19 ** PIKCHR is designed for use in an internet facing web environment.
20 ** In particular, PIKCHR is designed to safely generate benign SVG from
21 ** source text that provided by a hostile agent.
22 **
23 ** This code was originally written by D. Richard Hipp using documentation
24 ** from prior PIC implementations but without reference to prior code.
25 ** All of the code in this project is original.
26 **
27 ** This file implements a C-language subroutine that accepts a string
28 ** of PIKCHR language text and generates a second string of SVG output that
29 ** renders the drawing defined by the input.  Space to hold the returned
30 ** string is obtained from malloc() and should be freed by the caller.
31 ** NULL might be returned if there is a memory allocation error.
32 **
33 ** If there are errors in the PIKCHR input, the output will consist of an
34 ** error message and the original PIKCHR input text (inside of <pre>...</pre>).
35 **
36 ** The subroutine implemented by this file is intended to be stand-alone.
37 ** It uses no external routines other than routines commonly found in
38 ** the standard C library.
39 **
40 ****************************************************************************
41 ** COMPILING:
42 **
43 ** The original source text is a mixture of C99 and "Lemon"
44 ** (See https://sqlite.org/src/file/doc/lemon.html).  Lemon is an LALR(1)
45 ** parser generator program, similar to Yacc.  The grammar of the
46 ** input language is specified in Lemon.  C-code is attached.  Lemon
47 ** runs to generate a single output file ("pikchr.c") which is then
48 ** compiled to generate the Pikchr library.  This header comment is
49 ** preserved in the Lemon output, so you might be reading this in either
50 ** the generated "pikchr.c" file that is output by Lemon, or in the
51 ** "pikchr.y" source file that is input into Lemon.  If you make changes,
52 ** you should change the input source file "pikchr.y", not the
53 ** Lemon-generated output file.
54 **
55 ** Basic compilation steps:
56 **
57 **      lemon pikchr.y
58 **      cc pikchr.c -o pikchr.o
59 **
60 ** Add -DPIKCHR_SHELL to add a main() routine that reads input files
61 ** and sends them through Pikchr, for testing.  Add -DPIKCHR_FUZZ for
62 ** -fsanitizer=fuzzer testing.
63 **
64 ****************************************************************************
65 ** IMPLEMENTATION NOTES (for people who want to understand the internal
66 ** operation of this software, perhaps to extend the code or to fix bugs):
67 **
68 ** Each call to pikchr() uses a single instance of the Pik structure to
69 ** track its internal state.  The Pik structure lives for the duration
70 ** of the pikchr() call.
71 **
72 ** The input is a sequence of objects or "statements".  Each statement is
73 ** parsed into a PObj object.  These are stored on an extensible array
74 ** called PList.  All parameters to each PObj are computed as the
75 ** object is parsed.  (Hence, the parameters to a PObj may only refer
76 ** to prior statements.) Once the PObj is completely assembled, it is
77 ** added to the end of a PList and never changes thereafter - except,
78 ** PObj objects that are part of a "[...]" block might have their
79 ** absolute position shifted when the outer [...] block is positioned.
80 ** But apart from this repositioning, PObj objects are unchanged once
81 ** they are added to the list. The order of statements on a PList does
82 ** not change.
83 **
84 ** After all input has been parsed, the top-level PList is walked to
85 ** generate output.  Sub-lists resulting from [...] blocks are scanned
86 ** as they are encountered.  All input must be collected and parsed ahead
87 ** of output generation because the size and position of statements must be
88 ** known in order to compute a bounding box on the output.
89 **
90 ** Each PObj is on a "layer".  (The common case is that all PObj's are
91 ** on a single layer, but multiple layers are possible.)  A separate pass
92 ** is made through the list for each layer.
93 **
94 ** After all output is generated, the Pik object and all the PList
95 ** and PObj objects are deallocated and the generated output string is
96 ** returned.  Upon any error, the Pik.nErr flag is set, processing quickly
97 ** stops, and the stack unwinds.  No attempt is made to continue reading
98 ** input after an error.
99 **
100 ** Most statements begin with a class name like "box" or "arrow" or "move".
101 ** There is a class named "text" which is used for statements that begin
102 ** with a string literal.  You can also specify the "text" class.
103 ** A Sublist ("[...]") is a single object that contains a pointer to
104 ** its substatements, all gathered onto a separate PList object.
105 **
106 ** Variables go into PVar objects that form a linked list.
107 **
108 ** Each PObj has zero or one names.  Input constructs that attempt
109 ** to assign a new name from an older name, for example:
110 **
111 **      Abc:  Abc + (0.5cm, 0)
112 **
113 ** Statements like these generate a new "noop" object at the specified
114 ** place and with the given name. As place-names are searched by scanning
115 ** the list in reverse order, this has the effect of overriding the "Abc"
116 ** name when referenced by subsequent objects.
117 */
118 #include <stdio.h>
119 #include <stdlib.h>
120 #include <string.h>
121 #include <ctype.h>
122 #include <math.h>
123 #include <assert.h>
124 #define count(X) (sizeof(X)/sizeof(X[0]))
125 #ifndef M_PI
126 # define M_PI 3.1415926535897932385
127 #endif
128 
129 /* Tag intentionally unused parameters with this macro to prevent
130 ** compiler warnings with -Wextra */
131 #define UNUSED_PARAMETER(X)  (void)(X)
132 
133 typedef struct Pik Pik;          /* Complete parsing context */
134 typedef struct PToken PToken;    /* A single token */
135 typedef struct PObj PObj;        /* A single diagram object */
136 typedef struct PList PList;      /* A list of diagram objects */
137 typedef struct PClass PClass;    /* Description of statements types */
138 typedef double PNum;             /* Numeric value */
139 typedef struct PRel PRel;        /* Absolute or percentage value */
140 typedef struct PPoint PPoint;    /* A position in 2-D space */
141 typedef struct PVar PVar;        /* script-defined variable */
142 typedef struct PBox PBox;        /* A bounding box */
143 typedef struct PMacro PMacro;    /* A "define" macro */
144 
145 /* Compass points */
146 #define CP_N      1
147 #define CP_NE     2
148 #define CP_E      3
149 #define CP_SE     4
150 #define CP_S      5
151 #define CP_SW     6
152 #define CP_W      7
153 #define CP_NW     8
154 #define CP_C      9   /* .center or .c */
155 #define CP_END   10   /* .end */
156 #define CP_START 11   /* .start */
157 
158 /* Heading angles corresponding to compass points */
159 static const PNum pik_hdg_angle[] = {
160 /* none  */   0.0,
161   /* N  */    0.0,
162   /* NE */   45.0,
163   /* E  */   90.0,
164   /* SE */  135.0,
165   /* S  */  180.0,
166   /* SW */  225.0,
167   /* W  */  270.0,
168   /* NW */  315.0,
169   /* C  */    0.0,
170 };
171 
172 /* Built-in functions */
173 #define FN_ABS    0
174 #define FN_COS    1
175 #define FN_INT    2
176 #define FN_MAX    3
177 #define FN_MIN    4
178 #define FN_SIN    5
179 #define FN_SQRT   6
180 
181 /* Text position and style flags.  Stored in PToken.eCode so limited
182 ** to 15 bits. */
183 #define TP_LJUST   0x0001  /* left justify......          */
184 #define TP_RJUST   0x0002  /*            ...Right justify */
185 #define TP_JMASK   0x0003  /* Mask for justification bits */
186 #define TP_ABOVE2  0x0004  /* Position text way above PObj.ptAt */
187 #define TP_ABOVE   0x0008  /* Position text above PObj.ptAt */
188 #define TP_CENTER  0x0010  /* On the line */
189 #define TP_BELOW   0x0020  /* Position text below PObj.ptAt */
190 #define TP_BELOW2  0x0040  /* Position text way below PObj.ptAt */
191 #define TP_VMASK   0x007c  /* Mask for text positioning flags */
192 #define TP_BIG     0x0100  /* Larger font */
193 #define TP_SMALL   0x0200  /* Smaller font */
194 #define TP_XTRA    0x0400  /* Amplify TP_BIG or TP_SMALL */
195 #define TP_SZMASK  0x0700  /* Font size mask */
196 #define TP_ITALIC  0x1000  /* Italic font */
197 #define TP_BOLD    0x2000  /* Bold font */
198 #define TP_FMASK   0x3000  /* Mask for font style */
199 #define TP_ALIGN   0x4000  /* Rotate to align with the line */
200 
201 /* An object to hold a position in 2-D space */
202 struct PPoint {
203   PNum x, y;             /* X and Y coordinates */
204 };
205 static const PPoint cZeroPoint = {0.0,0.0};
206 
207 /* A bounding box */
208 struct PBox {
209   PPoint sw, ne;         /* Lower-left and top-right corners */
210 };
211 
212 /* An Absolute or a relative distance.  The absolute distance
213 ** is stored in rAbs and the relative distance is stored in rRel.
214 ** Usually, one or the other will be 0.0.  When using a PRel to
215 ** update an existing value, the computation is usually something
216 ** like this:
217 **
218 **          value = PRel.rAbs + value*PRel.rRel
219 **
220 */
221 struct PRel {
222   PNum rAbs;            /* Absolute value */
223   PNum rRel;            /* Value relative to current value */
224 };
225 
226 /* A variable created by the ID = EXPR construct of the PIKCHR script
227 **
228 ** PIKCHR (and PIC) scripts do not use many varaibles, so it is reasonable
229 ** to store them all on a linked list.
230 */
231 struct PVar {
232   const char *zName;       /* Name of the variable */
233   PNum val;                /* Value of the variable */
234   PVar *pNext;             /* Next variable in a list of them all */
235 };
236 
237 /* A single token in the parser input stream
238 */
239 struct PToken {
240   const char *z;             /* Pointer to the token text */
241   unsigned int n;            /* Length of the token in bytes */
242   short int eCode;           /* Auxiliary code */
243   unsigned char eType;       /* The numeric parser code */
244   unsigned char eEdge;       /* Corner value for corner keywords */
245 };
246 
247 /* Return negative, zero, or positive if pToken is less than, equal to
248 ** or greater than the zero-terminated string z[]
249 */
pik_token_eq(PToken * pToken,const char * z)250 static int pik_token_eq(PToken *pToken, const char *z){
251   int c = strncmp(pToken->z,z,pToken->n);
252   if( c==0 && z[pToken->n]!=0 ) c = -1;
253   return c;
254 }
255 
256 /* Extra token types not generated by LEMON but needed by the
257 ** tokenizer
258 */
259 #define T_PARAMETER  253     /* $1, $2, ..., $9 */
260 #define T_WHITESPACE 254     /* Whitespace of comments */
261 #define T_ERROR      255     /* Any text that is not a valid token */
262 
263 /* Directions of movement */
264 #define DIR_RIGHT     0
265 #define DIR_DOWN      1
266 #define DIR_LEFT      2
267 #define DIR_UP        3
268 #define ValidDir(X)     ((X)>=0 && (X)<=3)
269 #define IsUpDown(X)     (((X)&1)==1)
270 #define IsLeftRight(X)  (((X)&1)==0)
271 
272 /* Bitmask for the various attributes for PObj.  These bits are
273 ** collected in PObj.mProp and PObj.mCalc to check for constraint
274 ** errors. */
275 #define A_WIDTH         0x0001
276 #define A_HEIGHT        0x0002
277 #define A_RADIUS        0x0004
278 #define A_THICKNESS     0x0008
279 #define A_DASHED        0x0010 /* Includes "dotted" */
280 #define A_FILL          0x0020
281 #define A_COLOR         0x0040
282 #define A_ARROW         0x0080
283 #define A_FROM          0x0100
284 #define A_CW            0x0200
285 #define A_AT            0x0400
286 #define A_TO            0x0800 /* one or more movement attributes */
287 #define A_FIT           0x1000
288 
289 
290 /* A single graphics object */
291 struct PObj {
292   const PClass *type;      /* Object type or class */
293   PToken errTok;           /* Reference token for error messages */
294   PPoint ptAt;             /* Reference point for the object */
295   PPoint ptEnter, ptExit;  /* Entry and exit points */
296   PList *pSublist;         /* Substructure for [...] objects */
297   char *zName;             /* Name assigned to this statement */
298   PNum w;                  /* "width" property */
299   PNum h;                  /* "height" property */
300   PNum rad;                /* "radius" property */
301   PNum sw;                 /* "thickness" property. (Mnemonic: "stroke width")*/
302   PNum dotted;             /* "dotted" property.   <=0.0 for off */
303   PNum dashed;             /* "dashed" property.   <=0.0 for off */
304   PNum fill;               /* "fill" property.  Negative for off */
305   PNum color;              /* "color" property */
306   PPoint with;             /* Position constraint from WITH clause */
307   char eWith;              /* Type of heading point on WITH clause */
308   char cw;                 /* True for clockwise arc */
309   char larrow;             /* Arrow at beginning (<- or <->) */
310   char rarrow;             /* Arrow at end  (-> or <->) */
311   char bClose;             /* True if "close" is seen */
312   char bChop;              /* True if "chop" is seen */
313   unsigned char nTxt;      /* Number of text values */
314   unsigned mProp;          /* Masks of properties set so far */
315   unsigned mCalc;          /* Values computed from other constraints */
316   PToken aTxt[5];          /* Text with .eCode holding TP flags */
317   int iLayer;              /* Rendering order */
318   int inDir, outDir;       /* Entry and exit directions */
319   int nPath;               /* Number of path points */
320   PPoint *aPath;           /* Array of path points */
321   PBox bbox;               /* Bounding box */
322 };
323 
324 /* A list of graphics objects */
325 struct PList {
326   int n;          /* Number of statements in the list */
327   int nAlloc;     /* Allocated slots in a[] */
328   PObj **a;       /* Pointers to individual objects */
329 };
330 
331 /* A macro definition */
332 struct PMacro {
333   PMacro *pNext;       /* Next in the list */
334   PToken macroName;    /* Name of the macro */
335   PToken macroBody;    /* Body of the macro */
336   int inUse;           /* Do not allow recursion */
337 };
338 
339 /* Each call to the pikchr() subroutine uses an instance of the following
340 ** object to pass around context to all of its subroutines.
341 */
342 struct Pik {
343   unsigned nErr;           /* Number of errors seen */
344   PToken sIn;              /* Input Pikchr-language text */
345   char *zOut;              /* Result accumulates here */
346   unsigned int nOut;       /* Bytes written to zOut[] so far */
347   unsigned int nOutAlloc;  /* Space allocated to zOut[] */
348   unsigned char eDir;      /* Current direction */
349   unsigned int mFlags;     /* Flags passed to pikchr() */
350   PObj *cur;               /* Object under construction */
351   PList *list;             /* Object list under construction */
352   PMacro *pMacros;         /* List of all defined macros */
353   PVar *pVar;              /* Application-defined variables */
354   PBox bbox;               /* Bounding box around all statements */
355                            /* Cache of layout values.  <=0.0 for unknown... */
356   PNum rScale;                 /* Multiply to convert inches to pixels */
357   PNum fontScale;              /* Scale fonts by this percent */
358   PNum charWidth;              /* Character width */
359   PNum charHeight;             /* Character height */
360   PNum wArrow;                 /* Width of arrowhead at the fat end */
361   PNum hArrow;                 /* Ht of arrowhead - dist from tip to fat end */
362   char bLayoutVars;            /* True if cache is valid */
363   char thenFlag;           /* True if "then" seen */
364   char samePath;           /* aTPath copied by "same" */
365   const char *zClass;      /* Class name for the <svg> */
366   int wSVG, hSVG;          /* Width and height of the <svg> */
367   int fgcolor;             /* foreground color value, or -1 for none */
368   int bgcolor;             /* background color value, or -1 for none */
369   /* Paths for lines are constructed here first, then transferred into
370   ** the PObj object at the end: */
371   int nTPath;              /* Number of entries on aTPath[] */
372   int mTPath;              /* For last entry, 1: x set,  2: y set */
373   PPoint aTPath[1000];     /* Path under construction */
374   /* Error contexts */
375   unsigned int nCtx;       /* Number of error contexts */
376   PToken aCtx[10];         /* Nested error contexts */
377 };
378 
379 /* Include PIKCHR_PLAINTEXT_ERRORS among the bits of mFlags on the 3rd
380 ** argument to pikchr() in order to cause error message text to come out
381 ** as text/plain instead of as text/html
382 */
383 #define PIKCHR_PLAINTEXT_ERRORS 0x0001
384 
385 /* Include PIKCHR_DARK_MODE among the mFlag bits to invert colors.
386 */
387 #define PIKCHR_DARK_MODE        0x0002
388 
389 /*
390 ** The behavior of an object class is defined by an instance of
391 ** this structure. This is the "virtual method" table.
392 */
393 struct PClass {
394   const char *zName;                     /* Name of class */
395   char isLine;                           /* True if a line class */
396   char eJust;                            /* Use box-style text justification */
397   void (*xInit)(Pik*,PObj*);              /* Initializer */
398   void (*xNumProp)(Pik*,PObj*,PToken*);   /* Value change notification */
399   void (*xCheck)(Pik*,PObj*);             /* Checks to do after parsing */
400   PPoint (*xChop)(Pik*,PObj*,PPoint*);    /* Chopper */
401   PPoint (*xOffset)(Pik*,PObj*,int);      /* Offset from .c to edge point */
402   void (*xFit)(Pik*,PObj*,PNum w,PNum h); /* Size to fit text */
403   void (*xRender)(Pik*,PObj*);            /* Render */
404 };
405 
406 
407 /* Forward declarations */
408 static void pik_append(Pik*, const char*,int);
409 static void pik_append_text(Pik*,const char*,int,int);
410 static void pik_append_num(Pik*,const char*,PNum);
411 static void pik_append_point(Pik*,const char*,PPoint*);
412 static void pik_append_x(Pik*,const char*,PNum,const char*);
413 static void pik_append_y(Pik*,const char*,PNum,const char*);
414 static void pik_append_xy(Pik*,const char*,PNum,PNum);
415 static void pik_append_dis(Pik*,const char*,PNum,const char*);
416 static void pik_append_arc(Pik*,PNum,PNum,PNum,PNum);
417 static void pik_append_clr(Pik*,const char*,PNum,const char*,int);
418 static void pik_append_style(Pik*,PObj*,int);
419 static void pik_append_txt(Pik*,PObj*, PBox*);
420 static void pik_draw_arrowhead(Pik*,PPoint*pFrom,PPoint*pTo,PObj*);
421 static void pik_chop(PPoint*pFrom,PPoint*pTo,PNum);
422 static void pik_error(Pik*,PToken*,const char*);
423 static void pik_elist_free(Pik*,PList*);
424 static void pik_elem_free(Pik*,PObj*);
425 static void pik_render(Pik*,PList*);
426 static PList *pik_elist_append(Pik*,PList*,PObj*);
427 static PObj *pik_elem_new(Pik*,PToken*,PToken*,PList*);
428 static void pik_set_direction(Pik*,int);
429 static void pik_elem_setname(Pik*,PObj*,PToken*);
430 static void pik_set_var(Pik*,PToken*,PNum,PToken*);
431 static PNum pik_value(Pik*,const char*,int,int*);
432 static PNum pik_lookup_color(Pik*,PToken*);
433 static PNum pik_get_var(Pik*,PToken*);
434 static PNum pik_atof(PToken*);
435 static void pik_after_adding_attributes(Pik*,PObj*);
436 static void pik_elem_move(PObj*,PNum dx, PNum dy);
437 static void pik_elist_move(PList*,PNum dx, PNum dy);
438 static void pik_set_numprop(Pik*,PToken*,PRel*);
439 static void pik_set_clrprop(Pik*,PToken*,PNum);
440 static void pik_set_dashed(Pik*,PToken*,PNum*);
441 static void pik_then(Pik*,PToken*,PObj*);
442 static void pik_add_direction(Pik*,PToken*,PRel*);
443 static void pik_move_hdg(Pik*,PRel*,PToken*,PNum,PToken*,PToken*);
444 static void pik_evenwith(Pik*,PToken*,PPoint*);
445 static void pik_set_from(Pik*,PObj*,PToken*,PPoint*);
446 static void pik_add_to(Pik*,PObj*,PToken*,PPoint*);
447 static void pik_close_path(Pik*,PToken*);
448 static void pik_set_at(Pik*,PToken*,PPoint*,PToken*);
449 static short int pik_nth_value(Pik*,PToken*);
450 static PObj *pik_find_nth(Pik*,PObj*,PToken*);
451 static PObj *pik_find_byname(Pik*,PObj*,PToken*);
452 static PPoint pik_place_of_elem(Pik*,PObj*,PToken*);
453 static int pik_bbox_isempty(PBox*);
454 static void pik_bbox_init(PBox*);
455 static void pik_bbox_addbox(PBox*,PBox*);
456 static void pik_bbox_add_xy(PBox*,PNum,PNum);
457 static void pik_bbox_addellipse(PBox*,PNum x,PNum y,PNum rx,PNum ry);
458 static void pik_add_txt(Pik*,PToken*,int);
459 static int pik_text_length(const PToken *pToken);
460 static void pik_size_to_fit(Pik*,PToken*,int);
461 static int pik_text_position(int,PToken*);
462 static PNum pik_property_of(PObj*,PToken*);
463 static PNum pik_func(Pik*,PToken*,PNum,PNum);
464 static PPoint pik_position_between(PNum x, PPoint p1, PPoint p2);
465 static PPoint pik_position_at_angle(PNum dist, PNum r, PPoint pt);
466 static PPoint pik_position_at_hdg(PNum dist, PToken *pD, PPoint pt);
467 static void pik_same(Pik *p, PObj*, PToken*);
468 static PPoint pik_nth_vertex(Pik *p, PToken *pNth, PToken *pErr, PObj *pObj);
469 static PToken pik_next_semantic_token(PToken *pThis);
470 static void pik_compute_layout_settings(Pik*);
471 static void pik_behind(Pik*,PObj*);
472 static PObj *pik_assert(Pik*,PNum,PToken*,PNum);
473 static PObj *pik_position_assert(Pik*,PPoint*,PToken*,PPoint*);
474 static PNum pik_dist(PPoint*,PPoint*);
475 static void pik_add_macro(Pik*,PToken *pId,PToken *pCode);
476 
477 
478 } // end %include
479 
480 %name pik_parser
481 %token_prefix T_
482 %token_type {PToken}
483 %extra_context {Pik *p}
484 
485 %fallback ID EDGEPT.
486 
487 // precedence rules.
488 %left OF.
489 %left PLUS MINUS.
490 %left STAR SLASH PERCENT.
491 %right UMINUS.
492 
493 %type statement_list {PList*}
494 %destructor statement_list {pik_elist_free(p,$$);}
495 %type statement {PObj*}
496 %destructor statement {pik_elem_free(p,$$);}
497 %type unnamed_statement {PObj*}
498 %destructor unnamed_statement {pik_elem_free(p,$$);}
499 %type basetype {PObj*}
500 %destructor basetype {pik_elem_free(p,$$);}
501 %type expr {PNum}
502 %type numproperty {PToken}
503 %type edge {PToken}
504 %type direction {PToken}
505 %type dashproperty {PToken}
506 %type colorproperty {PToken}
507 %type locproperty {PToken}
508 %type position {PPoint}
509 %type place {PPoint}
510 %type object {PObj*}
511 %type objectname {PObj*}
512 %type nth {PToken}
513 %type textposition {short int}
514 %type rvalue {PNum}
515 %type lvalue {PToken}
516 %type even {PToken}
517 %type relexpr {PRel}
518 %type optrelexpr {PRel}
519 
520 %syntax_error {
521   if( TOKEN.z && TOKEN.z[0] ){
522     pik_error(p, &TOKEN, "syntax error");
523   }else{
524     pik_error(p, 0, "syntax error");
525   }
526   UNUSED_PARAMETER(yymajor);
527 }
528 %stack_overflow {
529   pik_error(p, 0, "parser stack overflow");
530 }
531 
statement_list(X)532 document ::= statement_list(X).  {pik_render(p,X);}
533 
534 
statement_list(A)535 statement_list(A) ::= statement(X).   { A = pik_elist_append(p,0,X); }
statement_list(A)536 statement_list(A) ::= statement_list(B) EOL statement(X).
537                       { A = pik_elist_append(p,B,X); }
538 
539 
statement(A)540 statement(A) ::= .   { A = 0; }
statement(A)541 statement(A) ::= direction(D).  { pik_set_direction(p,D.eCode);  A=0; }
statement(A)542 statement(A) ::= lvalue(N) ASSIGN(OP) rvalue(X). {pik_set_var(p,&N,X,&OP); A=0;}
statement(A)543 statement(A) ::= PLACENAME(N) COLON unnamed_statement(X).
544                { A = X;  pik_elem_setname(p,X,&N); }
statement(A)545 statement(A) ::= PLACENAME(N) COLON position(P).
546                { A = pik_elem_new(p,0,0,0);
547                  if(A){ A->ptAt = P; pik_elem_setname(p,A,&N); }}
statement(A)548 statement(A) ::= unnamed_statement(X).  {A = X;}
statement(A)549 statement(A) ::= print prlist.  {pik_append(p,"<br>\n",5); A=0;}
550 
551 // assert() statements are undocumented and are intended for testing and
552 // debugging use only.  If the equality comparison of the assert() fails
553 // then an error message is generated.
statement(A)554 statement(A) ::= ASSERT LP expr(X) EQ(OP) expr(Y) RP. {A=pik_assert(p,X,&OP,Y);}
statement(A)555 statement(A) ::= ASSERT LP position(X) EQ(OP) position(Y) RP.
556                                           {A=pik_position_assert(p,&X,&OP,&Y);}
statement(A)557 statement(A) ::= DEFINE ID(ID) CODEBLOCK(C).  {A=0; pik_add_macro(p,&ID,&C);}
558 
lvalue(A)559 lvalue(A) ::= ID(A).
560 lvalue(A) ::= FILL(A).
561 lvalue(A) ::= COLOR(A).
562 lvalue(A) ::= THICKNESS(A).
563 
564 // PLACENAME might actually be a color name (ex: DarkBlue).  But we
565 // cannot make it part of expr due to parsing ambiguities.  The
566 // rvalue non-terminal means "general expression or a colorname"
567 rvalue(A) ::= expr(A).
568 rvalue(A) ::= PLACENAME(C).  {A = pik_lookup_color(p,&C);}
569 
570 print ::= PRINT.
571 prlist ::= pritem.
572 prlist ::= prlist prsep pritem.
FILL(X)573 pritem ::= FILL(X).        {pik_append_num(p,"",pik_value(p,X.z,X.n,0));}
COLOR(X)574 pritem ::= COLOR(X).       {pik_append_num(p,"",pik_value(p,X.z,X.n,0));}
THICKNESS(X)575 pritem ::= THICKNESS(X).   {pik_append_num(p,"",pik_value(p,X.z,X.n,0));}
rvalue(X)576 pritem ::= rvalue(X).      {pik_append_num(p,"",X);}
STRING(S)577 pritem ::= STRING(S). {pik_append_text(p,S.z+1,S.n-2,0);}
578 prsep  ::= COMMA. {pik_append(p, " ", 1);}
579 
unnamed_statement(A)580 unnamed_statement(A) ::= basetype(X) attribute_list.
581                           {A = X; pik_after_adding_attributes(p,A);}
582 
basetype(A)583 basetype(A) ::= CLASSNAME(N).            {A = pik_elem_new(p,&N,0,0); }
basetype(A)584 basetype(A) ::= STRING(N) textposition(P).
585                             {N.eCode = P; A = pik_elem_new(p,0,&N,0); }
basetype(A)586 basetype(A) ::= LB savelist(L) statement_list(X) RB(E).
587       { p->list = L; A = pik_elem_new(p,0,0,X); if(A) A->errTok = E; }
588 
589 %type savelist {PList*}
590 // No destructor required as this same PList is also held by
591 // an "statement" non-terminal deeper on the stack.
savelist(A)592 savelist(A) ::= .   {A = p->list; p->list = 0;}
593 
direction(A)594 direction(A) ::= UP(A).
595 direction(A) ::= DOWN(A).
596 direction(A) ::= LEFT(A).
597 direction(A) ::= RIGHT(A).
598 
599 relexpr(A) ::= expr(B).             {A.rAbs = B; A.rRel = 0;}
relexpr(A)600 relexpr(A) ::= expr(B) PERCENT.     {A.rAbs = 0; A.rRel = B/100;}
optrelexpr(A)601 optrelexpr(A) ::= relexpr(A).
602 optrelexpr(A) ::= .                 {A.rAbs = 0; A.rRel = 1.0;}
603 
relexpr(X)604 attribute_list ::= relexpr(X) alist.    {pik_add_direction(p,0,&X);}
605 attribute_list ::= alist.
606 alist ::=.
607 alist ::= alist attribute.
numproperty(P)608 attribute ::= numproperty(P) relexpr(X).     { pik_set_numprop(p,&P,&X); }
dashproperty(P)609 attribute ::= dashproperty(P) expr(X).       { pik_set_dashed(p,&P,&X); }
dashproperty(P)610 attribute ::= dashproperty(P).               { pik_set_dashed(p,&P,0);  }
colorproperty(P)611 attribute ::= colorproperty(P) rvalue(X).    { pik_set_clrprop(p,&P,X); }
direction(D)612 attribute ::= go direction(D) optrelexpr(X). { pik_add_direction(p,&D,&X);}
direction(D)613 attribute ::= go direction(D) even position(P). {pik_evenwith(p,&D,&P);}
CLOSE(E)614 attribute ::= CLOSE(E).             { pik_close_path(p,&E); }
615 attribute ::= CHOP.                 { p->cur->bChop = 1; }
FROM(T)616 attribute ::= FROM(T) position(X).  { pik_set_from(p,p->cur,&T,&X); }
TO(T)617 attribute ::= TO(T) position(X).    { pik_add_to(p,p->cur,&T,&X); }
THEN(T)618 attribute ::= THEN(T).              { pik_then(p, &T, p->cur); }
THEN(E)619 attribute ::= THEN(E) optrelexpr(D) HEADING(H) expr(A).
620                                                 {pik_move_hdg(p,&D,&H,A,0,&E);}
THEN(E)621 attribute ::= THEN(E) optrelexpr(D) EDGEPT(C).  {pik_move_hdg(p,&D,0,0,&C,&E);}
GO(E)622 attribute ::= GO(E) optrelexpr(D) HEADING(H) expr(A).
623                                                 {pik_move_hdg(p,&D,&H,A,0,&E);}
GO(E)624 attribute ::= GO(E) optrelexpr(D) EDGEPT(C).    {pik_move_hdg(p,&D,0,0,&C,&E);}
625 attribute ::= boolproperty.
AT(A)626 attribute ::= AT(A) position(P).                    { pik_set_at(p,0,&P,&A); }
627 attribute ::= WITH withclause.
SAME(E)628 attribute ::= SAME(E).                          {pik_same(p,0,&E);}
SAME(E)629 attribute ::= SAME(E) AS object(X).             {pik_same(p,X,&E);}
STRING(T)630 attribute ::= STRING(T) textposition(P).        {pik_add_txt(p,&T,P);}
FIT(E)631 attribute ::= FIT(E).                           {pik_size_to_fit(p,&E,3); }
object(X)632 attribute ::= BEHIND object(X).                 {pik_behind(p,X);}
633 
634 go ::= GO.
635 go ::= .
636 
637 even ::= UNTIL EVEN WITH.
638 even ::= EVEN WITH.
639 
edge(E)640 withclause ::=  DOT_E edge(E) AT(A) position(P).{ pik_set_at(p,&E,&P,&A); }
edge(E)641 withclause ::=  edge(E) AT(A) position(P).      { pik_set_at(p,&E,&P,&A); }
642 
643 // Properties that require an argument
644 numproperty(A) ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS(P).  {A = P;}
645 
646 // Properties with optional arguments
dashproperty(A)647 dashproperty(A) ::= DOTTED(A).
648 dashproperty(A) ::= DASHED(A).
649 
650 // Color properties
651 colorproperty(A) ::= FILL(A).
652 colorproperty(A) ::= COLOR(A).
653 
654 // Properties with no argument
655 boolproperty ::= CW.          {p->cur->cw = 1;}
656 boolproperty ::= CCW.         {p->cur->cw = 0;}
657 boolproperty ::= LARROW.      {p->cur->larrow=1; p->cur->rarrow=0; }
658 boolproperty ::= RARROW.      {p->cur->larrow=0; p->cur->rarrow=1; }
659 boolproperty ::= LRARROW.     {p->cur->larrow=1; p->cur->rarrow=1; }
660 boolproperty ::= INVIS.       {p->cur->sw = 0.0;}
661 boolproperty ::= THICK.       {p->cur->sw *= 1.5;}
662 boolproperty ::= THIN.        {p->cur->sw *= 0.67;}
663 boolproperty ::= SOLID.       {p->cur->sw = pik_value(p,"thickness",9,0);
664                                p->cur->dotted = p->cur->dashed = 0.0;}
665 
textposition(A)666 textposition(A) ::= .   {A = 0;}
667 textposition(A) ::= textposition(B)
668    CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL(F).
669                         {A = (short int)pik_text_position(B,&F);}
670 
671 
position(A)672 position(A) ::= expr(X) COMMA expr(Y).                {A.x=X; A.y=Y;}
position(A)673 position(A) ::= place(A).
674 position(A) ::= place(B) PLUS expr(X) COMMA expr(Y).  {A.x=B.x+X; A.y=B.y+Y;}
position(A)675 position(A) ::= place(B) MINUS expr(X) COMMA expr(Y). {A.x=B.x-X; A.y=B.y-Y;}
position(A)676 position(A) ::= place(B) PLUS LP expr(X) COMMA expr(Y) RP.
677                                                       {A.x=B.x+X; A.y=B.y+Y;}
position(A)678 position(A) ::= place(B) MINUS LP expr(X) COMMA expr(Y) RP.
679                                                       {A.x=B.x-X; A.y=B.y-Y;}
position(A)680 position(A) ::= LP position(X) COMMA position(Y) RP.  {A.x=X.x; A.y=Y.y;}
position(A)681 position(A) ::= LP position(X) RP.                    {A=X;}
position(A)682 position(A) ::= expr(X) between position(P1) AND position(P2).
683                                        {A = pik_position_between(X,P1,P2);}
position(A)684 position(A) ::= expr(X) LT position(P1) COMMA position(P2) GT.
685                                        {A = pik_position_between(X,P1,P2);}
position(A)686 position(A) ::= expr(X) ABOVE position(B).    {A=B; A.y += X;}
position(A)687 position(A) ::= expr(X) BELOW position(B).    {A=B; A.y -= X;}
position(A)688 position(A) ::= expr(X) LEFT OF position(B).  {A=B; A.x -= X;}
position(A)689 position(A) ::= expr(X) RIGHT OF position(B). {A=B; A.x += X;}
position(A)690 position(A) ::= expr(D) ON HEADING EDGEPT(E) OF position(P).
691                                         {A = pik_position_at_hdg(D,&E,P);}
position(A)692 position(A) ::= expr(D) HEADING EDGEPT(E) OF position(P).
693                                         {A = pik_position_at_hdg(D,&E,P);}
position(A)694 position(A) ::= expr(D) EDGEPT(E) OF position(P).
695                                         {A = pik_position_at_hdg(D,&E,P);}
position(A)696 position(A) ::= expr(D) ON HEADING expr(G) FROM position(P).
697                                         {A = pik_position_at_angle(D,G,P);}
position(A)698 position(A) ::= expr(D) HEADING expr(G) FROM position(P).
699                                         {A = pik_position_at_angle(D,G,P);}
700 
701 between ::= WAY BETWEEN.
702 between ::= BETWEEN.
703 between ::= OF THE WAY BETWEEN.
704 
705 // place2 is the same as place, but excludes the forms like
706 // "RIGHT of object" to avoid a parsing ambiguity with "place .x"
707 // and "place .y" expressions
708 %type place2 {PPoint}
709 
place(A)710 place(A) ::= place2(A).
711 place(A) ::= edge(X) OF object(O).           {A = pik_place_of_elem(p,O,&X);}
place2(A)712 place2(A) ::= object(O).                     {A = pik_place_of_elem(p,O,0);}
place2(A)713 place2(A) ::= object(O) DOT_E edge(X).       {A = pik_place_of_elem(p,O,&X);}
place2(A)714 place2(A) ::= NTH(N) VERTEX(E) OF object(X). {A = pik_nth_vertex(p,&N,&E,X);}
715 
edge(A)716 edge(A) ::= CENTER(A).
717 edge(A) ::= EDGEPT(A).
718 edge(A) ::= TOP(A).
719 edge(A) ::= BOTTOM(A).
720 edge(A) ::= START(A).
721 edge(A) ::= END(A).
722 edge(A) ::= RIGHT(A).
723 edge(A) ::= LEFT(A).
724 
725 object(A) ::= objectname(A).
726 object(A) ::= nth(N).                     {A = pik_find_nth(p,0,&N);}
727 object(A) ::= nth(N) OF|IN object(B).     {A = pik_find_nth(p,B,&N);}
728 
objectname(A)729 objectname(A) ::= THIS.                   {A = p->cur;}
objectname(A)730 objectname(A) ::= PLACENAME(N).           {A = pik_find_byname(p,0,&N);}
objectname(A)731 objectname(A) ::= objectname(B) DOT_U PLACENAME(N).
732                                           {A = pik_find_byname(p,B,&N);}
733 
nth(A)734 nth(A) ::= NTH(N) CLASSNAME(ID).      {A=ID; A.eCode = pik_nth_value(p,&N); }
nth(A)735 nth(A) ::= NTH(N) LAST CLASSNAME(ID). {A=ID; A.eCode = -pik_nth_value(p,&N); }
nth(A)736 nth(A) ::= LAST CLASSNAME(ID).        {A=ID; A.eCode = -1;}
nth(A)737 nth(A) ::= LAST(ID).                  {A=ID; A.eCode = -1;}
nth(A)738 nth(A) ::= NTH(N) LB(ID) RB.          {A=ID; A.eCode = pik_nth_value(p,&N);}
nth(A)739 nth(A) ::= NTH(N) LAST LB(ID) RB.     {A=ID; A.eCode = -pik_nth_value(p,&N);}
nth(A)740 nth(A) ::= LAST LB(ID) RB.            {A=ID; A.eCode = -1; }
741 
expr(A)742 expr(A) ::= expr(X) PLUS expr(Y).                 {A=X+Y;}
expr(A)743 expr(A) ::= expr(X) MINUS expr(Y).                {A=X-Y;}
expr(A)744 expr(A) ::= expr(X) STAR expr(Y).                 {A=X*Y;}
expr(A)745 expr(A) ::= expr(X) SLASH(E) expr(Y).             {
746   if( Y==0.0 ){ pik_error(p, &E, "division by zero"); A = 0.0; }
747   else{ A = X/Y; }
748 }
expr(A)749 expr(A) ::= MINUS expr(X). [UMINUS]               {A=-X;}
expr(A)750 expr(A) ::= PLUS expr(X). [UMINUS]                {A=X;}
expr(A)751 expr(A) ::= LP expr(X) RP.                        {A=X;}
752 expr(A) ::= LP FILL|COLOR|THICKNESS(X) RP.        {A=pik_get_var(p,&X);}
expr(A)753 expr(A) ::= NUMBER(N).                            {A=pik_atof(&N);}
expr(A)754 expr(A) ::= ID(N).                                {A=pik_get_var(p,&N);}
expr(A)755 expr(A) ::= FUNC1(F) LP expr(X) RP.               {A = pik_func(p,&F,X,0.0);}
expr(A)756 expr(A) ::= FUNC2(F) LP expr(X) COMMA expr(Y) RP. {A = pik_func(p,&F,X,Y);}
expr(A)757 expr(A) ::= DIST LP position(X) COMMA position(Y) RP. {A = pik_dist(&X,&Y);}
expr(A)758 expr(A) ::= place2(B) DOT_XY X.                   {A = B.x;}
expr(A)759 expr(A) ::= place2(B) DOT_XY Y.                   {A = B.y;}
expr(A)760 expr(A) ::= object(B) DOT_L numproperty(P).       {A=pik_property_of(B,&P);}
expr(A)761 expr(A) ::= object(B) DOT_L dashproperty(P).      {A=pik_property_of(B,&P);}
expr(A)762 expr(A) ::= object(B) DOT_L colorproperty(P).     {A=pik_property_of(B,&P);}
763 
764 
765 %code {
766 
767 
768 /* Chart of the 148 official CSS color names with their
769 ** corresponding RGB values thru Color Module Level 4:
770 ** https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
771 **
772 ** Two new names "None" and "Off" are added with a value
773 ** of -1.
774 */
775 static const struct {
776   const char *zName;  /* Name of the color */
777   int val;            /* RGB value */
778 } aColor[] = {
779   { "AliceBlue",                   0xf0f8ff },
780   { "AntiqueWhite",                0xfaebd7 },
781   { "Aqua",                        0x00ffff },
782   { "Aquamarine",                  0x7fffd4 },
783   { "Azure",                       0xf0ffff },
784   { "Beige",                       0xf5f5dc },
785   { "Bisque",                      0xffe4c4 },
786   { "Black",                       0x000000 },
787   { "BlanchedAlmond",              0xffebcd },
788   { "Blue",                        0x0000ff },
789   { "BlueViolet",                  0x8a2be2 },
790   { "Brown",                       0xa52a2a },
791   { "BurlyWood",                   0xdeb887 },
792   { "CadetBlue",                   0x5f9ea0 },
793   { "Chartreuse",                  0x7fff00 },
794   { "Chocolate",                   0xd2691e },
795   { "Coral",                       0xff7f50 },
796   { "CornflowerBlue",              0x6495ed },
797   { "Cornsilk",                    0xfff8dc },
798   { "Crimson",                     0xdc143c },
799   { "Cyan",                        0x00ffff },
800   { "DarkBlue",                    0x00008b },
801   { "DarkCyan",                    0x008b8b },
802   { "DarkGoldenrod",               0xb8860b },
803   { "DarkGray",                    0xa9a9a9 },
804   { "DarkGreen",                   0x006400 },
805   { "DarkGrey",                    0xa9a9a9 },
806   { "DarkKhaki",                   0xbdb76b },
807   { "DarkMagenta",                 0x8b008b },
808   { "DarkOliveGreen",              0x556b2f },
809   { "DarkOrange",                  0xff8c00 },
810   { "DarkOrchid",                  0x9932cc },
811   { "DarkRed",                     0x8b0000 },
812   { "DarkSalmon",                  0xe9967a },
813   { "DarkSeaGreen",                0x8fbc8f },
814   { "DarkSlateBlue",               0x483d8b },
815   { "DarkSlateGray",               0x2f4f4f },
816   { "DarkSlateGrey",               0x2f4f4f },
817   { "DarkTurquoise",               0x00ced1 },
818   { "DarkViolet",                  0x9400d3 },
819   { "DeepPink",                    0xff1493 },
820   { "DeepSkyBlue",                 0x00bfff },
821   { "DimGray",                     0x696969 },
822   { "DimGrey",                     0x696969 },
823   { "DodgerBlue",                  0x1e90ff },
824   { "Firebrick",                   0xb22222 },
825   { "FloralWhite",                 0xfffaf0 },
826   { "ForestGreen",                 0x228b22 },
827   { "Fuchsia",                     0xff00ff },
828   { "Gainsboro",                   0xdcdcdc },
829   { "GhostWhite",                  0xf8f8ff },
830   { "Gold",                        0xffd700 },
831   { "Goldenrod",                   0xdaa520 },
832   { "Gray",                        0x808080 },
833   { "Green",                       0x008000 },
834   { "GreenYellow",                 0xadff2f },
835   { "Grey",                        0x808080 },
836   { "Honeydew",                    0xf0fff0 },
837   { "HotPink",                     0xff69b4 },
838   { "IndianRed",                   0xcd5c5c },
839   { "Indigo",                      0x4b0082 },
840   { "Ivory",                       0xfffff0 },
841   { "Khaki",                       0xf0e68c },
842   { "Lavender",                    0xe6e6fa },
843   { "LavenderBlush",               0xfff0f5 },
844   { "LawnGreen",                   0x7cfc00 },
845   { "LemonChiffon",                0xfffacd },
846   { "LightBlue",                   0xadd8e6 },
847   { "LightCoral",                  0xf08080 },
848   { "LightCyan",                   0xe0ffff },
849   { "LightGoldenrodYellow",        0xfafad2 },
850   { "LightGray",                   0xd3d3d3 },
851   { "LightGreen",                  0x90ee90 },
852   { "LightGrey",                   0xd3d3d3 },
853   { "LightPink",                   0xffb6c1 },
854   { "LightSalmon",                 0xffa07a },
855   { "LightSeaGreen",               0x20b2aa },
856   { "LightSkyBlue",                0x87cefa },
857   { "LightSlateGray",              0x778899 },
858   { "LightSlateGrey",              0x778899 },
859   { "LightSteelBlue",              0xb0c4de },
860   { "LightYellow",                 0xffffe0 },
861   { "Lime",                        0x00ff00 },
862   { "LimeGreen",                   0x32cd32 },
863   { "Linen",                       0xfaf0e6 },
864   { "Magenta",                     0xff00ff },
865   { "Maroon",                      0x800000 },
866   { "MediumAquamarine",            0x66cdaa },
867   { "MediumBlue",                  0x0000cd },
868   { "MediumOrchid",                0xba55d3 },
869   { "MediumPurple",                0x9370db },
870   { "MediumSeaGreen",              0x3cb371 },
871   { "MediumSlateBlue",             0x7b68ee },
872   { "MediumSpringGreen",           0x00fa9a },
873   { "MediumTurquoise",             0x48d1cc },
874   { "MediumVioletRed",             0xc71585 },
875   { "MidnightBlue",                0x191970 },
876   { "MintCream",                   0xf5fffa },
877   { "MistyRose",                   0xffe4e1 },
878   { "Moccasin",                    0xffe4b5 },
879   { "NavajoWhite",                 0xffdead },
880   { "Navy",                        0x000080 },
881   { "None",                              -1 },  /* Non-standard addition */
882   { "Off",                               -1 },  /* Non-standard addition */
883   { "OldLace",                     0xfdf5e6 },
884   { "Olive",                       0x808000 },
885   { "OliveDrab",                   0x6b8e23 },
886   { "Orange",                      0xffa500 },
887   { "OrangeRed",                   0xff4500 },
888   { "Orchid",                      0xda70d6 },
889   { "PaleGoldenrod",               0xeee8aa },
890   { "PaleGreen",                   0x98fb98 },
891   { "PaleTurquoise",               0xafeeee },
892   { "PaleVioletRed",               0xdb7093 },
893   { "PapayaWhip",                  0xffefd5 },
894   { "PeachPuff",                   0xffdab9 },
895   { "Peru",                        0xcd853f },
896   { "Pink",                        0xffc0cb },
897   { "Plum",                        0xdda0dd },
898   { "PowderBlue",                  0xb0e0e6 },
899   { "Purple",                      0x800080 },
900   { "RebeccaPurple",               0x663399 },
901   { "Red",                         0xff0000 },
902   { "RosyBrown",                   0xbc8f8f },
903   { "RoyalBlue",                   0x4169e1 },
904   { "SaddleBrown",                 0x8b4513 },
905   { "Salmon",                      0xfa8072 },
906   { "SandyBrown",                  0xf4a460 },
907   { "SeaGreen",                    0x2e8b57 },
908   { "Seashell",                    0xfff5ee },
909   { "Sienna",                      0xa0522d },
910   { "Silver",                      0xc0c0c0 },
911   { "SkyBlue",                     0x87ceeb },
912   { "SlateBlue",                   0x6a5acd },
913   { "SlateGray",                   0x708090 },
914   { "SlateGrey",                   0x708090 },
915   { "Snow",                        0xfffafa },
916   { "SpringGreen",                 0x00ff7f },
917   { "SteelBlue",                   0x4682b4 },
918   { "Tan",                         0xd2b48c },
919   { "Teal",                        0x008080 },
920   { "Thistle",                     0xd8bfd8 },
921   { "Tomato",                      0xff6347 },
922   { "Turquoise",                   0x40e0d0 },
923   { "Violet",                      0xee82ee },
924   { "Wheat",                       0xf5deb3 },
925   { "White",                       0xffffff },
926   { "WhiteSmoke",                  0xf5f5f5 },
927   { "Yellow",                      0xffff00 },
928   { "YellowGreen",                 0x9acd32 },
929 };
930 
931 /* Built-in variable names.
932 **
933 ** This array is constant.  When a script changes the value of one of
934 ** these built-ins, a new PVar record is added at the head of
935 ** the Pik.pVar list, which is searched first.  Thus the new PVar entry
936 ** will override this default value.
937 **
938 ** Units are in inches, except for "color" and "fill" which are
939 ** interpreted as 24-bit RGB values.
940 **
941 ** Binary search used.  Must be kept in sorted order.
942 */
943 static const struct { const char *zName; PNum val; } aBuiltin[] = {
944   { "arcrad",      0.25  },
945   { "arrowhead",   2.0   },
946   { "arrowht",     0.08  },
947   { "arrowwid",    0.06  },
948   { "boxht",       0.5   },
949   { "boxrad",      0.0   },
950   { "boxwid",      0.75  },
951   { "charht",      0.14  },
952   { "charwid",     0.08  },
953   { "circlerad",   0.25  },
954   { "color",       0.0   },
955   { "cylht",       0.5   },
956   { "cylrad",      0.075 },
957   { "cylwid",      0.75  },
958   { "dashwid",     0.05  },
959   { "dotrad",      0.015 },
960   { "ellipseht",   0.5   },
961   { "ellipsewid",  0.75  },
962   { "fileht",      0.75  },
963   { "filerad",     0.15  },
964   { "filewid",     0.5   },
965   { "fill",        -1.0  },
966   { "lineht",      0.5   },
967   { "linewid",     0.5   },
968   { "movewid",     0.5   },
969   { "ovalht",      0.5   },
970   { "ovalwid",     1.0   },
971   { "scale",       1.0   },
972   { "textht",      0.5   },
973   { "textwid",     0.75  },
974   { "thickness",   0.015 },
975 };
976 
977 
978 /* Methods for the "arc" class */
arcInit(Pik * p,PObj * pObj)979 static void arcInit(Pik *p, PObj *pObj){
980   pObj->w = pik_value(p, "arcrad",6,0);
981   pObj->h = pObj->w;
982 }
983 /* Hack: Arcs are here rendered as quadratic Bezier curves rather
984 ** than true arcs.  Multiple reasons: (1) the legacy-PIC parameters
985 ** that control arcs are obscure and I could not figure out what they
986 ** mean based on available documentation.  (2) Arcs are rarely used,
987 ** and so do not seem that important.
988 */
arcControlPoint(int cw,PPoint f,PPoint t,PNum rScale)989 static PPoint arcControlPoint(int cw, PPoint f, PPoint t, PNum rScale){
990   PPoint m;
991   PNum dx, dy;
992   m.x = 0.5*(f.x+t.x);
993   m.y = 0.5*(f.y+t.y);
994   dx = t.x - f.x;
995   dy = t.y - f.y;
996   if( cw ){
997     m.x -= 0.5*rScale*dy;
998     m.y += 0.5*rScale*dx;
999   }else{
1000     m.x += 0.5*rScale*dy;
1001     m.y -= 0.5*rScale*dx;
1002   }
1003   return m;
1004 }
arcCheck(Pik * p,PObj * pObj)1005 static void arcCheck(Pik *p, PObj *pObj){
1006   PPoint m;
1007   if( p->nTPath>2 ){
1008     pik_error(p, &pObj->errTok, "arc geometry error");
1009     return;
1010   }
1011   m = arcControlPoint(pObj->cw, p->aTPath[0], p->aTPath[1], 0.5);
1012   pik_bbox_add_xy(&pObj->bbox, m.x, m.y);
1013 }
arcRender(Pik * p,PObj * pObj)1014 static void arcRender(Pik *p, PObj *pObj){
1015   PPoint f, m, t;
1016   if( pObj->nPath<2 ) return;
1017   if( pObj->sw<=0.0 ) return;
1018   f = pObj->aPath[0];
1019   t = pObj->aPath[1];
1020   m = arcControlPoint(pObj->cw,f,t,1.0);
1021   if( pObj->larrow ){
1022     pik_draw_arrowhead(p,&m,&f,pObj);
1023   }
1024   if( pObj->rarrow ){
1025     pik_draw_arrowhead(p,&m,&t,pObj);
1026   }
1027   pik_append_xy(p,"<path d=\"M", f.x, f.y);
1028   pik_append_xy(p,"Q", m.x, m.y);
1029   pik_append_xy(p," ", t.x, t.y);
1030   pik_append(p,"\" ",2);
1031   pik_append_style(p,pObj,0);
1032   pik_append(p,"\" />\n", -1);
1033 
1034   pik_append_txt(p, pObj, 0);
1035 }
1036 
1037 
1038 /* Methods for the "arrow" class */
arrowInit(Pik * p,PObj * pObj)1039 static void arrowInit(Pik *p, PObj *pObj){
1040   pObj->w = pik_value(p, "linewid",7,0);
1041   pObj->h = pik_value(p, "lineht",6,0);
1042   pObj->rad = pik_value(p, "linerad",7,0);
1043   pObj->rarrow = 1;
1044 }
1045 
1046 /* Methods for the "box" class */
boxInit(Pik * p,PObj * pObj)1047 static void boxInit(Pik *p, PObj *pObj){
1048   pObj->w = pik_value(p, "boxwid",6,0);
1049   pObj->h = pik_value(p, "boxht",5,0);
1050   pObj->rad = pik_value(p, "boxrad",6,0);
1051 }
1052 /* Return offset from the center of the box to the compass point
1053 ** given by parameter cp */
boxOffset(Pik * p,PObj * pObj,int cp)1054 static PPoint boxOffset(Pik *p, PObj *pObj, int cp){
1055   PPoint pt = cZeroPoint;
1056   PNum w2 = 0.5*pObj->w;
1057   PNum h2 = 0.5*pObj->h;
1058   PNum rad = pObj->rad;
1059   PNum rx;
1060   if( rad<=0.0 ){
1061     rx = 0.0;
1062   }else{
1063     if( rad>w2 ) rad = w2;
1064     if( rad>h2 ) rad = h2;
1065     rx = 0.29289321881345252392*rad;
1066   }
1067   switch( cp ){
1068     case CP_C:                                   break;
1069     case CP_N:   pt.x = 0.0;      pt.y = h2;     break;
1070     case CP_NE:  pt.x = w2-rx;    pt.y = h2-rx;  break;
1071     case CP_E:   pt.x = w2;       pt.y = 0.0;    break;
1072     case CP_SE:  pt.x = w2-rx;    pt.y = rx-h2;  break;
1073     case CP_S:   pt.x = 0.0;      pt.y = -h2;    break;
1074     case CP_SW:  pt.x = rx-w2;    pt.y = rx-h2;  break;
1075     case CP_W:   pt.x = -w2;      pt.y = 0.0;    break;
1076     case CP_NW:  pt.x = rx-w2;    pt.y = h2-rx;  break;
1077     default:     assert(0);
1078   }
1079   UNUSED_PARAMETER(p);
1080   return pt;
1081 }
boxChop(Pik * p,PObj * pObj,PPoint * pPt)1082 static PPoint boxChop(Pik *p, PObj *pObj, PPoint *pPt){
1083   PNum dx, dy;
1084   int cp = CP_C;
1085   PPoint chop = pObj->ptAt;
1086   if( pObj->w<=0.0 ) return chop;
1087   if( pObj->h<=0.0 ) return chop;
1088   dx = (pPt->x - pObj->ptAt.x)*pObj->h/pObj->w;
1089   dy = (pPt->y - pObj->ptAt.y);
1090   if( dx>0.0 ){
1091     if( dy>=2.414*dx ){
1092       cp = CP_N;
1093     }else if( dy>=0.414*dx ){
1094       cp = CP_NE;
1095     }else if( dy>=-0.414*dx ){
1096       cp = CP_E;
1097     }else if( dy>-2.414*dx ){
1098       cp = CP_SE;
1099     }else{
1100       cp = CP_S;
1101     }
1102   }else{
1103     if( dy>=-2.414*dx ){
1104       cp = CP_N;
1105     }else if( dy>=-0.414*dx ){
1106       cp = CP_NW;
1107     }else if( dy>=0.414*dx ){
1108       cp = CP_W;
1109     }else if( dy>2.414*dx ){
1110       cp = CP_SW;
1111     }else{
1112       cp = CP_S;
1113     }
1114   }
1115   chop = pObj->type->xOffset(p,pObj,cp);
1116   chop.x += pObj->ptAt.x;
1117   chop.y += pObj->ptAt.y;
1118   return chop;
1119 }
boxFit(Pik * p,PObj * pObj,PNum w,PNum h)1120 static void boxFit(Pik *p, PObj *pObj, PNum w, PNum h){
1121   if( w>0 ) pObj->w = w;
1122   if( h>0 ) pObj->h = h;
1123   UNUSED_PARAMETER(p);
1124 }
boxRender(Pik * p,PObj * pObj)1125 static void boxRender(Pik *p, PObj *pObj){
1126   PNum w2 = 0.5*pObj->w;
1127   PNum h2 = 0.5*pObj->h;
1128   PNum rad = pObj->rad;
1129   PPoint pt = pObj->ptAt;
1130   if( pObj->sw>0.0 ){
1131     if( rad<=0.0 ){
1132       pik_append_xy(p,"<path d=\"M", pt.x-w2,pt.y-h2);
1133       pik_append_xy(p,"L", pt.x+w2,pt.y-h2);
1134       pik_append_xy(p,"L", pt.x+w2,pt.y+h2);
1135       pik_append_xy(p,"L", pt.x-w2,pt.y+h2);
1136       pik_append(p,"Z\" ",-1);
1137     }else{
1138       /*
1139       **         ----       - y3
1140       **        /    \
1141       **       /      \     _ y2
1142       **      |        |
1143       **      |        |    _ y1
1144       **       \      /
1145       **        \    /
1146       **         ----       _ y0
1147       **
1148       **      '  '  '  '
1149       **     x0 x1 x2 x3
1150       */
1151       PNum x0,x1,x2,x3,y0,y1,y2,y3;
1152       if( rad>w2 ) rad = w2;
1153       if( rad>h2 ) rad = h2;
1154       x0 = pt.x - w2;
1155       x1 = x0 + rad;
1156       x3 = pt.x + w2;
1157       x2 = x3 - rad;
1158       y0 = pt.y - h2;
1159       y1 = y0 + rad;
1160       y3 = pt.y + h2;
1161       y2 = y3 - rad;
1162       pik_append_xy(p,"<path d=\"M", x1, y0);
1163       if( x2>x1 ) pik_append_xy(p, "L", x2, y0);
1164       pik_append_arc(p, rad, rad, x3, y1);
1165       if( y2>y1 ) pik_append_xy(p, "L", x3, y2);
1166       pik_append_arc(p, rad, rad, x2, y3);
1167       if( x2>x1 ) pik_append_xy(p, "L", x1, y3);
1168       pik_append_arc(p, rad, rad, x0, y2);
1169       if( y2>y1 ) pik_append_xy(p, "L", x0, y1);
1170       pik_append_arc(p, rad, rad, x1, y0);
1171       pik_append(p,"Z\" ",-1);
1172     }
1173     pik_append_style(p,pObj,3);
1174     pik_append(p,"\" />\n", -1);
1175   }
1176   pik_append_txt(p, pObj, 0);
1177 }
1178 
1179 /* Methods for the "circle" class */
circleInit(Pik * p,PObj * pObj)1180 static void circleInit(Pik *p, PObj *pObj){
1181   pObj->w = pik_value(p, "circlerad",9,0)*2;
1182   pObj->h = pObj->w;
1183   pObj->rad = 0.5*pObj->w;
1184 }
circleNumProp(Pik * p,PObj * pObj,PToken * pId)1185 static void circleNumProp(Pik *p, PObj *pObj, PToken *pId){
1186   /* For a circle, the width must equal the height and both must
1187   ** be twice the radius.  Enforce those constraints. */
1188   switch( pId->eType ){
1189     case T_RADIUS:
1190       pObj->w = pObj->h = 2.0*pObj->rad;
1191       break;
1192     case T_WIDTH:
1193       pObj->h = pObj->w;
1194       pObj->rad = 0.5*pObj->w;
1195       break;
1196     case T_HEIGHT:
1197       pObj->w = pObj->h;
1198       pObj->rad = 0.5*pObj->w;
1199       break;
1200   }
1201   UNUSED_PARAMETER(p);
1202 }
circleChop(Pik * p,PObj * pObj,PPoint * pPt)1203 static PPoint circleChop(Pik *p, PObj *pObj, PPoint *pPt){
1204   PPoint chop;
1205   PNum dx = pPt->x - pObj->ptAt.x;
1206   PNum dy = pPt->y - pObj->ptAt.y;
1207   PNum dist = hypot(dx,dy);
1208   if( dist<pObj->rad ) return pObj->ptAt;
1209   chop.x = pObj->ptAt.x + dx*pObj->rad/dist;
1210   chop.y = pObj->ptAt.y + dy*pObj->rad/dist;
1211   UNUSED_PARAMETER(p);
1212   return chop;
1213 }
circleFit(Pik * p,PObj * pObj,PNum w,PNum h)1214 static void circleFit(Pik *p, PObj *pObj, PNum w, PNum h){
1215   PNum mx = 0.0;
1216   if( w>0 ) mx = w;
1217   if( h>mx ) mx = h;
1218   if( w*h>0 && (w*w + h*h) > mx*mx ){
1219     mx = hypot(w,h);
1220   }
1221   if( mx>0.0 ){
1222     pObj->rad = 0.5*mx;
1223     pObj->w = pObj->h = mx;
1224   }
1225   UNUSED_PARAMETER(p);
1226 }
1227 
circleRender(Pik * p,PObj * pObj)1228 static void circleRender(Pik *p, PObj *pObj){
1229   PNum r = pObj->rad;
1230   PPoint pt = pObj->ptAt;
1231   if( pObj->sw>0.0 ){
1232     pik_append_x(p,"<circle cx=\"", pt.x, "\"");
1233     pik_append_y(p," cy=\"", pt.y, "\"");
1234     pik_append_dis(p," r=\"", r, "\" ");
1235     pik_append_style(p,pObj,3);
1236     pik_append(p,"\" />\n", -1);
1237   }
1238   pik_append_txt(p, pObj, 0);
1239 }
1240 
1241 /* Methods for the "cylinder" class */
cylinderInit(Pik * p,PObj * pObj)1242 static void cylinderInit(Pik *p, PObj *pObj){
1243   pObj->w = pik_value(p, "cylwid",6,0);
1244   pObj->h = pik_value(p, "cylht",5,0);
1245   pObj->rad = pik_value(p, "cylrad",6,0); /* Minor radius of ellipses */
1246 }
cylinderFit(Pik * p,PObj * pObj,PNum w,PNum h)1247 static void cylinderFit(Pik *p, PObj *pObj, PNum w, PNum h){
1248   if( w>0 ) pObj->w = w;
1249   if( h>0 ) pObj->h = h + 0.25*pObj->rad + pObj->sw;
1250   UNUSED_PARAMETER(p);
1251 }
cylinderRender(Pik * p,PObj * pObj)1252 static void cylinderRender(Pik *p, PObj *pObj){
1253   PNum w2 = 0.5*pObj->w;
1254   PNum h2 = 0.5*pObj->h;
1255   PNum rad = pObj->rad;
1256   PPoint pt = pObj->ptAt;
1257   if( pObj->sw>0.0 ){
1258     if( rad>h2 ){
1259       rad = h2;
1260     }else if( rad<0 ){
1261       rad = 0;
1262     }
1263     pik_append_xy(p,"<path d=\"M", pt.x-w2,pt.y+h2-rad);
1264     pik_append_xy(p,"L", pt.x-w2,pt.y-h2+rad);
1265     pik_append_arc(p,w2,rad,pt.x+w2,pt.y-h2+rad);
1266     pik_append_xy(p,"L", pt.x+w2,pt.y+h2-rad);
1267     pik_append_arc(p,w2,rad,pt.x-w2,pt.y+h2-rad);
1268     pik_append_arc(p,w2,rad,pt.x+w2,pt.y+h2-rad);
1269     pik_append(p,"\" ",-1);
1270     pik_append_style(p,pObj,3);
1271     pik_append(p,"\" />\n", -1);
1272   }
1273   pik_append_txt(p, pObj, 0);
1274 }
cylinderOffset(Pik * p,PObj * pObj,int cp)1275 static PPoint cylinderOffset(Pik *p, PObj *pObj, int cp){
1276   PPoint pt = cZeroPoint;
1277   PNum w2 = pObj->w*0.5;
1278   PNum h1 = pObj->h*0.5;
1279   PNum h2 = h1 - pObj->rad;
1280   switch( cp ){
1281     case CP_C:                                break;
1282     case CP_N:   pt.x = 0.0;   pt.y = h1;     break;
1283     case CP_NE:  pt.x = w2;    pt.y = h2;     break;
1284     case CP_E:   pt.x = w2;    pt.y = 0.0;    break;
1285     case CP_SE:  pt.x = w2;    pt.y = -h2;    break;
1286     case CP_S:   pt.x = 0.0;   pt.y = -h1;    break;
1287     case CP_SW:  pt.x = -w2;   pt.y = -h2;    break;
1288     case CP_W:   pt.x = -w2;   pt.y = 0.0;    break;
1289     case CP_NW:  pt.x = -w2;   pt.y = h2;     break;
1290     default:     assert(0);
1291   }
1292   UNUSED_PARAMETER(p);
1293   return pt;
1294 }
1295 
1296 /* Methods for the "dot" class */
dotInit(Pik * p,PObj * pObj)1297 static void dotInit(Pik *p, PObj *pObj){
1298   pObj->rad = pik_value(p, "dotrad",6,0);
1299   pObj->h = pObj->w = pObj->rad*6;
1300   pObj->fill = pObj->color;
1301 }
dotNumProp(Pik * p,PObj * pObj,PToken * pId)1302 static void dotNumProp(Pik *p, PObj *pObj, PToken *pId){
1303   switch( pId->eType ){
1304     case T_COLOR:
1305       pObj->fill = pObj->color;
1306       break;
1307     case T_FILL:
1308       pObj->color = pObj->fill;
1309       break;
1310   }
1311   UNUSED_PARAMETER(p);
1312 }
dotCheck(Pik * p,PObj * pObj)1313 static void dotCheck(Pik *p, PObj *pObj){
1314   pObj->w = pObj->h = 0;
1315   pik_bbox_addellipse(&pObj->bbox, pObj->ptAt.x, pObj->ptAt.y,
1316                        pObj->rad, pObj->rad);
1317   UNUSED_PARAMETER(p);
1318 }
dotOffset(Pik * p,PObj * pObj,int cp)1319 static PPoint dotOffset(Pik *p, PObj *pObj, int cp){
1320   UNUSED_PARAMETER(p);
1321   UNUSED_PARAMETER(pObj);
1322   UNUSED_PARAMETER(cp);
1323   return cZeroPoint;
1324 }
dotRender(Pik * p,PObj * pObj)1325 static void dotRender(Pik *p, PObj *pObj){
1326   PNum r = pObj->rad;
1327   PPoint pt = pObj->ptAt;
1328   if( pObj->sw>0.0 ){
1329     pik_append_x(p,"<circle cx=\"", pt.x, "\"");
1330     pik_append_y(p," cy=\"", pt.y, "\"");
1331     pik_append_dis(p," r=\"", r, "\"");
1332     pik_append_style(p,pObj,2);
1333     pik_append(p,"\" />\n", -1);
1334   }
1335   pik_append_txt(p, pObj, 0);
1336 }
1337 
1338 
1339 
1340 /* Methods for the "ellipse" class */
ellipseInit(Pik * p,PObj * pObj)1341 static void ellipseInit(Pik *p, PObj *pObj){
1342   pObj->w = pik_value(p, "ellipsewid",10,0);
1343   pObj->h = pik_value(p, "ellipseht",9,0);
1344 }
ellipseChop(Pik * p,PObj * pObj,PPoint * pPt)1345 static PPoint ellipseChop(Pik *p, PObj *pObj, PPoint *pPt){
1346   PPoint chop;
1347   PNum s, dq, dist;
1348   PNum dx = pPt->x - pObj->ptAt.x;
1349   PNum dy = pPt->y - pObj->ptAt.y;
1350   if( pObj->w<=0.0 ) return pObj->ptAt;
1351   if( pObj->h<=0.0 ) return pObj->ptAt;
1352   s = pObj->h/pObj->w;
1353   dq = dx*s;
1354   dist = hypot(dq,dy);
1355   if( dist<pObj->h ) return pObj->ptAt;
1356   chop.x = pObj->ptAt.x + 0.5*dq*pObj->h/(dist*s);
1357   chop.y = pObj->ptAt.y + 0.5*dy*pObj->h/dist;
1358   UNUSED_PARAMETER(p);
1359   return chop;
1360 }
ellipseOffset(Pik * p,PObj * pObj,int cp)1361 static PPoint ellipseOffset(Pik *p, PObj *pObj, int cp){
1362   PPoint pt = cZeroPoint;
1363   PNum w = pObj->w*0.5;
1364   PNum w2 = w*0.70710678118654747608;
1365   PNum h = pObj->h*0.5;
1366   PNum h2 = h*0.70710678118654747608;
1367   switch( cp ){
1368     case CP_C:                                break;
1369     case CP_N:   pt.x = 0.0;   pt.y = h;      break;
1370     case CP_NE:  pt.x = w2;    pt.y = h2;     break;
1371     case CP_E:   pt.x = w;     pt.y = 0.0;    break;
1372     case CP_SE:  pt.x = w2;    pt.y = -h2;    break;
1373     case CP_S:   pt.x = 0.0;   pt.y = -h;     break;
1374     case CP_SW:  pt.x = -w2;   pt.y = -h2;    break;
1375     case CP_W:   pt.x = -w;    pt.y = 0.0;    break;
1376     case CP_NW:  pt.x = -w2;   pt.y = h2;     break;
1377     default:     assert(0);
1378   }
1379   UNUSED_PARAMETER(p);
1380   return pt;
1381 }
ellipseRender(Pik * p,PObj * pObj)1382 static void ellipseRender(Pik *p, PObj *pObj){
1383   PNum w = pObj->w;
1384   PNum h = pObj->h;
1385   PPoint pt = pObj->ptAt;
1386   if( pObj->sw>0.0 ){
1387     pik_append_x(p,"<ellipse cx=\"", pt.x, "\"");
1388     pik_append_y(p," cy=\"", pt.y, "\"");
1389     pik_append_dis(p," rx=\"", w/2.0, "\"");
1390     pik_append_dis(p," ry=\"", h/2.0, "\" ");
1391     pik_append_style(p,pObj,3);
1392     pik_append(p,"\" />\n", -1);
1393   }
1394   pik_append_txt(p, pObj, 0);
1395 }
1396 
1397 /* Methods for the "file" object */
fileInit(Pik * p,PObj * pObj)1398 static void fileInit(Pik *p, PObj *pObj){
1399   pObj->w = pik_value(p, "filewid",7,0);
1400   pObj->h = pik_value(p, "fileht",6,0);
1401   pObj->rad = pik_value(p, "filerad",7,0);
1402 }
1403 /* Return offset from the center of the file to the compass point
1404 ** given by parameter cp */
fileOffset(Pik * p,PObj * pObj,int cp)1405 static PPoint fileOffset(Pik *p, PObj *pObj, int cp){
1406   PPoint pt = cZeroPoint;
1407   PNum w2 = 0.5*pObj->w;
1408   PNum h2 = 0.5*pObj->h;
1409   PNum rx = pObj->rad;
1410   PNum mn = w2<h2 ? w2 : h2;
1411   if( rx>mn ) rx = mn;
1412   if( rx<mn*0.25 ) rx = mn*0.25;
1413   pt.x = pt.y = 0.0;
1414   rx *= 0.5;
1415   switch( cp ){
1416     case CP_C:                                   break;
1417     case CP_N:   pt.x = 0.0;      pt.y = h2;     break;
1418     case CP_NE:  pt.x = w2-rx;    pt.y = h2-rx;  break;
1419     case CP_E:   pt.x = w2;       pt.y = 0.0;    break;
1420     case CP_SE:  pt.x = w2;       pt.y = -h2;    break;
1421     case CP_S:   pt.x = 0.0;      pt.y = -h2;    break;
1422     case CP_SW:  pt.x = -w2;      pt.y = -h2;    break;
1423     case CP_W:   pt.x = -w2;      pt.y = 0.0;    break;
1424     case CP_NW:  pt.x = -w2;      pt.y = h2;     break;
1425     default:     assert(0);
1426   }
1427   UNUSED_PARAMETER(p);
1428   return pt;
1429 }
fileFit(Pik * p,PObj * pObj,PNum w,PNum h)1430 static void fileFit(Pik *p, PObj *pObj, PNum w, PNum h){
1431   if( w>0 ) pObj->w = w;
1432   if( h>0 ) pObj->h = h + 2*pObj->rad;
1433   UNUSED_PARAMETER(p);
1434 }
fileRender(Pik * p,PObj * pObj)1435 static void fileRender(Pik *p, PObj *pObj){
1436   PNum w2 = 0.5*pObj->w;
1437   PNum h2 = 0.5*pObj->h;
1438   PNum rad = pObj->rad;
1439   PPoint pt = pObj->ptAt;
1440   PNum mn = w2<h2 ? w2 : h2;
1441   if( rad>mn ) rad = mn;
1442   if( rad<mn*0.25 ) rad = mn*0.25;
1443   if( pObj->sw>0.0 ){
1444     pik_append_xy(p,"<path d=\"M", pt.x-w2,pt.y-h2);
1445     pik_append_xy(p,"L", pt.x+w2,pt.y-h2);
1446     pik_append_xy(p,"L", pt.x+w2,pt.y+(h2-rad));
1447     pik_append_xy(p,"L", pt.x+(w2-rad),pt.y+h2);
1448     pik_append_xy(p,"L", pt.x-w2,pt.y+h2);
1449     pik_append(p,"Z\" ",-1);
1450     pik_append_style(p,pObj,1);
1451     pik_append(p,"\" />\n",-1);
1452     pik_append_xy(p,"<path d=\"M", pt.x+(w2-rad), pt.y+h2);
1453     pik_append_xy(p,"L", pt.x+(w2-rad),pt.y+(h2-rad));
1454     pik_append_xy(p,"L", pt.x+w2, pt.y+(h2-rad));
1455     pik_append(p,"\" ",-1);
1456     pik_append_style(p,pObj,0);
1457     pik_append(p,"\" />\n",-1);
1458   }
1459   pik_append_txt(p, pObj, 0);
1460 }
1461 
1462 
1463 /* Methods for the "line" class */
lineInit(Pik * p,PObj * pObj)1464 static void lineInit(Pik *p, PObj *pObj){
1465   pObj->w = pik_value(p, "linewid",7,0);
1466   pObj->h = pik_value(p, "lineht",6,0);
1467   pObj->rad = pik_value(p, "linerad",7,0);
1468 }
lineOffset(Pik * p,PObj * pObj,int cp)1469 static PPoint lineOffset(Pik *p, PObj *pObj, int cp){
1470 #if 0
1471   /* In legacy PIC, the .center of an unclosed line is half way between
1472   ** its .start and .end. */
1473   if( cp==CP_C && !pObj->bClose ){
1474     PPoint out;
1475     out.x = 0.5*(pObj->ptEnter.x + pObj->ptExit.x) - pObj->ptAt.x;
1476     out.y = 0.5*(pObj->ptEnter.x + pObj->ptExit.y) - pObj->ptAt.y;
1477     return out;
1478   }
1479 #endif
1480   return boxOffset(p,pObj,cp);
1481 }
lineRender(Pik * p,PObj * pObj)1482 static void lineRender(Pik *p, PObj *pObj){
1483   int i;
1484   if( pObj->sw>0.0 ){
1485     const char *z = "<path d=\"M";
1486     int n = pObj->nPath;
1487     if( pObj->larrow ){
1488       pik_draw_arrowhead(p,&pObj->aPath[1],&pObj->aPath[0],pObj);
1489     }
1490     if( pObj->rarrow ){
1491       pik_draw_arrowhead(p,&pObj->aPath[n-2],&pObj->aPath[n-1],pObj);
1492     }
1493     for(i=0; i<pObj->nPath; i++){
1494       pik_append_xy(p,z,pObj->aPath[i].x,pObj->aPath[i].y);
1495       z = "L";
1496     }
1497     if( pObj->bClose ){
1498       pik_append(p,"Z",1);
1499     }else{
1500       pObj->fill = -1.0;
1501     }
1502     pik_append(p,"\" ",-1);
1503     pik_append_style(p,pObj,pObj->bClose?3:0);
1504     pik_append(p,"\" />\n", -1);
1505   }
1506   pik_append_txt(p, pObj, 0);
1507 }
1508 
1509 /* Methods for the "move" class */
moveInit(Pik * p,PObj * pObj)1510 static void moveInit(Pik *p, PObj *pObj){
1511   pObj->w = pik_value(p, "movewid",7,0);
1512   pObj->h = pObj->w;
1513   pObj->fill = -1.0;
1514   pObj->color = -1.0;
1515   pObj->sw = -1.0;
1516 }
moveRender(Pik * p,PObj * pObj)1517 static void moveRender(Pik *p, PObj *pObj){
1518   /* No-op */
1519   UNUSED_PARAMETER(p);
1520   UNUSED_PARAMETER(pObj);
1521 }
1522 
1523 /* Methods for the "oval" class */
ovalInit(Pik * p,PObj * pObj)1524 static void ovalInit(Pik *p, PObj *pObj){
1525   pObj->h = pik_value(p, "ovalht",6,0);
1526   pObj->w = pik_value(p, "ovalwid",7,0);
1527   pObj->rad = 0.5*(pObj->h<pObj->w?pObj->h:pObj->w);
1528 }
ovalNumProp(Pik * p,PObj * pObj,PToken * pId)1529 static void ovalNumProp(Pik *p, PObj *pObj, PToken *pId){
1530   UNUSED_PARAMETER(p);
1531   UNUSED_PARAMETER(pId);
1532   /* Always adjust the radius to be half of the smaller of
1533   ** the width and height. */
1534   pObj->rad = 0.5*(pObj->h<pObj->w?pObj->h:pObj->w);
1535 }
ovalFit(Pik * p,PObj * pObj,PNum w,PNum h)1536 static void ovalFit(Pik *p, PObj *pObj, PNum w, PNum h){
1537   UNUSED_PARAMETER(p);
1538   if( w>0 ) pObj->w = w;
1539   if( h>0 ) pObj->h = h;
1540   if( pObj->w<pObj->h ) pObj->w = pObj->h;
1541   pObj->rad = 0.5*(pObj->h<pObj->w?pObj->h:pObj->w);
1542 }
1543 
1544 
1545 
1546 /* Methods for the "spline" class */
splineInit(Pik * p,PObj * pObj)1547 static void splineInit(Pik *p, PObj *pObj){
1548   pObj->w = pik_value(p, "linewid",7,0);
1549   pObj->h = pik_value(p, "lineht",6,0);
1550   pObj->rad = 1000;
1551 }
1552 /* Return a point along the path from "f" to "t" that is r units
1553 ** prior to reaching "t", except if the path is less than 2*r total,
1554 ** return the midpoint.
1555 */
radiusMidpoint(PPoint f,PPoint t,PNum r,int * pbMid)1556 static PPoint radiusMidpoint(PPoint f, PPoint t, PNum r, int *pbMid){
1557   PNum dx = t.x - f.x;
1558   PNum dy = t.y - f.y;
1559   PNum dist = hypot(dx,dy);
1560   PPoint m;
1561   if( dist<=0.0 ) return t;
1562   dx /= dist;
1563   dy /= dist;
1564   if( r > 0.5*dist ){
1565     r = 0.5*dist;
1566     *pbMid = 1;
1567   }else{
1568     *pbMid = 0;
1569   }
1570   m.x = t.x - r*dx;
1571   m.y = t.y - r*dy;
1572   return m;
1573 }
radiusPath(Pik * p,PObj * pObj,PNum r)1574 static void radiusPath(Pik *p, PObj *pObj, PNum r){
1575   int i;
1576   int n = pObj->nPath;
1577   const PPoint *a = pObj->aPath;
1578   PPoint m;
1579   PPoint an = a[n-1];
1580   int isMid = 0;
1581   int iLast = pObj->bClose ? n : n-1;
1582 
1583   pik_append_xy(p,"<path d=\"M", a[0].x, a[0].y);
1584   m = radiusMidpoint(a[0], a[1], r, &isMid);
1585   pik_append_xy(p," L ",m.x,m.y);
1586   for(i=1; i<iLast; i++){
1587     an = i<n-1 ? a[i+1] : a[0];
1588     m = radiusMidpoint(an,a[i],r, &isMid);
1589     pik_append_xy(p," Q ",a[i].x,a[i].y);
1590     pik_append_xy(p," ",m.x,m.y);
1591     if( !isMid ){
1592       m = radiusMidpoint(a[i],an,r, &isMid);
1593       pik_append_xy(p," L ",m.x,m.y);
1594     }
1595   }
1596   pik_append_xy(p," L ",an.x,an.y);
1597   if( pObj->bClose ){
1598     pik_append(p,"Z",1);
1599   }else{
1600     pObj->fill = -1.0;
1601   }
1602   pik_append(p,"\" ",-1);
1603   pik_append_style(p,pObj,pObj->bClose?3:0);
1604   pik_append(p,"\" />\n", -1);
1605 }
splineRender(Pik * p,PObj * pObj)1606 static void splineRender(Pik *p, PObj *pObj){
1607   if( pObj->sw>0.0 ){
1608     int n = pObj->nPath;
1609     PNum r = pObj->rad;
1610     if( n<3 || r<=0.0 ){
1611       lineRender(p,pObj);
1612       return;
1613     }
1614     if( pObj->larrow ){
1615       pik_draw_arrowhead(p,&pObj->aPath[1],&pObj->aPath[0],pObj);
1616     }
1617     if( pObj->rarrow ){
1618       pik_draw_arrowhead(p,&pObj->aPath[n-2],&pObj->aPath[n-1],pObj);
1619     }
1620     radiusPath(p,pObj,pObj->rad);
1621   }
1622   pik_append_txt(p, pObj, 0);
1623 }
1624 
1625 
1626 /* Methods for the "text" class */
textInit(Pik * p,PObj * pObj)1627 static void textInit(Pik *p, PObj *pObj){
1628   pik_value(p, "textwid",7,0);
1629   pik_value(p, "textht",6,0);
1630   pObj->sw = 0.0;
1631 }
textOffset(Pik * p,PObj * pObj,int cp)1632 static PPoint textOffset(Pik *p, PObj *pObj, int cp){
1633   /* Automatically slim-down the width and height of text
1634   ** statements so that the bounding box tightly encloses the text,
1635   ** then get boxOffset() to do the offset computation.
1636   */
1637   pik_size_to_fit(p, &pObj->errTok,3);
1638   return boxOffset(p, pObj, cp);
1639 }
1640 
1641 /* Methods for the "sublist" class */
sublistInit(Pik * p,PObj * pObj)1642 static void sublistInit(Pik *p, PObj *pObj){
1643   PList *pList = pObj->pSublist;
1644   int i;
1645   UNUSED_PARAMETER(p);
1646   pik_bbox_init(&pObj->bbox);
1647   for(i=0; i<pList->n; i++){
1648     pik_bbox_addbox(&pObj->bbox, &pList->a[i]->bbox);
1649   }
1650   pObj->w = pObj->bbox.ne.x - pObj->bbox.sw.x;
1651   pObj->h = pObj->bbox.ne.y - pObj->bbox.sw.y;
1652   pObj->ptAt.x = 0.5*(pObj->bbox.ne.x + pObj->bbox.sw.x);
1653   pObj->ptAt.y = 0.5*(pObj->bbox.ne.y + pObj->bbox.sw.y);
1654   pObj->mCalc |= A_WIDTH|A_HEIGHT|A_RADIUS;
1655 }
1656 
1657 
1658 /*
1659 ** The following array holds all the different kinds of objects.
1660 ** The special [] object is separate.
1661 */
1662 static const PClass aClass[] = {
1663    {  /* name */          "arc",
1664       /* isline */        1,
1665       /* eJust */         0,
1666       /* xInit */         arcInit,
1667       /* xNumProp */      0,
1668       /* xCheck */        arcCheck,
1669       /* xChop */         0,
1670       /* xOffset */       boxOffset,
1671       /* xFit */          0,
1672       /* xRender */       arcRender
1673    },
1674    {  /* name */          "arrow",
1675       /* isline */        1,
1676       /* eJust */         0,
1677       /* xInit */         arrowInit,
1678       /* xNumProp */      0,
1679       /* xCheck */        0,
1680       /* xChop */         0,
1681       /* xOffset */       lineOffset,
1682       /* xFit */          0,
1683       /* xRender */       splineRender
1684    },
1685    {  /* name */          "box",
1686       /* isline */        0,
1687       /* eJust */         1,
1688       /* xInit */         boxInit,
1689       /* xNumProp */      0,
1690       /* xCheck */        0,
1691       /* xChop */         boxChop,
1692       /* xOffset */       boxOffset,
1693       /* xFit */          boxFit,
1694       /* xRender */       boxRender
1695    },
1696    {  /* name */          "circle",
1697       /* isline */        0,
1698       /* eJust */         0,
1699       /* xInit */         circleInit,
1700       /* xNumProp */      circleNumProp,
1701       /* xCheck */        0,
1702       /* xChop */         circleChop,
1703       /* xOffset */       ellipseOffset,
1704       /* xFit */          circleFit,
1705       /* xRender */       circleRender
1706    },
1707    {  /* name */          "cylinder",
1708       /* isline */        0,
1709       /* eJust */         1,
1710       /* xInit */         cylinderInit,
1711       /* xNumProp */      0,
1712       /* xCheck */        0,
1713       /* xChop */         boxChop,
1714       /* xOffset */       cylinderOffset,
1715       /* xFit */          cylinderFit,
1716       /* xRender */       cylinderRender
1717    },
1718    {  /* name */          "dot",
1719       /* isline */        0,
1720       /* eJust */         0,
1721       /* xInit */         dotInit,
1722       /* xNumProp */      dotNumProp,
1723       /* xCheck */        dotCheck,
1724       /* xChop */         circleChop,
1725       /* xOffset */       dotOffset,
1726       /* xFit */          0,
1727       /* xRender */       dotRender
1728    },
1729    {  /* name */          "ellipse",
1730       /* isline */        0,
1731       /* eJust */         0,
1732       /* xInit */         ellipseInit,
1733       /* xNumProp */      0,
1734       /* xCheck */        0,
1735       /* xChop */         ellipseChop,
1736       /* xOffset */       ellipseOffset,
1737       /* xFit */          boxFit,
1738       /* xRender */       ellipseRender
1739    },
1740    {  /* name */          "file",
1741       /* isline */        0,
1742       /* eJust */         1,
1743       /* xInit */         fileInit,
1744       /* xNumProp */      0,
1745       /* xCheck */        0,
1746       /* xChop */         boxChop,
1747       /* xOffset */       fileOffset,
1748       /* xFit */          fileFit,
1749       /* xRender */       fileRender
1750    },
1751    {  /* name */          "line",
1752       /* isline */        1,
1753       /* eJust */         0,
1754       /* xInit */         lineInit,
1755       /* xNumProp */      0,
1756       /* xCheck */        0,
1757       /* xChop */         0,
1758       /* xOffset */       lineOffset,
1759       /* xFit */          0,
1760       /* xRender */       splineRender
1761    },
1762    {  /* name */          "move",
1763       /* isline */        1,
1764       /* eJust */         0,
1765       /* xInit */         moveInit,
1766       /* xNumProp */      0,
1767       /* xCheck */        0,
1768       /* xChop */         0,
1769       /* xOffset */       boxOffset,
1770       /* xFit */          0,
1771       /* xRender */       moveRender
1772    },
1773    {  /* name */          "oval",
1774       /* isline */        0,
1775       /* eJust */         1,
1776       /* xInit */         ovalInit,
1777       /* xNumProp */      ovalNumProp,
1778       /* xCheck */        0,
1779       /* xChop */         boxChop,
1780       /* xOffset */       boxOffset,
1781       /* xFit */          ovalFit,
1782       /* xRender */       boxRender
1783    },
1784    {  /* name */          "spline",
1785       /* isline */        1,
1786       /* eJust */         0,
1787       /* xInit */         splineInit,
1788       /* xNumProp */      0,
1789       /* xCheck */        0,
1790       /* xChop */         0,
1791       /* xOffset */       lineOffset,
1792       /* xFit */          0,
1793       /* xRender */       splineRender
1794    },
1795    {  /* name */          "text",
1796       /* isline */        0,
1797       /* eJust */         0,
1798       /* xInit */         textInit,
1799       /* xNumProp */      0,
1800       /* xCheck */        0,
1801       /* xChop */         boxChop,
1802       /* xOffset */       textOffset,
1803       /* xFit */          boxFit,
1804       /* xRender */       boxRender
1805    },
1806 };
1807 static const PClass sublistClass =
1808    {  /* name */          "[]",
1809       /* isline */        0,
1810       /* eJust */         0,
1811       /* xInit */         sublistInit,
1812       /* xNumProp */      0,
1813       /* xCheck */        0,
1814       /* xChop */         0,
1815       /* xOffset */       boxOffset,
1816       /* xFit */          0,
1817       /* xRender */       0
1818    };
1819 static const PClass noopClass =
1820    {  /* name */          "noop",
1821       /* isline */        0,
1822       /* eJust */         0,
1823       /* xInit */         0,
1824       /* xNumProp */      0,
1825       /* xCheck */        0,
1826       /* xChop */         0,
1827       /* xOffset */       boxOffset,
1828       /* xFit */          0,
1829       /* xRender */       0
1830    };
1831 
1832 
1833 /*
1834 ** Reduce the length of the line segment by amt (if possible) by
1835 ** modifying the location of *t.
1836 */
pik_chop(PPoint * f,PPoint * t,PNum amt)1837 static void pik_chop(PPoint *f, PPoint *t, PNum amt){
1838   PNum dx = t->x - f->x;
1839   PNum dy = t->y - f->y;
1840   PNum dist = hypot(dx,dy);
1841   PNum r;
1842   if( dist<=amt ){
1843     *t = *f;
1844     return;
1845   }
1846   r = 1.0 - amt/dist;
1847   t->x = f->x + r*dx;
1848   t->y = f->y + r*dy;
1849 }
1850 
1851 /*
1852 ** Draw an arrowhead on the end of the line segment from pFrom to pTo.
1853 ** Also, shorten the line segment (by changing the value of pTo) so that
1854 ** the shaft of the arrow does not extend into the arrowhead.
1855 */
pik_draw_arrowhead(Pik * p,PPoint * f,PPoint * t,PObj * pObj)1856 static void pik_draw_arrowhead(Pik *p, PPoint *f, PPoint *t, PObj *pObj){
1857   PNum dx = t->x - f->x;
1858   PNum dy = t->y - f->y;
1859   PNum dist = hypot(dx,dy);
1860   PNum h = p->hArrow * pObj->sw;
1861   PNum w = p->wArrow * pObj->sw;
1862   PNum e1, ddx, ddy;
1863   PNum bx, by;
1864   if( pObj->color<0.0 ) return;
1865   if( pObj->sw<=0.0 ) return;
1866   if( dist<=0.0 ) return;  /* Unable */
1867   dx /= dist;
1868   dy /= dist;
1869   e1 = dist - h;
1870   if( e1<0.0 ){
1871     e1 = 0.0;
1872     h = dist;
1873   }
1874   ddx = -w*dy;
1875   ddy = w*dx;
1876   bx = f->x + e1*dx;
1877   by = f->y + e1*dy;
1878   pik_append_xy(p,"<polygon points=\"", t->x, t->y);
1879   pik_append_xy(p," ",bx-ddx, by-ddy);
1880   pik_append_xy(p," ",bx+ddx, by+ddy);
1881   pik_append_clr(p,"\" style=\"fill:",pObj->color,"\"/>\n",0);
1882   pik_chop(f,t,h/2);
1883 }
1884 
1885 /*
1886 ** Compute the relative offset to an edge location from the reference for a
1887 ** an statement.
1888 */
pik_elem_offset(Pik * p,PObj * pObj,int cp)1889 static PPoint pik_elem_offset(Pik *p, PObj *pObj, int cp){
1890   return pObj->type->xOffset(p, pObj, cp);
1891 }
1892 
1893 
1894 /*
1895 ** Append raw text to zOut
1896 */
pik_append(Pik * p,const char * zText,int n)1897 static void pik_append(Pik *p, const char *zText, int n){
1898   if( n<0 ) n = (int)strlen(zText);
1899   if( p->nOut+n>=p->nOutAlloc ){
1900     int nNew = (p->nOut+n)*2 + 1;
1901     char *z = realloc(p->zOut, nNew);
1902     if( z==0 ){
1903       pik_error(p, 0, 0);
1904       return;
1905     }
1906     p->zOut = z;
1907     p->nOutAlloc = n;
1908   }
1909   memcpy(p->zOut+p->nOut, zText, n);
1910   p->nOut += n;
1911   p->zOut[p->nOut] = 0;
1912 }
1913 
1914 /*
1915 ** Append text to zOut with HTML characters escaped.
1916 **
1917 **   *  The space character is changed into non-breaking space (U+00a0)
1918 **      if mFlags has the 0x01 bit set. This is needed when outputting
1919 **      text to preserve leading and trailing whitespace.  Turns out we
1920 **      cannot use &nbsp; as that is an HTML-ism and is not valid in XML.
1921 **
1922 **   *  The "&" character is changed into "&amp;" if mFlags has the
1923 **      0x02 bit set.  This is needed when generating error message text.
1924 **
1925 **   *  Except for the above, only "<" and ">" are escaped.
1926 */
pik_append_text(Pik * p,const char * zText,int n,int mFlags)1927 static void pik_append_text(Pik *p, const char *zText, int n, int mFlags){
1928   int i;
1929   char c = 0;
1930   int bQSpace = mFlags & 1;
1931   int bQAmp = mFlags & 2;
1932   if( n<0 ) n = (int)strlen(zText);
1933   while( n>0 ){
1934     for(i=0; i<n; i++){
1935       c = zText[i];
1936       if( c=='<' || c=='>' ) break;
1937       if( c==' ' && bQSpace ) break;
1938       if( c=='&' && bQAmp ) break;
1939     }
1940     if( i ) pik_append(p, zText, i);
1941     if( i==n ) break;
1942     switch( c ){
1943       case '<': {  pik_append(p, "&lt;", 4);  break;  }
1944       case '>': {  pik_append(p, "&gt;", 4);  break;  }
1945       case '&': {  pik_append(p, "&amp;", 5);  break;  }
1946       case ' ': {  pik_append(p, "\302\240;", 2);  break;  }
1947     }
1948     i++;
1949     n -= i;
1950     zText += i;
1951     i = 0;
1952   }
1953 }
1954 
1955 /*
1956 ** Append error message text.  This is either a raw append, or an append
1957 ** with HTML escapes, depending on whether the PIKCHR_PLAINTEXT_ERRORS flag
1958 ** is set.
1959 */
pik_append_errtxt(Pik * p,const char * zText,int n)1960 static void pik_append_errtxt(Pik *p, const char *zText, int n){
1961   if( p->mFlags & PIKCHR_PLAINTEXT_ERRORS ){
1962     pik_append(p, zText, n);
1963   }else{
1964     pik_append_text(p, zText, n, 0);
1965   }
1966 }
1967 
1968 /* Append a PNum value
1969 */
pik_append_num(Pik * p,const char * z,PNum v)1970 static void pik_append_num(Pik *p, const char *z,PNum v){
1971   char buf[100];
1972   snprintf(buf, sizeof(buf)-1, "%.10g", (double)v);
1973   buf[sizeof(buf)-1] = 0;
1974   pik_append(p, z, -1);
1975   pik_append(p, buf, -1);
1976 }
1977 
1978 /* Append a PPoint value  (Used for debugging only)
1979 */
pik_append_point(Pik * p,const char * z,PPoint * pPt)1980 static void pik_append_point(Pik *p, const char *z, PPoint *pPt){
1981   char buf[100];
1982   snprintf(buf, sizeof(buf)-1, "%.10g,%.10g",
1983           (double)pPt->x, (double)pPt->y);
1984   buf[sizeof(buf)-1] = 0;
1985   pik_append(p, z, -1);
1986   pik_append(p, buf, -1);
1987 }
1988 
1989 /*
1990 ** Invert the RGB color so that it is appropriate for dark mode.
1991 ** Variable x hold the initial color.  The color is intended for use
1992 ** as a background color if isBg is true, and as a foreground color
1993 ** if isBg is false.
1994 */
pik_color_to_dark_mode(int x,int isBg)1995 static int pik_color_to_dark_mode(int x, int isBg){
1996   int r, g, b;
1997   int mn, mx;
1998   x = 0xffffff - x;
1999   r = (x>>16) & 0xff;
2000   g = (x>>8) & 0xff;
2001   b = x & 0xff;
2002   mx = r;
2003   if( g>mx ) mx = g;
2004   if( b>mx ) mx = b;
2005   mn = r;
2006   if( g<mn ) mn = g;
2007   if( b<mn ) mn = b;
2008   r = mn + (mx-r);
2009   g = mn + (mx-g);
2010   b = mn + (mx-b);
2011   if( isBg ){
2012     if( mx>127 ){
2013       r = (127*r)/mx;
2014       g = (127*g)/mx;
2015       b = (127*b)/mx;
2016     }
2017   }else{
2018     if( mn<128 && mx>mn ){
2019       r = 127 + ((r-mn)*128)/(mx-mn);
2020       g = 127 + ((g-mn)*128)/(mx-mn);
2021       b = 127 + ((b-mn)*128)/(mx-mn);
2022     }
2023   }
2024   return r*0x10000 + g*0x100 + b;
2025 }
2026 
2027 /* Append a PNum value surrounded by text.  Do coordinate transformations
2028 ** on the value.
2029 */
pik_append_x(Pik * p,const char * z1,PNum v,const char * z2)2030 static void pik_append_x(Pik *p, const char *z1, PNum v, const char *z2){
2031   char buf[200];
2032   v -= p->bbox.sw.x;
2033   snprintf(buf, sizeof(buf)-1, "%s%d%s", z1, (int)(p->rScale*v), z2);
2034   buf[sizeof(buf)-1] = 0;
2035   pik_append(p, buf, -1);
2036 }
pik_append_y(Pik * p,const char * z1,PNum v,const char * z2)2037 static void pik_append_y(Pik *p, const char *z1, PNum v, const char *z2){
2038   char buf[200];
2039   v = p->bbox.ne.y - v;
2040   snprintf(buf, sizeof(buf)-1, "%s%d%s", z1, (int)(p->rScale*v), z2);
2041   buf[sizeof(buf)-1] = 0;
2042   pik_append(p, buf, -1);
2043 }
pik_append_xy(Pik * p,const char * z1,PNum x,PNum y)2044 static void pik_append_xy(Pik *p, const char *z1, PNum x, PNum y){
2045   char buf[200];
2046   x = x - p->bbox.sw.x;
2047   y = p->bbox.ne.y - y;
2048   snprintf(buf, sizeof(buf)-1, "%s%d,%d", z1,
2049        (int)(p->rScale*x), (int)(p->rScale*y));
2050   buf[sizeof(buf)-1] = 0;
2051   pik_append(p, buf, -1);
2052 }
pik_append_dis(Pik * p,const char * z1,PNum v,const char * z2)2053 static void pik_append_dis(Pik *p, const char *z1, PNum v, const char *z2){
2054   char buf[200];
2055   snprintf(buf, sizeof(buf)-1, "%s%g%s", z1, p->rScale*v, z2);
2056   buf[sizeof(buf)-1] = 0;
2057   pik_append(p, buf, -1);
2058 }
2059 
2060 /* Append a color specification to the output.
2061 **
2062 ** In PIKCHR_DARK_MODE, the color is inverted.  The "bg" flags indicates that
2063 ** the color is intended for use as a background color if true, or as a
2064 ** foreground color if false.  The distinction only matters for color
2065 ** inversions in PIKCHR_DARK_MODE.
2066 */
pik_append_clr(Pik * p,const char * z1,PNum v,const char * z2,int bg)2067 static void pik_append_clr(Pik *p,const char *z1,PNum v,const char *z2,int bg){
2068   char buf[200];
2069   int x = (int)v;
2070   int r, g, b;
2071   if( x==0 && p->fgcolor>0 && !bg ){
2072     x = p->fgcolor;
2073   }else if( bg && x>=0xffffff && p->bgcolor>0 ){
2074     x = p->bgcolor;
2075   }else if( p->mFlags & PIKCHR_DARK_MODE ){
2076     x = pik_color_to_dark_mode(x,bg);
2077   }
2078   r = (x>>16) & 0xff;
2079   g = (x>>8) & 0xff;
2080   b = x & 0xff;
2081   snprintf(buf, sizeof(buf)-1, "%srgb(%d,%d,%d)%s", z1, r, g, b, z2);
2082   buf[sizeof(buf)-1] = 0;
2083   pik_append(p, buf, -1);
2084 }
2085 
2086 /* Append an SVG path A record:
2087 **
2088 **    A r1 r2 0 0 0 x y
2089 */
pik_append_arc(Pik * p,PNum r1,PNum r2,PNum x,PNum y)2090 static void pik_append_arc(Pik *p, PNum r1, PNum r2, PNum x, PNum y){
2091   char buf[200];
2092   x = x - p->bbox.sw.x;
2093   y = p->bbox.ne.y - y;
2094   snprintf(buf, sizeof(buf)-1, "A%d %d 0 0 0 %d %d",
2095      (int)(p->rScale*r1), (int)(p->rScale*r2),
2096      (int)(p->rScale*x), (int)(p->rScale*y));
2097   buf[sizeof(buf)-1] = 0;
2098   pik_append(p, buf, -1);
2099 }
2100 
2101 /* Append a style="..." text.  But, leave the quote unterminated, in case
2102 ** the caller wants to add some more.
2103 **
2104 ** eFill is non-zero to fill in the background, or 0 if no fill should
2105 ** occur.  Non-zero values of eFill determine the "bg" flag to pik_append_clr()
2106 ** for cases when pObj->fill==pObj->color
2107 **
2108 **     1        fill is background, and color is foreground.
2109 **     2        fill and color are both foreground.  (Used by "dot" objects)
2110 **     3        fill and color are both background.  (Used by most other objs)
2111 */
pik_append_style(Pik * p,PObj * pObj,int eFill)2112 static void pik_append_style(Pik *p, PObj *pObj, int eFill){
2113   int clrIsBg = 0;
2114   pik_append(p, " style=\"", -1);
2115   if( pObj->fill>=0 && eFill ){
2116     int fillIsBg = 1;
2117     if( pObj->fill==pObj->color ){
2118       if( eFill==2 ) fillIsBg = 0;
2119       if( eFill==3 ) clrIsBg = 1;
2120     }
2121     pik_append_clr(p, "fill:", pObj->fill, ";", fillIsBg);
2122   }else{
2123     pik_append(p,"fill:none;",-1);
2124   }
2125   if( pObj->sw>0.0 && pObj->color>=0.0 ){
2126     PNum sw = pObj->sw;
2127     pik_append_dis(p, "stroke-width:", sw, ";");
2128     if( pObj->nPath>2 && pObj->rad<=pObj->sw ){
2129       pik_append(p, "stroke-linejoin:round;", -1);
2130     }
2131     pik_append_clr(p, "stroke:",pObj->color,";",clrIsBg);
2132     if( pObj->dotted>0.0 ){
2133       PNum v = pObj->dotted;
2134       if( sw<2.1/p->rScale ) sw = 2.1/p->rScale;
2135       pik_append_dis(p,"stroke-dasharray:",sw,"");
2136       pik_append_dis(p,",",v,";");
2137     }else if( pObj->dashed>0.0 ){
2138       PNum v = pObj->dashed;
2139       pik_append_dis(p,"stroke-dasharray:",v,"");
2140       pik_append_dis(p,",",v,";");
2141     }
2142   }
2143 }
2144 
2145 /*
2146 ** Compute the vertical locations for all text items in the
2147 ** object pObj.  In other words, set every pObj->aTxt[*].eCode
2148 ** value to contain exactly one of: TP_ABOVE2, TP_ABOVE, TP_CENTER,
2149 ** TP_BELOW, or TP_BELOW2 is set.
2150 */
pik_txt_vertical_layout(PObj * pObj)2151 static void pik_txt_vertical_layout(PObj *pObj){
2152   int n, i;
2153   PToken *aTxt;
2154   n = pObj->nTxt;
2155   if( n==0 ) return;
2156   aTxt = pObj->aTxt;
2157   if( n==1 ){
2158     if( (aTxt[0].eCode & TP_VMASK)==0 ){
2159       aTxt[0].eCode |= TP_CENTER;
2160     }
2161   }else{
2162     int allSlots = 0;
2163     int aFree[5];
2164     int iSlot;
2165     int j, mJust;
2166     /* If there is more than one TP_ABOVE, change the first to TP_ABOVE2. */
2167     for(j=mJust=0, i=n-1; i>=0; i--){
2168       if( aTxt[i].eCode & TP_ABOVE ){
2169         if( j==0 ){
2170           j++;
2171           mJust = aTxt[i].eCode & TP_JMASK;
2172         }else if( j==1 && mJust!=0 && (aTxt[i].eCode & mJust)==0 ){
2173           j++;
2174         }else{
2175           aTxt[i].eCode = (aTxt[i].eCode & ~TP_VMASK) | TP_ABOVE2;
2176           break;
2177         }
2178       }
2179     }
2180     /* If there is more than one TP_BELOW, change the last to TP_BELOW2 */
2181     for(j=mJust=0, i=0; i<n; i++){
2182       if( aTxt[i].eCode & TP_BELOW ){
2183         if( j==0 ){
2184           j++;
2185           mJust = aTxt[i].eCode & TP_JMASK;
2186         }else if( j==1 && mJust!=0 && (aTxt[i].eCode & mJust)==0 ){
2187           j++;
2188         }else{
2189           aTxt[i].eCode = (aTxt[i].eCode & ~TP_VMASK) | TP_BELOW2;
2190           break;
2191         }
2192       }
2193     }
2194     /* Compute a mask of all slots used */
2195     for(i=0; i<n; i++) allSlots |= aTxt[i].eCode & TP_VMASK;
2196     /* Set of an array of available slots */
2197     if( n==2
2198      && ((aTxt[0].eCode|aTxt[1].eCode)&TP_JMASK)==(TP_LJUST|TP_RJUST)
2199     ){
2200       /* Special case of two texts that have opposite justification:
2201       ** Allow them both to float to center. */
2202       iSlot = 2;
2203       aFree[0] = aFree[1] = TP_CENTER;
2204     }else{
2205       /* Set up the arrow so that available slots are filled from top to
2206       ** bottom */
2207       iSlot = 0;
2208       if( n>=4 && (allSlots & TP_ABOVE2)==0 ) aFree[iSlot++] = TP_ABOVE2;
2209       if( (allSlots & TP_ABOVE)==0 ) aFree[iSlot++] = TP_ABOVE;
2210       if( (n&1)!=0 ) aFree[iSlot++] = TP_CENTER;
2211       if( (allSlots & TP_BELOW)==0 ) aFree[iSlot++] = TP_BELOW;
2212       if( n>=4 && (allSlots & TP_BELOW2)==0 ) aFree[iSlot++] = TP_BELOW2;
2213     }
2214     /* Set the VMASK for all unassigned texts */
2215     for(i=iSlot=0; i<n; i++){
2216       if( (aTxt[i].eCode & TP_VMASK)==0 ){
2217         aTxt[i].eCode |= aFree[iSlot++];
2218       }
2219     }
2220   }
2221 }
2222 
2223 /* Return the font scaling factor associated with the input text attribute.
2224 */
pik_font_scale(PToken * t)2225 static PNum pik_font_scale(PToken *t){
2226   PNum scale = 1.0;
2227   if( t->eCode & TP_BIG    ) scale *= 1.25;
2228   if( t->eCode & TP_SMALL  ) scale *= 0.8;
2229   if( t->eCode & TP_XTRA   ) scale *= scale;
2230   return scale;
2231 }
2232 
2233 /* Append multiple <text> SVG elements for the text fields of the PObj.
2234 ** Parameters:
2235 **
2236 **    p          The Pik object into which we are rendering
2237 **
2238 **    pObj       Object containing the text to be rendered
2239 **
2240 **    pBox       If not NULL, do no rendering at all.  Instead
2241 **               expand the box object so that it will include all
2242 **               of the text.
2243 */
pik_append_txt(Pik * p,PObj * pObj,PBox * pBox)2244 static void pik_append_txt(Pik *p, PObj *pObj, PBox *pBox){
2245   PNum jw;          /* Justification margin relative to center */
2246   PNum ha2 = 0.0;   /* Height of the top row of text */
2247   PNum ha1 = 0.0;   /* Height of the second "above" row */
2248   PNum hc = 0.0;    /* Height of the center row */
2249   PNum hb1 = 0.0;   /* Height of the first "below" row of text */
2250   PNum hb2 = 0.0;   /* Height of the second "below" row */
2251   PNum yBase = 0.0;
2252   int n, i, nz;
2253   PNum x, y, orig_y, s;
2254   const char *z;
2255   PToken *aTxt;
2256   unsigned allMask = 0;
2257 
2258   if( p->nErr ) return;
2259   if( pObj->nTxt==0 ) return;
2260   aTxt = pObj->aTxt;
2261   n = pObj->nTxt;
2262   pik_txt_vertical_layout(pObj);
2263   x = pObj->ptAt.x;
2264   for(i=0; i<n; i++) allMask |= pObj->aTxt[i].eCode;
2265   if( pObj->type->isLine ){
2266     hc = pObj->sw*1.5;
2267   }else if( pObj->rad>0.0 && pObj->type->xInit==cylinderInit ){
2268     yBase = -0.75*pObj->rad;
2269   }
2270   if( allMask & TP_CENTER ){
2271     for(i=0; i<n; i++){
2272       if( pObj->aTxt[i].eCode & TP_CENTER ){
2273         s = pik_font_scale(pObj->aTxt+i);
2274         if( hc<s*p->charHeight ) hc = s*p->charHeight;
2275       }
2276     }
2277   }
2278   if( allMask & TP_ABOVE ){
2279     for(i=0; i<n; i++){
2280       if( pObj->aTxt[i].eCode & TP_ABOVE ){
2281         s = pik_font_scale(pObj->aTxt+i)*p->charHeight;
2282         if( ha1<s ) ha1 = s;
2283       }
2284     }
2285     if( allMask & TP_ABOVE2 ){
2286       for(i=0; i<n; i++){
2287         if( pObj->aTxt[i].eCode & TP_ABOVE2 ){
2288           s = pik_font_scale(pObj->aTxt+i)*p->charHeight;
2289           if( ha2<s ) ha2 = s;
2290         }
2291       }
2292     }
2293   }
2294   if( allMask & TP_BELOW ){
2295     for(i=0; i<n; i++){
2296       if( pObj->aTxt[i].eCode & TP_BELOW ){
2297         s = pik_font_scale(pObj->aTxt+i)*p->charHeight;
2298         if( hb1<s ) hb1 = s;
2299       }
2300     }
2301     if( allMask & TP_BELOW2 ){
2302       for(i=0; i<n; i++){
2303         if( pObj->aTxt[i].eCode & TP_BELOW2 ){
2304           s = pik_font_scale(pObj->aTxt+i)*p->charHeight;
2305           if( hb2<s ) hb2 = s;
2306         }
2307       }
2308     }
2309   }
2310   if( pObj->type->eJust==1 ){
2311     jw = 0.5*(pObj->w - 0.5*(p->charWidth + pObj->sw));
2312   }else{
2313     jw = 0.0;
2314   }
2315   for(i=0; i<n; i++){
2316     PToken *t = &aTxt[i];
2317     PNum xtraFontScale = pik_font_scale(t);
2318     PNum nx = 0;
2319     orig_y = pObj->ptAt.y;
2320     y = yBase;
2321     if( t->eCode & TP_ABOVE2 ) y += 0.5*hc + ha1 + 0.5*ha2;
2322     if( t->eCode & TP_ABOVE  ) y += 0.5*hc + 0.5*ha1;
2323     if( t->eCode & TP_BELOW  ) y -= 0.5*hc + 0.5*hb1;
2324     if( t->eCode & TP_BELOW2 ) y -= 0.5*hc + hb1 + 0.5*hb2;
2325     if( t->eCode & TP_LJUST  ) nx -= jw;
2326     if( t->eCode & TP_RJUST  ) nx += jw;
2327 
2328     if( pBox!=0 ){
2329       /* If pBox is not NULL, do not draw any <text>.  Instead, just expand
2330       ** pBox to include the text */
2331       PNum cw = pik_text_length(t)*p->charWidth*xtraFontScale*0.01;
2332       PNum ch = p->charHeight*0.5*xtraFontScale;
2333       PNum x0, y0, x1, y1;  /* Boundary of text relative to pObj->ptAt */
2334       if( t->eCode & TP_BOLD ) cw *= 1.1;
2335       if( t->eCode & TP_RJUST ){
2336         x0 = nx;
2337         y0 = y-ch;
2338         x1 = nx-cw;
2339         y1 = y+ch;
2340       }else if( t->eCode & TP_LJUST ){
2341         x0 = nx;
2342         y0 = y-ch;
2343         x1 = nx+cw;
2344         y1 = y+ch;
2345       }else{
2346         x0 = nx+cw/2;
2347         y0 = y+ch;
2348         x1 = nx-cw/2;
2349         y1 = y-ch;
2350       }
2351       if( (t->eCode & TP_ALIGN)!=0 && pObj->nPath>=2 ){
2352         int nn = pObj->nPath;
2353         PNum dx = pObj->aPath[nn-1].x - pObj->aPath[0].x;
2354         PNum dy = pObj->aPath[nn-1].y - pObj->aPath[0].y;
2355         if( dx!=0 || dy!=0 ){
2356           PNum dist = hypot(dx,dy);
2357           PNum tt;
2358           dx /= dist;
2359           dy /= dist;
2360           tt = dx*x0 - dy*y0;
2361           y0 = dy*x0 - dx*y0;
2362           x0 = tt;
2363           tt = dx*x1 - dy*y1;
2364           y1 = dy*x1 - dx*y1;
2365           x1 = tt;
2366         }
2367       }
2368       pik_bbox_add_xy(pBox, x+x0, orig_y+y0);
2369       pik_bbox_add_xy(pBox, x+x1, orig_y+y1);
2370       continue;
2371     }
2372     nx += x;
2373     y += orig_y;
2374 
2375     pik_append_x(p, "<text x=\"", nx, "\"");
2376     pik_append_y(p, " y=\"", y, "\"");
2377     if( t->eCode & TP_RJUST ){
2378       pik_append(p, " text-anchor=\"end\"", -1);
2379     }else if( t->eCode & TP_LJUST ){
2380       pik_append(p, " text-anchor=\"start\"", -1);
2381     }else{
2382       pik_append(p, " text-anchor=\"middle\"", -1);
2383     }
2384     if( t->eCode & TP_ITALIC ){
2385       pik_append(p, " font-style=\"italic\"", -1);
2386     }
2387     if( t->eCode & TP_BOLD ){
2388       pik_append(p, " font-weight=\"bold\"", -1);
2389     }
2390     if( pObj->color>=0.0 ){
2391       pik_append_clr(p, " fill=\"", pObj->color, "\"",0);
2392     }
2393     xtraFontScale *= p->fontScale;
2394     if( xtraFontScale<=0.99 || xtraFontScale>=1.01 ){
2395       pik_append_num(p, " font-size=\"", xtraFontScale*100.0);
2396       pik_append(p, "%\"", 2);
2397     }
2398     if( (t->eCode & TP_ALIGN)!=0 && pObj->nPath>=2 ){
2399       int nn = pObj->nPath;
2400       PNum dx = pObj->aPath[nn-1].x - pObj->aPath[0].x;
2401       PNum dy = pObj->aPath[nn-1].y - pObj->aPath[0].y;
2402       if( dx!=0 || dy!=0 ){
2403         PNum ang = atan2(dy,dx)*-180/M_PI;
2404         pik_append_num(p, " transform=\"rotate(", ang);
2405         pik_append_xy(p, " ", x, orig_y);
2406         pik_append(p,")\"",2);
2407       }
2408     }
2409     pik_append(p," dominant-baseline=\"central\">",-1);
2410     if( t->n>=2 && t->z[0]=='"' ){
2411       z = t->z+1;
2412       nz = t->n-2;
2413     }else{
2414       z = t->z;
2415       nz = t->n;
2416     }
2417     while( nz>0 ){
2418       int j;
2419       for(j=0; j<nz && z[j]!='\\'; j++){}
2420       if( j ) pik_append_text(p, z, j, 1);
2421       if( j<nz && (j+1==nz || z[j+1]=='\\') ){
2422         pik_append(p, "&#92;", -1);
2423         j++;
2424       }
2425       nz -= j+1;
2426       z += j+1;
2427     }
2428     pik_append(p, "</text>\n", -1);
2429   }
2430 }
2431 
2432 /*
2433 ** Append text (that will go inside of a <pre>...</pre>) that
2434 ** shows the context of an error token.
2435 */
pik_error_context(Pik * p,PToken * pErr,int nContext)2436 static void pik_error_context(Pik *p, PToken *pErr, int nContext){
2437   int iErrPt;           /* Index of first byte of error from start of input */
2438   int iErrCol;          /* Column of the error token on its line */
2439   int iStart;           /* Start position of the error context */
2440   int iEnd;             /* End position of the error context */
2441   int iLineno;          /* Line number of the error */
2442   int iFirstLineno;     /* Line number of start of error context */
2443   int i;                /* Loop counter */
2444   int iBump = 0;        /* Bump the location of the error cursor */
2445   char zLineno[20];     /* Buffer in which to generate line numbers */
2446 
2447   iErrPt = (int)(pErr->z - p->sIn.z);
2448   if( iErrPt>=(int)p->sIn.n ){
2449     iErrPt = p->sIn.n-1;
2450     iBump = 1;
2451   }else{
2452     while( iErrPt>0 && (p->sIn.z[iErrPt]=='\n' || p->sIn.z[iErrPt]=='\r') ){
2453       iErrPt--;
2454       iBump = 1;
2455     }
2456   }
2457   iLineno = 1;
2458   for(i=0; i<iErrPt; i++){
2459     if( p->sIn.z[i]=='\n' ){
2460       iLineno++;
2461     }
2462   }
2463   iStart = 0;
2464   iFirstLineno = 1;
2465   while( iFirstLineno+nContext<iLineno ){
2466     while( p->sIn.z[iStart]!='\n' ){ iStart++; }
2467     iStart++;
2468     iFirstLineno++;
2469   }
2470   for(iEnd=iErrPt; p->sIn.z[iEnd]!=0 && p->sIn.z[iEnd]!='\n'; iEnd++){}
2471   i = iStart;
2472   while( iFirstLineno<=iLineno ){
2473     snprintf(zLineno,sizeof(zLineno)-1,"/* %4d */  ", iFirstLineno++);
2474     zLineno[sizeof(zLineno)-1] = 0;
2475     pik_append(p, zLineno, -1);
2476     for(i=iStart; p->sIn.z[i]!=0 && p->sIn.z[i]!='\n'; i++){}
2477     pik_append_errtxt(p, p->sIn.z+iStart, i-iStart);
2478     iStart = i+1;
2479     pik_append(p, "\n", 1);
2480   }
2481   for(iErrCol=0, i=iErrPt; i>0 && p->sIn.z[i]!='\n'; iErrCol++, i--){}
2482   for(i=0; i<iErrCol+11+iBump; i++){ pik_append(p, " ", 1); }
2483   for(i=0; i<(int)pErr->n; i++) pik_append(p, "^", 1);
2484   pik_append(p, "\n", 1);
2485 }
2486 
2487 
2488 /*
2489 ** Generate an error message for the output.  pErr is the token at which
2490 ** the error should point.  zMsg is the text of the error message. If
2491 ** either pErr or zMsg is NULL, generate an out-of-memory error message.
2492 **
2493 ** This routine is a no-op if there has already been an error reported.
2494 */
pik_error(Pik * p,PToken * pErr,const char * zMsg)2495 static void pik_error(Pik *p, PToken *pErr, const char *zMsg){
2496   int i;
2497   if( p==0 ) return;
2498   if( p->nErr ) return;
2499   p->nErr++;
2500   if( zMsg==0 ){
2501     if( p->mFlags & PIKCHR_PLAINTEXT_ERRORS ){
2502       pik_append(p, "\nOut of memory\n", -1);
2503     }else{
2504       pik_append(p, "\n<div><p>Out of memory</p></div>\n", -1);
2505     }
2506     return;
2507   }
2508   if( pErr==0 ){
2509     pik_append(p, "\n", 1);
2510     pik_append_errtxt(p, zMsg, -1);
2511     return;
2512   }
2513   if( (p->mFlags & PIKCHR_PLAINTEXT_ERRORS)==0 ){
2514     pik_append(p, "<div><pre>\n", -1);
2515   }
2516   pik_error_context(p, pErr, 5);
2517   pik_append(p, "ERROR: ", -1);
2518   pik_append_errtxt(p, zMsg, -1);
2519   pik_append(p, "\n", 1);
2520   for(i=p->nCtx-1; i>=0; i--){
2521     pik_append(p, "Called from:\n", -1);
2522     pik_error_context(p, &p->aCtx[i], 0);
2523   }
2524   if( (p->mFlags & PIKCHR_PLAINTEXT_ERRORS)==0 ){
2525     pik_append(p, "</pre></div>\n", -1);
2526   }
2527 }
2528 
2529 /*
2530 ** Process an "assert( e1 == e2 )" statement.  Always return NULL.
2531 */
pik_assert(Pik * p,PNum e1,PToken * pEq,PNum e2)2532 static PObj *pik_assert(Pik *p, PNum e1, PToken *pEq, PNum e2){
2533   char zE1[100], zE2[100], zMsg[300];
2534 
2535   /* Convert the numbers to strings using %g for comparison.  This
2536   ** limits the precision of the comparison to account for rounding error. */
2537   snprintf(zE1, sizeof(zE1), "%g", e1); zE1[sizeof(zE1)-1] = 0;
2538   snprintf(zE2, sizeof(zE2), "%g", e2); zE1[sizeof(zE2)-1] = 0;
2539   if( strcmp(zE1,zE2)!=0 ){
2540     snprintf(zMsg, sizeof(zMsg), "%.50s != %.50s", zE1, zE2);
2541     pik_error(p, pEq, zMsg);
2542   }
2543   return 0;
2544 }
2545 
2546 /*
2547 ** Process an "assert( place1 == place2 )" statement.  Always return NULL.
2548 */
pik_position_assert(Pik * p,PPoint * e1,PToken * pEq,PPoint * e2)2549 static PObj *pik_position_assert(Pik *p, PPoint *e1, PToken *pEq, PPoint *e2){
2550   char zE1[100], zE2[100], zMsg[210];
2551 
2552   /* Convert the numbers to strings using %g for comparison.  This
2553   ** limits the precision of the comparison to account for rounding error. */
2554   snprintf(zE1, sizeof(zE1), "(%g,%g)", e1->x, e1->y); zE1[sizeof(zE1)-1] = 0;
2555   snprintf(zE2, sizeof(zE2), "(%g,%g)", e2->x, e2->y); zE1[sizeof(zE2)-1] = 0;
2556   if( strcmp(zE1,zE2)!=0 ){
2557     snprintf(zMsg, sizeof(zMsg), "%s != %s", zE1, zE2);
2558     pik_error(p, pEq, zMsg);
2559   }
2560   return 0;
2561 }
2562 
2563 /* Free a complete list of objects */
pik_elist_free(Pik * p,PList * pList)2564 static void pik_elist_free(Pik *p, PList *pList){
2565   int i;
2566   if( pList==0 ) return;
2567   for(i=0; i<pList->n; i++){
2568     pik_elem_free(p, pList->a[i]);
2569   }
2570   free(pList->a);
2571   free(pList);
2572   return;
2573 }
2574 
2575 /* Free a single object, and its substructure */
pik_elem_free(Pik * p,PObj * pObj)2576 static void pik_elem_free(Pik *p, PObj *pObj){
2577   if( pObj==0 ) return;
2578   free(pObj->zName);
2579   pik_elist_free(p, pObj->pSublist);
2580   free(pObj->aPath);
2581   free(pObj);
2582 }
2583 
2584 /* Convert a numeric literal into a number.  Return that number.
2585 ** There is no error handling because the tokenizer has already
2586 ** assured us that the numeric literal is valid.
2587 **
2588 ** Allowed number forms:
2589 **
2590 **   (1)    Floating point literal
2591 **   (2)    Same as (1) but followed by a unit: "cm", "mm", "in",
2592 **          "px", "pt", or "pc".
2593 **   (3)    Hex integers: 0x000000
2594 **
2595 ** This routine returns the result in inches.  If a different unit
2596 ** is specified, the conversion happens automatically.
2597 */
pik_atof(PToken * num)2598 PNum pik_atof(PToken *num){
2599   char *endptr;
2600   PNum ans;
2601   if( num->n>=3 && num->z[0]=='0' && (num->z[1]=='x'||num->z[1]=='X') ){
2602     return (PNum)strtol(num->z+2, 0, 16);
2603   }
2604   ans = strtod(num->z, &endptr);
2605   if( (int)(endptr - num->z)==(int)num->n-2 ){
2606     char c1 = endptr[0];
2607     char c2 = endptr[1];
2608     if( c1=='c' && c2=='m' ){
2609       ans /= 2.54;
2610     }else if( c1=='m' && c2=='m' ){
2611       ans /= 25.4;
2612     }else if( c1=='p' && c2=='x' ){
2613       ans /= 96;
2614     }else if( c1=='p' && c2=='t' ){
2615       ans /= 72;
2616     }else if( c1=='p' && c2=='c' ){
2617       ans /= 6;
2618     }
2619   }
2620   return ans;
2621 }
2622 
2623 /*
2624 ** Compute the distance between two points
2625 */
pik_dist(PPoint * pA,PPoint * pB)2626 static PNum pik_dist(PPoint *pA, PPoint *pB){
2627   PNum dx, dy;
2628   dx = pB->x - pA->x;
2629   dy = pB->y - pA->y;
2630   return hypot(dx,dy);
2631 }
2632 
2633 /* Return true if a bounding box is empty.
2634 */
pik_bbox_isempty(PBox * p)2635 static int pik_bbox_isempty(PBox *p){
2636   return p->sw.x>p->ne.x;
2637 }
2638 
2639 /* Initialize a bounding box to an empty container
2640 */
pik_bbox_init(PBox * p)2641 static void pik_bbox_init(PBox *p){
2642   p->sw.x = 1.0;
2643   p->sw.y = 1.0;
2644   p->ne.x = 0.0;
2645   p->ne.y = 0.0;
2646 }
2647 
2648 /* Enlarge the PBox of the first argument so that it fully
2649 ** covers the second PBox
2650 */
pik_bbox_addbox(PBox * pA,PBox * pB)2651 static void pik_bbox_addbox(PBox *pA, PBox *pB){
2652   if( pik_bbox_isempty(pA) ){
2653     *pA = *pB;
2654   }
2655   if( pik_bbox_isempty(pB) ) return;
2656   if( pA->sw.x>pB->sw.x ) pA->sw.x = pB->sw.x;
2657   if( pA->sw.y>pB->sw.y ) pA->sw.y = pB->sw.y;
2658   if( pA->ne.x<pB->ne.x ) pA->ne.x = pB->ne.x;
2659   if( pA->ne.y<pB->ne.y ) pA->ne.y = pB->ne.y;
2660 }
2661 
2662 /* Enlarge the PBox of the first argument, if necessary, so that
2663 ** it contains the point described by the 2nd and 3rd arguments.
2664 */
pik_bbox_add_xy(PBox * pA,PNum x,PNum y)2665 static void pik_bbox_add_xy(PBox *pA, PNum x, PNum y){
2666   if( pik_bbox_isempty(pA) ){
2667     pA->ne.x = x;
2668     pA->ne.y = y;
2669     pA->sw.x = x;
2670     pA->sw.y = y;
2671     return;
2672   }
2673   if( pA->sw.x>x ) pA->sw.x = x;
2674   if( pA->sw.y>y ) pA->sw.y = y;
2675   if( pA->ne.x<x ) pA->ne.x = x;
2676   if( pA->ne.y<y ) pA->ne.y = y;
2677 }
2678 
2679 /* Enlarge the PBox so that it is able to contain an ellipse
2680 ** centered at x,y and with radiuses rx and ry.
2681 */
pik_bbox_addellipse(PBox * pA,PNum x,PNum y,PNum rx,PNum ry)2682 static void pik_bbox_addellipse(PBox *pA, PNum x, PNum y, PNum rx, PNum ry){
2683   if( pik_bbox_isempty(pA) ){
2684     pA->ne.x = x+rx;
2685     pA->ne.y = y+ry;
2686     pA->sw.x = x-rx;
2687     pA->sw.y = y-ry;
2688     return;
2689   }
2690   if( pA->sw.x>x-rx ) pA->sw.x = x-rx;
2691   if( pA->sw.y>y-ry ) pA->sw.y = y-ry;
2692   if( pA->ne.x<x+rx ) pA->ne.x = x+rx;
2693   if( pA->ne.y<y+ry ) pA->ne.y = y+ry;
2694 }
2695 
2696 
2697 
2698 /* Append a new object onto the end of an object list.  The
2699 ** object list is created if it does not already exist.  Return
2700 ** the new object list.
2701 */
pik_elist_append(Pik * p,PList * pList,PObj * pObj)2702 static PList *pik_elist_append(Pik *p, PList *pList, PObj *pObj){
2703   if( pObj==0 ) return pList;
2704   if( pList==0 ){
2705     pList = malloc(sizeof(*pList));
2706     if( pList==0 ){
2707       pik_error(p, 0, 0);
2708       pik_elem_free(p, pObj);
2709       return 0;
2710     }
2711     memset(pList, 0, sizeof(*pList));
2712   }
2713   if( pList->n>=pList->nAlloc ){
2714     int nNew = (pList->n+5)*2;
2715     PObj **pNew = realloc(pList->a, sizeof(PObj*)*nNew);
2716     if( pNew==0 ){
2717       pik_error(p, 0, 0);
2718       pik_elem_free(p, pObj);
2719       return pList;
2720     }
2721     pList->nAlloc = nNew;
2722     pList->a = pNew;
2723   }
2724   pList->a[pList->n++] = pObj;
2725   p->list = pList;
2726   return pList;
2727 }
2728 
2729 /* Convert an object class name into a PClass pointer
2730 */
pik_find_class(PToken * pId)2731 static const PClass *pik_find_class(PToken *pId){
2732   int first = 0;
2733   int last = count(aClass) - 1;
2734   do{
2735     int mid = (first+last)/2;
2736     int c = strncmp(aClass[mid].zName, pId->z, pId->n);
2737     if( c==0 ){
2738       c = aClass[mid].zName[pId->n]!=0;
2739       if( c==0 ) return &aClass[mid];
2740     }
2741     if( c<0 ){
2742       first = mid + 1;
2743     }else{
2744       last = mid - 1;
2745     }
2746   }while( first<=last );
2747   return 0;
2748 }
2749 
2750 /* Allocate and return a new PObj object.
2751 **
2752 ** If pId!=0 then pId is an identifier that defines the object class.
2753 ** If pStr!=0 then it is a STRING literal that defines a text object.
2754 ** If pSublist!=0 then this is a [...] object. If all three parameters
2755 ** are NULL then this is a no-op object used to define a PLACENAME.
2756 */
pik_elem_new(Pik * p,PToken * pId,PToken * pStr,PList * pSublist)2757 static PObj *pik_elem_new(Pik *p, PToken *pId, PToken *pStr,PList *pSublist){
2758   PObj *pNew;
2759   int miss = 0;
2760 
2761   if( p->nErr ) return 0;
2762   pNew = malloc( sizeof(*pNew) );
2763   if( pNew==0 ){
2764     pik_error(p,0,0);
2765     pik_elist_free(p, pSublist);
2766     return 0;
2767   }
2768   memset(pNew, 0, sizeof(*pNew));
2769   p->cur = pNew;
2770   p->nTPath = 1;
2771   p->thenFlag = 0;
2772   if( p->list==0 || p->list->n==0 ){
2773     pNew->ptAt.x = pNew->ptAt.y = 0.0;
2774     pNew->eWith = CP_C;
2775   }else{
2776     PObj *pPrior = p->list->a[p->list->n-1];
2777     pNew->ptAt = pPrior->ptExit;
2778     switch( p->eDir ){
2779       default:         pNew->eWith = CP_W;   break;
2780       case DIR_LEFT:   pNew->eWith = CP_E;   break;
2781       case DIR_UP:     pNew->eWith = CP_S;   break;
2782       case DIR_DOWN:   pNew->eWith = CP_N;   break;
2783     }
2784   }
2785   p->aTPath[0] = pNew->ptAt;
2786   pNew->with = pNew->ptAt;
2787   pNew->outDir = pNew->inDir = p->eDir;
2788   pNew->iLayer = (int)pik_value(p, "layer", 5, &miss);
2789   if( miss ) pNew->iLayer = 1000;
2790   if( pNew->iLayer<0 ) pNew->iLayer = 0;
2791   if( pSublist ){
2792     pNew->type = &sublistClass;
2793     pNew->pSublist = pSublist;
2794     sublistClass.xInit(p,pNew);
2795     return pNew;
2796   }
2797   if( pStr ){
2798     PToken n;
2799     n.z = "text";
2800     n.n = 4;
2801     pNew->type = pik_find_class(&n);
2802     assert( pNew->type!=0 );
2803     pNew->errTok = *pStr;
2804     pNew->type->xInit(p, pNew);
2805     pik_add_txt(p, pStr, pStr->eCode);
2806     return pNew;
2807   }
2808   if( pId ){
2809     const PClass *pClass;
2810     pNew->errTok = *pId;
2811     pClass = pik_find_class(pId);
2812     if( pClass ){
2813       pNew->type = pClass;
2814       pNew->sw = pik_value(p, "thickness",9,0);
2815       pNew->fill = pik_value(p, "fill",4,0);
2816       pNew->color = pik_value(p, "color",5,0);
2817       pClass->xInit(p, pNew);
2818       return pNew;
2819     }
2820     pik_error(p, pId, "unknown object type");
2821     pik_elem_free(p, pNew);
2822     return 0;
2823   }
2824   pNew->type = &noopClass;
2825   pNew->ptExit = pNew->ptEnter = pNew->ptAt;
2826   return pNew;
2827 }
2828 
2829 /*
2830 ** If the ID token in the argument is the name of a macro, return
2831 ** the PMacro object for that macro
2832 */
pik_find_macro(Pik * p,PToken * pId)2833 static PMacro *pik_find_macro(Pik *p, PToken *pId){
2834   PMacro *pMac;
2835   for(pMac = p->pMacros; pMac; pMac=pMac->pNext){
2836     if( pMac->macroName.n==pId->n
2837      && strncmp(pMac->macroName.z,pId->z,pId->n)==0
2838     ){
2839       return pMac;
2840     }
2841   }
2842   return 0;
2843 }
2844 
2845 /* Add a new macro
2846 */
pik_add_macro(Pik * p,PToken * pId,PToken * pCode)2847 static void pik_add_macro(
2848   Pik *p,          /* Current Pikchr diagram */
2849   PToken *pId,     /* The ID token that defines the macro name */
2850   PToken *pCode    /* Macro body inside of {...} */
2851 ){
2852   PMacro *pNew = pik_find_macro(p, pId);
2853   if( pNew==0 ){
2854     pNew = malloc( sizeof(*pNew) );
2855     if( pNew==0 ){
2856       pik_error(p, 0, 0);
2857       return;
2858     }
2859     pNew->pNext = p->pMacros;
2860     p->pMacros = pNew;
2861     pNew->macroName = *pId;
2862   }
2863   pNew->macroBody.z = pCode->z+1;
2864   pNew->macroBody.n = pCode->n-2;
2865   pNew->inUse = 0;
2866 }
2867 
2868 
2869 /*
2870 ** Set the output direction and exit point for an object
2871 */
pik_elem_set_exit(PObj * pObj,int eDir)2872 static void pik_elem_set_exit(PObj *pObj, int eDir){
2873   assert( ValidDir(eDir) );
2874   pObj->outDir = eDir;
2875   if( !pObj->type->isLine || pObj->bClose ){
2876     pObj->ptExit = pObj->ptAt;
2877     switch( pObj->outDir ){
2878       default:         pObj->ptExit.x += pObj->w*0.5;  break;
2879       case DIR_LEFT:   pObj->ptExit.x -= pObj->w*0.5;  break;
2880       case DIR_UP:     pObj->ptExit.y += pObj->h*0.5;  break;
2881       case DIR_DOWN:   pObj->ptExit.y -= pObj->h*0.5;  break;
2882     }
2883   }
2884 }
2885 
2886 /* Change the layout direction.
2887 */
pik_set_direction(Pik * p,int eDir)2888 static void pik_set_direction(Pik *p, int eDir){
2889   assert( ValidDir(eDir) );
2890   p->eDir = (unsigned char)eDir;
2891 
2892   /* It seems to make sense to reach back into the last object and
2893   ** change its exit point (its ".end") to correspond to the new
2894   ** direction.  Things just seem to work better this way.  However,
2895   ** legacy PIC does *not* do this.
2896   **
2897   ** The difference can be seen in a script like this:
2898   **
2899   **      arrow; circle; down; arrow
2900   **
2901   ** You can make pikchr render the above exactly like PIC
2902   ** by deleting the following three lines.  But I (drh) think
2903   ** it works better with those lines in place.
2904   */
2905   if( p->list && p->list->n ){
2906     pik_elem_set_exit(p->list->a[p->list->n-1], eDir);
2907   }
2908 }
2909 
2910 /* Move all coordinates contained within an object (and within its
2911 ** substructure) by dx, dy
2912 */
pik_elem_move(PObj * pObj,PNum dx,PNum dy)2913 static void pik_elem_move(PObj *pObj, PNum dx, PNum dy){
2914   int i;
2915   pObj->ptAt.x += dx;
2916   pObj->ptAt.y += dy;
2917   pObj->ptEnter.x += dx;
2918   pObj->ptEnter.y += dy;
2919   pObj->ptExit.x += dx;
2920   pObj->ptExit.y += dy;
2921   pObj->bbox.ne.x += dx;
2922   pObj->bbox.ne.y += dy;
2923   pObj->bbox.sw.x += dx;
2924   pObj->bbox.sw.y += dy;
2925   for(i=0; i<pObj->nPath; i++){
2926     pObj->aPath[i].x += dx;
2927     pObj->aPath[i].y += dy;
2928   }
2929   if( pObj->pSublist ){
2930     pik_elist_move(pObj->pSublist, dx, dy);
2931   }
2932 }
pik_elist_move(PList * pList,PNum dx,PNum dy)2933 static void pik_elist_move(PList *pList, PNum dx, PNum dy){
2934   int i;
2935   for(i=0; i<pList->n; i++){
2936     pik_elem_move(pList->a[i], dx, dy);
2937   }
2938 }
2939 
2940 /*
2941 ** Check to see if it is ok to set the value of paraemeter mThis.
2942 ** Return 0 if it is ok. If it not ok, generate an appropriate
2943 ** error message and return non-zero.
2944 **
2945 ** Flags are set in pObj so that the same object or conflicting
2946 ** objects may not be set again.
2947 **
2948 ** To be ok, bit mThis must be clear and no more than one of
2949 ** the bits identified by mBlockers may be set.
2950 */
pik_param_ok(Pik * p,PObj * pObj,PToken * pId,int mThis)2951 static int pik_param_ok(
2952   Pik *p,             /* For storing the error message (if any) */
2953   PObj *pObj,       /* The object under construction */
2954   PToken *pId,        /* Make the error point to this token */
2955   int mThis           /* Value we are trying to set */
2956 ){
2957   if( pObj->mProp & mThis ){
2958     pik_error(p, pId, "value is already set");
2959     return 1;
2960   }
2961   if( pObj->mCalc & mThis ){
2962     pik_error(p, pId, "value already fixed by prior constraints");
2963     return 1;
2964   }
2965   pObj->mProp |= mThis;
2966   return 0;
2967 }
2968 
2969 
2970 /*
2971 ** Set a numeric property like "width 7" or "radius 200%".
2972 **
2973 ** The rAbs term is an absolute value to add in.  rRel is
2974 ** a relative value by which to change the current value.
2975 */
pik_set_numprop(Pik * p,PToken * pId,PRel * pVal)2976 void pik_set_numprop(Pik *p, PToken *pId, PRel *pVal){
2977   PObj *pObj = p->cur;
2978   switch( pId->eType ){
2979     case T_HEIGHT:
2980       if( pik_param_ok(p, pObj, pId, A_HEIGHT) ) return;
2981       pObj->h = pObj->h*pVal->rRel + pVal->rAbs;
2982       break;
2983     case T_WIDTH:
2984       if( pik_param_ok(p, pObj, pId, A_WIDTH) ) return;
2985       pObj->w = pObj->w*pVal->rRel + pVal->rAbs;
2986       break;
2987     case T_RADIUS:
2988       if( pik_param_ok(p, pObj, pId, A_RADIUS) ) return;
2989       pObj->rad = pObj->rad*pVal->rRel + pVal->rAbs;
2990       break;
2991     case T_DIAMETER:
2992       if( pik_param_ok(p, pObj, pId, A_RADIUS) ) return;
2993       pObj->rad = pObj->rad*pVal->rRel + 0.5*pVal->rAbs; /* diam it 2x rad */
2994       break;
2995     case T_THICKNESS:
2996       if( pik_param_ok(p, pObj, pId, A_THICKNESS) ) return;
2997       pObj->sw = pObj->sw*pVal->rRel + pVal->rAbs;
2998       break;
2999   }
3000   if( pObj->type->xNumProp ){
3001     pObj->type->xNumProp(p, pObj, pId);
3002   }
3003   return;
3004 }
3005 
3006 /*
3007 ** Set a color property.  The argument is an RGB value.
3008 */
pik_set_clrprop(Pik * p,PToken * pId,PNum rClr)3009 void pik_set_clrprop(Pik *p, PToken *pId, PNum rClr){
3010   PObj *pObj = p->cur;
3011   switch( pId->eType ){
3012     case T_FILL:
3013       if( pik_param_ok(p, pObj, pId, A_FILL) ) return;
3014       pObj->fill = rClr;
3015       break;
3016     case T_COLOR:
3017       if( pik_param_ok(p, pObj, pId, A_COLOR) ) return;
3018       pObj->color = rClr;
3019       break;
3020   }
3021   if( pObj->type->xNumProp ){
3022     pObj->type->xNumProp(p, pObj, pId);
3023   }
3024   return;
3025 }
3026 
3027 /*
3028 ** Set a "dashed" property like "dash 0.05"
3029 **
3030 ** Use the value supplied by pVal if available.  If pVal==0, use
3031 ** a default.
3032 */
pik_set_dashed(Pik * p,PToken * pId,PNum * pVal)3033 void pik_set_dashed(Pik *p, PToken *pId, PNum *pVal){
3034   PObj *pObj = p->cur;
3035   PNum v;
3036   switch( pId->eType ){
3037     case T_DOTTED:  {
3038       v = pVal==0 ? pik_value(p,"dashwid",7,0) : *pVal;
3039       pObj->dotted = v;
3040       pObj->dashed = 0.0;
3041       break;
3042     }
3043     case T_DASHED:  {
3044       v = pVal==0 ? pik_value(p,"dashwid",7,0) : *pVal;
3045       pObj->dashed = v;
3046       pObj->dotted = 0.0;
3047       break;
3048     }
3049   }
3050 }
3051 
3052 /*
3053 ** If the current path information came from a "same" or "same as"
3054 ** reset it.
3055 */
pik_reset_samepath(Pik * p)3056 static void pik_reset_samepath(Pik *p){
3057   if( p->samePath ){
3058     p->samePath = 0;
3059     p->nTPath = 1;
3060   }
3061 }
3062 
3063 
3064 /* Add a new term to the path for a line-oriented object by transferring
3065 ** the information in the ptTo field over onto the path and into ptFrom
3066 ** resetting the ptTo.
3067 */
pik_then(Pik * p,PToken * pToken,PObj * pObj)3068 static void pik_then(Pik *p, PToken *pToken, PObj *pObj){
3069   int n;
3070   if( !pObj->type->isLine ){
3071     pik_error(p, pToken, "use with line-oriented objects only");
3072     return;
3073   }
3074   n = p->nTPath - 1;
3075   if( n<1 && (pObj->mProp & A_FROM)==0 ){
3076     pik_error(p, pToken, "no prior path points");
3077     return;
3078   }
3079   p->thenFlag = 1;
3080 }
3081 
3082 /* Advance to the next entry in p->aTPath.  Return its index.
3083 */
pik_next_rpath(Pik * p,PToken * pErr)3084 static int pik_next_rpath(Pik *p, PToken *pErr){
3085   int n = p->nTPath - 1;
3086   if( n+1>=(int)count(p->aTPath) ){
3087     pik_error(0, pErr, "too many path elements");
3088     return n;
3089   }
3090   n++;
3091   p->nTPath++;
3092   p->aTPath[n] = p->aTPath[n-1];
3093   p->mTPath = 0;
3094   return n;
3095 }
3096 
3097 /* Add a direction term to an object.  "up 0.5", or "left 3", or "down"
3098 ** or "down 50%".
3099 */
pik_add_direction(Pik * p,PToken * pDir,PRel * pVal)3100 static void pik_add_direction(Pik *p, PToken *pDir, PRel *pVal){
3101   PObj *pObj = p->cur;
3102   int n;
3103   int dir;
3104   if( !pObj->type->isLine ){
3105     if( pDir ){
3106       pik_error(p, pDir, "use with line-oriented objects only");
3107     }else{
3108       PToken x = pik_next_semantic_token(&pObj->errTok);
3109       pik_error(p, &x, "syntax error");
3110     }
3111     return;
3112   }
3113   pik_reset_samepath(p);
3114   n = p->nTPath - 1;
3115   if( p->thenFlag || p->mTPath==3 || n==0 ){
3116     n = pik_next_rpath(p, pDir);
3117     p->thenFlag = 0;
3118   }
3119   dir = pDir ? pDir->eCode : p->eDir;
3120   switch( dir ){
3121     case DIR_UP:
3122        if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir);
3123        p->aTPath[n].y += pVal->rAbs + pObj->h*pVal->rRel;
3124        p->mTPath |= 2;
3125        break;
3126     case DIR_DOWN:
3127        if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir);
3128        p->aTPath[n].y -= pVal->rAbs + pObj->h*pVal->rRel;
3129        p->mTPath |= 2;
3130        break;
3131     case DIR_RIGHT:
3132        if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir);
3133        p->aTPath[n].x += pVal->rAbs + pObj->w*pVal->rRel;
3134        p->mTPath |= 1;
3135        break;
3136     case DIR_LEFT:
3137        if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir);
3138        p->aTPath[n].x -= pVal->rAbs + pObj->w*pVal->rRel;
3139        p->mTPath |= 1;
3140        break;
3141   }
3142   pObj->outDir = dir;
3143 }
3144 
3145 /* Process a movement attribute of one of these forms:
3146 **
3147 **         pDist   pHdgKW  rHdg    pEdgept
3148 **     GO distance HEADING angle
3149 **     GO distance               compasspoint
3150 */
pik_move_hdg(Pik * p,PRel * pDist,PToken * pHeading,PNum rHdg,PToken * pEdgept,PToken * pErr)3151 static void pik_move_hdg(
3152   Pik *p,              /* The Pikchr context */
3153   PRel *pDist,         /* Distance to move */
3154   PToken *pHeading,    /* "heading" keyword if present */
3155   PNum rHdg,           /* Angle argument to "heading" keyword */
3156   PToken *pEdgept,     /* EDGEPT keyword "ne", "sw", etc... */
3157   PToken *pErr         /* Token to use for error messages */
3158 ){
3159   PObj *pObj = p->cur;
3160   int n;
3161   PNum rDist = pDist->rAbs + pik_value(p,"linewid",7,0)*pDist->rRel;
3162   if( !pObj->type->isLine ){
3163     pik_error(p, pErr, "use with line-oriented objects only");
3164     return;
3165   }
3166   pik_reset_samepath(p);
3167   do{
3168     n = pik_next_rpath(p, pErr);
3169   }while( n<1 );
3170   if( pHeading ){
3171     if( rHdg<0.0 || rHdg>360.0 ){
3172       pik_error(p, pHeading, "headings should be between 0 and 360");
3173       return;
3174     }
3175   }else if( pEdgept->eEdge==CP_C ){
3176     pik_error(p, pEdgept, "syntax error");
3177     return;
3178   }else{
3179     rHdg = pik_hdg_angle[pEdgept->eEdge];
3180   }
3181   if( rHdg<=45.0 ){
3182     pObj->outDir = DIR_UP;
3183   }else if( rHdg<=135.0 ){
3184     pObj->outDir = DIR_RIGHT;
3185   }else if( rHdg<=225.0 ){
3186     pObj->outDir = DIR_DOWN;
3187   }else if( rHdg<=315.0 ){
3188     pObj->outDir = DIR_LEFT;
3189   }else{
3190     pObj->outDir = DIR_UP;
3191   }
3192   rHdg *= 0.017453292519943295769;  /* degrees to radians */
3193   p->aTPath[n].x += rDist*sin(rHdg);
3194   p->aTPath[n].y += rDist*cos(rHdg);
3195   p->mTPath = 2;
3196 }
3197 
3198 
3199 /* Process a movement attribute of the form "right until even with ..."
3200 **
3201 ** pDir is the first keyword, "right" or "left" or "up" or "down".
3202 ** The movement is in that direction until its closest approach to
3203 ** the point specified by pPoint.
3204 */
pik_evenwith(Pik * p,PToken * pDir,PPoint * pPlace)3205 static void pik_evenwith(Pik *p, PToken *pDir, PPoint *pPlace){
3206   PObj *pObj = p->cur;
3207   int n;
3208   if( !pObj->type->isLine ){
3209     pik_error(p, pDir, "use with line-oriented objects only");
3210     return;
3211   }
3212   pik_reset_samepath(p);
3213   n = p->nTPath - 1;
3214   if( p->thenFlag || p->mTPath==3 || n==0 ){
3215     n = pik_next_rpath(p, pDir);
3216     p->thenFlag = 0;
3217   }
3218   switch( pDir->eCode ){
3219     case DIR_DOWN:
3220     case DIR_UP:
3221        if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir);
3222        p->aTPath[n].y = pPlace->y;
3223        p->mTPath |= 2;
3224        break;
3225     case DIR_RIGHT:
3226     case DIR_LEFT:
3227        if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir);
3228        p->aTPath[n].x = pPlace->x;
3229        p->mTPath |= 1;
3230        break;
3231   }
3232   pObj->outDir = pDir->eCode;
3233 }
3234 
3235 /* Set the "from" of an object
3236 */
pik_set_from(Pik * p,PObj * pObj,PToken * pTk,PPoint * pPt)3237 static void pik_set_from(Pik *p, PObj *pObj, PToken *pTk, PPoint *pPt){
3238   if( !pObj->type->isLine ){
3239     pik_error(p, pTk, "use \"at\" to position this object");
3240     return;
3241   }
3242   if( pObj->mProp & A_FROM ){
3243     pik_error(p, pTk, "line start location already fixed");
3244     return;
3245   }
3246   if( pObj->bClose ){
3247     pik_error(p, pTk, "polygon is closed");
3248     return;
3249   }
3250   if( p->nTPath>1 ){
3251     PNum dx = pPt->x - p->aTPath[0].x;
3252     PNum dy = pPt->y - p->aTPath[0].y;
3253     int i;
3254     for(i=1; i<p->nTPath; i++){
3255       p->aTPath[i].x += dx;
3256       p->aTPath[i].y += dy;
3257     }
3258   }
3259   p->aTPath[0] = *pPt;
3260   p->mTPath = 3;
3261   pObj->mProp |= A_FROM;
3262 }
3263 
3264 /* Set the "to" of an object
3265 */
pik_add_to(Pik * p,PObj * pObj,PToken * pTk,PPoint * pPt)3266 static void pik_add_to(Pik *p, PObj *pObj, PToken *pTk, PPoint *pPt){
3267   int n = p->nTPath-1;
3268   if( !pObj->type->isLine ){
3269     pik_error(p, pTk, "use \"at\" to position this object");
3270     return;
3271   }
3272   if( pObj->bClose ){
3273     pik_error(p, pTk, "polygon is closed");
3274     return;
3275   }
3276   pik_reset_samepath(p);
3277   if( n==0 || p->mTPath==3 || p->thenFlag ){
3278     n = pik_next_rpath(p, pTk);
3279   }
3280   p->aTPath[n] = *pPt;
3281   p->mTPath = 3;
3282 }
3283 
pik_close_path(Pik * p,PToken * pErr)3284 static void pik_close_path(Pik *p, PToken *pErr){
3285   PObj *pObj = p->cur;
3286   if( p->nTPath<3 ){
3287     pik_error(p, pErr,
3288       "need at least 3 vertexes in order to close the polygon");
3289     return;
3290   }
3291   if( pObj->bClose ){
3292     pik_error(p, pErr, "polygon already closed");
3293     return;
3294   }
3295   pObj->bClose = 1;
3296 }
3297 
3298 /* Lower the layer of the current object so that it is behind the
3299 ** given object.
3300 */
pik_behind(Pik * p,PObj * pOther)3301 static void pik_behind(Pik *p, PObj *pOther){
3302   PObj *pObj = p->cur;
3303   if( p->nErr==0 && pObj->iLayer>=pOther->iLayer ){
3304     pObj->iLayer = pOther->iLayer - 1;
3305   }
3306 }
3307 
3308 
3309 /* Set the "at" of an object
3310 */
pik_set_at(Pik * p,PToken * pEdge,PPoint * pAt,PToken * pErrTok)3311 static void pik_set_at(Pik *p, PToken *pEdge, PPoint *pAt, PToken *pErrTok){
3312   PObj *pObj;
3313   static unsigned char eDirToCp[] = { CP_E, CP_S, CP_W, CP_N };
3314   if( p->nErr ) return;
3315   pObj = p->cur;
3316 
3317   if( pObj->type->isLine ){
3318     pik_error(p, pErrTok, "use \"from\" and \"to\" to position this object");
3319     return;
3320   }
3321   if( pObj->mProp & A_AT ){
3322     pik_error(p, pErrTok, "location fixed by prior \"at\"");
3323     return;
3324   }
3325   pObj->mProp |= A_AT;
3326   pObj->eWith = pEdge ? pEdge->eEdge : CP_C;
3327   if( pObj->eWith>=CP_END ){
3328     int dir = pObj->eWith==CP_END ? pObj->outDir : pObj->inDir;
3329     pObj->eWith = eDirToCp[dir];
3330   }
3331   pObj->with = *pAt;
3332 }
3333 
3334 /*
3335 ** Try to add a text attribute to an object
3336 */
pik_add_txt(Pik * p,PToken * pTxt,int iPos)3337 static void pik_add_txt(Pik *p, PToken *pTxt, int iPos){
3338   PObj *pObj = p->cur;
3339   PToken *pT;
3340   if( pObj->nTxt >= count(pObj->aTxt) ){
3341     pik_error(p, pTxt, "too many text terms");
3342     return;
3343   }
3344   pT = &pObj->aTxt[pObj->nTxt++];
3345   *pT = *pTxt;
3346   pT->eCode = (short)iPos;
3347 }
3348 
3349 /* Merge "text-position" flags
3350 */
pik_text_position(int iPrev,PToken * pFlag)3351 static int pik_text_position(int iPrev, PToken *pFlag){
3352   int iRes = iPrev;
3353   switch( pFlag->eType ){
3354     case T_LJUST:    iRes = (iRes&~TP_JMASK) | TP_LJUST;  break;
3355     case T_RJUST:    iRes = (iRes&~TP_JMASK) | TP_RJUST;  break;
3356     case T_ABOVE:    iRes = (iRes&~TP_VMASK) | TP_ABOVE;  break;
3357     case T_CENTER:   iRes = (iRes&~TP_VMASK) | TP_CENTER; break;
3358     case T_BELOW:    iRes = (iRes&~TP_VMASK) | TP_BELOW;  break;
3359     case T_ITALIC:   iRes |= TP_ITALIC;                   break;
3360     case T_BOLD:     iRes |= TP_BOLD;                     break;
3361     case T_ALIGNED:  iRes |= TP_ALIGN;                    break;
3362     case T_BIG:      if( iRes & TP_BIG ) iRes |= TP_XTRA;
3363                      else iRes = (iRes &~TP_SZMASK)|TP_BIG;   break;
3364     case T_SMALL:    if( iRes & TP_SMALL ) iRes |= TP_XTRA;
3365                      else iRes = (iRes &~TP_SZMASK)|TP_SMALL; break;
3366   }
3367   return iRes;
3368 }
3369 
3370 /*
3371 ** Table of scale-factor estimates for variable-width characters.
3372 ** Actual character widths vary by font.  These numbers are only
3373 ** guesses.  And this table only provides data for ASCII.
3374 **
3375 ** 100 means normal width.
3376 */
3377 static const unsigned char awChar[] = {
3378   /* Skip initial 32 control characters */
3379   /* ' ' */  45,
3380   /* '!' */  55,
3381   /* '"' */  62,
3382   /* '#' */  115,
3383   /* '$' */  90,
3384   /* '%' */  132,
3385   /* '&' */  125,
3386   /* '\''*/  40,
3387 
3388   /* '(' */  55,
3389   /* ')' */  55,
3390   /* '*' */  71,
3391   /* '+' */  115,
3392   /* ',' */  45,
3393   /* '-' */  48,
3394   /* '.' */  45,
3395   /* '/' */  50,
3396 
3397   /* '0' */  91,
3398   /* '1' */  91,
3399   /* '2' */  91,
3400   /* '3' */  91,
3401   /* '4' */  91,
3402   /* '5' */  91,
3403   /* '6' */  91,
3404   /* '7' */  91,
3405 
3406   /* '8' */  91,
3407   /* '9' */  91,
3408   /* ':' */  50,
3409   /* ';' */  50,
3410   /* '<' */ 120,
3411   /* '=' */ 120,
3412   /* '>' */ 120,
3413   /* '?' */  78,
3414 
3415   /* '@' */ 142,
3416   /* 'A' */ 102,
3417   /* 'B' */ 105,
3418   /* 'C' */ 110,
3419   /* 'D' */ 115,
3420   /* 'E' */ 105,
3421   /* 'F' */  98,
3422   /* 'G' */ 105,
3423 
3424   /* 'H' */ 125,
3425   /* 'I' */  58,
3426   /* 'J' */  58,
3427   /* 'K' */ 107,
3428   /* 'L' */  95,
3429   /* 'M' */ 145,
3430   /* 'N' */ 125,
3431   /* 'O' */ 115,
3432 
3433   /* 'P' */  95,
3434   /* 'Q' */ 115,
3435   /* 'R' */ 107,
3436   /* 'S' */  95,
3437   /* 'T' */  97,
3438   /* 'U' */ 118,
3439   /* 'V' */ 102,
3440   /* 'W' */ 150,
3441 
3442   /* 'X' */ 100,
3443   /* 'Y' */  93,
3444   /* 'Z' */ 100,
3445   /* '[' */  58,
3446   /* '\\'*/  50,
3447   /* ']' */  58,
3448   /* '^' */ 119,
3449   /* '_' */  72,
3450 
3451   /* '`' */  72,
3452   /* 'a' */  86,
3453   /* 'b' */  92,
3454   /* 'c' */  80,
3455   /* 'd' */  92,
3456   /* 'e' */  85,
3457   /* 'f' */  52,
3458   /* 'g' */  92,
3459 
3460   /* 'h' */  92,
3461   /* 'i' */  47,
3462   /* 'j' */  47,
3463   /* 'k' */  88,
3464   /* 'l' */  48,
3465   /* 'm' */ 135,
3466   /* 'n' */  92,
3467   /* 'o' */  86,
3468 
3469   /* 'p' */  92,
3470   /* 'q' */  92,
3471   /* 'r' */  69,
3472   /* 's' */  75,
3473   /* 't' */  58,
3474   /* 'u' */  92,
3475   /* 'v' */  80,
3476   /* 'w' */ 121,
3477 
3478   /* 'x' */  81,
3479   /* 'y' */  80,
3480   /* 'z' */  76,
3481   /* '{' */  91,
3482   /* '|'*/   49,
3483   /* '}' */  91,
3484   /* '~' */ 118,
3485 };
3486 
3487 /* Return an estimate of the width of the displayed characters
3488 ** in a character string.  The returned value is 100 times the
3489 ** average character width.
3490 **
3491 ** Omit "\" used to escape characters.  And count entities like
3492 ** "&lt;" as a single character.  Multi-byte UTF8 characters count
3493 ** as a single character.
3494 **
3495 ** Attempt to scale the answer by the actual characters seen.  Wide
3496 ** characters count more than narrow characters.  But the widths are
3497 ** only guesses.
3498 */
pik_text_length(const PToken * pToken)3499 static int pik_text_length(const PToken *pToken){
3500   int n = pToken->n;
3501   const char *z = pToken->z;
3502   int cnt, j;
3503   for(j=1, cnt=0; j<n-1; j++){
3504     char c = z[j];
3505     if( c=='\\' && z[j+1]!='&' ){
3506       c = z[++j];
3507     }else if( c=='&' ){
3508       int k;
3509       for(k=j+1; k<j+7 && z[k]!=0 && z[k]!=';'; k++){}
3510       if( z[k]==';' ) j = k;
3511       cnt += 150;
3512       continue;
3513     }
3514     if( (c & 0xc0)==0xc0 ){
3515       while( j+1<n-1 && (z[j+1]&0xc0)==0x80 ){ j++; }
3516       cnt += 100;
3517       continue;
3518     }
3519     if( c>=0x20 && c<=0x7e ){
3520       cnt += awChar[c-0x20];
3521     }else{
3522       cnt += 100;
3523     }
3524   }
3525   return cnt;
3526 }
3527 
3528 /* Adjust the width, height, and/or radius of the object so that
3529 ** it fits around the text that has been added so far.
3530 **
3531 **    (1) Only text specified prior to this attribute is considered.
3532 **    (2) The text size is estimated based on the charht and charwid
3533 **        variable settings.
3534 **    (3) The fitted attributes can be changed again after this
3535 **        attribute, for example using "width 110%" if this auto-fit
3536 **        underestimates the text size.
3537 **    (4) Previously set attributes will not be altered.  In other words,
3538 **        "width 1in fit" might cause the height to change, but the
3539 **        width is now set.
3540 **    (5) This only works for attributes that have an xFit method.
3541 **
3542 ** The eWhich parameter is:
3543 **
3544 **    1:   Fit horizontally only
3545 **    2:   Fit vertically only
3546 **    3:   Fit both ways
3547 */
pik_size_to_fit(Pik * p,PToken * pFit,int eWhich)3548 static void pik_size_to_fit(Pik *p, PToken *pFit, int eWhich){
3549   PObj *pObj;
3550   PNum w, h;
3551   PBox bbox;
3552   if( p->nErr ) return;
3553   pObj = p->cur;
3554 
3555   if( pObj->nTxt==0 ){
3556     pik_error(0, pFit, "no text to fit to");
3557     return;
3558   }
3559   if( pObj->type->xFit==0 ) return;
3560   pik_bbox_init(&bbox);
3561   pik_compute_layout_settings(p);
3562   pik_append_txt(p, pObj, &bbox);
3563   w = (eWhich & 1)!=0 ? (bbox.ne.x - bbox.sw.x) + p->charWidth : 0;
3564   if( eWhich & 2 ){
3565     PNum h1, h2;
3566     h1 = (bbox.ne.y - pObj->ptAt.y);
3567     h2 = (pObj->ptAt.y - bbox.sw.y);
3568     h = 2.0*( h1<h2 ? h2 : h1 ) + 0.5*p->charHeight;
3569   }else{
3570     h = 0;
3571   }
3572   pObj->type->xFit(p, pObj, w, h);
3573   pObj->mProp |= A_FIT;
3574 }
3575 
3576 /* Set a local variable name to "val".
3577 **
3578 ** The name might be a built-in variable or a color name.  In either case,
3579 ** a new application-defined variable is set.  Since app-defined variables
3580 ** are searched first, this will override any built-in variables.
3581 */
pik_set_var(Pik * p,PToken * pId,PNum val,PToken * pOp)3582 static void pik_set_var(Pik *p, PToken *pId, PNum val, PToken *pOp){
3583   PVar *pVar = p->pVar;
3584   while( pVar ){
3585     if( pik_token_eq(pId,pVar->zName)==0 ) break;
3586     pVar = pVar->pNext;
3587   }
3588   if( pVar==0 ){
3589     char *z;
3590     pVar = malloc( pId->n+1 + sizeof(*pVar) );
3591     if( pVar==0 ){
3592       pik_error(p, 0, 0);
3593       return;
3594     }
3595     pVar->zName = z = (char*)&pVar[1];
3596     memcpy(z, pId->z, pId->n);
3597     z[pId->n] = 0;
3598     pVar->pNext = p->pVar;
3599     pVar->val = pik_value(p, pId->z, pId->n, 0);
3600     p->pVar = pVar;
3601   }
3602   switch( pOp->eCode ){
3603     case T_PLUS:  pVar->val += val; break;
3604     case T_STAR:  pVar->val *= val; break;
3605     case T_MINUS: pVar->val -= val; break;
3606     case T_SLASH:
3607       if( val==0.0 ){
3608         pik_error(p, pOp, "division by zero");
3609       }else{
3610         pVar->val /= val;
3611       }
3612       break;
3613     default:      pVar->val = val; break;
3614   }
3615   p->bLayoutVars = 0;  /* Clear the layout setting cache */
3616 }
3617 
3618 /*
3619 ** Search for the variable named z[0..n-1] in:
3620 **
3621 **   * Application defined variables
3622 **   * Built-in variables
3623 **
3624 ** Return the value of the variable if found.  If not found
3625 ** return 0.0.  Also if pMiss is not NULL, then set it to 1
3626 ** if not found.
3627 **
3628 ** This routine is a subroutine to pik_get_var().  But it is also
3629 ** used by object implementations to look up (possibly overwritten)
3630 ** values for built-in variables like "boxwid".
3631 */
pik_value(Pik * p,const char * z,int n,int * pMiss)3632 static PNum pik_value(Pik *p, const char *z, int n, int *pMiss){
3633   PVar *pVar;
3634   int first, last, mid, c;
3635   for(pVar=p->pVar; pVar; pVar=pVar->pNext){
3636     if( strncmp(pVar->zName,z,n)==0 && pVar->zName[n]==0 ){
3637       return pVar->val;
3638     }
3639   }
3640   first = 0;
3641   last = count(aBuiltin)-1;
3642   while( first<=last ){
3643     mid = (first+last)/2;
3644     c = strncmp(z,aBuiltin[mid].zName,n);
3645     if( c==0 && aBuiltin[mid].zName[n] ) c = 1;
3646     if( c==0 ) return aBuiltin[mid].val;
3647     if( c>0 ){
3648       first = mid+1;
3649     }else{
3650       last = mid-1;
3651     }
3652   }
3653   if( pMiss ) *pMiss = 1;
3654   return 0.0;
3655 }
3656 
3657 /*
3658 ** Look up a color-name.  Unlike other names in this program, the
3659 ** color-names are not case sensitive.  So "DarkBlue" and "darkblue"
3660 ** and "DARKBLUE" all find the same value (139).
3661 **
3662 ** If not found, return -99.0.  Also post an error if p!=NULL.
3663 **
3664 ** Special color names "None" and "Off" return -1.0 without causing
3665 ** an error.
3666 */
pik_lookup_color(Pik * p,PToken * pId)3667 static PNum pik_lookup_color(Pik *p, PToken *pId){
3668   int first, last, mid, c = 0;
3669   first = 0;
3670   last = count(aColor)-1;
3671   while( first<=last ){
3672     const char *zClr;
3673     int c1, c2;
3674     unsigned int i;
3675     mid = (first+last)/2;
3676     zClr = aColor[mid].zName;
3677     for(i=0; i<pId->n; i++){
3678       c1 = zClr[i]&0x7f;
3679       if( isupper(c1) ) c1 = tolower(c1);
3680       c2 = pId->z[i]&0x7f;
3681       if( isupper(c2) ) c2 = tolower(c2);
3682       c = c2 - c1;
3683       if( c ) break;
3684     }
3685     if( c==0 && aColor[mid].zName[pId->n] ) c = -1;
3686     if( c==0 ) return (double)aColor[mid].val;
3687     if( c>0 ){
3688       first = mid+1;
3689     }else{
3690       last = mid-1;
3691     }
3692   }
3693   if( p ) pik_error(p, pId, "not a known color name");
3694   return -99.0;
3695 }
3696 
3697 /* Get the value of a variable.
3698 **
3699 ** Search in order:
3700 **
3701 **    *  Application defined variables
3702 **    *  Built-in variables
3703 **    *  Color names
3704 **
3705 ** If no such variable is found, throw an error.
3706 */
pik_get_var(Pik * p,PToken * pId)3707 static PNum pik_get_var(Pik *p, PToken *pId){
3708   int miss = 0;
3709   PNum v = pik_value(p, pId->z, pId->n, &miss);
3710   if( miss==0 ) return v;
3711   v = pik_lookup_color(0, pId);
3712   if( v>-90.0 ) return v;
3713   pik_error(p,pId,"no such variable");
3714   return 0.0;
3715 }
3716 
3717 /* Convert a T_NTH token (ex: "2nd", "5th"} into a numeric value and
3718 ** return that value.  Throw an error if the value is too big.
3719 */
pik_nth_value(Pik * p,PToken * pNth)3720 static short int pik_nth_value(Pik *p, PToken *pNth){
3721   int i = atoi(pNth->z);
3722   if( i>1000 ){
3723     pik_error(p, pNth, "value too big - max '1000th'");
3724     i = 1;
3725   }
3726   if( i==0 && pik_token_eq(pNth,"first")==0 ) i = 1;
3727   return (short int)i;
3728 }
3729 
3730 /* Search for the NTH object.
3731 **
3732 ** If pBasis is not NULL then it should be a [] object.  Use the
3733 ** sublist of that [] object for the search.  If pBasis is not a []
3734 ** object, then throw an error.
3735 **
3736 ** The pNth token describes the N-th search.  The pNth->eCode value
3737 ** is one more than the number of items to skip.  It is negative
3738 ** to search backwards.  If pNth->eType==T_ID, then it is the name
3739 ** of a class to search for.  If pNth->eType==T_LB, then
3740 ** search for a [] object.  If pNth->eType==T_LAST, then search for
3741 ** any type.
3742 **
3743 ** Raise an error if the item is not found.
3744 */
pik_find_nth(Pik * p,PObj * pBasis,PToken * pNth)3745 static PObj *pik_find_nth(Pik *p, PObj *pBasis, PToken *pNth){
3746   PList *pList;
3747   int i, n;
3748   const PClass *pClass;
3749   if( pBasis==0 ){
3750     pList = p->list;
3751   }else{
3752     pList = pBasis->pSublist;
3753   }
3754   if( pList==0 ){
3755     pik_error(p, pNth, "no such object");
3756     return 0;
3757   }
3758   if( pNth->eType==T_LAST ){
3759     pClass = 0;
3760   }else if( pNth->eType==T_LB ){
3761     pClass = &sublistClass;
3762   }else{
3763     pClass = pik_find_class(pNth);
3764     if( pClass==0 ){
3765       pik_error(0, pNth, "no such object type");
3766       return 0;
3767     }
3768   }
3769   n = pNth->eCode;
3770   if( n<0 ){
3771     for(i=pList->n-1; i>=0; i--){
3772       PObj *pObj = pList->a[i];
3773       if( pClass && pObj->type!=pClass ) continue;
3774       n++;
3775       if( n==0 ){ return pObj; }
3776     }
3777   }else{
3778     for(i=0; i<pList->n; i++){
3779       PObj *pObj = pList->a[i];
3780       if( pClass && pObj->type!=pClass ) continue;
3781       n--;
3782       if( n==0 ){ return pObj; }
3783     }
3784   }
3785   pik_error(p, pNth, "no such object");
3786   return 0;
3787 }
3788 
3789 /* Search for an object by name.
3790 **
3791 ** Search in pBasis->pSublist if pBasis is not NULL.  If pBasis is NULL
3792 ** then search in p->list.
3793 */
pik_find_byname(Pik * p,PObj * pBasis,PToken * pName)3794 static PObj *pik_find_byname(Pik *p, PObj *pBasis, PToken *pName){
3795   PList *pList;
3796   int i, j;
3797   if( pBasis==0 ){
3798     pList = p->list;
3799   }else{
3800     pList = pBasis->pSublist;
3801   }
3802   if( pList==0 ){
3803     pik_error(p, pName, "no such object");
3804     return 0;
3805   }
3806   /* First look explicitly tagged objects */
3807   for(i=pList->n-1; i>=0; i--){
3808     PObj *pObj = pList->a[i];
3809     if( pObj->zName && pik_token_eq(pName,pObj->zName)==0 ){
3810       return pObj;
3811     }
3812   }
3813   /* If not found, do a second pass looking for any object containing
3814   ** text which exactly matches pName */
3815   for(i=pList->n-1; i>=0; i--){
3816     PObj *pObj = pList->a[i];
3817     for(j=0; j<pObj->nTxt; j++){
3818       if( pObj->aTxt[j].n==pName->n+2
3819        && memcmp(pObj->aTxt[j].z+1,pName->z,pName->n)==0 ){
3820         return pObj;
3821       }
3822     }
3823   }
3824   pik_error(p, pName, "no such object");
3825   return 0;
3826 }
3827 
3828 /* Change most of the settings for the current object to be the
3829 ** same as the pOther object, or the most recent object of the same
3830 ** type if pOther is NULL.
3831 */
pik_same(Pik * p,PObj * pOther,PToken * pErrTok)3832 static void pik_same(Pik *p, PObj *pOther, PToken *pErrTok){
3833   PObj *pObj = p->cur;
3834   if( p->nErr ) return;
3835   if( pOther==0 ){
3836     int i;
3837     for(i=(p->list ? p->list->n : 0)-1; i>=0; i--){
3838       pOther = p->list->a[i];
3839       if( pOther->type==pObj->type ) break;
3840     }
3841     if( i<0 ){
3842       pik_error(p, pErrTok, "no prior objects of the same type");
3843       return;
3844     }
3845   }
3846   if( pOther->nPath && pObj->type->isLine ){
3847     PNum dx, dy;
3848     int i;
3849     dx = p->aTPath[0].x - pOther->aPath[0].x;
3850     dy = p->aTPath[0].y - pOther->aPath[0].y;
3851     for(i=1; i<pOther->nPath; i++){
3852       p->aTPath[i].x = pOther->aPath[i].x + dx;
3853       p->aTPath[i].y = pOther->aPath[i].y + dy;
3854     }
3855     p->nTPath = pOther->nPath;
3856     p->mTPath = 3;
3857     p->samePath = 1;
3858   }
3859   if( !pObj->type->isLine ){
3860     pObj->w = pOther->w;
3861     pObj->h = pOther->h;
3862   }
3863   pObj->rad = pOther->rad;
3864   pObj->sw = pOther->sw;
3865   pObj->dashed = pOther->dashed;
3866   pObj->dotted = pOther->dotted;
3867   pObj->fill = pOther->fill;
3868   pObj->color = pOther->color;
3869   pObj->cw = pOther->cw;
3870   pObj->larrow = pOther->larrow;
3871   pObj->rarrow = pOther->rarrow;
3872   pObj->bClose = pOther->bClose;
3873   pObj->bChop = pOther->bChop;
3874   pObj->inDir = pOther->inDir;
3875   pObj->outDir = pOther->outDir;
3876   pObj->iLayer = pOther->iLayer;
3877 }
3878 
3879 
3880 /* Return a "Place" associated with object pObj.  If pEdge is NULL
3881 ** return the center of the object.  Otherwise, return the corner
3882 ** described by pEdge.
3883 */
pik_place_of_elem(Pik * p,PObj * pObj,PToken * pEdge)3884 static PPoint pik_place_of_elem(Pik *p, PObj *pObj, PToken *pEdge){
3885   PPoint pt = cZeroPoint;
3886   const PClass *pClass;
3887   if( pObj==0 ) return pt;
3888   if( pEdge==0 ){
3889     return pObj->ptAt;
3890   }
3891   pClass = pObj->type;
3892   if( pEdge->eType==T_EDGEPT || (pEdge->eEdge>0 && pEdge->eEdge<CP_END) ){
3893     pt = pClass->xOffset(p, pObj, pEdge->eEdge);
3894     pt.x += pObj->ptAt.x;
3895     pt.y += pObj->ptAt.y;
3896     return pt;
3897   }
3898   if( pEdge->eType==T_START ){
3899     return pObj->ptEnter;
3900   }else{
3901     return pObj->ptExit;
3902   }
3903 }
3904 
3905 /* Do a linear interpolation of two positions.
3906 */
pik_position_between(PNum x,PPoint p1,PPoint p2)3907 static PPoint pik_position_between(PNum x, PPoint p1, PPoint p2){
3908   PPoint out;
3909   out.x = p2.x*x + p1.x*(1.0 - x);
3910   out.y = p2.y*x + p1.y*(1.0 - x);
3911   return out;
3912 }
3913 
3914 /* Compute the position that is dist away from pt at an heading angle of r
3915 **
3916 ** The angle is a compass heading in degrees.  North is 0 (or 360).
3917 ** East is 90.  South is 180.  West is 270.  And so forth.
3918 */
pik_position_at_angle(PNum dist,PNum r,PPoint pt)3919 static PPoint pik_position_at_angle(PNum dist, PNum r, PPoint pt){
3920   r *= 0.017453292519943295769;  /* degrees to radians */
3921   pt.x += dist*sin(r);
3922   pt.y += dist*cos(r);
3923   return pt;
3924 }
3925 
3926 /* Compute the position that is dist away at a compass point
3927 */
pik_position_at_hdg(PNum dist,PToken * pD,PPoint pt)3928 static PPoint pik_position_at_hdg(PNum dist, PToken *pD, PPoint pt){
3929   return pik_position_at_angle(dist, pik_hdg_angle[pD->eEdge], pt);
3930 }
3931 
3932 /* Return the coordinates for the n-th vertex of a line.
3933 */
pik_nth_vertex(Pik * p,PToken * pNth,PToken * pErr,PObj * pObj)3934 static PPoint pik_nth_vertex(Pik *p, PToken *pNth, PToken *pErr, PObj *pObj){
3935   static const PPoint zero = {0, 0};
3936   int n;
3937   if( p->nErr || pObj==0 ) return p->aTPath[0];
3938   if( !pObj->type->isLine ){
3939     pik_error(p, pErr, "object is not a line");
3940     return zero;
3941   }
3942   n = atoi(pNth->z);
3943   if( n<1 || n>pObj->nPath ){
3944     pik_error(p, pNth, "no such vertex");
3945     return zero;
3946   }
3947   return pObj->aPath[n-1];
3948 }
3949 
3950 /* Return the value of a property of an object.
3951 */
pik_property_of(PObj * pObj,PToken * pProp)3952 static PNum pik_property_of(PObj *pObj, PToken *pProp){
3953   PNum v = 0.0;
3954   switch( pProp->eType ){
3955     case T_HEIGHT:    v = pObj->h;            break;
3956     case T_WIDTH:     v = pObj->w;            break;
3957     case T_RADIUS:    v = pObj->rad;          break;
3958     case T_DIAMETER:  v = pObj->rad*2.0;      break;
3959     case T_THICKNESS: v = pObj->sw;           break;
3960     case T_DASHED:    v = pObj->dashed;       break;
3961     case T_DOTTED:    v = pObj->dotted;       break;
3962     case T_FILL:      v = pObj->fill;         break;
3963     case T_COLOR:     v = pObj->color;        break;
3964     case T_X:         v = pObj->ptAt.x;       break;
3965     case T_Y:         v = pObj->ptAt.y;       break;
3966     case T_TOP:       v = pObj->bbox.ne.y;    break;
3967     case T_BOTTOM:    v = pObj->bbox.sw.y;    break;
3968     case T_LEFT:      v = pObj->bbox.sw.x;    break;
3969     case T_RIGHT:     v = pObj->bbox.ne.x;    break;
3970   }
3971   return v;
3972 }
3973 
3974 /* Compute one of the built-in functions
3975 */
pik_func(Pik * p,PToken * pFunc,PNum x,PNum y)3976 static PNum pik_func(Pik *p, PToken *pFunc, PNum x, PNum y){
3977   PNum v = 0.0;
3978   switch( pFunc->eCode ){
3979     case FN_ABS:  v = v<0.0 ? -v : v;  break;
3980     case FN_COS:  v = cos(x);          break;
3981     case FN_INT:  v = rint(x);         break;
3982     case FN_SIN:  v = sin(x);          break;
3983     case FN_SQRT:
3984       if( x<0.0 ){
3985         pik_error(p, pFunc, "sqrt of negative value");
3986         v = 0.0;
3987       }else{
3988         v = sqrt(x);
3989       }
3990       break;
3991     case FN_MAX:  v = x>y ? x : y;   break;
3992     case FN_MIN:  v = x<y ? x : y;   break;
3993     default:      v = 0.0;
3994   }
3995   return v;
3996 }
3997 
3998 /* Attach a name to an object
3999 */
pik_elem_setname(Pik * p,PObj * pObj,PToken * pName)4000 static void pik_elem_setname(Pik *p, PObj *pObj, PToken *pName){
4001   if( pObj==0 ) return;
4002   if( pName==0 ) return;
4003   free(pObj->zName);
4004   pObj->zName = malloc(pName->n+1);
4005   if( pObj->zName==0 ){
4006     pik_error(p,0,0);
4007   }else{
4008     memcpy(pObj->zName,pName->z,pName->n);
4009     pObj->zName[pName->n] = 0;
4010   }
4011   return;
4012 }
4013 
4014 /*
4015 ** Search for object located at *pCenter that has an xChop method.
4016 ** Return a pointer to the object, or NULL if not found.
4017 */
pik_find_chopper(PList * pList,PPoint * pCenter)4018 static PObj *pik_find_chopper(PList *pList, PPoint *pCenter){
4019   int i;
4020   if( pList==0 ) return 0;
4021   for(i=pList->n-1; i>=0; i--){
4022     PObj *pObj = pList->a[i];
4023     if( pObj->type->xChop!=0
4024      && pObj->ptAt.x==pCenter->x
4025      && pObj->ptAt.y==pCenter->y
4026     ){
4027       return pObj;
4028     }else if( pObj->pSublist ){
4029       pObj = pik_find_chopper(pObj->pSublist,pCenter);
4030       if( pObj ) return pObj;
4031     }
4032   }
4033   return 0;
4034 }
4035 
4036 /*
4037 ** There is a line traveling from pFrom to pTo.
4038 **
4039 ** If point pTo is the exact enter of a choppable object,
4040 ** then adjust pTo by the appropriate amount in the direction
4041 ** of pFrom.
4042 */
pik_autochop(Pik * p,PPoint * pFrom,PPoint * pTo)4043 static void pik_autochop(Pik *p, PPoint *pFrom, PPoint *pTo){
4044   PObj *pObj = pik_find_chopper(p->list, pTo);
4045   if( pObj ){
4046     *pTo = pObj->type->xChop(p, pObj, pFrom);
4047   }
4048 }
4049 
4050 /* This routine runs after all attributes have been received
4051 ** on an object.
4052 */
pik_after_adding_attributes(Pik * p,PObj * pObj)4053 static void pik_after_adding_attributes(Pik *p, PObj *pObj){
4054   int i;
4055   PPoint ofst;
4056   PNum dx, dy;
4057 
4058   if( p->nErr ) return;
4059 
4060   /* Position block objects */
4061   if( pObj->type->isLine==0 ){
4062     /* A height or width less than or equal to zero means "autofit".
4063     ** Change the height or width to be big enough to contain the text,
4064     */
4065     if( pObj->h<=0.0 ){
4066       if( pObj->nTxt==0 ){
4067         pObj->h = 0.0;
4068       }else if( pObj->w<=0.0 ){
4069         pik_size_to_fit(p, &pObj->errTok, 3);
4070       }else{
4071         pik_size_to_fit(p, &pObj->errTok, 2);
4072       }
4073     }
4074     if( pObj->w<=0.0 ){
4075       if( pObj->nTxt==0 ){
4076         pObj->w = 0.0;
4077       }else{
4078         pik_size_to_fit(p, &pObj->errTok, 1);
4079       }
4080     }
4081     ofst = pik_elem_offset(p, pObj, pObj->eWith);
4082     dx = (pObj->with.x - ofst.x) - pObj->ptAt.x;
4083     dy = (pObj->with.y - ofst.y) - pObj->ptAt.y;
4084     if( dx!=0 || dy!=0 ){
4085       pik_elem_move(pObj, dx, dy);
4086     }
4087   }
4088 
4089   /* For a line object with no movement specified, a single movement
4090   ** of the default length in the current direction
4091   */
4092   if( pObj->type->isLine && p->nTPath<2 ){
4093     pik_next_rpath(p, 0);
4094     assert( p->nTPath==2 );
4095     switch( pObj->inDir ){
4096       default:        p->aTPath[1].x += pObj->w; break;
4097       case DIR_DOWN:  p->aTPath[1].y -= pObj->h; break;
4098       case DIR_LEFT:  p->aTPath[1].x -= pObj->w; break;
4099       case DIR_UP:    p->aTPath[1].y += pObj->h; break;
4100     }
4101     if( pObj->type->xInit==arcInit ){
4102       pObj->outDir = (pObj->inDir + (pObj->cw ? 1 : 3))%4;
4103       p->eDir = (unsigned char)pObj->outDir;
4104       switch( pObj->outDir ){
4105         default:        p->aTPath[1].x += pObj->w; break;
4106         case DIR_DOWN:  p->aTPath[1].y -= pObj->h; break;
4107         case DIR_LEFT:  p->aTPath[1].x -= pObj->w; break;
4108         case DIR_UP:    p->aTPath[1].y += pObj->h; break;
4109       }
4110     }
4111   }
4112 
4113   /* Initialize the bounding box prior to running xCheck */
4114   pik_bbox_init(&pObj->bbox);
4115 
4116   /* Run object-specific code */
4117   if( pObj->type->xCheck!=0 ){
4118     pObj->type->xCheck(p,pObj);
4119     if( p->nErr ) return;
4120   }
4121 
4122   /* Compute final bounding box, entry and exit points, center
4123   ** point (ptAt) and path for the object
4124   */
4125   if( pObj->type->isLine ){
4126     pObj->aPath = malloc( sizeof(PPoint)*p->nTPath );
4127     if( pObj->aPath==0 ){
4128       pik_error(p, 0, 0);
4129       return;
4130     }else{
4131       pObj->nPath = p->nTPath;
4132       for(i=0; i<p->nTPath; i++){
4133         pObj->aPath[i] = p->aTPath[i];
4134       }
4135     }
4136 
4137     /* "chop" processing:
4138     ** If the line goes to the center of an object with an
4139     ** xChop method, then use the xChop method to trim the line.
4140     */
4141     if( pObj->bChop && pObj->nPath>=2 ){
4142       int n = pObj->nPath;
4143       pik_autochop(p, &pObj->aPath[n-2], &pObj->aPath[n-1]);
4144       pik_autochop(p, &pObj->aPath[1], &pObj->aPath[0]);
4145     }
4146 
4147     pObj->ptEnter = pObj->aPath[0];
4148     pObj->ptExit = pObj->aPath[pObj->nPath-1];
4149 
4150     /* Compute the center of the line based on the bounding box over
4151     ** the vertexes.  This is a difference from PIC.  In Pikchr, the
4152     ** center of a line is the center of its bounding box. In PIC, the
4153     ** center of a line is halfway between its .start and .end.  For
4154     ** straight lines, this is the same point, but for multi-segment
4155     ** lines the result is usually diferent */
4156     for(i=0; i<pObj->nPath; i++){
4157       pik_bbox_add_xy(&pObj->bbox, pObj->aPath[i].x, pObj->aPath[i].y);
4158     }
4159     pObj->ptAt.x = (pObj->bbox.ne.x + pObj->bbox.sw.x)/2.0;
4160     pObj->ptAt.y = (pObj->bbox.ne.y + pObj->bbox.sw.y)/2.0;
4161 
4162     /* Reset the width and height of the object to be the width and height
4163     ** of the bounding box over vertexes */
4164     pObj->w = pObj->bbox.ne.x - pObj->bbox.sw.x;
4165     pObj->h = pObj->bbox.ne.y - pObj->bbox.sw.y;
4166 
4167     /* If this is a polygon (if it has the "close" attribute), then
4168     ** adjust the exit point */
4169     if( pObj->bClose ){
4170       /* For "closed" lines, the .end is one of the .e, .s, .w, or .n
4171       ** points of the bounding box, as with block objects. */
4172       pik_elem_set_exit(pObj, pObj->inDir);
4173     }
4174   }else{
4175     PNum w2 = pObj->w/2.0;
4176     PNum h2 = pObj->h/2.0;
4177     pObj->ptEnter = pObj->ptAt;
4178     pObj->ptExit = pObj->ptAt;
4179     switch( pObj->inDir ){
4180       default:         pObj->ptEnter.x -= w2;  break;
4181       case DIR_LEFT:   pObj->ptEnter.x += w2;  break;
4182       case DIR_UP:     pObj->ptEnter.y -= h2;  break;
4183       case DIR_DOWN:   pObj->ptEnter.y += h2;  break;
4184     }
4185     switch( pObj->outDir ){
4186       default:         pObj->ptExit.x += w2;  break;
4187       case DIR_LEFT:   pObj->ptExit.x -= w2;  break;
4188       case DIR_UP:     pObj->ptExit.y += h2;  break;
4189       case DIR_DOWN:   pObj->ptExit.y -= h2;  break;
4190     }
4191     pik_bbox_add_xy(&pObj->bbox, pObj->ptAt.x - w2, pObj->ptAt.y - h2);
4192     pik_bbox_add_xy(&pObj->bbox, pObj->ptAt.x + w2, pObj->ptAt.y + h2);
4193   }
4194   p->eDir = (unsigned char)pObj->outDir;
4195 }
4196 
4197 /* Show basic information about each object as a comment in the
4198 ** generated HTML.  Used for testing and debugging.  Activated
4199 ** by the (undocumented) "debug = 1;"
4200 ** command.
4201 */
pik_elem_render(Pik * p,PObj * pObj)4202 static void pik_elem_render(Pik *p, PObj *pObj){
4203   char *zDir;
4204   if( pObj==0 ) return;
4205   pik_append(p,"<!-- ", -1);
4206   if( pObj->zName ){
4207     pik_append_text(p, pObj->zName, -1, 0);
4208     pik_append(p, ": ", 2);
4209   }
4210   pik_append_text(p, pObj->type->zName, -1, 0);
4211   if( pObj->nTxt ){
4212     pik_append(p, " \"", 2);
4213     pik_append_text(p, pObj->aTxt[0].z+1, pObj->aTxt[0].n-2, 1);
4214     pik_append(p, "\"", 1);
4215   }
4216   pik_append_num(p, " w=", pObj->w);
4217   pik_append_num(p, " h=", pObj->h);
4218   pik_append_point(p, " center=", &pObj->ptAt);
4219   pik_append_point(p, " enter=", &pObj->ptEnter);
4220   switch( pObj->outDir ){
4221     default:        zDir = " right";  break;
4222     case DIR_LEFT:  zDir = " left";   break;
4223     case DIR_UP:    zDir = " up";     break;
4224     case DIR_DOWN:  zDir = " down";   break;
4225   }
4226   pik_append_point(p, " exit=", &pObj->ptExit);
4227   pik_append(p, zDir, -1);
4228   pik_append(p, " -->\n", -1);
4229 }
4230 
4231 /* Render a list of objects
4232 */
pik_elist_render(Pik * p,PList * pList)4233 void pik_elist_render(Pik *p, PList *pList){
4234   int i;
4235   int iNextLayer = 0;
4236   int iThisLayer;
4237   int bMoreToDo;
4238   int miss = 0;
4239   int mDebug = (int)pik_value(p, "debug", 5, 0);
4240   PNum colorLabel;
4241   do{
4242     bMoreToDo = 0;
4243     iThisLayer = iNextLayer;
4244     iNextLayer = 0x7fffffff;
4245     for(i=0; i<pList->n; i++){
4246       PObj *pObj = pList->a[i];
4247       void (*xRender)(Pik*,PObj*);
4248       if( pObj->iLayer>iThisLayer ){
4249         if( pObj->iLayer<iNextLayer ) iNextLayer = pObj->iLayer;
4250         bMoreToDo = 1;
4251         continue; /* Defer until another round */
4252       }else if( pObj->iLayer<iThisLayer ){
4253         continue;
4254       }
4255       if( mDebug & 1 ) pik_elem_render(p, pObj);
4256       xRender = pObj->type->xRender;
4257       if( xRender ){
4258         xRender(p, pObj);
4259       }
4260       if( pObj->pSublist ){
4261         pik_elist_render(p, pObj->pSublist);
4262       }
4263     }
4264   }while( bMoreToDo );
4265 
4266   /* If the color_debug_label value is defined, then go through
4267   ** and paint a dot at every label location */
4268   colorLabel = pik_value(p, "debug_label_color", 17, &miss);
4269   if( miss==0 && colorLabel>=0.0 ){
4270     PObj dot;
4271     memset(&dot, 0, sizeof(dot));
4272     dot.type = &noopClass;
4273     dot.rad = 0.015;
4274     dot.sw = 0.015;
4275     dot.fill = colorLabel;
4276     dot.color = colorLabel;
4277     dot.nTxt = 1;
4278     dot.aTxt[0].eCode = TP_ABOVE;
4279     for(i=0; i<pList->n; i++){
4280       PObj *pObj = pList->a[i];
4281       if( pObj->zName==0 ) continue;
4282       dot.ptAt = pObj->ptAt;
4283       dot.aTxt[0].z = pObj->zName;
4284       dot.aTxt[0].n = (int)strlen(pObj->zName);
4285       dotRender(p, &dot);
4286     }
4287   }
4288 }
4289 
4290 /* Add all objects of the list pList to the bounding box
4291 */
pik_bbox_add_elist(Pik * p,PList * pList,PNum wArrow)4292 static void pik_bbox_add_elist(Pik *p, PList *pList, PNum wArrow){
4293   int i;
4294   for(i=0; i<pList->n; i++){
4295     PObj *pObj = pList->a[i];
4296     if( pObj->sw>0.0 ) pik_bbox_addbox(&p->bbox, &pObj->bbox);
4297     pik_append_txt(p, pObj, &p->bbox);
4298     if( pObj->pSublist ) pik_bbox_add_elist(p, pObj->pSublist, wArrow);
4299 
4300 
4301     /* Expand the bounding box to account for arrowheads on lines */
4302     if( pObj->type->isLine && pObj->nPath>0 ){
4303       if( pObj->larrow ){
4304         pik_bbox_addellipse(&p->bbox, pObj->aPath[0].x, pObj->aPath[0].y,
4305                             wArrow, wArrow);
4306       }
4307       if( pObj->rarrow ){
4308         int j = pObj->nPath-1;
4309         pik_bbox_addellipse(&p->bbox, pObj->aPath[j].x, pObj->aPath[j].y,
4310                             wArrow, wArrow);
4311       }
4312     }
4313   }
4314 }
4315 
4316 /* Recompute key layout parameters from variables. */
pik_compute_layout_settings(Pik * p)4317 static void pik_compute_layout_settings(Pik *p){
4318   PNum thickness;  /* Line thickness */
4319   PNum wArrow;     /* Width of arrowheads */
4320 
4321   /* Set up rendering parameters */
4322   if( p->bLayoutVars ) return;
4323   thickness = pik_value(p,"thickness",9,0);
4324   if( thickness<=0.01 ) thickness = 0.01;
4325   wArrow = 0.5*pik_value(p,"arrowwid",8,0);
4326   p->wArrow = wArrow/thickness;
4327   p->hArrow = pik_value(p,"arrowht",7,0)/thickness;
4328   p->fontScale = pik_value(p,"fontscale",9,0);
4329   if( p->fontScale<=0.0 ) p->fontScale = 1.0;
4330   p->rScale = 144.0;
4331   p->charWidth = pik_value(p,"charwid",7,0)*p->fontScale;
4332   p->charHeight = pik_value(p,"charht",6,0)*p->fontScale;
4333   p->bLayoutVars = 1;
4334 }
4335 
4336 /* Render a list of objects.  Write the SVG into p->zOut.
4337 ** Delete the input object_list before returnning.
4338 */
pik_render(Pik * p,PList * pList)4339 static void pik_render(Pik *p, PList *pList){
4340   if( pList==0 ) return;
4341   if( p->nErr==0 ){
4342     PNum thickness;  /* Stroke width */
4343     PNum margin;     /* Extra bounding box margin */
4344     PNum w, h;       /* Drawing width and height */
4345     PNum wArrow;
4346     PNum pikScale;   /* Value of the "scale" variable */
4347     int miss = 0;
4348 
4349     /* Set up rendering parameters */
4350     pik_compute_layout_settings(p);
4351     thickness = pik_value(p,"thickness",9,0);
4352     if( thickness<=0.01 ) thickness = 0.01;
4353     margin = pik_value(p,"margin",6,0);
4354     margin += thickness;
4355     wArrow = p->wArrow*thickness;
4356     miss = 0;
4357     p->fgcolor = (int)pik_value(p,"fgcolor",7,&miss);
4358     if( miss ){
4359       PToken t;
4360       t.z = "fgcolor";
4361       t.n = 7;
4362       p->fgcolor = (int)pik_lookup_color(0, &t);
4363     }
4364     miss = 0;
4365     p->bgcolor = (int)pik_value(p,"bgcolor",7,&miss);
4366     if( miss ){
4367       PToken t;
4368       t.z = "bgcolor";
4369       t.n = 7;
4370       p->bgcolor = (int)pik_lookup_color(0, &t);
4371     }
4372 
4373     /* Compute a bounding box over all objects so that we can know
4374     ** how big to declare the SVG canvas */
4375     pik_bbox_init(&p->bbox);
4376     pik_bbox_add_elist(p, pList, wArrow);
4377 
4378     /* Expand the bounding box slightly to account for line thickness
4379     ** and the optional "margin = EXPR" setting. */
4380     p->bbox.ne.x += margin + pik_value(p,"rightmargin",11,0);
4381     p->bbox.ne.y += margin + pik_value(p,"topmargin",9,0);
4382     p->bbox.sw.x -= margin + pik_value(p,"leftmargin",10,0);
4383     p->bbox.sw.y -= margin + pik_value(p,"bottommargin",12,0);
4384 
4385     /* Output the SVG */
4386     pik_append(p, "<svg xmlns='http://www.w3.org/2000/svg'",-1);
4387     if( p->zClass ){
4388       pik_append(p, " class=\"", -1);
4389       pik_append(p, p->zClass, -1);
4390       pik_append(p, "\"", 1);
4391     }
4392     w = p->bbox.ne.x - p->bbox.sw.x;
4393     h = p->bbox.ne.y - p->bbox.sw.y;
4394     p->wSVG = (int)(p->rScale*w);
4395     p->hSVG = (int)(p->rScale*h);
4396     pikScale = pik_value(p,"scale",5,0);
4397     if( pikScale>=0.001 && pikScale<=1000.0
4398      && (pikScale<0.99 || pikScale>1.01)
4399     ){
4400       p->wSVG = (int)(p->wSVG*pikScale);
4401       p->hSVG = (int)(p->hSVG*pikScale);
4402       pik_append_num(p, " width=\"", p->wSVG);
4403       pik_append_num(p, "\" height=\"", p->hSVG);
4404       pik_append(p, "\"", 1);
4405     }
4406     pik_append_dis(p, " viewBox=\"0 0 ",w,"");
4407     pik_append_dis(p, " ",h,"\">\n");
4408     pik_elist_render(p, pList);
4409     pik_append(p,"</svg>\n", -1);
4410   }else{
4411     p->wSVG = -1;
4412     p->hSVG = -1;
4413   }
4414   pik_elist_free(p, pList);
4415 }
4416 
4417 
4418 
4419 /*
4420 ** An array of this structure defines a list of keywords.
4421 */
4422 typedef struct PikWord {
4423   char *zWord;             /* Text of the keyword */
4424   unsigned char nChar;     /* Length of keyword text in bytes */
4425   unsigned char eType;     /* Token code */
4426   unsigned char eCode;     /* Extra code for the token */
4427   unsigned char eEdge;     /* CP_* code for corner/edge keywords */
4428 } PikWord;
4429 
4430 /*
4431 ** Keywords
4432 */
4433 static const PikWord pik_keywords[] = {
4434   { "above",      5,   T_ABOVE,     0,         0        },
4435   { "abs",        3,   T_FUNC1,     FN_ABS,    0        },
4436   { "aligned",    7,   T_ALIGNED,   0,         0        },
4437   { "and",        3,   T_AND,       0,         0        },
4438   { "as",         2,   T_AS,        0,         0        },
4439   { "assert",     6,   T_ASSERT,    0,         0        },
4440   { "at",         2,   T_AT,        0,         0        },
4441   { "behind",     6,   T_BEHIND,    0,         0        },
4442   { "below",      5,   T_BELOW,     0,         0        },
4443   { "between",    7,   T_BETWEEN,   0,         0        },
4444   { "big",        3,   T_BIG,       0,         0        },
4445   { "bold",       4,   T_BOLD,      0,         0        },
4446   { "bot",        3,   T_EDGEPT,    0,         CP_S     },
4447   { "bottom",     6,   T_BOTTOM,    0,         CP_S     },
4448   { "c",          1,   T_EDGEPT,    0,         CP_C     },
4449   { "ccw",        3,   T_CCW,       0,         0        },
4450   { "center",     6,   T_CENTER,    0,         CP_C     },
4451   { "chop",       4,   T_CHOP,      0,         0        },
4452   { "close",      5,   T_CLOSE,     0,         0        },
4453   { "color",      5,   T_COLOR,     0,         0        },
4454   { "cos",        3,   T_FUNC1,     FN_COS,    0        },
4455   { "cw",         2,   T_CW,        0,         0        },
4456   { "dashed",     6,   T_DASHED,    0,         0        },
4457   { "define",     6,   T_DEFINE,    0,         0        },
4458   { "diameter",   8,   T_DIAMETER,  0,         0        },
4459   { "dist",       4,   T_DIST,      0,         0        },
4460   { "dotted",     6,   T_DOTTED,    0,         0        },
4461   { "down",       4,   T_DOWN,      DIR_DOWN,  0        },
4462   { "e",          1,   T_EDGEPT,    0,         CP_E     },
4463   { "east",       4,   T_EDGEPT,    0,         CP_E     },
4464   { "end",        3,   T_END,       0,         CP_END   },
4465   { "even",       4,   T_EVEN,      0,         0        },
4466   { "fill",       4,   T_FILL,      0,         0        },
4467   { "first",      5,   T_NTH,       0,         0        },
4468   { "fit",        3,   T_FIT,       0,         0        },
4469   { "from",       4,   T_FROM,      0,         0        },
4470   { "go",         2,   T_GO,        0,         0        },
4471   { "heading",    7,   T_HEADING,   0,         0        },
4472   { "height",     6,   T_HEIGHT,    0,         0        },
4473   { "ht",         2,   T_HEIGHT,    0,         0        },
4474   { "in",         2,   T_IN,        0,         0        },
4475   { "int",        3,   T_FUNC1,     FN_INT,    0        },
4476   { "invis",      5,   T_INVIS,     0,         0        },
4477   { "invisible",  9,   T_INVIS,     0,         0        },
4478   { "italic",     6,   T_ITALIC,    0,         0        },
4479   { "last",       4,   T_LAST,      0,         0        },
4480   { "left",       4,   T_LEFT,      DIR_LEFT,  CP_W     },
4481   { "ljust",      5,   T_LJUST,     0,         0        },
4482   { "max",        3,   T_FUNC2,     FN_MAX,    0        },
4483   { "min",        3,   T_FUNC2,     FN_MIN,    0        },
4484   { "n",          1,   T_EDGEPT,    0,         CP_N     },
4485   { "ne",         2,   T_EDGEPT,    0,         CP_NE    },
4486   { "north",      5,   T_EDGEPT,    0,         CP_N     },
4487   { "nw",         2,   T_EDGEPT,    0,         CP_NW    },
4488   { "of",         2,   T_OF,        0,         0        },
4489   { "previous",   8,   T_LAST,      0,         0,       },
4490   { "print",      5,   T_PRINT,     0,         0        },
4491   { "rad",        3,   T_RADIUS,    0,         0        },
4492   { "radius",     6,   T_RADIUS,    0,         0        },
4493   { "right",      5,   T_RIGHT,     DIR_RIGHT, CP_E     },
4494   { "rjust",      5,   T_RJUST,     0,         0        },
4495   { "s",          1,   T_EDGEPT,    0,         CP_S     },
4496   { "same",       4,   T_SAME,      0,         0        },
4497   { "se",         2,   T_EDGEPT,    0,         CP_SE    },
4498   { "sin",        3,   T_FUNC1,     FN_SIN,    0        },
4499   { "small",      5,   T_SMALL,     0,         0        },
4500   { "solid",      5,   T_SOLID,     0,         0        },
4501   { "south",      5,   T_EDGEPT,    0,         CP_S     },
4502   { "sqrt",       4,   T_FUNC1,     FN_SQRT,   0        },
4503   { "start",      5,   T_START,     0,         CP_START },
4504   { "sw",         2,   T_EDGEPT,    0,         CP_SW    },
4505   { "t",          1,   T_TOP,       0,         CP_N     },
4506   { "the",        3,   T_THE,       0,         0        },
4507   { "then",       4,   T_THEN,      0,         0        },
4508   { "thick",      5,   T_THICK,     0,         0        },
4509   { "thickness",  9,   T_THICKNESS, 0,         0        },
4510   { "thin",       4,   T_THIN,      0,         0        },
4511   { "this",       4,   T_THIS,      0,         0        },
4512   { "to",         2,   T_TO,        0,         0        },
4513   { "top",        3,   T_TOP,       0,         CP_N     },
4514   { "until",      5,   T_UNTIL,     0,         0        },
4515   { "up",         2,   T_UP,        DIR_UP,    0        },
4516   { "vertex",     6,   T_VERTEX,    0,         0        },
4517   { "w",          1,   T_EDGEPT,    0,         CP_W     },
4518   { "way",        3,   T_WAY,       0,         0        },
4519   { "west",       4,   T_EDGEPT,    0,         CP_W     },
4520   { "wid",        3,   T_WIDTH,     0,         0        },
4521   { "width",      5,   T_WIDTH,     0,         0        },
4522   { "with",       4,   T_WITH,      0,         0        },
4523   { "x",          1,   T_X,         0,         0        },
4524   { "y",          1,   T_Y,         0,         0        },
4525 };
4526 
4527 /*
4528 ** Search a PikWordlist for the given keyword.  Return a pointer to the
4529 ** keyword entry found.  Or return 0 if not found.
4530 */
pik_find_word(const char * zIn,int n,const PikWord * aList,int nList)4531 static const PikWord *pik_find_word(
4532   const char *zIn,              /* Word to search for */
4533   int n,                        /* Length of zIn */
4534   const PikWord *aList,         /* List to search */
4535   int nList                     /* Number of entries in aList */
4536 ){
4537   int first = 0;
4538   int last = nList-1;
4539   while( first<=last ){
4540     int mid = (first + last)/2;
4541     int sz = aList[mid].nChar;
4542     int c = strncmp(zIn, aList[mid].zWord, sz<n ? sz : n);
4543     if( c==0 ){
4544       c = n - sz;
4545       if( c==0 ) return &aList[mid];
4546     }
4547     if( c<0 ){
4548       last = mid-1;
4549     }else{
4550       first = mid+1;
4551     }
4552   }
4553   return 0;
4554 }
4555 
4556 /*
4557 ** Set a symbolic debugger breakpoint on this routine to receive a
4558 ** breakpoint when the "#breakpoint" token is parsed.
4559 */
pik_breakpoint(const unsigned char * z)4560 static void pik_breakpoint(const unsigned char *z){
4561   /* Prevent C compilers from optimizing out this routine. */
4562   if( z[2]=='X' ) exit(1);
4563 }
4564 
4565 
4566 /*
4567 ** Return the length of next token.  The token starts on
4568 ** the pToken->z character.  Fill in other fields of the
4569 ** pToken object as appropriate.
4570 */
pik_token_length(PToken * pToken,int bAllowCodeBlock)4571 static int pik_token_length(PToken *pToken, int bAllowCodeBlock){
4572   const unsigned char *z = (const unsigned char*)pToken->z;
4573   int i;
4574   unsigned char c, c2;
4575   switch( z[0] ){
4576     case '\\': {
4577       pToken->eType = T_WHITESPACE;
4578       for(i=1; z[i]=='\r' || z[i]==' ' || z[i]=='\t'; i++){}
4579       if( z[i]=='\n'  ) return i+1;
4580       pToken->eType = T_ERROR;
4581       return 1;
4582     }
4583     case ';':
4584     case '\n': {
4585       pToken->eType = T_EOL;
4586       return 1;
4587     }
4588     case '"': {
4589       for(i=1; (c = z[i])!=0; i++){
4590         if( c=='\\' ){
4591           if( z[i+1]==0 ) break;
4592           i++;
4593           continue;
4594         }
4595         if( c=='"' ){
4596           pToken->eType = T_STRING;
4597           return i+1;
4598         }
4599       }
4600       pToken->eType = T_ERROR;
4601       return i;
4602     }
4603     case ' ':
4604     case '\t':
4605     case '\f':
4606     case '\r': {
4607       for(i=1; (c = z[i])==' ' || c=='\t' || c=='\r' || c=='\t'; i++){}
4608       pToken->eType = T_WHITESPACE;
4609       return i;
4610     }
4611     case '#': {
4612       for(i=1; (c = z[i])!=0 && c!='\n'; i++){}
4613       pToken->eType = T_WHITESPACE;
4614       /* If the comment is "#breakpoint" then invoke the pik_breakpoint()
4615       ** routine.  The pik_breakpoint() routie is a no-op that serves as
4616       ** a convenient place to set a gdb breakpoint when debugging. */
4617       if( strncmp((const char*)z,"#breakpoint",11)==0 ) pik_breakpoint(z);
4618       return i;
4619     }
4620     case '/': {
4621       if( z[1]=='*' ){
4622         for(i=2; z[i]!=0 && (z[i]!='*' || z[i+1]!='/'); i++){}
4623         if( z[i]=='*' ){
4624           pToken->eType = T_WHITESPACE;
4625           return i+2;
4626         }else{
4627           pToken->eType = T_ERROR;
4628           return i;
4629         }
4630       }else if( z[1]=='/' ){
4631         for(i=2; z[i]!=0 && z[i]!='\n'; i++){}
4632         pToken->eType = T_WHITESPACE;
4633         return i;
4634       }else if( z[1]=='=' ){
4635         pToken->eType = T_ASSIGN;
4636         pToken->eCode = T_SLASH;
4637         return 2;
4638       }else{
4639         pToken->eType = T_SLASH;
4640         return 1;
4641       }
4642     }
4643     case '+': {
4644       if( z[1]=='=' ){
4645         pToken->eType = T_ASSIGN;
4646         pToken->eCode = T_PLUS;
4647         return 2;
4648       }
4649       pToken->eType = T_PLUS;
4650       return 1;
4651     }
4652     case '*': {
4653       if( z[1]=='=' ){
4654         pToken->eType = T_ASSIGN;
4655         pToken->eCode = T_STAR;
4656         return 2;
4657       }
4658       pToken->eType = T_STAR;
4659       return 1;
4660     }
4661     case '%': {   pToken->eType = T_PERCENT; return 1; }
4662     case '(': {   pToken->eType = T_LP;      return 1; }
4663     case ')': {   pToken->eType = T_RP;      return 1; }
4664     case '[': {   pToken->eType = T_LB;      return 1; }
4665     case ']': {   pToken->eType = T_RB;      return 1; }
4666     case ',': {   pToken->eType = T_COMMA;   return 1; }
4667     case ':': {   pToken->eType = T_COLON;   return 1; }
4668     case '>': {   pToken->eType = T_GT;      return 1; }
4669     case '=': {
4670        if( z[1]=='=' ){
4671          pToken->eType = T_EQ;
4672          return 2;
4673        }
4674        pToken->eType = T_ASSIGN;
4675        pToken->eCode = T_ASSIGN;
4676        return 1;
4677     }
4678     case '-': {
4679       if( z[1]=='>' ){
4680         pToken->eType = T_RARROW;
4681         return 2;
4682       }else if( z[1]=='=' ){
4683         pToken->eType = T_ASSIGN;
4684         pToken->eCode = T_MINUS;
4685         return 2;
4686       }else{
4687         pToken->eType = T_MINUS;
4688         return 1;
4689       }
4690     }
4691     case '<': {
4692       if( z[1]=='-' ){
4693          if( z[2]=='>' ){
4694            pToken->eType = T_LRARROW;
4695            return 3;
4696          }else{
4697            pToken->eType = T_LARROW;
4698            return 2;
4699          }
4700       }else{
4701         pToken->eType = T_LT;
4702         return 1;
4703       }
4704     }
4705     case '{': {
4706       int len, depth;
4707       i = 1;
4708       if( bAllowCodeBlock ){
4709         depth = 1;
4710         while( z[i] && depth>0 ){
4711           PToken x;
4712           x.z = (char*)(z+i);
4713           len = pik_token_length(&x, 0);
4714           if( len==1 ){
4715             if( z[i]=='{' ) depth++;
4716             if( z[i]=='}' ) depth--;
4717           }
4718           i += len;
4719         }
4720       }else{
4721         depth = 0;
4722       }
4723       if( depth ){
4724         pToken->eType = T_ERROR;
4725         return 1;
4726       }
4727       pToken->eType = T_CODEBLOCK;
4728       return i;
4729     }
4730     default: {
4731       c = z[0];
4732       if( c=='.' ){
4733         unsigned char c1 = z[1];
4734         if( islower(c1) ){
4735           const PikWord *pFound;
4736           for(i=2; (c = z[i])>='a' && c<='z'; i++){}
4737           pFound = pik_find_word((const char*)z+1, i-1,
4738                                     pik_keywords, count(pik_keywords));
4739           if( pFound && (pFound->eEdge>0 ||
4740                          pFound->eType==T_EDGEPT ||
4741                          pFound->eType==T_START ||
4742                          pFound->eType==T_END )
4743           ){
4744             /* Dot followed by something that is a 2-D place value */
4745             pToken->eType = T_DOT_E;
4746           }else if( pFound && (pFound->eType==T_X || pFound->eType==T_Y) ){
4747             /* Dot followed by "x" or "y" */
4748             pToken->eType = T_DOT_XY;
4749           }else{
4750             /* Any other "dot" */
4751             pToken->eType = T_DOT_L;
4752           }
4753           return 1;
4754         }else if( isdigit(c1) ){
4755           i = 0;
4756           /* no-op.  Fall through to number handling */
4757         }else if( isupper(c1) ){
4758           for(i=2; (c = z[i])!=0 && (isalnum(c) || c=='_'); i++){}
4759           pToken->eType = T_DOT_U;
4760           return 1;
4761         }else{
4762           pToken->eType = T_ERROR;
4763           return 1;
4764         }
4765       }
4766       if( (c>='0' && c<='9') || c=='.' ){
4767         int nDigit;
4768         int isInt = 1;
4769         if( c!='.' ){
4770           nDigit = 1;
4771           for(i=1; (c = z[i])>='0' && c<='9'; i++){ nDigit++; }
4772           if( i==1 && (c=='x' || c=='X') ){
4773             for(i=2; (c = z[i])!=0 && isxdigit(c); i++){}
4774             pToken->eType = T_NUMBER;
4775             return i;
4776           }
4777         }else{
4778           isInt = 0;
4779           nDigit = 0;
4780           i = 0;
4781         }
4782         if( c=='.' ){
4783           isInt = 0;
4784           for(i++; (c = z[i])>='0' && c<='9'; i++){ nDigit++; }
4785         }
4786         if( nDigit==0 ){
4787           pToken->eType = T_ERROR;
4788           return i;
4789         }
4790         if( c=='e' || c=='E' ){
4791           int iBefore = i;
4792           i++;
4793           c2 = z[i];
4794           if( c2=='+' || c2=='-' ){
4795             i++;
4796             c2 = z[i];
4797           }
4798           if( c2<'0' || c>'9' ){
4799             /* This is not an exp */
4800             i = iBefore;
4801           }else{
4802             i++;
4803             isInt = 0;
4804             while( (c = z[i])>='0' && c<='9' ){ i++; }
4805           }
4806         }
4807         c2 = c ? z[i+1] : 0;
4808         if( isInt ){
4809           if( (c=='t' && c2=='h')
4810            || (c=='r' && c2=='d')
4811            || (c=='n' && c2=='d')
4812            || (c=='s' && c2=='t')
4813           ){
4814             pToken->eType = T_NTH;
4815             return i+2;
4816           }
4817         }
4818         if( (c=='i' && c2=='n')
4819          || (c=='c' && c2=='m')
4820          || (c=='m' && c2=='m')
4821          || (c=='p' && c2=='t')
4822          || (c=='p' && c2=='x')
4823          || (c=='p' && c2=='c')
4824         ){
4825           i += 2;
4826         }
4827         pToken->eType = T_NUMBER;
4828         return i;
4829       }else if( islower(c) ){
4830         const PikWord *pFound;
4831         for(i=1; (c =  z[i])!=0 && (isalnum(c) || c=='_'); i++){}
4832         pFound = pik_find_word((const char*)z, i,
4833                                pik_keywords, count(pik_keywords));
4834         if( pFound ){
4835           pToken->eType = pFound->eType;
4836           pToken->eCode = pFound->eCode;
4837           pToken->eEdge = pFound->eEdge;
4838           return i;
4839         }
4840         pToken->n = i;
4841         if( pik_find_class(pToken)!=0 ){
4842           pToken->eType = T_CLASSNAME;
4843         }else{
4844           pToken->eType = T_ID;
4845         }
4846         return i;
4847       }else if( c>='A' && c<='Z' ){
4848         for(i=1; (c =  z[i])!=0 && (isalnum(c) || c=='_'); i++){}
4849         pToken->eType = T_PLACENAME;
4850         return i;
4851       }else if( c=='$' && z[1]>='1' && z[1]<='9' && !isdigit(z[2]) ){
4852         pToken->eType = T_PARAMETER;
4853         pToken->eCode = z[1] - '1';
4854         return 2;
4855       }else if( c=='_' || c=='$' || c=='@' ){
4856         for(i=1; (c =  z[i])!=0 && (isalnum(c) || c=='_'); i++){}
4857         pToken->eType = T_ID;
4858         return i;
4859       }else{
4860         pToken->eType = T_ERROR;
4861         return 1;
4862       }
4863     }
4864   }
4865 }
4866 
4867 /*
4868 ** Return a pointer to the next non-whitespace token after pThis.
4869 ** This is used to help form error messages.
4870 */
pik_next_semantic_token(PToken * pThis)4871 static PToken pik_next_semantic_token(PToken *pThis){
4872   PToken x;
4873   int sz;
4874   int i = pThis->n;
4875   memset(&x, 0, sizeof(x));
4876   x.z = pThis->z;
4877   while(1){
4878     x.z = pThis->z + i;
4879     sz = pik_token_length(&x, 1);
4880     if( x.eType!=T_WHITESPACE ){
4881       x.n = sz;
4882       return x;
4883     }
4884     i += sz;
4885   }
4886 }
4887 
4888 /* Parser arguments to a macro invocation
4889 **
4890 **     (arg1, arg2, ...)
4891 **
4892 ** Arguments are comma-separated, except that commas within string
4893 ** literals or with (...), {...}, or [...] do not count.  The argument
4894 ** list begins and ends with parentheses.  There can be at most 9
4895 ** arguments.
4896 **
4897 ** Return the number of bytes in the argument list.
4898 */
pik_parse_macro_args(Pik * p,const char * z,int n,PToken * args,PToken * pOuter)4899 static unsigned int pik_parse_macro_args(
4900   Pik *p,
4901   const char *z,     /* Start of the argument list */
4902   int n,             /* Available bytes */
4903   PToken *args,      /* Fill in with the arguments */
4904   PToken *pOuter     /* Arguments of the next outer context, or NULL */
4905 ){
4906   int nArg = 0;
4907   int i, j, sz;
4908   int iStart;
4909   int depth = 0;
4910   PToken x;
4911   if( z[0]!='(' ) return 0;
4912   args[0].z = z+1;
4913   iStart = 1;
4914   for(i=1; i<n && z[i]!=')'; i+=sz){
4915     x.z = z+i;
4916     sz = pik_token_length(&x, 0);
4917     if( sz!=1 ) continue;
4918     if( z[i]==',' && depth<=0 ){
4919       args[nArg].n = i - iStart;
4920       if( nArg==8 ){
4921         x.z = z;
4922         x.n = 1;
4923         pik_error(p, &x, "too many macro arguments - max 9");
4924         return 0;
4925       }
4926       nArg++;
4927       args[nArg].z = z+i+1;
4928       iStart = i+1;
4929       depth = 0;
4930     }else if( z[i]=='(' || z[i]=='{' || z[i]=='[' ){
4931       depth++;
4932     }else if( z[i]==')' || z[i]=='}' || z[i]==']' ){
4933       depth--;
4934     }
4935   }
4936   if( z[i]==')' ){
4937     args[nArg].n = i - iStart;
4938     /* Remove leading and trailing whitespace from each argument.
4939     ** If what remains is one of $1, $2, ... $9 then transfer the
4940     ** corresponding argument from the outer context */
4941     for(j=0; j<=nArg; j++){
4942       PToken *t = &args[j];
4943       while( t->n>0 && isspace(t->z[0]) ){ t->n--; t->z++; }
4944       while( t->n>0 && isspace(t->z[t->n-1]) ){ t->n--; }
4945       if( t->n==2 && t->z[0]=='$' && t->z[1]>='1' && t->z[1]<='9' ){
4946         if( pOuter ) *t = pOuter[t->z[1]-'1'];
4947         else t->n = 0;
4948       }
4949     }
4950     return i+1;
4951   }
4952   x.z = z;
4953   x.n = 1;
4954   pik_error(p, &x, "unterminated macro argument list");
4955   return 0;
4956 }
4957 
4958 /*
4959 ** Split up the content of a PToken into multiple tokens and
4960 ** send each to the parser.
4961 */
pik_tokenize(Pik * p,PToken * pIn,yyParser * pParser,PToken * aParam)4962 void pik_tokenize(Pik *p, PToken *pIn, yyParser *pParser, PToken *aParam){
4963   unsigned int i;
4964   int sz = 0;
4965   PToken token;
4966   PMacro *pMac;
4967   for(i=0; i<pIn->n && pIn->z[i] && p->nErr==0; i+=sz){
4968     token.eCode = 0;
4969     token.eEdge = 0;
4970     token.z = pIn->z + i;
4971     sz = pik_token_length(&token, 1);
4972     if( token.eType==T_WHITESPACE ){
4973       /* no-op */
4974     }else if( sz>50000 ){
4975       token.n = 1;
4976       pik_error(p, &token, "token is too long - max length 50000 bytes");
4977       break;
4978     }else if( token.eType==T_ERROR ){
4979       token.n = (unsigned short)(sz & 0xffff);
4980       pik_error(p, &token, "unrecognized token");
4981       break;
4982     }else if( sz+i>pIn->n ){
4983       token.n = pIn->n - i;
4984       pik_error(p, &token, "syntax error");
4985       break;
4986     }else if( token.eType==T_PARAMETER ){
4987       /* Substitute a parameter into the input stream */
4988       if( aParam==0 || aParam[token.eCode].n==0 ){
4989         continue;
4990       }
4991       token.n = (unsigned short)(sz & 0xffff);
4992       if( p->nCtx>=count(p->aCtx) ){
4993         pik_error(p, &token, "macros nested too deep");
4994       }else{
4995         p->aCtx[p->nCtx++] = token;
4996         pik_tokenize(p, &aParam[token.eCode], pParser, 0);
4997         p->nCtx--;
4998       }
4999     }else if( token.eType==T_ID
5000                && (token.n = (unsigned short)(sz & 0xffff),
5001                    (pMac = pik_find_macro(p,&token))!=0)
5002     ){
5003       PToken args[9];
5004       unsigned int j = i+sz;
5005       if( pMac->inUse ){
5006         pik_error(p, &pMac->macroName, "recursive macro definition");
5007         break;
5008       }
5009       token.n = (short int)(sz & 0xffff);
5010       if( p->nCtx>=count(p->aCtx) ){
5011         pik_error(p, &token, "macros nested too deep");
5012         break;
5013       }
5014       pMac->inUse = 1;
5015       memset(args, 0, sizeof(args));
5016       p->aCtx[p->nCtx++] = token;
5017       sz += pik_parse_macro_args(p, pIn->z+j, pIn->n-j, args, aParam);
5018       pik_tokenize(p, &pMac->macroBody, pParser, args);
5019       p->nCtx--;
5020       pMac->inUse = 0;
5021     }else{
5022 #if 0
5023       printf("******** Token %s (%d): \"%.*s\" **************\n",
5024              yyTokenName[token.eType], token.eType,
5025              (int)(isspace(token.z[0]) ? 0 : sz), token.z);
5026 #endif
5027       token.n = (unsigned short)(sz & 0xffff);
5028       pik_parser(pParser, token.eType, token);
5029     }
5030   }
5031 }
5032 
5033 /*
5034 ** Parse the PIKCHR script contained in zText[].  Return a rendering.  Or
5035 ** if an error is encountered, return the error text.  The error message
5036 ** is HTML formatted.  So regardless of what happens, the return text
5037 ** is safe to be insertd into an HTML output stream.
5038 **
5039 ** If pnWidth and pnHeight are not NULL, then this routine writes the
5040 ** width and height of the <SVG> object into the integers that they
5041 ** point to.  A value of -1 is written if an error is seen.
5042 **
5043 ** If zClass is not NULL, then it is a class name to be included in
5044 ** the <SVG> markup.
5045 **
5046 ** The returned string is contained in memory obtained from malloc()
5047 ** and should be released by the caller.
5048 */
pikchr(const char * zText,const char * zClass,unsigned int mFlags,int * pnWidth,int * pnHeight)5049 char *pikchr(
5050   const char *zText,     /* Input PIKCHR source text.  zero-terminated */
5051   const char *zClass,    /* Add class="%s" to <svg> markup */
5052   unsigned int mFlags,   /* Flags used to influence rendering behavior */
5053   int *pnWidth,          /* Write width of <svg> here, if not NULL */
5054   int *pnHeight          /* Write height here, if not NULL */
5055 ){
5056   Pik s;
5057   yyParser sParse;
5058 
5059   memset(&s, 0, sizeof(s));
5060   s.sIn.z = zText;
5061   s.sIn.n = (unsigned int)strlen(zText);
5062   s.eDir = DIR_RIGHT;
5063   s.zClass = zClass;
5064   s.mFlags = mFlags;
5065   pik_parserInit(&sParse, &s);
5066 #if 0
5067   pik_parserTrace(stdout, "parser: ");
5068 #endif
5069   pik_tokenize(&s, &s.sIn, &sParse, 0);
5070   if( s.nErr==0 ){
5071     PToken token;
5072     memset(&token,0,sizeof(token));
5073     token.z = zText + (s.sIn.n>0 ? s.sIn.n-1 : 0);
5074     token.n = 1;
5075     pik_parser(&sParse, 0, token);
5076   }
5077   pik_parserFinalize(&sParse);
5078   if( s.zOut==0 && s.nErr==0 ){
5079     pik_append(&s, "<!-- empty pikchr diagram -->\n", -1);
5080   }
5081   while( s.pVar ){
5082     PVar *pNext = s.pVar->pNext;
5083     free(s.pVar);
5084     s.pVar = pNext;
5085   }
5086   while( s.pMacros ){
5087     PMacro *pNext = s.pMacros->pNext;
5088     free(s.pMacros);
5089     s.pMacros = pNext;
5090   }
5091   if( pnWidth ) *pnWidth = s.nErr ? -1 : s.wSVG;
5092   if( pnHeight ) *pnHeight = s.nErr ? -1 : s.hSVG;
5093   if( s.zOut ){
5094     s.zOut[s.nOut] = 0;
5095     s.zOut = realloc(s.zOut, s.nOut+1);
5096   }
5097   return s.zOut;
5098 }
5099 
5100 #if defined(PIKCHR_FUZZ)
5101 #include <stdint.h>
LLVMFuzzerTestOneInput(const uint8_t * aData,size_t nByte)5102 int LLVMFuzzerTestOneInput(const uint8_t *aData, size_t nByte){
5103   int w,h;
5104   char *zIn, *zOut;
5105   zIn = malloc( nByte + 1 );
5106   if( zIn==0 ) return 0;
5107   memcpy(zIn, aData, nByte);
5108   zIn[nByte] = 0;
5109   zOut = pikchr(zIn, "pikchr", 0, &w, &h);
5110   free(zIn);
5111   free(zOut);
5112   return 0;
5113 }
5114 #endif /* PIKCHR_FUZZ */
5115 
5116 #if defined(PIKCHR_SHELL)
5117 /* Print a usage comment for the shell and exit. */
usage(const char * argv0)5118 static void usage(const char *argv0){
5119   fprintf(stderr, "usage: %s [OPTIONS] FILE ...\n", argv0);
5120   fprintf(stderr,
5121     "Convert Pikchr input files into SVG.  Filename \"-\" means stdin.\n"
5122     "Options:\n"
5123     "   --dont-stop      Process all files even if earlier files have errors\n"
5124     "   --svg-only       Omit raw SVG without the HTML wrapper\n"
5125   );
5126   exit(1);
5127 }
5128 
5129 /* Send text to standard output, but escape HTML markup */
print_escape_html(const char * z)5130 static void print_escape_html(const char *z){
5131   int j;
5132   char c;
5133   while( z[0]!=0 ){
5134     for(j=0; (c = z[j])!=0 && c!='<' && c!='>' && c!='&'; j++){}
5135     if( j ) printf("%.*s", j, z);
5136     z += j+1;
5137     j = -1;
5138     if( c=='<' ){
5139       printf("&lt;");
5140     }else if( c=='>' ){
5141       printf("&gt;");
5142     }else if( c=='&' ){
5143       printf("&amp;");
5144     }else if( c==0 ){
5145       break;
5146     }
5147   }
5148 }
5149 
5150 /* Read the content of file zFilename into memory obtained from malloc()
5151 ** Return the memory.  If something goes wrong (ex: the file does not exist
5152 ** or cannot be opened) put an error message on stderr and return NULL.
5153 **
5154 ** If the filename is "-" read stdin.
5155 */
readFile(const char * zFilename)5156 static char *readFile(const char *zFilename){
5157   FILE *in;
5158   size_t n;
5159   size_t nUsed = 0;
5160   size_t nAlloc = 0;
5161   char *z = 0, *zNew = 0;
5162   in = strcmp(zFilename,"-")==0 ? stdin : fopen(zFilename, "rb");
5163   if( in==0 ){
5164     fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename);
5165     return 0;
5166   }
5167   while(1){
5168     if( nUsed+2>=nAlloc ){
5169       nAlloc = nAlloc*2 + 4000;
5170       zNew = realloc(z, nAlloc);
5171     }
5172     if( zNew==0 ){
5173       free(z);
5174       fprintf(stderr, "out of memory trying to allocate %lld bytes\n",
5175               (long long int)nAlloc);
5176       exit(1);
5177     }
5178     z = zNew;
5179     n = fread(z+nUsed, 1, nAlloc-nUsed-1, in);
5180     if( n<=0 ){
5181       break;
5182     }
5183     nUsed += n;
5184   }
5185   if( in!=stdin ) fclose(in);
5186   z[nUsed] = 0;
5187   return z;
5188 }
5189 
5190 
5191 /* Testing interface
5192 **
5193 ** Generate HTML on standard output that displays both the original
5194 ** input text and the rendered SVG for all files named on the command
5195 ** line.
5196 */
main(int argc,char ** argv)5197 int main(int argc, char **argv){
5198   int i;
5199   int bSvgOnly = 0;            /* Output SVG only.  No HTML wrapper */
5200   int bDontStop = 0;           /* Continue in spite of errors */
5201   int exitCode = 0;            /* What to return */
5202   int mFlags = 0;              /* mFlags argument to pikchr() */
5203   const char *zStyle = "";     /* Extra styling */
5204   const char *zHtmlHdr =
5205     "<!DOCTYPE html>\n"
5206     "<html lang=\"en-US\">\n"
5207     "<head>\n<title>PIKCHR Test</title>\n"
5208     "<style>\n"
5209     "  .hidden {\n"
5210     "     position: absolute !important;\n"
5211     "     opacity: 0 !important;\n"
5212     "     pointer-events: none !important;\n"
5213     "     display: none !important;\n"
5214     "  }\n"
5215     "</style>\n"
5216     "<script>\n"
5217     "  function toggleHidden(id){\n"
5218     "    for(var c of document.getElementById(id).children){\n"
5219     "      c.classList.toggle('hidden');\n"
5220     "    }\n"
5221     "  }\n"
5222     "</script>\n"
5223     "<meta charset=\"utf-8\">\n"
5224     "</head>\n"
5225     "<body>\n"
5226   ;
5227   if( argc<2 ) usage(argv[0]);
5228   for(i=1; i<argc; i++){
5229     char *zIn;
5230     char *zOut;
5231     int w, h;
5232 
5233     if( argv[i][0]=='-' && argv[i][1]!=0 ){
5234       char *z = argv[i];
5235       z++;
5236       if( z[0]=='-' ) z++;
5237       if( strcmp(z,"dont-stop")==0 ){
5238         bDontStop = 1;
5239       }else
5240       if( strcmp(z,"dark-mode")==0 ){
5241         zStyle = "color:white;background-color:black;";
5242         mFlags |= PIKCHR_DARK_MODE;
5243       }else
5244       if( strcmp(z,"svg-only")==0 ){
5245         if( zHtmlHdr==0 ){
5246           fprintf(stderr, "the \"%s\" option must come first\n",argv[i]);
5247           exit(1);
5248         }
5249         bSvgOnly = 1;
5250         mFlags |= PIKCHR_PLAINTEXT_ERRORS;
5251       }else
5252       {
5253         fprintf(stderr,"unknown option: \"%s\"\n", argv[i]);
5254         usage(argv[0]);
5255       }
5256       continue;
5257     }
5258     zIn = readFile(argv[i]);
5259     if( zIn==0 ) continue;
5260     zOut = pikchr(zIn, "pikchr", mFlags, &w, &h);
5261     if( w<0 ) exitCode = 1;
5262     if( zOut==0 ){
5263       fprintf(stderr, "pikchr() returns NULL.  Out of memory?\n");
5264       if( !bDontStop ) exit(1);
5265     }else if( bSvgOnly ){
5266       printf("%s\n", zOut);
5267     }else{
5268       if( zHtmlHdr ){
5269         printf("%s", zHtmlHdr);
5270         zHtmlHdr = 0;
5271       }
5272       printf("<h1>File %s</h1>\n", argv[i]);
5273       if( w<0 ){
5274         printf("<p>ERROR</p>\n%s\n", zOut);
5275       }else{
5276         printf("<div id=\"svg-%d\" onclick=\"toggleHidden('svg-%d')\">\n",i,i);
5277         printf("<div style='border:3px solid lightgray;max-width:%dpx;%s'>\n",
5278                w,zStyle);
5279         printf("%s</div>\n", zOut);
5280         printf("<pre class='hidden'>");
5281         print_escape_html(zIn);
5282         printf("</pre>\n</div>\n");
5283       }
5284     }
5285     free(zOut);
5286     free(zIn);
5287   }
5288   if( !bSvgOnly ){
5289     printf("</body></html>\n");
5290   }
5291   return exitCode ? EXIT_FAILURE : EXIT_SUCCESS;
5292 }
5293 #endif /* PIKCHR_SHELL */
5294 
5295 #ifdef PIKCHR_TCL
5296 #include <tcl.h>
5297 /*
5298 ** An interface to TCL
5299 **
5300 ** TCL command:     pikchr $INPUTTEXT
5301 **
5302 ** Returns a list of 3 elements which are the output text, the width, and
5303 ** the height.
5304 **
5305 ** Register the "pikchr" command by invoking Pikchr_Init(Tcl_Interp*).  Or
5306 ** compile this source file as a shared library and load it using the
5307 ** "load" command of Tcl.
5308 **
5309 ** Compile this source-code file into a shared library using a command
5310 ** similar to this:
5311 **
5312 **    gcc -c pikchr.so -DPIKCHR_TCL -shared pikchr.c
5313 */
pik_tcl_command(ClientData clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])5314 static int pik_tcl_command(
5315   ClientData clientData, /* Not Used */
5316   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
5317   int objc,              /* Number of arguments */
5318   Tcl_Obj *CONST objv[]  /* Command arguments */
5319 ){
5320   int w, h;              /* Width and height of the pikchr */
5321   const char *zIn;       /* Source text input */
5322   char *zOut;            /* SVG output text */
5323   Tcl_Obj *pRes;         /* The result TCL object */
5324 
5325   (void)clientData;
5326   if( objc!=2 ){
5327     Tcl_WrongNumArgs(interp, 1, objv, "PIKCHR_SOURCE_TEXT");
5328     return TCL_ERROR;
5329   }
5330   zIn = Tcl_GetString(objv[1]);
5331   w = h = 0;
5332   zOut = pikchr(zIn, "pikchr", 0, &w, &h);
5333   if( zOut==0 ){
5334     return TCL_ERROR;  /* Out of memory */
5335   }
5336   pRes = Tcl_NewObj();
5337   Tcl_ListObjAppendElement(0, pRes, Tcl_NewStringObj(zOut, -1));
5338   free(zOut);
5339   Tcl_ListObjAppendElement(0, pRes, Tcl_NewIntObj(w));
5340   Tcl_ListObjAppendElement(0, pRes, Tcl_NewIntObj(h));
5341   Tcl_SetObjResult(interp, pRes);
5342   return TCL_OK;
5343 }
5344 
5345 /* Invoke this routine to register the "pikchr" command with the interpreter
5346 ** given in the argument */
Pikchr_Init(Tcl_Interp * interp)5347 int Pikchr_Init(Tcl_Interp *interp){
5348   Tcl_CreateObjCommand(interp, "pikchr", pik_tcl_command, 0, 0);
5349   Tcl_PkgProvide (interp, PACKAGE_NAME, PACKAGE_VERSION);
5350   return TCL_OK;
5351 }
5352 
5353 
5354 #endif /* PIKCHR_TCL */
5355 
5356 
5357 } // end %code
5358