1 /* Copyright (C) 2003 Aladdin Enterprises. All rights reserved.
2
3 This software is provided AS-IS with no warranty, either express or
4 implied.
5
6 This software is distributed under license and may not be copied,
7 modified or distributed except as expressly authorized under the terms
8 of the license contained in the file LICENSE in this distribution.
9
10 For more information about licensing, please refer to
11 http://www.ghostscript.com/licensing/. For information on
12 commercial licensing, go to http://www.artifex.com/licensing/ or
13 contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14 San Rafael, CA 94903, U.S.A., +1(415)492-9861.
15 */
16
17 /*$Id: gzspotan.c,v 1.14 2005/05/12 02:01:32 alexcher Exp $ */
18 /* A spot analyzer device implementation. */
19 /*
20 This implements a spot topology analyzis and
21 stem recognition for True Type grid fitting.
22 */
23 #include "gx.h"
24 #include "gserrors.h"
25 #include "gsdevice.h"
26 #include "gzspotan.h"
27 #include "gxfixed.h"
28 #include "gxdevice.h"
29 #include "gxfdrop.h" /* Only for VD_* constants. */
30 #include "gzpath.h"
31 #include "memory_.h"
32 #include "math_.h"
33 #include "vdtrace.h"
34 #include <assert.h>
35
36 #define VD_TRAP_N_COLOR RGB(128, 128, 0)
37 #define VD_TRAP_U_COLOR RGB(0, 0, 255)
38 #define VD_CONT_COLOR RGB(0, 255, 0)
39 #define VD_STEM_COLOR RGB(255, 255, 255)
40 #define VD_HINT_COLOR RGB(255, 0, 0)
41
42 public_st_device_spot_analyzer();
43 private_st_san_trap();
44 private_st_san_trap_contact();
45
46 private dev_proc_open_device(san_open);
47 private dev_proc_close_device(san_close);
48 private dev_proc_get_clipping_box(san_get_clipping_box);
49
50
51 /* --------------------- List management ------------------------- */
52 /* fixme : use something like C++ patterns to generate same functions for various types. */
53
54
55 private inline void
free_trap_list(gs_memory_t * mem,gx_san_trap ** list)56 free_trap_list(gs_memory_t *mem, gx_san_trap **list)
57 {
58 gx_san_trap *t = *list, *t1;
59
60 for (t = *list; t != NULL; t = t1) {
61 t1 = t->link;
62 gs_free_object(mem, t, "free_trap_list");
63 }
64 *list = 0;
65 }
66
67 private inline void
free_cont_list(gs_memory_t * mem,gx_san_trap_contact ** list)68 free_cont_list(gs_memory_t *mem, gx_san_trap_contact **list)
69 {
70 gx_san_trap_contact *t = *list, *t1;
71
72 for (t = *list; t != NULL; t = t1) {
73 t1 = t->link;
74 gs_free_object(mem, t, "free_trap_list");
75 }
76 *list = 0;
77 }
78
79 private inline gx_san_trap *
trap_reserve(gx_device_spot_analyzer * padev)80 trap_reserve(gx_device_spot_analyzer *padev)
81 {
82 gx_san_trap *t = padev->trap_free;
83
84 if (t != NULL) {
85 padev->trap_free = t->link;
86 } else {
87 if (padev->trap_buffer_count > 10000)
88 return NULL;
89 t = gs_alloc_struct(padev->memory, gx_san_trap,
90 &st_san_trap, "trap_reserve");
91 if (t == NULL)
92 return NULL;
93 t->link = NULL;
94 if (padev->trap_buffer_last == NULL)
95 padev->trap_buffer = t;
96 else
97 padev->trap_buffer_last->link = t;
98 padev->trap_buffer_last = t;
99 padev->trap_buffer_count++;
100 }
101 return t;
102 }
103
104 private inline gx_san_trap_contact *
cont_reserve(gx_device_spot_analyzer * padev)105 cont_reserve(gx_device_spot_analyzer *padev)
106 {
107 gx_san_trap_contact *t = padev->cont_free;
108
109 if (t != NULL) {
110 padev->cont_free = t->link;
111 } else {
112 if (padev->cont_buffer_count > 10000)
113 return NULL;
114 t = gs_alloc_struct(padev->memory, gx_san_trap_contact,
115 &st_san_trap_contact, "cont_reserve");
116 if (t == NULL)
117 return NULL;
118 t->link = NULL;
119 if (padev->cont_buffer_last == NULL)
120 padev->cont_buffer = t;
121 else
122 padev->cont_buffer_last->link = t;
123 padev->cont_buffer_last = t;
124 padev->cont_buffer_count++;
125 }
126 return t;
127 }
128
129 private inline void
trap_unreserve(gx_device_spot_analyzer * padev,gx_san_trap * t)130 trap_unreserve(gx_device_spot_analyzer *padev, gx_san_trap *t)
131 {
132 /* Assuming the last reserved one. */
133 assert(t->link == padev->trap_free);
134 padev->trap_free = t;
135 }
136
137 private inline void
cont_unreserve(gx_device_spot_analyzer * padev,gx_san_trap_contact * t)138 cont_unreserve(gx_device_spot_analyzer *padev, gx_san_trap_contact *t)
139 {
140 /* Assuming the last reserved one. */
141 assert(t->link == padev->cont_free);
142 padev->cont_free = t;
143 }
144
145 private inline gx_san_trap *
band_list_last(const gx_san_trap * list)146 band_list_last(const gx_san_trap *list)
147 {
148 /* Assuming a non-empty cyclic list, and the anchor points to the first element. */
149 return list->prev;
150 }
151
152 private inline gx_san_trap_contact *
cont_list_last(const gx_san_trap_contact * list)153 cont_list_last(const gx_san_trap_contact *list)
154 {
155 /* Assuming a non-empty cyclic list, and the anchor points to the first element. */
156 return list->prev;
157 }
158
159 private inline void
band_list_remove(gx_san_trap ** list,gx_san_trap * t)160 band_list_remove(gx_san_trap **list, gx_san_trap *t)
161 {
162 /* Assuming a cyclic list, and the element is in it. */
163 if (t->next == t) {
164 *list = NULL;
165 } else {
166 if (*list == t)
167 *list = t->next;
168 t->next->prev = t->prev;
169 t->prev->next = t->next;
170 }
171 t->next = t->prev = NULL; /* Safety. */
172 }
173
174 private inline void
band_list_insert_last(gx_san_trap ** list,gx_san_trap * t)175 band_list_insert_last(gx_san_trap **list, gx_san_trap *t)
176 {
177 /* Assuming a cyclic list. */
178 if (*list == 0) {
179 *list = t->next = t->prev = t;
180 } else {
181 gx_san_trap *last = band_list_last(*list);
182 gx_san_trap *first = *list;
183
184 t->next = first;
185 t->prev = last;
186 last->next = first->prev = t;
187 }
188 }
189
190 private inline void
cont_list_insert_last(gx_san_trap_contact ** list,gx_san_trap_contact * t)191 cont_list_insert_last(gx_san_trap_contact **list, gx_san_trap_contact *t)
192 {
193 /* Assuming a cyclic list. */
194 if (*list == 0) {
195 *list = t->next = t->prev = t;
196 } else {
197 gx_san_trap_contact *last = cont_list_last(*list);
198 gx_san_trap_contact *first = *list;
199
200 t->next = first;
201 t->prev = last;
202 last->next = first->prev = t;
203 }
204 }
205
206 private inline bool
trap_is_last(const gx_san_trap * list,const gx_san_trap * t)207 trap_is_last(const gx_san_trap *list, const gx_san_trap *t)
208 {
209 /* Assuming a non-empty cyclic list, and the anchor points to the first element. */
210 return t->next == list;
211 }
212
213 /* ---------------------The device ---------------------------- */
214
215 /* The device descriptor */
216 /* Many of these procedures won't be called; they are set to NULL. */
217 private const gx_device_spot_analyzer gx_spot_analyzer_device =
218 {std_device_std_body(gx_device_spot_analyzer, 0, "spot analyzer",
219 0, 0, 1, 1),
220 {san_open,
221 NULL,
222 NULL,
223 NULL,
224 san_close,
225 NULL,
226 NULL,
227 NULL,
228 NULL,
229 NULL,
230 NULL,
231 NULL,
232 NULL,
233 NULL,
234 NULL,
235 NULL,
236 NULL,
237 NULL,
238 NULL,
239 NULL,
240 NULL,
241 NULL,
242 NULL,
243 NULL,
244 gx_default_fill_path,
245 NULL,
246 NULL,
247 NULL,
248 NULL,
249 NULL,
250 NULL,
251 NULL,
252 NULL,
253 NULL,
254 NULL,
255 NULL,
256 san_get_clipping_box,
257 NULL,
258 NULL,
259 NULL,
260 NULL,
261 NULL,
262 NULL,
263 gx_default_finish_copydevice,
264 NULL,
265 NULL,
266 NULL,
267 NULL,
268 NULL,
269 NULL,
270 NULL,
271 NULL,
272 NULL
273 }
274 };
275
276 private int
san_open(register gx_device * dev)277 san_open(register gx_device * dev)
278 {
279 gx_device_spot_analyzer * const padev = (gx_device_spot_analyzer *)dev;
280
281 padev->trap_buffer = padev->trap_buffer_last = NULL;
282 padev->cont_buffer = padev->cont_buffer_last = NULL;
283 padev->trap_buffer_count = 0;
284 padev->cont_buffer_count = 0;
285 padev->xmin = 0;
286 padev->xmax = -1;
287 return 0;
288 }
289
290 private int
san_close(gx_device * dev)291 san_close(gx_device * dev)
292 {
293 gx_device_spot_analyzer * const padev = (gx_device_spot_analyzer *)dev;
294
295 free_trap_list(padev->memory, &padev->trap_buffer);
296 free_cont_list(padev->memory, &padev->cont_buffer);
297 padev->trap_buffer_last = NULL;
298 padev->cont_buffer_last = NULL;
299 return 0;
300 }
301
302 void
san_get_clipping_box(gx_device * dev,gs_fixed_rect * pbox)303 san_get_clipping_box(gx_device * dev, gs_fixed_rect * pbox)
304 {
305 pbox->p.x = min_int;
306 pbox->p.y = min_int;
307 pbox->q.x = max_int;
308 pbox->q.y = max_int;
309 }
310
311 /* --------------------- Utilities ------------------------- */
312
313 private inline void
check_band_list(const gx_san_trap * list)314 check_band_list(const gx_san_trap *list)
315 {
316 #ifdef DEBUG
317 if (list != NULL) {
318 const gx_san_trap *t = list;
319
320 while (t->next != list) {
321 assert(t->xrtop <= t->next->xltop);
322 t = t->next;
323 }
324 }
325 #endif
326 }
327
328 private void
try_unite_last_trap(gx_device_spot_analyzer * padev,fixed xlbot)329 try_unite_last_trap(gx_device_spot_analyzer *padev, fixed xlbot)
330 {
331 if (padev->bot_band != NULL && padev->top_band != NULL) {
332 gx_san_trap *last = band_list_last(padev->top_band);
333 gx_san_trap *t = padev->bot_current;
334 /* If the last trapezoid is a prolongation of its bottom contact,
335 unite it and release the last trapezoid and the last contact. */
336 if (t != NULL && t->upper != NULL && last->xrbot < xlbot &&
337 (last->prev == last || last->prev->xrbot < last->xlbot)) {
338 if ((t->next == NULL || t->xrtop < t->next->xltop) &&
339 (t->upper->next == t->upper &&
340 t->l == last->l && t->r == last->r)) {
341 if (padev->bot_current == t)
342 padev->bot_current = (t == band_list_last(padev->bot_band) ? NULL : t->next);
343 assert(t->upper->upper == last);
344 band_list_remove(&padev->top_band, last);
345 band_list_remove(&padev->bot_band, t);
346 band_list_insert_last(&padev->top_band, t);
347 t->ytop = last->ytop;
348 t->xltop = last->xltop;
349 t->xrtop = last->xrtop;
350 t->rightmost &= last->rightmost;
351 t->leftmost &= last->leftmost;
352 vd_quad(t->xlbot, t->ybot, t->xrbot, t->ybot,
353 t->xrtop, t->ytop, t->xltop, t->ytop, 1, VD_TRAP_U_COLOR);
354 trap_unreserve(padev, last);
355 cont_unreserve(padev, t->upper);
356 t->upper = NULL;
357 }
358 }
359 }
360 }
361
362 private inline double
trap_area(gx_san_trap * t)363 trap_area(gx_san_trap *t)
364 {
365 return (double)(t->xrbot - t->xlbot + t->xrtop - t->xltop) * (t->ytop - t->ybot) / 2;
366 }
367
368 private inline double
trap_axis_length(gx_san_trap * t)369 trap_axis_length(gx_san_trap *t)
370 {
371 double xbot = (t->xlbot + t->xrbot) / 2.0;
372 double xtop = (t->xltop + t->xrtop) / 2.0;
373
374 return hypot(xtop - xbot, (double)t->ytop - t->ybot); /* See Bug 687238 */
375 }
376
377 private inline bool
is_stem_boundaries(gx_san_trap * t,int side_mask)378 is_stem_boundaries(gx_san_trap *t, int side_mask)
379 {
380 double dx, norm, cosine;
381 const double cosine_threshold = 0.9; /* Arbitrary */
382 double dy = t->ytop - t->ybot;
383
384 if (side_mask & 1) {
385 dx = t->xltop - t->xlbot;
386 norm = hypot(dx, dy);
387 cosine = dx / norm;
388 if (any_abs(cosine) > cosine_threshold)
389 return false;
390 }
391 if (side_mask & 2) {
392 dx = t->xrtop - t->xrbot;
393 norm = hypot(dx, dy);
394 cosine = dx / norm;
395 if (any_abs(cosine) > cosine_threshold)
396 return false;
397 }
398 return true;
399 }
400
401 /* --------------------- Accessories ------------------------- */
402
403 /* Obtain a spot analyzer device. */
404 int
gx_san__obtain(gs_memory_t * mem,gx_device_spot_analyzer ** ppadev)405 gx_san__obtain(gs_memory_t *mem, gx_device_spot_analyzer **ppadev)
406 {
407 gx_device_spot_analyzer *padev;
408 int code;
409
410 if (*ppadev != 0) {
411 (*ppadev)->lock++;
412 return 0;
413 }
414 padev = gs_alloc_struct(mem, gx_device_spot_analyzer,
415 &st_device_spot_analyzer, "gx_san__obtain");
416 if (padev == 0)
417 return_error(gs_error_VMerror);
418 gx_device_init((gx_device *)padev, (const gx_device *)&gx_spot_analyzer_device,
419 mem, false);
420 code = gs_opendevice((gx_device *)padev);
421 if (code < 0) {
422 gs_free_object(mem, padev, "gx_san__obtain");
423 return code;
424 }
425 padev->lock = 1;
426 *ppadev = padev;
427 return 0;
428 }
429
430 void
gx_san__release(gx_device_spot_analyzer ** ppadev)431 gx_san__release(gx_device_spot_analyzer **ppadev)
432 {
433 gx_device_spot_analyzer *padev = *ppadev;
434
435 if (padev == NULL) {
436 eprintf("Extra call to gx_san__release.");
437 return;
438 }
439 if(--padev->lock < 0) {
440 eprintf("Wrong lock to gx_san__release.");
441 return;
442 }
443 if (padev->lock == 0) {
444 *ppadev = NULL;
445 rc_decrement(padev, "gx_san__release");
446 }
447 }
448
449 /* Start accumulating a path. */
450 void
gx_san_begin(gx_device_spot_analyzer * padev)451 gx_san_begin(gx_device_spot_analyzer *padev)
452 {
453 padev->bot_band = NULL;
454 padev->top_band = NULL;
455 padev->bot_current = NULL;
456 padev->trap_free = padev->trap_buffer;
457 padev->cont_free = padev->cont_buffer;
458 }
459
460 /* Store a tarpezoid. */
461 /* Assumes an Y-band scanning order with increasing X inside a band. */
462 int
gx_san_trap_store(gx_device_spot_analyzer * padev,fixed ybot,fixed ytop,fixed xlbot,fixed xrbot,fixed xltop,fixed xrtop,const segment * l,const segment * r,int dir_l,int dir_r)463 gx_san_trap_store(gx_device_spot_analyzer *padev,
464 fixed ybot, fixed ytop, fixed xlbot, fixed xrbot, fixed xltop, fixed xrtop,
465 const segment *l, const segment *r, int dir_l, int dir_r)
466 {
467 gx_san_trap *last;
468
469 if (padev->top_band != NULL && padev->top_band->ytop != ytop) {
470 try_unite_last_trap(padev, max_int);
471 /* Step to a new band. */
472 padev->bot_band = padev->bot_current = padev->top_band;
473 padev->top_band = NULL;
474 }
475 if (padev->bot_band != NULL && padev->bot_band->ytop != ybot) {
476 /* The Y-projection of the spot is not contiguous. */
477 padev->top_band = NULL;
478 }
479 if (padev->top_band != NULL)
480 try_unite_last_trap(padev, xlbot);
481 check_band_list(padev->bot_band);
482 check_band_list(padev->top_band);
483 /* Make new trapezoid. */
484 last = trap_reserve(padev);
485 if (last == NULL)
486 return_error(gs_error_VMerror);
487 last->ybot = ybot;
488 last->ytop = ytop;
489 last->xlbot = xlbot;
490 last->xrbot = xrbot;
491 last->xltop = xltop;
492 last->xrtop = xrtop;
493 last->l = l;
494 last->r = r;
495 last->dir_l = dir_l;
496 last->dir_r = dir_r;
497 last->upper = 0;
498 last->fork = 0;
499 last->visited = false;
500 last->leftmost = last->rightmost = true;
501 vd_quad(last->xlbot, last->ybot, last->xrbot, last->ybot,
502 last->xrtop, last->ytop, last->xltop, last->ytop, 1, VD_TRAP_N_COLOR);
503 if (padev->top_band != NULL) {
504 padev->top_band->rightmost = false;
505 last->leftmost = false;
506 }
507 band_list_insert_last(&padev->top_band, last);
508 check_band_list(padev->top_band);
509 while (padev->bot_current != NULL && padev->bot_current->xrtop < xlbot)
510 padev->bot_current = (trap_is_last(padev->bot_band, padev->bot_current)
511 ? NULL : padev->bot_current->next);
512 if (padev->bot_current != 0) {
513 gx_san_trap *t = padev->bot_current;
514 gx_san_trap *bot_last = band_list_last(padev->bot_band);
515
516 while(t->xltop <= xrbot) {
517 gx_san_trap_contact *cont = cont_reserve(padev);
518
519 if (cont == NULL)
520 return_error(gs_error_VMerror);
521 cont->lower = t;
522 cont->upper = last;
523 vd_bar((t->xltop + t->xrtop + t->xlbot + t->xrbot) / 4, (t->ytop + t->ybot) / 2,
524 (last->xltop + last->xrtop + last->xlbot + last->xrbot) / 4,
525 (last->ytop + last->ybot) / 2, 0, VD_CONT_COLOR);
526 cont_list_insert_last(&t->upper, cont);
527 last->fork++;
528 if (t == bot_last)
529 break;
530 t = t->next;
531 }
532 }
533 if (padev->xmin > padev->xmax) {
534 padev->xmin = min(xlbot, xltop);
535 padev->xmax = max(xrbot, xrtop);
536 } else {
537 padev->xmin = min(padev->xmin, min(xlbot, xltop));
538 padev->xmax = max(padev->xmax, max(xrbot, xrtop));
539 }
540 return 0;
541 }
542
543
544 /* Finish accumulating a path. */
545 void
gx_san_end(const gx_device_spot_analyzer * padev)546 gx_san_end(const gx_device_spot_analyzer *padev)
547 {
548 }
549
550 private int
hint_by_trap(gx_device_spot_analyzer * padev,int side_mask,void * client_data,gx_san_trap * t0,gx_san_trap * t1,double ave_width,int (* handler)(void * client_data,gx_san_sect * ss))551 hint_by_trap(gx_device_spot_analyzer *padev, int side_mask,
552 void *client_data, gx_san_trap *t0, gx_san_trap *t1, double ave_width,
553 int (*handler)(void *client_data, gx_san_sect *ss))
554 { gx_san_trap *t;
555 double w, wd, best_width_diff = ave_width * 10;
556 gx_san_trap *best_trap = NULL;
557 bool at_top = false;
558 gx_san_sect sect;
559 int code;
560
561 for (t = t0; ; t = t->upper->upper) {
562 w = t->xrbot - t->xlbot;
563 wd = any_abs(w - ave_width);
564 if (w > 0 && wd < best_width_diff) {
565 best_width_diff = wd;
566 best_trap = t;
567 }
568 if (t == t1)
569 break;
570 }
571 w = t->xrtop - t->xltop;
572 wd = any_abs(w - ave_width);
573 if (w > 0 && wd < best_width_diff) {
574 best_width_diff = wd;
575 best_trap = t;
576 at_top = true;
577 }
578 if (best_trap != NULL) {
579 /* Make a stem section hint at_top of best_trap : */
580 sect.yl = at_top ? best_trap->ytop : best_trap->ybot;
581 sect.yr = sect.yl;
582 sect.xl = at_top ? best_trap->xltop : best_trap->xlbot;
583 sect.xr = at_top ? best_trap->xrtop : best_trap->xrbot;
584 sect.l = best_trap->l;
585 sect.r = best_trap->r;
586 vd_bar(sect.xl, sect.yl, sect.xr, sect.yr, 0, VD_HINT_COLOR);
587 code = handler(client_data, §);
588 if (code < 0)
589 return code;
590 }
591 return 0;
592 }
593
594 private inline void
choose_by_vector(fixed x0,fixed y0,fixed x1,fixed y1,const segment * s,double * slope,double * len,const segment ** store_segm,fixed * store_x,fixed * store_y)595 choose_by_vector(fixed x0, fixed y0, fixed x1, fixed y1, const segment *s,
596 double *slope, double *len, const segment **store_segm, fixed *store_x, fixed *store_y)
597 {
598 if (y0 != y1) {
599 double t = (double)any_abs(x1 - x0) / any_abs(y1 - y0);
600 double l = any_abs(y1 - y0); /* Don't want 'hypot'. */
601
602 if (*slope > t || (*slope == t && l > *len)) {
603 *slope = t;
604 *len = l;
605 *store_segm = s;
606 *store_x = x1;
607 *store_y = y1;
608 }
609 }
610 }
611
612 private inline void
choose_by_tangent(const segment * p,const segment * s,double * slope,double * len,const segment ** store_segm,fixed * store_x,fixed * store_y,fixed ybot,fixed ytop)613 choose_by_tangent(const segment *p, const segment *s,
614 double *slope, double *len, const segment **store_segm, fixed *store_x, fixed *store_y,
615 fixed ybot, fixed ytop)
616 {
617 if (s->type == s_curve) {
618 const curve_segment *c = (const curve_segment *)s;
619 vd_curve(p->pt.x, p->pt.y, c->p1.x, c->p1.y, c->p2.x, c->p2.y,
620 s->pt.x, s->pt.y, 0, VD_HINT_COLOR);
621 if (ybot <= p->pt.y && p->pt.y <= ytop)
622 choose_by_vector(c->p1.x, c->p1.y, p->pt.x, p->pt.y, s, slope, len, store_segm, store_x, store_y);
623 if (ybot <= s->pt.y && s->pt.y <= ytop)
624 choose_by_vector(c->p2.x, c->p2.y, s->pt.x, s->pt.y, s, slope, len, store_segm, store_x, store_y);
625 } else {
626 vd_bar(p->pt.x, p->pt.y, s->pt.x, s->pt.y, 0, VD_HINT_COLOR);
627 choose_by_vector(s->pt.x, s->pt.y, p->pt.x, p->pt.y, s, slope, len, store_segm, store_x, store_y);
628 }
629 }
630
631 private gx_san_trap *
upper_neighbour(gx_san_trap * t0,int left_right)632 upper_neighbour(gx_san_trap *t0, int left_right)
633 {
634 gx_san_trap_contact *cont = t0->upper, *c0 = cont, *c;
635 fixed x = (!left_right ? cont->upper->xlbot : cont->upper->xrbot);
636
637 for (c = c0->next; c != c0; c = c->next) {
638 fixed xx = (!left_right ? c->upper->xlbot : c->upper->xrbot);
639
640 if ((xx - x) * (left_right * 2 - 1) > 0) {
641 cont = c;
642 x = xx;
643 }
644 }
645 return cont->upper;
646 }
647
648 private int
hint_by_tangent(gx_device_spot_analyzer * padev,int side_mask,void * client_data,gx_san_trap * t0,gx_san_trap * t1,double ave_width,int (* handler)(void * client_data,gx_san_sect * ss))649 hint_by_tangent(gx_device_spot_analyzer *padev, int side_mask,
650 void *client_data, gx_san_trap *t0, gx_san_trap *t1, double ave_width,
651 int (*handler)(void *client_data, gx_san_sect *ss))
652 { gx_san_trap *t;
653 gx_san_sect sect;
654 double slope0 = 0.2, slope1 = slope0, len0 = 0, len1 = 0;
655 const segment *s, *p;
656 int left_right = (side_mask & 1 ? 0 : 1);
657 int code;
658
659 sect.l = sect.r = NULL;
660 sect.xl = t0->xltop; /* only for vdtrace. */
661 sect.xr = t0->xrtop; /* only for vdtrace. */
662 sect.yl = sect.yr = t0->ytop; /* only for vdtrace. */
663 sect.side_mask = side_mask;
664 for (t = t0; ; t = upper_neighbour(t, left_right)) {
665 if (side_mask & 1) {
666 s = t->l;
667 if (t->dir_l < 0)
668 s = (s->type == s_line_close ? ((const line_close_segment *)s)->sub->next : s->next);
669 p = (s->type == s_start ? ((const subpath *)s)->last->prev : s->prev);
670 choose_by_tangent(p, s, &slope0, &len0, §.l, §.xl, §.yl, t->ybot, t->ytop);
671 }
672 if (side_mask & 2) {
673 s = t->r;
674 if (t->dir_r < 0)
675 s = (s->type == s_line_close ? ((const line_close_segment *)s)->sub->next : s->next);
676 p = (s->type == s_start ? ((const subpath *)s)->last->prev : s->prev);
677 choose_by_tangent(p, s, &slope1, &len1, §.r, §.xr, §.yr, t->ybot, t->ytop);
678 }
679 if (t == t1)
680 break;
681 }
682 if ((sect.l != NULL || !(side_mask & 1)) &&
683 (sect.r != NULL || !(side_mask & 2))) {
684 const int w = 3;
685
686 if (!(side_mask & 1)) {
687 if (sect.xr < (padev->xmin * w + padev->xmax) / (w + 1))
688 return 0;
689 sect.xl = padev->xmin - 1000; /* ignore side */
690 }
691 if (!(side_mask & 2)) {
692 if (sect.xl > (padev->xmax * w + padev->xmin) / (w + 1))
693 return 0;
694 sect.xr = padev->xmax + 1000; /* ignore side */
695 }
696 vd_bar(sect.xl, sect.yl, sect.xr, sect.yr, 0, VD_HINT_COLOR);
697 code = handler(client_data, §);
698 if (code < 0)
699 return code;
700 }
701 return 0;
702 }
703
704 /* Generate stems. */
705 private int
gx_san_generate_stems_aux(gx_device_spot_analyzer * padev,bool overall_hints,void * client_data,int (* handler)(void * client_data,gx_san_sect * ss))706 gx_san_generate_stems_aux(gx_device_spot_analyzer *padev,
707 bool overall_hints, void *client_data,
708 int (*handler)(void *client_data, gx_san_sect *ss))
709 {
710 gx_san_trap *t0;
711 const bool by_trap = false;
712 int k;
713
714 /* Overall hints : */
715 /* An overall hint designates an outer side of a glyph,
716 being nearly parallel to a coordinate axis.
717 It aligns a stem end rather than stem sides.
718 See t1_hinter__overall_hstem.
719 */
720 for (k = 0; overall_hints && k < 2; k++) { /* left, right. */
721 for (t0 = padev->trap_buffer; t0 != padev->trap_free; t0 = t0->link) {
722 if (!t0->visited && (!k ? t0->leftmost : t0->rightmost)) {
723 if (is_stem_boundaries(t0, 1 << k)) {
724 gx_san_trap *t1 = t0, *tt = t0, *t = t0;
725 int code;
726
727 while (t->upper != NULL) {
728 t = upper_neighbour(tt, k);
729 if (!k ? !t->leftmost : !t->rightmost) {
730 break;
731 }
732 if (!is_stem_boundaries(t, 1 << k)) {
733 t->visited = true;
734 break;
735 }
736 if ((!k ? tt->xltop : tt->xrtop) != (!k ? t->xlbot : t->xrbot))
737 break; /* Not a contigouos boundary. */
738 t->visited = true;
739 tt = t;
740 }
741 if (!k ? !t->leftmost : !t->rightmost)
742 continue;
743 t1 = t;
744 /* leftmost/rightmost boundary from t0 to t1. */
745 code = hint_by_tangent(padev, 1 << k, client_data, t0, t1, 0, handler);
746 if (code < 0)
747 return code;
748 }
749 }
750 }
751 for (t0 = padev->trap_buffer; t0 != padev->trap_free; t0 = t0->link)
752 t0->visited = false;
753 }
754 /* Stem hints : */
755 for (t0 = padev->trap_buffer; t0 != padev->trap_free; t0 = t0->link) {
756 if (!t0->visited) {
757 if (is_stem_boundaries(t0, 3)) {
758 gx_san_trap_contact *cont = t0->upper;
759 gx_san_trap *t1 = t0, *t;
760 double area = 0, length = 0, ave_width;
761
762 while(cont != NULL && cont->next == cont /* <= 1 descendent. */) {
763 gx_san_trap *t = cont->upper;
764
765 if (!is_stem_boundaries(t, 3)) {
766 t->visited = true;
767 break;
768 }
769 if (t->fork > 1)
770 break; /* > 1 accendents. */
771 if (t1->xltop != t->xlbot || t1->xrtop != t->xrbot)
772 break; /* Not a contigouos boundary. */
773 t1 = t;
774 cont = t1->upper;
775 t1->visited = true;
776 }
777 /* We've got a stem suspection from t0 to t1. */
778 vd_quad(t0->xlbot, t0->ybot, t0->xrbot, t0->ybot,
779 t1->xrtop, t1->ytop, t1->xltop, t1->ytop, 1, VD_STEM_COLOR);
780 for (t = t0; ; t = t->upper->upper) {
781 length += trap_axis_length(t);
782 area += trap_area(t);
783 if (t == t1)
784 break;
785 }
786 ave_width = area / length;
787 if (length > ave_width / ( 2.0 /* arbitrary */)) {
788 /* We've got a stem from t0 to t1. */
789 int code = (by_trap ? hint_by_trap : hint_by_tangent)(padev,
790 3, client_data, t0, t1, ave_width, handler);
791
792 if (code < 0)
793 return code;
794 }
795 }
796 }
797 t0->visited = true;
798 }
799 return 0;
800 }
801
802 int
gx_san_generate_stems(gx_device_spot_analyzer * padev,bool overall_hints,void * client_data,int (* handler)(void * client_data,gx_san_sect * ss))803 gx_san_generate_stems(gx_device_spot_analyzer *padev,
804 bool overall_hints, void *client_data,
805 int (*handler)(void *client_data, gx_san_sect *ss))
806 {
807 int code;
808
809 vd_get_dc('f');
810 vd_set_shift(0, 0);
811 vd_set_scale(VD_SCALE);
812 vd_set_origin(0, 0);
813 code = gx_san_generate_stems_aux(padev, overall_hints, client_data, handler);
814 vd_release_dc;
815 return code;
816 }
817