/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, §); |
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, §.l, §.xl, §.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, §.r, §.xr, §.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, §); |
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 | } |