/src/ghostpdl/base/gxpdash.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 | | /* Dash expansion for paths */ |
18 | | #include "math_.h" |
19 | | #include "gx.h" |
20 | | #include "gsmatrix.h" /* for gscoord.h */ |
21 | | #include "gscoord.h" |
22 | | #include "gxfixed.h" |
23 | | #include "gxarith.h" |
24 | | #include "gxgstate.h" |
25 | | #include "gsline.h" |
26 | | #include "gzline.h" |
27 | | #include "gzpath.h" |
28 | | |
29 | | /* Expand a dashed path into explicit segments. */ |
30 | | /* The path contains no curves. */ |
31 | | static int subpath_expand_dashes(const subpath *, gx_path *, |
32 | | const gs_gstate *, |
33 | | const gx_dash_params *); |
34 | | int |
35 | | gx_path_add_dash_expansion(const gx_path * ppath_old, gx_path * ppath, |
36 | | const gs_gstate * pgs) |
37 | 2.10k | { |
38 | 2.10k | const subpath *psub; |
39 | 2.10k | const gx_dash_params *dash = &gs_currentlineparams(pgs)->dash; |
40 | 2.10k | int code = 0; |
41 | | |
42 | 2.10k | if (dash->pattern_size == 0) |
43 | 0 | return gx_path_copy(ppath_old, ppath); |
44 | 4.21k | for (psub = ppath_old->first_subpath; psub != 0 && code >= 0; |
45 | 2.11k | psub = (const subpath *)psub->last->next |
46 | 2.10k | ) |
47 | 2.11k | code = subpath_expand_dashes(psub, ppath, pgs, dash); |
48 | 2.10k | return code; |
49 | 2.10k | } |
50 | | |
51 | | static int |
52 | | subpath_expand_dashes(const subpath * psub, gx_path * ppath, |
53 | | const gs_gstate * pgs, const gx_dash_params * dash) |
54 | 2.11k | { |
55 | 2.11k | const float *pattern = dash->pattern; |
56 | 2.11k | int count, index; |
57 | 2.11k | bool ink_on; |
58 | 2.11k | double elt_length; |
59 | 2.11k | fixed x0 = psub->pt.x, y0 = psub->pt.y; |
60 | 2.11k | fixed x, y; |
61 | 2.11k | const segment *pseg; |
62 | 2.11k | int wrap = (dash->init_ink_on && psub->is_closed ? -1 : 0); |
63 | 2.11k | int drawing = wrap; |
64 | 2.11k | segment_notes notes = ~sn_not_first; |
65 | 2.11k | const gx_line_params *pgs_lp = gs_currentlineparams_inline(pgs); |
66 | 2.11k | bool zero_length = true; |
67 | 2.11k | int code; |
68 | 2.11k | gs_line_cap cap; |
69 | 2.11k | segment_notes start_notes, end_notes; |
70 | | |
71 | 2.11k | if (wrap) { |
72 | | /* If we are wrapping around, then we use dash caps throughout */ |
73 | 277 | cap = pgs_lp->dash_cap; |
74 | 277 | start_notes = sn_dash_head; |
75 | 1.83k | } else { |
76 | | /* Otherwise, start off with a start cap */ |
77 | 1.83k | cap = pgs_lp->start_cap; |
78 | 1.83k | start_notes = 0; |
79 | 1.83k | } |
80 | | |
81 | 2.11k | if ((code = gx_path_add_point(ppath, x0, y0)) < 0) |
82 | 0 | return code; |
83 | | /* |
84 | | * To do the right thing at the beginning of a closed path, we have |
85 | | * to skip any initial line, and then redo it at the end of the |
86 | | * path. Drawing = -1 while skipping, 0 while drawing normally, and |
87 | | * 1 on the second round. Note that drawing != 0 implies ink_on. |
88 | | */ |
89 | 2.38k | top:count = dash->pattern_size; |
90 | 2.38k | ink_on = dash->init_ink_on; |
91 | 2.38k | index = dash->init_index; |
92 | 2.38k | elt_length = dash->init_dist_left; |
93 | 2.38k | x = x0, y = y0; |
94 | 2.38k | pseg = (const segment *)psub; |
95 | 26.8k | while ((pseg = pseg->next) != 0 && pseg->type != s_start) { |
96 | 24.7k | fixed sx = pseg->pt.x, sy = pseg->pt.y; |
97 | 24.7k | fixed udx = sx - x, udy = sy - y; |
98 | 24.7k | double length, dx, dy; |
99 | 24.7k | double scale = 1; |
100 | 24.7k | double left; |
101 | 24.7k | int gap = pseg->type == s_gap; |
102 | | |
103 | 24.7k | if (!(udx | udy)) { /* degenerate */ |
104 | 166 | if (pgs_lp->dot_length == 0 && |
105 | 166 | cap != gs_cap_round) { |
106 | | /* From PLRM, stroke operator : |
107 | | If a subpath is degenerate (consists of a single-point closed path |
108 | | or of two or more points at the same coordinates), |
109 | | stroke paints it only if round line caps have been specified */ |
110 | 166 | if (zero_length || pseg->type != s_line_close) |
111 | 166 | continue; |
112 | 166 | } |
113 | 0 | dx = 0, dy = 0, length = 0; |
114 | 24.5k | } else { |
115 | 24.5k | gs_point d; |
116 | | |
117 | 24.5k | zero_length = false; |
118 | 24.5k | dx = udx, dy = udy; /* scaled as fixed */ |
119 | 24.5k | code = gs_gstate_idtransform(pgs, dx, dy, &d); |
120 | 24.5k | if (code < 0) { |
121 | 0 | d.x = 0; d.y = 0; |
122 | | /* Swallow the error */ |
123 | 0 | code = 0; |
124 | 0 | } |
125 | 24.5k | length = hypot(d.x, d.y) * (1.0 / fixed_1); |
126 | 24.5k | if (gs_gstate_currentdashadapt(pgs)) { |
127 | 0 | double reps = length / dash->pattern_length; |
128 | |
|
129 | 0 | scale = reps / ceil(reps); |
130 | | /* Ensure we're starting at the start of a */ |
131 | | /* repetition. (This shouldn't be necessary, */ |
132 | | /* but it is.) */ |
133 | 0 | count = dash->pattern_size; |
134 | 0 | ink_on = dash->init_ink_on; |
135 | 0 | index = dash->init_index; |
136 | 0 | elt_length = dash->init_dist_left * scale; |
137 | 0 | } |
138 | 24.5k | } |
139 | 24.5k | left = length; |
140 | 209k | while (left > elt_length) { /* We are using up the line segment. */ |
141 | 185k | double fraction = elt_length / length; |
142 | 185k | fixed fx = (fixed) (dx * fraction); |
143 | 185k | fixed fy = (fixed) (dy * fraction); |
144 | 185k | fixed nx = x + fx; |
145 | 185k | fixed ny = y + fy; |
146 | | |
147 | 185k | if (ink_on && !gap) { |
148 | 93.5k | if (drawing >= 0) { |
149 | 93.2k | if (left >= elt_length && any_abs(fx) + any_abs(fy) < fixed_half) |
150 | 27.7k | code = gx_path_add_dash_notes(ppath, nx, ny, udx, udy, |
151 | 27.7k | ((notes & pseg->notes)| |
152 | 27.7k | start_notes| |
153 | 27.7k | sn_dash_tail)); |
154 | 65.5k | else |
155 | 65.5k | code = gx_path_add_line_notes(ppath, nx, ny, |
156 | 65.5k | ((notes & pseg->notes)| |
157 | 65.5k | start_notes| |
158 | 65.5k | sn_dash_tail)); |
159 | 93.2k | } |
160 | 93.5k | notes |= sn_not_first; |
161 | 93.5k | } else { |
162 | 92.0k | if (drawing > 0) /* done */ |
163 | 277 | return 0; |
164 | 91.8k | code = gx_path_add_point(ppath, nx, ny); |
165 | 91.8k | notes &= ~sn_not_first; |
166 | 91.8k | drawing = 0; |
167 | 91.8k | } |
168 | 185k | if (code < 0) |
169 | 0 | return code; |
170 | 185k | left -= elt_length; |
171 | 185k | ink_on = !ink_on; |
172 | 185k | start_notes = sn_dash_head; |
173 | 185k | if (++index == count) |
174 | 100k | index = 0; |
175 | 185k | elt_length = pattern[index] * scale; |
176 | 185k | x = nx, y = ny; |
177 | 185k | } |
178 | 24.3k | elt_length -= left; |
179 | | /* Handle the last dash of a segment. */ |
180 | 24.3k | if (wrap) { |
181 | | /* We are wrapping, therefore we always use the dash cap */ |
182 | 1.10k | end_notes = sn_dash_tail; |
183 | 23.2k | } else { |
184 | | /* Look ahead to see if we have any more non-degenerate segments |
185 | | * before the next move or end of subpath. (i.e. should we use an |
186 | | * end cap or a dash cap?) */ |
187 | 23.2k | const segment *pseg2 = pseg->next; |
188 | | |
189 | 23.2k | end_notes = 0; |
190 | 23.2k | while (pseg2 != 0 && pseg2->type != s_start) |
191 | 21.3k | { |
192 | 21.3k | if ((pseg2->pt.x != sx) || (pseg2->pt.x != sy)) { |
193 | | /* Non degenerate. We aren't the last one */ |
194 | 21.3k | end_notes = sn_dash_tail; |
195 | 21.3k | break; |
196 | 21.3k | } |
197 | 0 | pseg2 = pseg2->next; |
198 | 0 | } |
199 | 23.2k | } |
200 | 24.3k | on:if (ink_on && !gap) { |
201 | 11.8k | if (drawing >= 0) { |
202 | 11.8k | if (pseg->type == s_line_close && drawing > 0) |
203 | 0 | code = gx_path_close_subpath_notes(ppath, |
204 | 0 | ((notes & pseg->notes)| |
205 | 0 | start_notes | |
206 | 0 | end_notes)); |
207 | 11.8k | else if ((any_abs(sx - x) + any_abs(sy - y) < fixed_half) && |
208 | 11.8k | (udx | udy)) |
209 | | /* If we only need to move a short distance, then output |
210 | | * dash notes to ensure that the stroke tangent remains |
211 | | * accurate. There is no point in outputting such dash |
212 | | * notes if we don't have any useful information to put |
213 | | * in the note though (if udx == 0 && udy == 0). */ |
214 | 505 | code = gx_path_add_dash_notes(ppath, sx, sy, udx, udy, |
215 | 505 | ((notes & pseg->notes)| |
216 | 505 | start_notes | end_notes)); |
217 | 11.3k | else |
218 | 11.3k | code = gx_path_add_line_notes(ppath, sx, sy, |
219 | 11.3k | ((notes & pseg->notes)| |
220 | 11.3k | start_notes | end_notes)); |
221 | 11.8k | notes |= sn_not_first; |
222 | 11.8k | } |
223 | 12.5k | } else { |
224 | 12.5k | code = gx_path_add_point(ppath, sx, sy); |
225 | 12.5k | notes &= ~sn_not_first; |
226 | 12.5k | if (elt_length < fixed2float(fixed_epsilon) && |
227 | 12.5k | (pseg->next == 0 || |
228 | 10 | pseg->next->type == s_start || |
229 | 10 | pseg->next->type == s_gap || |
230 | 10 | elt_length == 0)) { |
231 | | /* |
232 | | * Ink is off, but we're within epsilon of the end |
233 | | * of the dash element. |
234 | | * "Stretch" a little so we get a dot. |
235 | | * Also if the next dash pattern is zero length, |
236 | | * use the last segment orientation. |
237 | | */ |
238 | 10 | double elt_length1; |
239 | | |
240 | 10 | if (code < 0) |
241 | 0 | return code; |
242 | 10 | if (++index == count) |
243 | 10 | index = 0; |
244 | 10 | elt_length1 = pattern[index] * scale; |
245 | 10 | if (pseg->next == 0 || |
246 | 10 | pseg->next->type == s_start || |
247 | 10 | pseg->next->type == s_gap) { |
248 | 10 | elt_length = elt_length1; |
249 | 10 | left = 0; |
250 | 10 | ink_on = true; |
251 | 10 | goto on; |
252 | 10 | } |
253 | | /* Looking ahead one dash pattern element. |
254 | | If it is zero length, apply to the current segment |
255 | | (at its end). */ |
256 | 0 | if (elt_length1 == 0) { |
257 | 0 | left = 0; |
258 | 0 | code = gx_path_add_dash_notes(ppath, sx, sy, udx, udy, |
259 | 0 | ((notes & pseg->notes)| |
260 | 0 | start_notes | end_notes)); |
261 | 0 | if (++index == count) |
262 | 0 | index = 0; |
263 | 0 | elt_length = pattern[index] * scale; |
264 | 0 | ink_on = false; |
265 | 0 | } else if (--index == 0) { |
266 | | /* Revert lookahead. */ |
267 | 0 | index = count - 1; |
268 | 0 | } |
269 | 0 | } |
270 | 12.5k | if (drawing > 0) /* done */ |
271 | 0 | return code; |
272 | 12.5k | drawing = 0; |
273 | 12.5k | } |
274 | 24.3k | if (code < 0) |
275 | 0 | return code; |
276 | 24.3k | x = sx, y = sy; |
277 | 24.3k | cap = pgs_lp->dash_cap; |
278 | 24.3k | } |
279 | | /* Check for wraparound. */ |
280 | 2.11k | if (wrap && drawing <= 0) { /* We skipped some initial lines. */ |
281 | | /* Go back and do them now. */ |
282 | 277 | drawing = 1; |
283 | 277 | goto top; |
284 | 277 | } |
285 | 1.83k | return 0; |
286 | 2.11k | } |