Coverage Report

Created: 2025-06-24 07:01

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