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 as that is an HTML-ism and is not valid in XML.
1921 **
1922 ** * The "&" character is changed into "&" 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, "<", 4); break; }
1944 case '>': { pik_append(p, ">", 4); break; }
1945 case '&': { pik_append(p, "&", 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, "\", -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 ** "<" 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("<");
5140 }else if( c=='>' ){
5141 printf(">");
5142 }else if( c=='&' ){
5143 printf("&");
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