xref: /plan9/sys/src/cmd/gs/src/gzspotan.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
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, &sect);
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, &sect.l, &sect.xl, &sect.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, &sect.r, &sect.xr, &sect.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, &sect);
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