1 /*
2 **
3 ** This library module contains the pamdraw routines.
4 **
5 ** Copyright (C) 1989, 1991 by Jef Poskanzer.
6 **
7 ** Modified from ppm to pam by Willem van Schaik, Feb 2011
8 **
9 ** Permission to use, copy, modify, and distribute this software and its
10 ** documentation for any purpose and without fee is hereby granted, provided
11 ** that the above copyright notice appear in all copies and that both that
12 ** copyright notice and this permission notice appear in supporting
13 ** documentation. This software is provided "as is" without express or
14 ** implied warranty.
15 **
16 ** The character drawing routines are by John Walker
17 ** Copyright (C) 1994 by John Walker, kelvin@fourmilab.ch
18 */
19
20 #include <assert.h>
21 #include <stdlib.h>
22
23 #include "netpbm/pm_config.h"
24 #include "netpbm/pm_c_util.h"
25 #include "netpbm/mallocvar.h"
26
27 #include "pam.h"
28 #include "ppmdfont.h"
29
30 #include "pamdraw.h"
31
32
33 struct penpos {
34 int x;
35 int y;
36 };
37
38 struct rectangle {
39 /* ((0,0),(0,0)) means empty. */
40 /* 'lr' is guaranteed not to be left of or above 'ul' */
41 struct penpos ul;
42 struct penpos lr;
43 };
44
45 static struct rectangle const emptyRectangle = {
46 {0, 0},
47 {0, 0},
48 };
49
50
51
52 static pamd_point
makePoint(int const x,int const y)53 makePoint(int const x,
54 int const y) {
55
56 return pamd_makePoint(x, y);
57 }
58
59
60
61 static pamd_point
middlePoint(pamd_point const a,pamd_point const b)62 middlePoint(pamd_point const a,
63 pamd_point const b) {
64
65 pamd_point retval;
66
67 retval.x = (a.x + b.x) / 2;
68 retval.y = (a.y + b.y) / 2;
69
70 return retval;
71 }
72
73
74
75 static bool
pointsEqual(pamd_point const a,pamd_point const b)76 pointsEqual(pamd_point const a,
77 pamd_point const b) {
78
79 return a.x == b.x && a.y == b.y;
80 }
81
82
83
84 static bool
pointIsWithinBounds(pamd_point const p,unsigned int const cols,unsigned int const rows)85 pointIsWithinBounds(pamd_point const p,
86 unsigned int const cols,
87 unsigned int const rows) {
88
89 return (p.x >= 0 && p.x < cols && p.y >= 0 && p.y < rows);
90 }
91
92
93
94 static pamd_point
vectorSum(pamd_point const a,pamd_point const b)95 vectorSum(pamd_point const a,
96 pamd_point const b) {
97
98 return makePoint(a.x + b.x, a.y + b.y);
99 }
100
101
102
103 static long int const DDA_SCALE = 8192;
104
105 #define PAMD_MAXCOORD 32767
106 /*
107 Several factors govern the limit of x, y coordination values.
108
109 The limit must be representable as (signed) int for coordinates to
110 be carried in struct penpos (immediately above).
111
112 The following calculation, done with long ints, must not overflow:
113 cy0 = cy0 + (y1 - cy0) * (cols - 1 - cx0) / (x1 - cx0);
114
115 The following must not overflow when DDA_SCALE is set to 8092:
116 dy = (y1 - y0) * DDA_SCALE / abs(x1 - x0);
117
118 Overflow conditions for pamd_text are rather complicated, for commands
119 come from an external PPMD font file. See comments below.
120 */
121
122
123
124 void
pamd_validateCoord(int const c)125 pamd_validateCoord(int const c) {
126
127 if (c < -PAMD_MAXCOORD || c > PAMD_MAXCOORD)
128 pm_error("Coordinate out of bounds: %d", c);
129 }
130
131
132
133 void
pamd_validatePoint(pamd_point const p)134 pamd_validatePoint(pamd_point const p) {
135
136 if (p.x < -PAMD_MAXCOORD || p.x > PAMD_MAXCOORD)
137 pm_error("x coordinate of (%d, %d) out of bounds", p.x, p.y);
138
139 if (p.y < -PAMD_MAXCOORD || p.y > PAMD_MAXCOORD)
140 pm_error("y coordinate of (%d, %d) out of bounds", p.x, p.y);
141 }
142
143
144
145 static void
drawPoint(pamd_drawproc drawproc,const void * const clientdata,tuple ** const tuples,int const cols,int const rows,int const depth,sample const maxval,pamd_point const p)146 drawPoint(pamd_drawproc drawproc,
147 const void * const clientdata,
148 tuple ** const tuples,
149 int const cols,
150 int const rows,
151 int const depth,
152 sample const maxval,
153 pamd_point const p) {
154 /*----------------------------------------------------------------------------
155 Draw a single point, assuming that it is within the bounds of the
156 image.
157 -----------------------------------------------------------------------------*/
158 int i;
159
160 if (drawproc == PAMD_NULLDRAWPROC) {
161 assert(p.x >= 0); assert(p.x < cols);
162 assert(p.y >= 0); assert(p.y < rows);
163
164 for (i = 0; i < depth; i++)
165 tuples[p.y][p.x][i] = (sample) *((tuple *) clientdata + i);
166 } else {
167 drawproc(tuples, cols, rows, depth, maxval, p, clientdata);
168 }
169 }
170
171
172
173 void
pamd_point_drawproc(tuple ** const tuples,unsigned int const cols,unsigned int const rows,unsigned int const depth,sample const maxval,pamd_point const p,const void * const clientdata)174 pamd_point_drawproc(tuple ** const tuples,
175 unsigned int const cols,
176 unsigned int const rows,
177 unsigned int const depth,
178 sample const maxval,
179 pamd_point const p,
180 const void * const clientdata) {
181
182 unsigned int i;
183
184 if ((p.x >= 0) && (p.x < cols) && (p.y >= 0) && (p.y < rows))
185 for (i = 0; i < depth; ++i)
186 tuples[p.y][p.x][i] = (sample) *((tuple *) clientdata + i);
187 }
188
189
190
191 static void
findRectangleIntersection(struct rectangle const rect1,struct rectangle const rect2,struct rectangle * const intersectionP)192 findRectangleIntersection(struct rectangle const rect1,
193 struct rectangle const rect2,
194 struct rectangle * const intersectionP) {
195 /*----------------------------------------------------------------------------
196 Find the intersection between rectangles 'rect1' and 'rect2'.
197 Return it as *intersectionP.
198 -----------------------------------------------------------------------------*/
199 struct penpos tentativeUl, tentativeLr;
200
201 tentativeUl.x = MAX(rect1.ul.x, rect2.ul.x);
202 tentativeUl.y = MAX(rect1.ul.y, rect2.ul.y);
203 tentativeLr.x = MIN(rect1.lr.x, rect2.lr.x);
204 tentativeLr.y = MIN(rect1.lr.y, rect2.lr.y);
205
206 if (tentativeLr.x <= tentativeUl.x ||
207 tentativeLr.y <= tentativeUl.y) {
208 /* No intersection */
209 *intersectionP = emptyRectangle;
210 } else {
211 intersectionP->ul = tentativeUl;
212 intersectionP->lr = tentativeLr;
213 }
214 }
215
216
217
218 void
pamd_filledrectangle(tuple ** const tuples,int const cols,int const rows,int const depth,sample const maxval,int const left,int const top,int const width,int const height,pamd_drawproc drawProc,const void * const clientdata)219 pamd_filledrectangle(tuple ** const tuples,
220 int const cols,
221 int const rows,
222 int const depth,
223 sample const maxval,
224 int const left,
225 int const top,
226 int const width,
227 int const height,
228 pamd_drawproc drawProc,
229 const void * const clientdata) {
230
231 struct rectangle image, request, intersection;
232 unsigned int row;
233
234 if (width < 0)
235 pm_error("negative width %d passed to pamd_filledrectanglep", width);
236 if (height < 0)
237 pm_error("negative height %d passed to pamd_filledrectanglep", height);
238 if (cols < 0)
239 pm_error("negative image width %d passed to pamd_filledrectanglep",
240 cols);
241 if (rows < 0)
242 pm_error("negative image height %d passed to pamd_filledrectanglep",
243 rows);
244
245 request.ul.x = left;
246 request.ul.y = top;
247 request.lr.x = left + width;
248 request.lr.y = top + height;
249
250 image.ul.x = 0;
251 image.ul.y = 0;
252 image.lr.x = cols;
253 image.lr.y = rows;
254
255 findRectangleIntersection(image, request, &intersection);
256
257 /* Draw. */
258 for (row = intersection.ul.y; row < intersection.lr.y; ++row) {
259 unsigned int col;
260 for (col = intersection.ul.x; col < intersection.lr.x; ++col)
261 drawPoint(drawProc, clientdata,
262 tuples, cols, rows, depth, maxval, makePoint(col, row));
263 }
264 }
265
266
267
268 /* Outline drawing stuff. */
269
270 static int linetype = PAMD_LINETYPE_NORMAL;
271
272
273
274 int
pamd_setlinetype(int const type)275 pamd_setlinetype(int const type) {
276
277 int old;
278
279 old = linetype;
280 linetype = type;
281 return old;
282 }
283
284
285 static bool lineclip = TRUE;
286
287
288
289 int
pamd_setlineclip(int const newSetting)290 pamd_setlineclip(int const newSetting) {
291
292 bool previousSetting;
293
294 previousSetting = lineclip;
295
296 lineclip = newSetting;
297
298 return previousSetting;
299 }
300
301
302
303 static void
clipEnd0(pamd_point const p0,pamd_point const p1,int const cols,int const rows,pamd_point * const c0P,bool * const noLineP)304 clipEnd0(pamd_point const p0,
305 pamd_point const p1,
306 int const cols,
307 int const rows,
308 pamd_point * const c0P,
309 bool * const noLineP) {
310 /*----------------------------------------------------------------------------
311 Given a line that goes from p0 to p1, where any of these coordinates may be
312 anywhere in space -- not just in the frame, clip the p0 end to bring it
313 into the frame. Return the clipped-to location as *c0P.
314
315 Iff this is not possible because the entire line described is
316 outside the frame, return *nolineP == true.
317
318 The frame is 'cols' columns starting at 0, by 'rows' rows starting
319 at 0.
320 -----------------------------------------------------------------------------*/
321 pamd_point c0;
322 bool noLine;
323
324 c0 = p0; /* initial value */
325 noLine = FALSE; /* initial value */
326
327 /* Clip End 0 of the line horizontally */
328 if (c0.x < 0) {
329 if (p1.x < 0)
330 noLine = TRUE;
331 else {
332 c0.y = c0.y + (p1.y - c0.y) * (-c0.x) / (p1.x - c0.x);
333 c0.x = 0;
334 }
335 } else if (c0.x >= cols) {
336 if (p1.x >= cols)
337 noLine = TRUE;
338 else {
339 c0.y = c0.y + (p1.y - c0.y) * (cols - 1 - c0.x) / (p1.x - c0.x);
340 c0.x = cols - 1;
341 }
342 }
343
344 /* Clip End 0 of the line vertically */
345 if (c0.y < 0) {
346 if (p1.y < 0)
347 noLine = TRUE;
348 else {
349 c0.x = c0.x + (p1.x - c0.x) * (-c0.y) / (p1.y - c0.y);
350 c0.y = 0;
351 }
352 } else if (c0.y >= rows) {
353 if (p1.y >= rows)
354 noLine = TRUE;
355 else {
356 c0.x = c0.x + (p1.x - c0.x) * (rows - 1 - c0.y) / (p1.y - c0.y);
357 c0.y = rows - 1;
358 }
359 }
360
361 /* Clipping vertically may have moved the endpoint out of frame
362 horizontally. If so, we know the other endpoint is also out of
363 frame horizontally and the line misses the frame entirely.
364 */
365 if (c0.x < 0 || c0.x >= cols) {
366 assert(p1.x < 0 || p1.x >= cols);
367 noLine = TRUE;
368 }
369 *c0P = c0;
370 *noLineP = noLine;
371 }
372
373
374
375 static void
clipEnd1(pamd_point const p0,pamd_point const p1,int const cols,int const rows,pamd_point * const c1P)376 clipEnd1(pamd_point const p0,
377 pamd_point const p1,
378 int const cols,
379 int const rows,
380 pamd_point * const c1P) {
381 /*----------------------------------------------------------------------------
382 Given a line that goes from p0 to p1, where p0 is within the frame, but p1
383 can be anywhere in space, clip the p1 end to bring it into the frame.
384 Return the clipped-to location as *c1P.
385
386 This is guaranteed to be possible, since we already know at least one point
387 (i.e. p0) is in the frame.
388
389 The frame is 'cols' columns starting at 0, by 'rows' rows starting
390 at 0.
391 -----------------------------------------------------------------------------*/
392 pamd_point c1;
393 /* The current clipped location of p1; we clip it multiple times
394 to get the final location.
395 */
396 /* p0 is in the frame: */
397 assert(p0.x >= 0 && p0.x < cols);
398 assert(p0.y >= 0 && p0.y < rows);
399
400 /* Clip End 1 of the line horizontally */
401 c1 = p1; /* initial value */
402
403 if (c1.x < 0) {
404 /* We know the line isn't vertical, since End 0 is in the frame
405 and End 1 is left of frame.
406 */
407 c1.y = c1.y + (p0.y - c1.y) * (-c1.x) / (p0.x - c1.x);
408 c1.x = 0;
409 } else if (c1.x >= cols) {
410 /* We know the line isn't vertical, since End 0 is in the frame
411 and End 1 is right of frame.
412 */
413 c1.y = c1.y + (p0.y - c1.y) * (cols - 1 - c1.x) / (p0.x - c1.x);
414 c1.x = cols - 1;
415 }
416
417 /* Clip End 1 of the line vertically */
418 if (c1.y < 0) {
419 /* We know the line isn't horizontal, since End 0 is in the frame
420 and End 1 is above frame.
421 */
422 c1.x = c1.x + (p0.x - c1.x) * (-c1.y) / (p0.y - c1.y);
423 c1.y = 0;
424 } else if (c1.y >= rows) {
425 /* We know the line isn't horizontal, since End 0 is in the frame
426 and End 1 is below frame.
427 */
428 c1.x = c1.x + (p0.x - c1.x) * (rows - 1 - c1.y) / (p0.y - c1.y);
429 c1.y = rows - 1;
430 }
431
432 *c1P = c1;
433 }
434
435
436
437 static void
clipLine(pamd_point const p0,pamd_point const p1,int const cols,int const rows,pamd_point * const c0P,pamd_point * const c1P,bool * const noLineP)438 clipLine(pamd_point const p0,
439 pamd_point const p1,
440 int const cols,
441 int const rows,
442 pamd_point * const c0P,
443 pamd_point * const c1P,
444 bool * const noLineP) {
445 /*----------------------------------------------------------------------------
446 Clip the line that goes from p0 to p1 so that none of it is outside the
447 boundaries of the raster with width 'cols' and height 'rows'
448
449 The clipped line goes from *c0P to *c1P.
450
451 But if the entire line is outside the boundaries (i.e. we clip the
452 entire line), return *noLineP true and the other values undefined.
453 -----------------------------------------------------------------------------*/
454 pamd_point c0, c1;
455 /* The line we successively modify. Starts out as the input
456 line and ends up as the output line.
457 */
458 bool noLine;
459
460 clipEnd0(p0, p1, cols, rows, &c0, &noLine);
461
462 if (!noLine) {
463 /* p0 is in the frame: */
464 assert(c0.x >= 0 && c0.x < cols);
465 assert(c0.y >= 0 && c0.y < rows);
466
467 clipEnd1(c0, p1, cols, rows, &c1);
468 }
469
470 *c0P = c0;
471 *c1P = c1;
472 *noLineP = noLine;
473 }
474
475
476
477 static void
drawShallowLine(pamd_drawproc drawProc,const void * const clientdata,tuple ** const tuples,int const cols,int const rows,int const depth,sample const maxval,pamd_point const p0,pamd_point const p1)478 drawShallowLine(pamd_drawproc drawProc,
479 const void * const clientdata,
480 tuple ** const tuples,
481 int const cols,
482 int const rows,
483 int const depth,
484 sample const maxval,
485 pamd_point const p0,
486 pamd_point const p1) {
487 /*----------------------------------------------------------------------------
488 Draw a line that is more horizontal than vertical.
489
490 Don't clip.
491
492 Assume the line has distinct start and end points (i.e. it's at least
493 two points).
494 -----------------------------------------------------------------------------*/
495 /* Loop over X domain. */
496 long dy, srow;
497 int dx, col, row, prevrow;
498
499 if (p1.x > p0.x)
500 dx = 1;
501 else
502 dx = -1;
503 dy = (p1.y - p0.y) * DDA_SCALE / abs(p1.x - p0.x);
504 prevrow = row = p0.y;
505 srow = row * DDA_SCALE + DDA_SCALE / 2;
506 col = p0.x;
507 for ( ; ; ) {
508 if (linetype == PAMD_LINETYPE_NODIAGS && row != prevrow) {
509 drawPoint(drawProc, clientdata,
510 tuples, cols, rows, depth, maxval,
511 makePoint(col, prevrow));
512 prevrow = row;
513 }
514 drawPoint(drawProc, clientdata, tuples, cols, rows, depth, maxval,
515 makePoint(col, row));
516 if (col == p1.x)
517 break;
518 srow += dy;
519 row = srow / DDA_SCALE;
520 col += dx;
521 }
522 }
523
524
525
526 static void
drawSteepLine(pamd_drawproc drawProc,const void * const clientdata,tuple ** const tuples,int const cols,int const rows,int const depth,sample const maxval,pamd_point const p0,pamd_point const p1)527 drawSteepLine(pamd_drawproc drawProc,
528 const void * const clientdata,
529 tuple ** const tuples,
530 int const cols,
531 int const rows,
532 int const depth,
533 sample const maxval,
534 pamd_point const p0,
535 pamd_point const p1) {
536 /*----------------------------------------------------------------------------
537 Draw a line that is more vertical than horizontal.
538
539 Don't clip.
540
541 Assume the line has distinct start and end points (i.e. it's at least
542 two points).
543 -----------------------------------------------------------------------------*/
544 /* Loop over Y domain. */
545
546 long dx, scol;
547 int dy, col, row, prevcol;
548
549 if (p1.y > p0.y)
550 dy = 1;
551 else
552 dy = -1;
553 dx = (p1.x - p0.x) * DDA_SCALE / abs(p1.y - p0.y);
554 row = p0.y;
555 prevcol = col = p0.x;
556 scol = col * DDA_SCALE + DDA_SCALE / 2;
557 for ( ; ; ) {
558 if (linetype == PAMD_LINETYPE_NODIAGS && col != prevcol) {
559 drawPoint(drawProc, clientdata,
560 tuples, cols, rows, depth, maxval,
561 makePoint(prevcol, row));
562 prevcol = col;
563 }
564 drawPoint(drawProc, clientdata, tuples, cols, rows, depth, maxval,
565 makePoint(col, row));
566 if (row == p1.y)
567 break;
568 row += dy;
569 scol += dx;
570 col = scol / DDA_SCALE;
571 }
572 }
573
574
575
576 void
pamd_line(tuple ** const tuples,int const cols,int const rows,int const depth,sample const maxval,pamd_point const p0,pamd_point const p1,pamd_drawproc drawProc,const void * const clientdata)577 pamd_line(tuple ** const tuples,
578 int const cols,
579 int const rows,
580 int const depth,
581 sample const maxval,
582 pamd_point const p0,
583 pamd_point const p1,
584 pamd_drawproc drawProc,
585 const void * const clientdata) {
586
587 pamd_point c0, c1;
588 bool noLine; /* There's no line left after clipping */
589
590 pamd_validateCoord(cols);
591 pamd_validateCoord(rows);
592 pamd_validatePoint(p0);
593 pamd_validatePoint(p1);
594
595 if (lineclip) {
596 clipLine(p0, p1, cols, rows, &c0, &c1, &noLine);
597 } else {
598 c0 = p0;
599 c1 = p1;
600 noLine = FALSE;
601 }
602
603 if (noLine) {
604 /* Nothing to draw */
605 } else if (pointsEqual(c0, c1)) {
606 /* This line is just a point. Because there aren't two
607 distinct endpoints, we have a special case.
608 */
609 drawPoint(drawProc, clientdata, tuples, cols, rows, depth, maxval, c0);
610 } else {
611 /* Draw, using a simple DDA. */
612 if (abs(c1.x - c0.x) > abs(c1.y - c0.y))
613 drawShallowLine(drawProc, clientdata, tuples, cols, rows,
614 depth, maxval, c0, c1);
615 else
616 drawSteepLine(drawProc, clientdata, tuples, cols, rows,
617 depth, maxval, c0, c1);
618 }
619 }
620
621
622
623 static unsigned int
distanceFromLine(pamd_point const p,pamd_point const l0,pamd_point const l1)624 distanceFromLine(pamd_point const p,
625 pamd_point const l0,
626 pamd_point const l1) {
627 /*----------------------------------------------------------------------------
628 Compute, sort of, the distance between point 'p' and the line through
629 'l0' and 'l1'.
630
631 I don't really know the signficance of this measurement.
632 -----------------------------------------------------------------------------*/
633
634 pamd_point const middle = middlePoint(l0, l1);
635
636 return (abs(p.x - middle.x) + abs(p.y - middle.y));
637 }
638
639
640
641 void
pamd_spline3(tuple ** const tuples,int const cols,int const rows,int const depth,sample const maxval,pamd_point const p0,pamd_point const ctl,pamd_point const p1,pamd_drawproc drawProc,const void * const clientdata)642 pamd_spline3(tuple ** const tuples,
643 int const cols,
644 int const rows,
645 int const depth,
646 sample const maxval,
647 pamd_point const p0,
648 pamd_point const ctl,
649 pamd_point const p1,
650 pamd_drawproc drawProc,
651 const void * const clientdata) {
652
653 static unsigned int const splineThresh = 3;
654 /* The limit of recursion */
655
656 if (distanceFromLine(ctl, p0, p1) <= splineThresh) {
657 /* The control point is pretty close to the straight line that
658 joins the endpoints, so we'll just draw a straight line.
659 */
660 pamd_line(
661 tuples, cols, rows, depth, maxval, p0, p1, drawProc, clientdata);
662 } else {
663 /* We want some curvature, so pick a point (b) sort of between the
664 two endpoints and the control point and then draw a spline
665 between each of the endpoints and (b):
666 */
667 pamd_point const a = middlePoint(p0, ctl);
668 pamd_point const c = middlePoint(ctl, p1);
669 pamd_point const b = middlePoint(a, c);
670
671 pamd_spline3(
672 tuples, cols, rows, depth, maxval, p0, a, b, drawProc, clientdata);
673
674 pamd_spline3(
675 tuples, cols, rows, depth, maxval, b, c, p1, drawProc, clientdata);
676 }
677 }
678
679
680
681 void
pamd_polyspline(tuple ** const tuples,unsigned int const cols,unsigned int const rows,unsigned int const depth,sample const maxval,pamd_point const p0,unsigned int const nc,pamd_point * const c,pamd_point const p1,pamd_drawproc drawProc,const void * const clientdata)682 pamd_polyspline(tuple ** const tuples,
683 unsigned int const cols,
684 unsigned int const rows,
685 unsigned int const depth,
686 sample const maxval,
687 pamd_point const p0,
688 unsigned int const nc,
689 pamd_point * const c,
690 pamd_point const p1,
691 pamd_drawproc drawProc,
692 const void * const clientdata) {
693
694 pamd_point p;
695
696 unsigned int i;
697
698 assert(nc > 0);
699
700 p = p0;
701 for (i = 0; i < nc - 1; ++i) {
702 pamd_point const n = middlePoint(c[i], c[i+1]);
703 pamd_spline3(
704 tuples, cols, rows, depth, maxval, p, c[i], n,
705 drawProc, clientdata);
706 p = n;
707 }
708 pamd_spline3(
709 tuples, cols, rows, depth, maxval, p, c[nc - 1], p1,
710 drawProc, clientdata);
711 }
712
713
714
715 void
pamd_spline4(tuple ** const tuples,unsigned int const cols,unsigned int const rows,unsigned int const depth,sample const maxval,pamd_point const endPt0,pamd_point const endPt1,pamd_point const ctlPt0,pamd_point const ctlPt1,pamd_drawproc drawproc,const void * const clientdata)716 pamd_spline4(tuple ** const tuples,
717 unsigned int const cols,
718 unsigned int const rows,
719 unsigned int const depth,
720 sample const maxval,
721 pamd_point const endPt0,
722 pamd_point const endPt1,
723 pamd_point const ctlPt0,
724 pamd_point const ctlPt1,
725 pamd_drawproc drawproc,
726 const void * const clientdata) {
727 /*----------------------------------------------------------------------------
728 Draw a cubic spline from 'endPt0' to 'endPt1', using 'ctlPt0' and
729 'ctlPt1' as control points in the classic way: a line through
730 'endPt0' and 'ctlPt0' is tangent to the curve at 'entPt0' and the
731 length of that line controls "enthusiasm," whatever that is.
732 Same for 'endPt1' and 'ctlPt1'.
733 -----------------------------------------------------------------------------*/
734
735 pm_error("pamd_spline4() has not been written yet!");
736
737 }
738
739
740
741 void
pamd_circle(tuple ** const tuples,unsigned int const cols,unsigned int const rows,unsigned int const depth,sample const maxval,pamd_point const center,unsigned int const radius,pamd_drawproc drawProc,const void * const clientData)742 pamd_circle(tuple ** const tuples,
743 unsigned int const cols,
744 unsigned int const rows,
745 unsigned int const depth,
746 sample const maxval,
747 pamd_point const center,
748 unsigned int const radius,
749 pamd_drawproc drawProc,
750 const void * const clientData) {
751 /*----------------------------------------------------------------------------
752 If lineclip mode is on, draw only points within the image.
753 If lineclip is off, "draw" all points (by designated drawproc). Note
754 that the drawproc can't actually draw a point outside the image, but
755 it might maintain state that is affected by imaginary points outside
756 the image.
757
758 Initial point is 3 o'clock.
759 -----------------------------------------------------------------------------*/
760 if (radius >= DDA_SCALE)
761 pm_error("Error drawing circle. Radius %d is too large.", radius);
762
763 pamd_validateCoord(center.x + radius);
764 pamd_validateCoord(center.y + radius);
765 pamd_validateCoord(center.x - radius);
766 pamd_validateCoord(center.y - radius);
767
768 if (radius > 0) {
769 long const e = DDA_SCALE / radius;
770
771 pamd_point const p0 = makePoint(radius, 0); /* 3 o'clock */
772 /* The starting point around the circle, assuming (0, 0) center */
773 pamd_point p;
774 /* Current drawing position in the circle, assuming (0,0) center */
775 bool onFirstPoint;
776 bool prevPointExists;
777 pamd_point prevPoint;
778 /* Previous drawing position, assuming (0, 0) center*/
779 long sx, sy; /* 'p', scaled by DDA_SCALE */
780
781 p = p0;
782
783 sx = p.x * DDA_SCALE + DDA_SCALE / 2;
784 sy = p.y * DDA_SCALE + DDA_SCALE / 2;
785
786 onFirstPoint = TRUE;
787 prevPointExists = FALSE;
788
789 while (onFirstPoint || !pointsEqual(p, p0)) {
790 if (prevPointExists && pointsEqual(p, prevPoint)) {
791 /* We're on the same point we were on last time (we moved less
792 than a point's worth). Just keep moving.
793 */
794 } else {
795 pamd_point const imagePoint = vectorSum(center,p);
796 if (!lineclip || pointIsWithinBounds(imagePoint, cols, rows))
797 drawPoint(drawProc, clientData,
798 tuples, cols, rows, depth, maxval, imagePoint);
799
800 prevPoint = p;
801 prevPointExists = TRUE;
802 }
803
804 if (!pointsEqual(p, p0))
805 onFirstPoint = FALSE;
806
807 sx += e * sy / DDA_SCALE;
808 sy -= e * sx / DDA_SCALE;
809 p = makePoint(sx / DDA_SCALE, sy / DDA_SCALE);
810 }
811 }
812 }
813
814
815
816 /* Arbitrary fill stuff. */
817
818 typedef struct {
819 pamd_point point;
820 int edge;
821 } coord;
822
823 typedef struct fillState {
824 int n;
825 /* Number of elements in 'coords' */
826 int size;
827 int curedge;
828 int segstart;
829 int ydir;
830 int startydir;
831 coord * coords;
832 } fillState;
833
834 typedef struct fillobj {
835
836 /* The only reason we have a struct fillState separate from
837 struct fillobj is that the drawproc interface is defined to
838 have drawing not modify the fillobj, i.e. it passed
839 const fillobj * to the drawing program.
840 */
841 struct fillState * stateP;
842 } fillobj;
843
844 #define SOME 1000
845
846 static int oldclip;
847
848
849
850 struct fillobj *
pamd_fill_create(void)851 pamd_fill_create(void) {
852
853 fillobj * fillObjP;
854 struct fillState * stateP;
855
856 MALLOCVAR(fillObjP);
857 if (fillObjP == NULL)
858 pm_error("out of memory allocating a fillhandle");
859
860 MALLOCVAR(stateP);
861 if (stateP == NULL)
862 pm_error("out of memory allocating a fillhandle");
863
864 stateP->n = 0;
865 stateP->size = SOME;
866 MALLOCARRAY(stateP->coords, stateP->size);
867 if (stateP->coords == NULL)
868 pm_error("out of memory allocating a fillhandle");
869 stateP->curedge = 0;
870
871 fillObjP->stateP = stateP;
872
873 /* Turn off line clipping. */
874 /* UGGH! We must eliminate this global variable */
875 oldclip = pamd_setlineclip(0);
876
877 return fillObjP;
878 }
879
880
881
882 void
pamd_fill_destroy(struct fillobj * const fillObjP)883 pamd_fill_destroy(struct fillobj * const fillObjP) {
884
885 free(fillObjP->stateP->coords);
886 free(fillObjP->stateP);
887 free(fillObjP);
888 }
889
890
891
892 static void
addCoord(struct fillState * const stateP,pamd_point const point)893 addCoord(struct fillState * const stateP,
894 pamd_point const point) {
895
896 stateP->coords[stateP->n].point = point;
897 stateP->coords[stateP->n].edge = stateP->curedge;
898
899 ++stateP->n;
900 }
901
902
903
904 static void
startNewSegment(struct fillState * const stateP)905 startNewSegment(struct fillState * const stateP) {
906 /*----------------------------------------------------------------------------
907 Close off the segment we're currently building and start a new one.
908 -----------------------------------------------------------------------------*/
909 if (stateP->startydir != 0 && stateP->ydir != 0) {
910 /* There's stuff in the current segment. */
911 if (stateP->startydir == stateP->ydir) {
912 /* Oops, first edge and last edge of current segment are the same.
913 Change all points in the first edge to be in the last.
914 */
915 int const firstEdge = stateP->coords[stateP->segstart].edge;
916 int const lastEdge = stateP->coords[stateP->n - 1].edge;
917 coord * const segStartCoordP = &stateP->coords[stateP->segstart];
918 coord * const segEndCoordP = &stateP->coords[stateP->n];
919
920 coord * fcP;
921
922 for (fcP = segStartCoordP;
923 fcP < segEndCoordP && fcP->edge == firstEdge;
924 ++fcP)
925 fcP->edge = lastEdge;
926 }
927 }
928 /* And start new segment. */
929 ++stateP->curedge;
930 stateP->segstart = stateP->n;
931 stateP->ydir = 0;
932 stateP->startydir = 0;
933 }
934
935
936
937 static void
continueSegment(struct fillState * const stateP,int const dy)938 continueSegment(struct fillState * const stateP,
939 int const dy) {
940 /*----------------------------------------------------------------------------
941 'dy' is how much the current point is above the previous one.
942 -----------------------------------------------------------------------------*/
943 if (dy != 0) {
944 if (stateP->ydir != 0 && stateP->ydir != dy) {
945 /* Direction changed. Insert a fake coord, old
946 position but new edge number.
947 */
948 ++stateP->curedge;
949 addCoord(stateP, stateP->coords[stateP->n - 1].point);
950 }
951 stateP->ydir = dy;
952 if (stateP->startydir == 0)
953 stateP->startydir = dy;
954 }
955 }
956
957
958
959 /* pamd_fill_drawproc() is a drawproc that turns an outline drawing function
960 into a filled shape function. This is a somewhat off-label application of
961 a drawproc: A drawproc is intended just to draw a point. So e.g. you
962 might draw a circle with a fat brush by calling pamd_circle with a drawproc
963 that draws a point as a 10-tuple disk.
964
965 But pamd_fill_drawproc() just draws a point the trivial way: as one tuple.
966 However, it tracks every point that is drawn in a form that a subsequent
967 pamd_fill() call can use to to fill in the shape drawn, assuming it turns
968 out to be a closed shape.
969 */
970
971 void
pamd_fill_drawproc(tuple ** const tuples,unsigned int const cols,unsigned int const rows,unsigned int const depth,sample const maxval,pamd_point const p,const void * const clientdata)972 pamd_fill_drawproc(tuple ** const tuples,
973 unsigned int const cols,
974 unsigned int const rows,
975 unsigned int const depth,
976 sample const maxval,
977 pamd_point const p,
978 const void * const clientdata) {
979
980 const fillobj * const fillObjP = clientdata;
981 struct fillState * const stateP = fillObjP->stateP;
982
983 /* Make room for two more coords, the max we might add. */
984 if (stateP->n + 2 > stateP->size) {
985 stateP->size += SOME;
986 REALLOCARRAY(stateP->coords, stateP->size);
987 if (stateP->coords == NULL)
988 pm_error("out of memory enlarging a fillhandle");
989 }
990
991 if (stateP->n == 0) {
992 /* Start first segment. */
993 stateP->segstart = stateP->n;
994 stateP->ydir = 0;
995 stateP->startydir = 0;
996 addCoord(stateP, p);
997 } else {
998 pamd_point const prevPoint = stateP->coords[stateP->n - 1].point;
999 int const dx = p.x - prevPoint.x;
1000 int const dy = p.y - prevPoint.y;
1001
1002 if (dx == 0 && dy == 0) {
1003 /* These are the same coords we had last time; don't bother */
1004 } else {
1005 if (abs(dx) > 1 || abs(dy) > 1)
1006 startNewSegment(stateP);
1007 else
1008 continueSegment(stateP, dy);
1009
1010 addCoord(stateP, p);
1011 }
1012 }
1013 }
1014
1015
1016
1017 #ifndef LITERAL_FN_DEF_MATCH
1018 static qsort_comparison_fn yxCompare;
1019 #endif
1020
1021 static int
yxCompare(const void * const c1Arg,const void * const c2Arg)1022 yxCompare(const void * const c1Arg,
1023 const void * const c2Arg) {
1024
1025 const coord * const c1P = c1Arg;
1026 const coord * const c2P = c2Arg;
1027
1028 pamd_point const p1 = c1P->point;
1029 pamd_point const p2 = c2P->point;
1030
1031 int retval;
1032
1033 if (p1.y > p2.y)
1034 retval = 1;
1035 else if (p1.y < p2.y)
1036 retval = -1;
1037 else if (p1.x > p2.x)
1038 retval = 1;
1039 else if (p1.x < p2.x)
1040 retval = -1;
1041 else
1042 retval = 0;
1043
1044 return retval;
1045 }
1046
1047
1048
1049 void
pamd_fill(tuple ** const tuples,int const cols,int const rows,int const depth,sample const maxval,struct fillobj * const fillObjP,pamd_drawproc drawProc,const void * const clientdata)1050 pamd_fill(tuple ** const tuples,
1051 int const cols,
1052 int const rows,
1053 int const depth,
1054 sample const maxval,
1055 struct fillobj * const fillObjP,
1056 pamd_drawproc drawProc,
1057 const void * const clientdata) {
1058
1059 struct fillState * const fh = fillObjP->stateP;
1060
1061 int pedge;
1062 int i, edge, lx, rx, py;
1063 coord * cp;
1064 bool eq;
1065 bool leftside;
1066
1067 /* Close off final segment. */
1068 if (fh->n > 0 && fh->startydir != 0 && fh->ydir != 0) {
1069 if (fh->startydir == fh->ydir) {
1070 /* Oops, first edge and last edge are the same. */
1071 coord * fcp;
1072 const coord * const fcpLast = & (fh->coords[fh->n - 1]);
1073 int lastedge, oldedge;
1074
1075 lastedge = fh->coords[fh->n - 1].edge;
1076 fcp = &(fh->coords[fh->segstart]);
1077 oldedge = fcp->edge;
1078 for ( ; fcp<=fcpLast && fcp->edge == oldedge; ++fcp )
1079 fcp->edge = lastedge;
1080 }
1081 }
1082 /* Restore clipping now. */
1083 pamd_setlineclip(oldclip);
1084
1085 /* Sort the coords by Y, secondarily by X. */
1086 qsort((char*) fh->coords, fh->n, sizeof(coord), yxCompare);
1087
1088 /* Find equal coords with different edge numbers, and swap if necessary. */
1089 edge = -1;
1090 for (i = 0; i < fh->n; ++i) {
1091 cp = &fh->coords[i];
1092 if (i > 1 && eq && cp->edge != edge && cp->edge == pedge) {
1093 /* Swap .-1 and .-2. */
1094 coord t;
1095
1096 t = fh->coords[i-1];
1097 fh->coords[i-1] = fh->coords[i-2];
1098 fh->coords[i-2] = t;
1099 }
1100 if (i > 0) {
1101 if (cp->point.x == lx && cp->point.y == py) {
1102 eq = TRUE;
1103 if (cp->edge != edge && cp->edge == pedge) {
1104 /* Swap . and .-1. */
1105 coord t;
1106
1107 t = *cp;
1108 *cp = fh->coords[i-1];
1109 fh->coords[i-1] = t;
1110 }
1111 } else
1112 eq = FALSE;
1113 }
1114 lx = cp->point.x;
1115 py = cp->point.y;
1116 pedge = edge;
1117 edge = cp->edge;
1118 }
1119
1120 /* Ok, now run through the coords filling spans. */
1121 for (i = 0; i < fh->n; ++i) {
1122 cp = &fh->coords[i];
1123 if (i == 0) {
1124 lx = rx = cp->point.x;
1125 py = cp->point.y;
1126 edge = cp->edge;
1127 leftside = TRUE;
1128 } else {
1129 if (cp->point.y != py) {
1130 /* Row changed. Emit old span and start a new one. */
1131 pamd_filledrectangle(
1132 tuples, cols, rows, depth, maxval, lx, py, rx - lx + 1, 1,
1133 drawProc, clientdata);
1134 lx = rx = cp->point.x;
1135 py = cp->point.y;
1136 edge = cp->edge;
1137 leftside = TRUE;
1138 } else {
1139 if (cp->edge == edge) {
1140 /* Continuation of side. */
1141 rx = cp->point.x;
1142 } else {
1143 /* Edge changed. Is it a span? */
1144 if (leftside) {
1145 rx = cp->point.x;
1146 leftside = FALSE;
1147 } else {
1148 /* Got a span to fill. */
1149 pamd_filledrectangle(
1150 tuples, cols, rows, depth, maxval,
1151 lx, py, rx - lx + 1, 1, drawProc, clientdata);
1152 lx = rx = cp->point.x;
1153 leftside = TRUE;
1154 }
1155 edge = cp->edge;
1156 }
1157 }
1158 }
1159 }
1160 }
1161
1162
1163
1164 /* Table used to look up sine of angles from 0 through 90 degrees.
1165 The value returned is the sine * 65536. Symmetry is used to
1166 obtain sine and cosine for arbitrary angles using this table. */
1167
1168 static long sintab[] = {
1169 0, 1143, 2287, 3429, 4571, 5711, 6850, 7986, 9120, 10252, 11380,
1170 12504, 13625, 14742, 15854, 16961, 18064, 19160, 20251, 21336,
1171 22414, 23486, 24550, 25606, 26655, 27696, 28729, 29752, 30767,
1172 31772, 32768, 33753, 34728, 35693, 36647, 37589, 38521, 39440,
1173 40347, 41243, 42125, 42995, 43852, 44695, 45525, 46340, 47142,
1174 47929, 48702, 49460, 50203, 50931, 51643, 52339, 53019, 53683,
1175 54331, 54963, 55577, 56175, 56755, 57319, 57864, 58393, 58903,
1176 59395, 59870, 60326, 60763, 61183, 61583, 61965, 62328, 62672,
1177 62997, 63302, 63589, 63856, 64103, 64331, 64540, 64729, 64898,
1178 65047, 65176, 65286, 65376, 65446, 65496, 65526, 65536
1179 };
1180
1181 static int extleft, exttop, extright, extbottom; /* To accumulate extents */
1182
1183
1184
1185 /* LINTLIBRARY */
1186
1187 static long
isin(int const argDeg)1188 isin(int const argDeg) {
1189 /*----------------------------------------------------------------------------
1190 Return sine of an angle in integral degrees. The value returned is 65536
1191 times the sine.
1192 -----------------------------------------------------------------------------*/
1193
1194 int deg360;
1195 /* The argument reduced to the range 0-360 degrees */
1196
1197 if (argDeg < 0) {
1198 deg360 = (360 - ((- argDeg) % 360)) % 360;
1199 } else if (argDeg >= 360) {
1200 deg360 = argDeg % 360;
1201 } else
1202 deg360 = argDeg;
1203
1204 /* Now look up from table according to quadrant. */
1205
1206 if (deg360 <= 90) {
1207 return sintab[deg360];
1208 } else if (deg360 <= 180) {
1209 return sintab[180 - deg360];
1210 } else if (deg360 <= 270) {
1211 return -sintab[deg360 - 180];
1212 }
1213 return -sintab[360 - deg360];
1214 }
1215
1216
1217
1218 static long
icos(int const deg)1219 icos(int const deg) {
1220 /*----------------------------------------------------------------------------
1221 Return cosine of an angle in integral degrees. The value returned is 65536
1222 times the cosine
1223 -----------------------------------------------------------------------------*/
1224 return isin(deg + 90);
1225 }
1226
1227
1228
1229 static int
twosCompByteValue(unsigned char const c)1230 twosCompByteValue(unsigned char const c) {
1231 /*----------------------------------------------------------------------------
1232 E.g. if 'c' is 0x5, return 5. If 'c' is 0xF0, return -16.
1233 -----------------------------------------------------------------------------*/
1234 return (char)c;
1235 }
1236
1237
1238
1239 static int
glyphSkipBefore(const struct ppmd_glyph * const glyphP)1240 glyphSkipBefore(const struct ppmd_glyph * const glyphP) {
1241
1242 return twosCompByteValue(glyphP->header.skipBefore);
1243 }
1244
1245
1246
1247 static int
glyphWidth(const struct ppmd_glyph * const glyphP)1248 glyphWidth(const struct ppmd_glyph * const glyphP) {
1249
1250 return twosCompByteValue(glyphP->header.skipAfter) -
1251 twosCompByteValue(glyphP->header.skipBefore);
1252 }
1253
1254
1255 static pamd_point
commandPoint(const struct ppmd_glyphCommand * const commandP)1256 commandPoint(const struct ppmd_glyphCommand * const commandP) {
1257 /*----------------------------------------------------------------------------
1258 Return the point which is the argument of glyph drawing command
1259 *commandP. The origin of the coordinate system for this point
1260 is the center of the glyph cell and the scale is the scale of the
1261 font, so (-10, -10) means the upper left corner of the glyph cell.
1262 -----------------------------------------------------------------------------*/
1263 return makePoint(twosCompByteValue(commandP->x),
1264 twosCompByteValue(commandP->y));
1265 }
1266
1267 #define Scalef 21 /* Font design size */
1268 #define Descend 9 /* Descender offset */
1269
1270
1271 static pamd_point
textPosFromFontPos(pamd_point const fontPos,pamd_point const textBoxOrigin,pamd_point const center,pamd_point const glyphOrigin,unsigned int const height,long const rotcos,long const rotsin)1272 textPosFromFontPos(pamd_point const fontPos,
1273 pamd_point const textBoxOrigin,
1274 pamd_point const center,
1275 pamd_point const glyphOrigin,
1276 unsigned int const height,
1277 long const rotcos,
1278 long const rotsin) {
1279 /*----------------------------------------------------------------------------
1280 'fontPos' is a position within a glyph as told by the font definition.
1281 It is relative to the center of the glyph, in units of font tuples
1282 (1/21 of a glyph cell).
1283
1284 We return the position on the canvas of that point.
1285
1286 That takes into account where in the text box we are, where the text box
1287 is on the canvas, the size of the characters, and the rotation of the
1288 text box.
1289 -----------------------------------------------------------------------------*/
1290 pamd_point const ptl = vectorSum(center, fontPos);
1291 /* Position relative to the top left of the standard glyph cell */
1292
1293 pamd_point const pl = vectorSum(glyphOrigin, ptl);
1294 /* Position relative to the top left of the whole text box,
1295 assuming the text box is horizontal and has font scale.
1296 */
1297
1298 pamd_point const ps = makePoint((pl.x * (int)height) / Scalef,
1299 (pl.y * (int)height) / Scalef);
1300 /* Same as above, but with the text box its actual size */
1301
1302 pamd_point const retval =
1303 makePoint(textBoxOrigin.x +
1304 (ps.x * rotcos - (ps.y-(int)height) * rotsin) / 65536,
1305 textBoxOrigin.y +
1306 (ps.x * rotsin + (ps.y-(int)height) * rotcos) / 65536);
1307
1308 pamd_validatePoint(retval);
1309
1310 return retval;
1311 }
1312
1313
1314
1315 static void
drawGlyph(const struct ppmd_glyph * const glyphP,pamd_point const glyphOrigin,tuple ** const tuples,unsigned int const cols,unsigned int const rows,unsigned int const depth,sample const maxval,int const height,pamd_point const textBoxOrigin,long const rotcos,long const rotsin,pamd_drawproc drawProc,const void * const clientdata,unsigned int * const cursorAdvanceP)1316 drawGlyph(const struct ppmd_glyph * const glyphP,
1317 pamd_point const glyphOrigin,
1318 tuple ** const tuples,
1319 unsigned int const cols,
1320 unsigned int const rows,
1321 unsigned int const depth,
1322 sample const maxval,
1323 int const height,
1324 pamd_point const textBoxOrigin,
1325 long const rotcos,
1326 long const rotsin,
1327 pamd_drawproc drawProc,
1328 const void * const clientdata,
1329 unsigned int * const cursorAdvanceP
1330 ) {
1331 /*----------------------------------------------------------------------------
1332 'glyphOrigin' is the position relative to the upper left corner of the text
1333 box of the upper left corner of this glyph cell. It is in units of font
1334 tuples (so you have to scale it by the font size to actual distance on
1335 the canvas).
1336
1337 We return as *cursorAdvanceP the amount to the right of this glyph cell
1338 the next glyph cell on the line (if any) should be.
1339
1340 The actual glyph cell may be a little to the left of the nominal position
1341 because of kerning. The font says how much to shift the cell left.
1342
1343 'textBoxOrigin' is the left end of the baseline of the top line in the
1344 text box, in the coordinate system of the canvas. 'rotcos' and 'rotsin'
1345 tell how that text box is rotated with respect to the horizontal on the
1346 canvas.
1347
1348 'height' is the height in canvas tuples of a glyph. This is a scale factor
1349 to convert font coordinates to canvas coordinates.
1350 -----------------------------------------------------------------------------*/
1351 pamd_point const center = makePoint(-glyphSkipBefore(glyphP), Scalef/2);
1352 /* This is what you have to add to the coordinates in a glyph
1353 command, which are relative to the center of the glyph, to get
1354 coordinates relative to the upper left corner of the glyph
1355 */
1356 pamd_point p;
1357 /* Current drawing position within the glyph. Origin is the top
1358 left of the glyph cell. Units are font tuples.
1359 */
1360 unsigned int commandNum;
1361
1362 p = textPosFromFontPos(makePoint(0, 0),
1363 textBoxOrigin,
1364 center,
1365 glyphOrigin,
1366 height,
1367 rotcos, rotsin); /* initial value */
1368
1369 for (commandNum = 0;
1370 commandNum < glyphP->header.commandCount;
1371 ++commandNum) {
1372
1373 const struct ppmd_glyphCommand * const commandP =
1374 &glyphP->commandList[commandNum];
1375
1376 switch (commandP->verb) {
1377 case CMD_NOOP:
1378 break;
1379 case CMD_DRAWLINE:
1380 {
1381 pamd_point const n = textPosFromFontPos(commandPoint(commandP),
1382 textBoxOrigin,
1383 center,
1384 glyphOrigin,
1385 height,
1386 rotcos, rotsin);
1387
1388 pamd_line(tuples, cols, rows, depth, maxval, p, n,
1389 drawProc, clientdata);
1390
1391 p = n;
1392 }
1393 break;
1394 case CMD_MOVEPEN:
1395 p = textPosFromFontPos(commandPoint(commandP),
1396 textBoxOrigin,
1397 center,
1398 glyphOrigin,
1399 height,
1400 rotcos, rotsin);
1401 break;
1402 }
1403 }
1404 *cursorAdvanceP = glyphWidth(glyphP);
1405 }
1406
1407
1408
1409 void
pamd_text(tuple ** const tuples,int const cols,int const rows,int const depth,sample const maxval,pamd_point const pos,int const height,int const angle,const char * const sArg,pamd_drawproc drawProc,const void * const clientdata)1410 pamd_text(tuple** const tuples,
1411 int const cols,
1412 int const rows,
1413 int const depth,
1414 sample const maxval,
1415 pamd_point const pos,
1416 int const height,
1417 int const angle,
1418 const char * const sArg,
1419 pamd_drawproc drawProc,
1420 const void * const clientdata) {
1421 /*----------------------------------------------------------------------------
1422 Draw the zero-terminated string 'sArg', with its baseline starting at point
1423 'pos', inclined by 'angle' degrees to the X axis, with letters 'height'
1424 tuples high (descenders will extend below the baseline). We pass the
1425 supplied drawproc and clientdata to pamd_linep, which performs the actual
1426 drawing.
1427
1428 There may be multiple lines of text. The baseline of the topmost line
1429 starts at 'pos'.
1430 -----------------------------------------------------------------------------*/
1431 const struct ppmd_font * const fontP = ppmd_get_font();
1432
1433 long rotsin, rotcos;
1434 pamd_point p;
1435 const char * s;
1436
1437 pamd_validatePoint(pos);
1438
1439 p = makePoint(0, 0);
1440 rotsin = isin(-angle);
1441 rotcos = icos(-angle);
1442
1443 for (s = &sArg[0]; *s; ) {
1444 unsigned char const ch = *s++;
1445
1446 if (ch >= fontP->header.firstCodePoint &&
1447 ch < fontP->header.firstCodePoint + fontP->header.characterCount) {
1448
1449 const struct ppmd_glyph * const glyphP =
1450 &fontP->glyphTable[ch - fontP->header.firstCodePoint];
1451
1452 unsigned int cursorAdvance;
1453
1454 pamd_validatePoint(p);
1455
1456 drawGlyph(glyphP, p, tuples, cols, rows, depth, maxval,
1457 height, pos, rotcos, rotsin,
1458 drawProc, clientdata, &cursorAdvance);
1459 p.x += cursorAdvance;
1460 } else if (ch == '\n') {
1461 /* Move to the left edge of the next line down */
1462 p.y += Scalef + Descend;
1463 p.x = 0;
1464 }
1465 }
1466 }
1467
1468
1469
1470 #ifndef LITERAL_FN_DEF_MATCH
1471 static pamd_drawproc extetnsDrawproc;
1472 #endif
1473
1474 static void
extentsDrawproc(tuple ** const tuples,unsigned int const cols,unsigned int const rows,unsigned int const depth,sample const maxval,pamd_point const p,const void * const clientdata)1475 extentsDrawproc(tuple** const tuples,
1476 unsigned int const cols,
1477 unsigned int const rows,
1478 unsigned int const depth,
1479 sample const maxval,
1480 pamd_point const p,
1481 const void * const clientdata) {
1482 /*----------------------------------------------------------------------------
1483 Drawproc which just accumulates the extents rectangle bounding the
1484 text.
1485 -----------------------------------------------------------------------------*/
1486 extleft = MIN(extleft, p.x);
1487 exttop = MIN(exttop, p.y);
1488 extright = MAX(extright, p.x);
1489 extbottom = MAX(extbottom, p.y);
1490 }
1491
1492
1493
1494 void
pamd_text_box(int const height,int const angle,const char * const s,int * const leftP,int * const topP,int * const rightP,int * const bottomP)1495 pamd_text_box(int const height,
1496 int const angle,
1497 const char * const s,
1498 int * const leftP,
1499 int * const topP,
1500 int * const rightP,
1501 int * const bottomP) {
1502 /*----------------------------------------------------------------------------
1503 Calculate extents rectangle for a given piece of text. For most
1504 applications where extents are needed, angle should be zero to obtain the
1505 unrotated extents. If you need the extents box for post-rotation text,
1506 however, you can set angle nonzero and it will be calculated correctly.
1507 -----------------------------------------------------------------------------*/
1508 extleft = 32767;
1509 exttop = 32767;
1510 extright = -32767;
1511 extbottom = -32767;
1512
1513 pamd_text(NULL, 32767, 32767, 255, 255, makePoint(1000, 1000),
1514 height, angle, s,
1515 extentsDrawproc, NULL);
1516
1517 *leftP = extleft - 1000;
1518 *topP = exttop - 1000;
1519 *rightP = extright - 1000;
1520 *bottomP = extbottom - 1000;
1521 }
1522
1523
1524
1525