Coverage Report

Created: 2026-02-14 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mupdf/source/fitz/draw-path.c
Line
Count
Source
1
// Copyright (C) 2004-2026 Artifex Software, Inc.
2
//
3
// This file is part of MuPDF.
4
//
5
// MuPDF is free software: you can redistribute it and/or modify it under the
6
// terms of the GNU Affero General Public License as published by the Free
7
// Software Foundation, either version 3 of the License, or (at your option)
8
// any later version.
9
//
10
// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
// details.
14
//
15
// You should have received a copy of the GNU Affero General Public License
16
// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17
//
18
// Alternative licensing terms are available from the licensor.
19
// For commercial licensing, see <https://www.artifex.com/> or contact
20
// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21
// CA 94129, USA, for further information.
22
23
#include "mupdf/fitz.h"
24
#include "draw-imp.h"
25
26
#include <math.h>
27
#include <float.h>
28
#include <assert.h>
29
30
0
#define MAX_DEPTH 8
31
32
/*
33
  When stroking/filling, we now label the edges as we emit them.
34
35
  For filling, we walk the outline of the shape in order, so everything
36
  is labelled as '0'.
37
38
  For stroking, we walk up both sides of the stroke at once; the forward
39
  side (0), and the reverse side (1). When we get to the top, either
40
  both sides join back to where they started, or we cap them.
41
42
  The start cap is labelled 2, the end cap is labelled 0.
43
44
  These labels are ignored for edge based rasterization, but are required
45
  for edgebuffer based rasterization.
46
47
  Consider the following simplified ascii art diagram of a stroke from
48
  left to right with 3 sections.
49
50
  |            0           0           0
51
  |      +----->-----+----->-----+----->-----+
52
  |      |                                   |
53
  |      ^ 2   A           B           C     v 0
54
  |      |                                   |
55
  |      +-----<-----+-----<-----+-----<-----+
56
  |            1           1           1
57
58
  Edge 0 is sent in order (the top edge of A then B then C, left to right
59
  in the above diagram). Edge 1 is sent in reverse order (the bottom edge
60
  of A then B then C, still left to right in the above diagram, even though
61
  the sense of the line is right to left).
62
63
  Finally any caps required are sent, 0 and 2.
64
65
  It would be nicer if we could roll edge 2 into edge 1, but to do that
66
  we'd need to know in advance if a stroke was closed or not, so we have
67
  special case code in the edgebuffer based rasterizer to cope with this.
68
*/
69
70
static void
71
line(fz_context *ctx, fz_rasterizer *rast, fz_matrix ctm, float x0, float y0, float x1, float y1)
72
0
{
73
0
  float tx0 = ctm.a * x0 + ctm.c * y0 + ctm.e;
74
0
  float ty0 = ctm.b * x0 + ctm.d * y0 + ctm.f;
75
0
  float tx1 = ctm.a * x1 + ctm.c * y1 + ctm.e;
76
0
  float ty1 = ctm.b * x1 + ctm.d * y1 + ctm.f;
77
0
  fz_insert_rasterizer(ctx, rast, tx0, ty0, tx1, ty1, 0);
78
0
}
79
80
static void
81
bezier(fz_context *ctx, fz_rasterizer *rast, fz_matrix ctm, float flatness,
82
  float xa, float ya,
83
  float xb, float yb,
84
  float xc, float yc,
85
  float xd, float yd, int depth)
86
0
{
87
0
  float dmax;
88
0
  float xab, yab;
89
0
  float xbc, ybc;
90
0
  float xcd, ycd;
91
0
  float xabc, yabc;
92
0
  float xbcd, ybcd;
93
0
  float xabcd, yabcd;
94
95
  /* termination check */
96
0
  dmax = fz_abs(xa - xb);
97
0
  dmax = fz_max(dmax, fz_abs(ya - yb));
98
0
  dmax = fz_max(dmax, fz_abs(xd - xc));
99
0
  dmax = fz_max(dmax, fz_abs(yd - yc));
100
0
  if (dmax < flatness || depth >= MAX_DEPTH)
101
0
  {
102
0
    line(ctx, rast, ctm, xa, ya, xd, yd);
103
0
    return;
104
0
  }
105
106
0
  xab = xa + xb;
107
0
  yab = ya + yb;
108
0
  xbc = xb + xc;
109
0
  ybc = yb + yc;
110
0
  xcd = xc + xd;
111
0
  ycd = yc + yd;
112
113
0
  xabc = xab + xbc;
114
0
  yabc = yab + ybc;
115
0
  xbcd = xbc + xcd;
116
0
  ybcd = ybc + ycd;
117
118
0
  xabcd = xabc + xbcd;
119
0
  yabcd = yabc + ybcd;
120
121
0
  xab *= 0.5f; yab *= 0.5f;
122
  /* xbc *= 0.5f; ybc *= 0.5f; */
123
0
  xcd *= 0.5f; ycd *= 0.5f;
124
125
0
  xabc *= 0.25f; yabc *= 0.25f;
126
0
  xbcd *= 0.25f; ybcd *= 0.25f;
127
128
0
  xabcd *= 0.125f; yabcd *= 0.125f;
129
130
0
  bezier(ctx, rast, ctm, flatness, xa, ya, xab, yab, xabc, yabc, xabcd, yabcd, depth + 1);
131
0
  bezier(ctx, rast, ctm, flatness, xabcd, yabcd, xbcd, ybcd, xcd, ycd, xd, yd, depth + 1);
132
0
}
133
134
/* AIX uses 'quad' as a definition, so we can't use that here. */
135
static void
136
quadratic(fz_context *ctx, fz_rasterizer *rast, fz_matrix ctm, float flatness,
137
  float xa, float ya,
138
  float xb, float yb,
139
  float xc, float yc, int depth)
140
0
{
141
0
  float dmax;
142
0
  float xab, yab;
143
0
  float xbc, ybc;
144
0
  float xabc, yabc;
145
146
  /* termination check */
147
0
  dmax = fz_abs(xa - xb);
148
0
  dmax = fz_max(dmax, fz_abs(ya - yb));
149
0
  dmax = fz_max(dmax, fz_abs(xc - xb));
150
0
  dmax = fz_max(dmax, fz_abs(yc - yb));
151
0
  if (dmax < flatness || depth >= MAX_DEPTH)
152
0
  {
153
0
    line(ctx, rast, ctm, xa, ya, xc, yc);
154
0
    return;
155
0
  }
156
157
0
  xab = xa + xb;
158
0
  yab = ya + yb;
159
0
  xbc = xb + xc;
160
0
  ybc = yb + yc;
161
162
0
  xabc = xab + xbc;
163
0
  yabc = yab + ybc;
164
165
0
  xab *= 0.5f; yab *= 0.5f;
166
0
  xbc *= 0.5f; ybc *= 0.5f;
167
168
0
  xabc *= 0.25f; yabc *= 0.25f;
169
170
0
  quadratic(ctx, rast, ctm, flatness, xa, ya, xab, yab, xabc, yabc, depth + 1);
171
0
  quadratic(ctx, rast, ctm, flatness, xabc, yabc, xbc, ybc, xc, yc, depth + 1);
172
0
}
173
174
typedef struct
175
{
176
  fz_rasterizer *rast;
177
  fz_matrix ctm;
178
  float flatness;
179
  fz_point b;
180
  fz_point c;
181
}
182
flatten_arg;
183
184
static void
185
flatten_moveto(fz_context *ctx, void *arg_, float x, float y)
186
0
{
187
0
  flatten_arg *arg = (flatten_arg *)arg_;
188
189
  /* implicit closepath before moveto */
190
0
  if (arg->c.x != arg->b.x || arg->c.y != arg->b.y)
191
0
    line(ctx, arg->rast, arg->ctm, arg->c.x, arg->c.y, arg->b.x, arg->b.y);
192
0
  arg->c.x = arg->b.x = x;
193
0
  arg->c.y = arg->b.y = y;
194
195
0
  fz_gap_rasterizer(ctx, arg->rast);
196
0
}
197
198
static void
199
flatten_lineto(fz_context *ctx, void *arg_, float x, float y)
200
0
{
201
0
  flatten_arg *arg = (flatten_arg *)arg_;
202
203
0
  line(ctx, arg->rast, arg->ctm, arg->c.x, arg->c.y, x, y);
204
0
  arg->c.x = x;
205
0
  arg->c.y = y;
206
0
}
207
208
static void
209
flatten_curveto(fz_context *ctx, void *arg_, float x1, float y1, float x2, float y2, float x3, float y3)
210
0
{
211
0
  flatten_arg *arg = (flatten_arg *)arg_;
212
213
0
  bezier(ctx, arg->rast, arg->ctm, arg->flatness, arg->c.x, arg->c.y, x1, y1, x2, y2, x3, y3, 0);
214
0
  arg->c.x = x3;
215
0
  arg->c.y = y3;
216
0
}
217
218
static void
219
flatten_quadto(fz_context *ctx, void *arg_, float x1, float y1, float x2, float y2)
220
0
{
221
0
  flatten_arg *arg = (flatten_arg *)arg_;
222
223
0
  quadratic(ctx, arg->rast, arg->ctm, arg->flatness, arg->c.x, arg->c.y, x1, y1, x2, y2, 0);
224
0
  arg->c.x = x2;
225
0
  arg->c.y = y2;
226
0
}
227
228
static void
229
flatten_close(fz_context *ctx, void *arg_)
230
0
{
231
0
  flatten_arg *arg = (flatten_arg *)arg_;
232
233
0
  line(ctx, arg->rast, arg->ctm, arg->c.x, arg->c.y, arg->b.x, arg->b.y);
234
0
  arg->c.x = arg->b.x;
235
0
  arg->c.y = arg->b.y;
236
0
}
237
238
static void
239
flatten_rectto(fz_context *ctx, void *arg_, float x0, float y0, float x1, float y1)
240
0
{
241
0
  flatten_arg *arg = (flatten_arg *)arg_;
242
0
  fz_matrix ctm = arg->ctm;
243
244
0
  flatten_moveto(ctx, arg_, x0, y0);
245
246
0
  if (fz_antidropout_rasterizer(ctx, arg->rast))
247
0
  {
248
    /* In the case where we have an axis aligned rectangle, do some
249
     * horrid antidropout stuff. */
250
0
    if (ctm.b == 0 && ctm.c == 0)
251
0
    {
252
0
      float tx0 = ctm.a * x0 + ctm.e;
253
0
      float ty0 = ctm.d * y0 + ctm.f;
254
0
      float tx1 = ctm.a * x1 + ctm.e;
255
0
      float ty1 = ctm.d * y1 + ctm.f;
256
0
      fz_insert_rasterizer_rect(ctx, arg->rast, tx0, ty0, tx1, ty1);
257
0
      return;
258
0
    }
259
0
    else if (ctm.a == 0 && ctm.d == 0)
260
0
    {
261
0
      float tx0 = ctm.c * y0 + ctm.e;
262
0
      float ty0 = ctm.b * x0 + ctm.f;
263
0
      float tx1 = ctm.c * y1 + ctm.e;
264
0
      float ty1 = ctm.b * x1 + ctm.f;
265
0
      fz_insert_rasterizer_rect(ctx, arg->rast, tx0, ty1, tx1, ty0);
266
0
      return;
267
0
    }
268
0
  }
269
270
0
  flatten_lineto(ctx, arg_, x1, y0);
271
0
  flatten_lineto(ctx, arg_, x1, y1);
272
0
  flatten_lineto(ctx, arg_, x0, y1);
273
0
  flatten_close(ctx, arg_);
274
0
}
275
276
static const fz_path_walker flatten_proc =
277
{
278
  flatten_moveto,
279
  flatten_lineto,
280
  flatten_curveto,
281
  flatten_close,
282
  flatten_quadto,
283
  NULL,
284
  NULL,
285
  flatten_rectto
286
};
287
288
static int
289
do_flatten_fill(fz_context *ctx, fz_rasterizer *rast, const fz_path *path, fz_matrix ctm, float flatness)
290
0
{
291
0
  flatten_arg arg;
292
293
0
  arg.rast = rast;
294
0
  arg.ctm = ctm;
295
0
  arg.flatness = flatness;
296
0
  arg.b.x = arg.b.y = arg.c.x = arg.c.y = 0;
297
298
0
  fz_walk_path(ctx, path, &flatten_proc, &arg);
299
0
  if (arg.c.x != arg.b.x || arg.c.y != arg.b.y)
300
0
    line(ctx, rast, ctm, arg.c.x, arg.c.y, arg.b.x, arg.b.y);
301
302
0
  fz_gap_rasterizer(ctx, rast);
303
304
0
  return fz_is_empty_irect(fz_bound_rasterizer(ctx, rast));
305
0
}
306
307
int
308
fz_flatten_fill_path(fz_context *ctx, fz_rasterizer *rast, const fz_path *path, fz_matrix ctm, float flatness, fz_irect scissor, fz_irect *bbox)
309
0
{
310
0
  int empty;
311
0
  fz_irect local_bbox;
312
0
  if (!bbox)
313
0
    bbox = &local_bbox;
314
315
  /* If we're given an empty scissor, sanitize it. This makes life easier
316
   * down the line. */
317
0
  if (fz_is_empty_irect(scissor))
318
0
    scissor.x1 = scissor.x0, scissor.y1 = scissor.y0;
319
320
0
  if (fz_reset_rasterizer(ctx, rast, scissor))
321
0
  {
322
0
    empty = do_flatten_fill(ctx, rast, path, ctm, flatness);
323
0
    if (empty)
324
0
      return *bbox = fz_empty_irect, 1;
325
0
    fz_postindex_rasterizer(ctx, rast);
326
0
  }
327
328
0
  empty = do_flatten_fill(ctx, rast, path, ctm, flatness);
329
0
  if (empty)
330
0
    return *bbox = fz_empty_irect, 1;
331
332
0
  *bbox = fz_intersect_irect(scissor, fz_bound_rasterizer(ctx, rast));
333
0
  return fz_is_empty_irect(*bbox);
334
0
}
335
336
typedef struct sctx
337
{
338
  fz_rasterizer *rast;
339
  fz_matrix ctm;
340
  float flatness;
341
  const fz_stroke_state *stroke;
342
343
  int linejoin;
344
  float linewidth;
345
  float miterlimit;
346
  fz_point beg[2];
347
  fz_point seg[2];
348
  int sn;
349
  int not_just_moves;
350
  int from_bezier;
351
  fz_point cur;
352
353
  fz_rect rect;
354
  const float *dash_list;
355
  float dash_phase;
356
  int dash_len;
357
  float dash_total;
358
  int toggle, cap;
359
  int offset;
360
  float phase;
361
  fz_point dash_cur;
362
  fz_point dash_beg;
363
364
  float dirn_x;
365
  float dirn_y;
366
} sctx;
367
368
static void
369
fz_add_line(fz_context *ctx, sctx *s, float x0, float y0, float x1, float y1, int rev)
370
0
{
371
0
  float tx0 = s->ctm.a * x0 + s->ctm.c * y0 + s->ctm.e;
372
0
  float ty0 = s->ctm.b * x0 + s->ctm.d * y0 + s->ctm.f;
373
0
  float tx1 = s->ctm.a * x1 + s->ctm.c * y1 + s->ctm.e;
374
0
  float ty1 = s->ctm.b * x1 + s->ctm.d * y1 + s->ctm.f;
375
376
0
  fz_insert_rasterizer(ctx, s->rast, tx0, ty0, tx1, ty1, rev);
377
0
}
378
379
static void
380
fz_add_horiz_rect(fz_context *ctx, sctx *s, float x0, float y0, float x1, float y1)
381
0
{
382
0
  if (fz_antidropout_rasterizer(ctx, s->rast)) {
383
0
    if (s->ctm.b == 0 && s->ctm.c == 0)
384
0
    {
385
0
      float tx0 = s->ctm.a * x0 + s->ctm.e;
386
0
      float ty0 = s->ctm.d * y0 + s->ctm.f;
387
0
      float tx1 = s->ctm.a * x1 + s->ctm.e;
388
0
      float ty1 = s->ctm.d * y1 + s->ctm.f;
389
0
      fz_insert_rasterizer_rect(ctx, s->rast, tx1, ty1, tx0, ty0);
390
0
      return;
391
0
    }
392
0
    else if (s->ctm.a == 0 && s->ctm.d == 0)
393
0
    {
394
0
      float tx0 = s->ctm.c * y0 + s->ctm.e;
395
0
      float ty0 = s->ctm.b * x0 + s->ctm.f;
396
0
      float tx1 = s->ctm.c * y1 + s->ctm.e;
397
0
      float ty1 = s->ctm.b * x1 + s->ctm.f;
398
0
      fz_insert_rasterizer_rect(ctx, s->rast, tx1, ty0, tx0, ty1);
399
0
      return;
400
0
    }
401
0
  }
402
403
0
  fz_add_line(ctx, s, x0, y0, x1, y0, 0);
404
0
  fz_add_line(ctx, s, x1, y1, x0, y1, 1);
405
0
}
406
407
static void
408
fz_add_vert_rect(fz_context *ctx, sctx *s, float x0, float y0, float x1, float y1)
409
0
{
410
0
  if (fz_antidropout_rasterizer(ctx, s->rast))
411
0
  {
412
0
    if (s->ctm.b == 0 && s->ctm.c == 0)
413
0
    {
414
0
      float tx0 = s->ctm.a * x0 + s->ctm.e;
415
0
      float ty0 = s->ctm.d * y0 + s->ctm.f;
416
0
      float tx1 = s->ctm.a * x1 + s->ctm.e;
417
0
      float ty1 = s->ctm.d * y1 + s->ctm.f;
418
0
      fz_insert_rasterizer_rect(ctx, s->rast, tx0, ty1, tx1, ty0);
419
0
      return;
420
0
    }
421
0
    else if (s->ctm.a == 0 && s->ctm.d == 0)
422
0
    {
423
0
      float tx0 = s->ctm.c * y0 + s->ctm.e;
424
0
      float ty0 = s->ctm.b * x0 + s->ctm.f;
425
0
      float tx1 = s->ctm.c * y1 + s->ctm.e;
426
0
      float ty1 = s->ctm.b * x1 + s->ctm.f;
427
0
      fz_insert_rasterizer_rect(ctx, s->rast, tx0, ty0, tx1, ty1);
428
0
      return;
429
0
    }
430
0
  }
431
0
432
0
  fz_add_line(ctx, s, x1, y0, x0, y0, 0);
433
0
  fz_add_line(ctx, s, x0, y1, x1, y1, 1);
434
0
}
435
436
static void
437
fz_add_arc(fz_context *ctx, sctx *s,
438
  float xc, float yc,
439
  float x0, float y0,
440
  float x1, float y1,
441
  int rev)
442
0
{
443
0
  float th0, th1, r;
444
0
  float theta;
445
0
  float ox, oy, nx, ny;
446
0
  int n, i;
447
448
0
  r = fabsf(s->linewidth);
449
0
  theta = 2 * FZ_SQRT2 * sqrtf(s->flatness / r);
450
0
  th0 = atan2f(y0, x0);
451
0
  th1 = atan2f(y1, x1);
452
453
0
  if (r > 0)
454
0
  {
455
0
    if (th0 < th1)
456
0
      th0 += FZ_PI * 2;
457
0
    n = ceilf((th0 - th1) / theta);
458
0
  }
459
0
  else
460
0
  {
461
0
    if (th1 < th0)
462
0
      th1 += FZ_PI * 2;
463
0
    n = ceilf((th1 - th0) / theta);
464
0
  }
465
466
0
  if (rev)
467
0
  {
468
0
    ox = x1;
469
0
    oy = y1;
470
0
    for (i = n-1; i > 0; i--)
471
0
    {
472
0
      theta = th0 + (th1 - th0) * i / n;
473
0
      nx = cosf(theta) * r;
474
0
      ny = sinf(theta) * r;
475
0
      fz_add_line(ctx, s, xc + nx, yc + ny, xc + ox, yc + oy, rev);
476
0
      ox = nx;
477
0
      oy = ny;
478
0
    }
479
480
0
    fz_add_line(ctx, s, xc + x0, yc + y0, xc + ox, yc + oy, rev);
481
0
  }
482
0
  else
483
0
  {
484
0
    ox = x0;
485
0
    oy = y0;
486
0
    for (i = 1; i < n; i++)
487
0
    {
488
0
      theta = th0 + (th1 - th0) * i / n;
489
0
      nx = cosf(theta) * r;
490
0
      ny = sinf(theta) * r;
491
0
      fz_add_line(ctx, s, xc + ox, yc + oy, xc + nx, yc + ny, rev);
492
0
      ox = nx;
493
0
      oy = ny;
494
0
    }
495
496
0
    fz_add_line(ctx, s, xc + ox, yc + oy, xc + x1, yc + y1, rev);
497
0
  }
498
0
}
499
500
/* FLT_TINY * FLT_TINY is approximately FLT_EPSILON */
501
0
#define FLT_TINY 3.4e-4F
502
static int find_normal_vectors(float dx, float dy, float linewidth, float *dlx, float *dly)
503
0
{
504
0
  if (dx == 0)
505
0
  {
506
0
    if (dy < FLT_TINY && dy > - FLT_TINY)
507
0
      goto tiny;
508
0
    else if (dy > 0)
509
0
      *dlx = linewidth;
510
0
    else
511
0
      *dlx = -linewidth;
512
0
    *dly = 0;
513
0
  }
514
0
  else if (dy == 0)
515
0
  {
516
0
    if (dx < FLT_TINY && dx > - FLT_TINY)
517
0
      goto tiny;
518
0
    else if (dx > 0)
519
0
      *dly = -linewidth;
520
0
    else
521
0
      *dly = linewidth;
522
0
    *dlx = 0;
523
0
  }
524
0
  else
525
0
  {
526
0
    float sq = dx * dx + dy * dy;
527
0
    float scale;
528
529
0
    if (sq < FLT_EPSILON)
530
0
      goto tiny;
531
0
    scale = linewidth / sqrtf(sq);
532
0
    *dlx = dy * scale;
533
0
    *dly = -dx * scale;
534
0
  }
535
0
  return 0;
536
0
tiny:
537
0
  *dlx = 0;
538
0
  *dly = 0;
539
0
  return 1;
540
0
}
541
542
static void
543
fz_add_line_join(fz_context *ctx, sctx *s, float ax, float ay, float bx, float by, float cx, float cy, int join_under)
544
0
{
545
0
  float miterlimit = s->miterlimit;
546
0
  float linewidth = s->linewidth;
547
0
  fz_linejoin linejoin = s->linejoin;
548
0
  float dx0, dy0;
549
0
  float dx1, dy1;
550
0
  float dlx0, dly0;
551
0
  float dlx1, dly1;
552
0
  float dmx, dmy;
553
0
  float dmr2;
554
0
  float scale;
555
0
  float cross;
556
0
  int rev = 0;
557
558
0
  dx0 = bx - ax;
559
0
  dy0 = by - ay;
560
561
0
  dx1 = cx - bx;
562
0
  dy1 = cy - by;
563
564
0
  cross = dx1 * dy0 - dx0 * dy1;
565
  /* Ensure that cross >= 0 */
566
0
  if (cross < 0)
567
0
  {
568
0
    float tmp;
569
0
    tmp = dx1; dx1 = -dx0; dx0 = -tmp;
570
0
    tmp = dy1; dy1 = -dy0; dy0 = -tmp;
571
0
    cross = -cross;
572
0
    rev = !rev;
573
0
  }
574
575
0
  if (find_normal_vectors(dx0, dy0, linewidth, &dlx0, &dly0))
576
0
    linejoin = FZ_LINEJOIN_BEVEL;
577
578
0
  if (find_normal_vectors(dx1, dy1, linewidth, &dlx1, &dly1))
579
0
    linejoin = FZ_LINEJOIN_BEVEL;
580
581
0
  dmx = (dlx0 + dlx1) * 0.5f;
582
0
  dmy = (dly0 + dly1) * 0.5f;
583
0
  dmr2 = dmx * dmx + dmy * dmy;
584
585
0
  if (cross * cross < FLT_EPSILON && dx0 * dx1 + dy0 * dy1 >= 0)
586
0
    linejoin = FZ_LINEJOIN_BEVEL;
587
588
  /* XPS miter joins are clipped at miterlength, rather than simply
589
   * being converted to bevelled joins. */
590
0
  if (linejoin == FZ_LINEJOIN_MITER_XPS)
591
0
  {
592
0
    if (cross == 0)
593
0
      linejoin = FZ_LINEJOIN_BEVEL;
594
0
    else if (dmr2 * miterlimit * miterlimit >= linewidth * linewidth)
595
0
      linejoin = FZ_LINEJOIN_MITER;
596
0
  }
597
0
  else if (linejoin == FZ_LINEJOIN_MITER)
598
0
    if (dmr2 * miterlimit * miterlimit < linewidth * linewidth)
599
0
      linejoin = FZ_LINEJOIN_BEVEL;
600
601
0
  if (join_under)
602
0
  {
603
0
    fz_add_line(ctx, s, bx + dlx1, by + dly1, bx + dlx0, by + dly0, !rev);
604
0
  }
605
0
  else if (rev)
606
0
  {
607
0
    fz_add_line(ctx, s, bx + dlx1, by + dly1, bx, by, 0);
608
0
    fz_add_line(ctx, s, bx, by, bx + dlx0, by + dly0, 0);
609
0
  }
610
0
  else
611
0
  {
612
0
    fz_add_line(ctx, s, bx, by, bx + dlx0, by + dly0, 1);
613
0
    fz_add_line(ctx, s, bx + dlx1, by + dly1, bx, by, 1);
614
0
  }
615
616
0
  switch (linejoin)
617
0
  {
618
0
  case FZ_LINEJOIN_MITER_XPS:
619
0
  {
620
0
    float k, t0x, t0y, t1x, t1y;
621
622
0
    scale = linewidth * linewidth / dmr2;
623
0
    dmx *= scale;
624
0
    dmy *= scale;
625
0
    k = (scale - linewidth * miterlimit / sqrtf(dmr2)) / (scale - 1);
626
0
    t0x = bx - dmx + k * (dmx - dlx0);
627
0
    t0y = by - dmy + k * (dmy - dly0);
628
0
    t1x = bx - dmx + k * (dmx - dlx1);
629
0
    t1y = by - dmy + k * (dmy - dly1);
630
631
0
    if (rev)
632
0
    {
633
0
      fz_add_line(ctx, s, t1x, t1y, bx - dlx1, by - dly1, 1);
634
0
      fz_add_line(ctx, s, t0x, t0y, t1x, t1y, 1);
635
0
      fz_add_line(ctx, s, bx - dlx0, by - dly0, t0x, t0y, 1);
636
0
    }
637
0
    else
638
0
    {
639
0
      fz_add_line(ctx, s, bx - dlx0, by - dly0, t0x, t0y, 0);
640
0
      fz_add_line(ctx, s, t0x, t0y, t1x, t1y, 0);
641
0
      fz_add_line(ctx, s, t1x, t1y, bx - dlx1, by - dly1, 0);
642
0
    }
643
0
    break;
644
0
  }
645
0
  case FZ_LINEJOIN_MITER:
646
0
    scale = linewidth * linewidth / dmr2;
647
0
    dmx *= scale;
648
0
    dmy *= scale;
649
650
0
    if (rev)
651
0
    {
652
0
      fz_add_line(ctx, s, bx - dmx, by - dmy, bx - dlx1, by - dly1, 1);
653
0
      fz_add_line(ctx, s, bx - dlx0, by - dly0, bx - dmx, by - dmy, 1);
654
0
    }
655
0
    else
656
0
    {
657
0
      fz_add_line(ctx, s, bx - dlx0, by - dly0, bx - dmx, by - dmy, 0);
658
0
      fz_add_line(ctx, s, bx - dmx, by - dmy, bx - dlx1, by - dly1, 0);
659
0
    }
660
0
    break;
661
662
0
  case FZ_LINEJOIN_BEVEL:
663
0
    fz_add_line(ctx, s, bx - dlx0, by - dly0, bx - dlx1, by - dly1, rev);
664
0
    break;
665
666
0
  case FZ_LINEJOIN_ROUND:
667
0
    fz_add_arc(ctx, s, bx, by, -dlx0, -dly0, -dlx1, -dly1, rev);
668
0
    break;
669
670
0
  default:
671
0
    assert("Invalid line join" == NULL);
672
0
  }
673
0
}
674
675
static void
676
do_linecap(fz_context *ctx, sctx *s, float bx, float by, fz_linecap linecap, int rev, float dlx, float dly)
677
0
{
678
0
  float flatness = s->flatness;
679
0
  float linewidth = s->linewidth;
680
681
0
  switch (linecap)
682
0
  {
683
0
  case FZ_LINECAP_BUTT:
684
0
    fz_add_line(ctx, s, bx - dlx, by - dly, bx + dlx, by + dly, rev);
685
0
    break;
686
687
0
  case FZ_LINECAP_ROUND:
688
0
  {
689
0
    int i;
690
0
    int n = ceilf(FZ_PI / (2.0f * FZ_SQRT2 * sqrtf(flatness / linewidth)));
691
0
    float ox = bx - dlx;
692
0
    float oy = by - dly;
693
0
    for (i = 1; i < n; i++)
694
0
    {
695
0
      float theta = FZ_PI * i / n;
696
0
      float cth = cosf(theta);
697
0
      float sth = sinf(theta);
698
0
      float nx = bx - dlx * cth - dly * sth;
699
0
      float ny = by - dly * cth + dlx * sth;
700
0
      fz_add_line(ctx, s, ox, oy, nx, ny, rev);
701
0
      ox = nx;
702
0
      oy = ny;
703
0
    }
704
0
    fz_add_line(ctx, s, ox, oy, bx + dlx, by + dly, rev);
705
0
    break;
706
0
  }
707
708
0
  case FZ_LINECAP_SQUARE:
709
0
    fz_add_line(ctx, s, bx - dlx, by - dly,
710
0
      bx - dlx - dly, by - dly + dlx, rev);
711
0
    fz_add_line(ctx, s, bx - dlx - dly, by - dly + dlx,
712
0
      bx + dlx - dly, by + dly + dlx, rev);
713
0
    fz_add_line(ctx, s, bx + dlx - dly, by + dly + dlx,
714
0
      bx + dlx, by + dly, rev);
715
0
    break;
716
717
0
  case FZ_LINECAP_TRIANGLE:
718
0
  {
719
0
    float mx = -dly;
720
0
    float my = dlx;
721
0
    fz_add_line(ctx, s, bx - dlx, by - dly, bx + mx, by + my, rev);
722
0
    fz_add_line(ctx, s, bx + mx, by + my, bx + dlx, by + dly, rev);
723
0
    break;
724
0
  }
725
726
0
  default:
727
0
    assert("Invalid line cap" == NULL);
728
0
  }
729
0
}
730
731
static void
732
fz_add_line_cap(fz_context *ctx, sctx *s, float ax, float ay, float bx, float by, fz_linecap linecap, int rev)
733
0
{
734
0
  float linewidth = s->linewidth;
735
0
  float dx = bx - ax;
736
0
  float dy = by - ay;
737
738
0
  float scale = linewidth / sqrtf(dx * dx + dy * dy);
739
0
  float dlx = dy * scale;
740
0
  float dly = -dx * scale;
741
742
0
  do_linecap(ctx, s, bx, by, linecap, rev, dlx, dly);
743
0
}
744
745
static void
746
fz_add_zero_len_cap(fz_context *ctx, sctx *s, float ax, float ay, fz_linecap linecap, int rev)
747
0
{
748
0
  float linewidth = s->linewidth;
749
0
  float dx = rev ? -s->dirn_x : s->dirn_x;
750
0
  float dy = rev ? -s->dirn_y : s->dirn_y;
751
0
  float scale, dlx, dly;
752
753
0
  if (dx == 0 && dy == 0)
754
0
    return;
755
756
0
  scale = linewidth / sqrtf(dx * dx + dy * dy);
757
0
  dlx = dy * scale;
758
0
  dly = -dx * scale;
759
760
0
  do_linecap(ctx, s, ax, ay, linecap, rev, dlx, dly);
761
0
}
762
763
static void
764
fz_add_line_dot(fz_context *ctx, sctx *s, float ax, float ay)
765
0
{
766
0
  float flatness = s->flatness;
767
0
  float linewidth = s->linewidth;
768
0
  int n = ceilf(FZ_PI / (FZ_SQRT2 * sqrtf(flatness / linewidth)));
769
0
  float ox = ax - linewidth;
770
0
  float oy = ay;
771
0
  int i;
772
773
0
  if (n < 3)
774
0
    n = 3;
775
0
  for (i = 1; i < n; i++)
776
0
  {
777
0
    float theta = FZ_PI * 2 * i / n;
778
0
    float cth = cosf(theta);
779
0
    float sth = sinf(theta);
780
0
    float nx = ax - cth * linewidth;
781
0
    float ny = ay + sth * linewidth;
782
0
    fz_add_line(ctx, s, ox, oy, nx, ny, 0);
783
0
    ox = nx;
784
0
    oy = ny;
785
0
  }
786
787
0
  fz_add_line(ctx, s, ox, oy, ax - linewidth, ay, 0);
788
0
}
789
790
static void
791
fz_stroke_flush(fz_context *ctx, sctx *s, fz_linecap start_cap, fz_linecap end_cap)
792
0
{
793
0
  if (s->sn == 1)
794
0
  {
795
0
    fz_add_line_cap(ctx, s, s->beg[1].x, s->beg[1].y, s->beg[0].x, s->beg[0].y, start_cap, 2);
796
0
    fz_add_line_cap(ctx, s, s->seg[0].x, s->seg[0].y, s->seg[1].x, s->seg[1].y, end_cap, 0);
797
0
  }
798
0
  else if (s->not_just_moves)
799
0
  {
800
0
    if (s->cap == FZ_LINECAP_ROUND)
801
0
    {
802
0
      fz_add_line_dot(ctx, s, s->beg[0].x, s->beg[0].y);
803
0
    }
804
0
    else
805
0
    {
806
0
      fz_add_zero_len_cap(ctx, s, s->beg[0].x, s->beg[0].y, s->cap, 2);
807
0
      fz_add_zero_len_cap(ctx, s, s->beg[0].x, s->beg[0].y, s->cap, 0);
808
0
    }
809
0
  }
810
811
0
  fz_gap_rasterizer(ctx, s->rast);
812
0
}
813
814
static void
815
fz_stroke_moveto(fz_context *ctx, void *s_, float x, float y)
816
0
{
817
0
  struct sctx *s = (struct sctx *)s_;
818
819
0
  s->seg[0].x = s->beg[0].x = x;
820
0
  s->seg[0].y = s->beg[0].y = y;
821
0
  s->sn = 0;
822
0
  s->not_just_moves = 0;
823
0
  s->from_bezier = 0;
824
0
  s->dirn_x = 0;
825
0
  s->dirn_y = 0;
826
0
}
827
828
static void
829
fz_stroke_lineto_aux(fz_context *ctx, sctx *s, float x, float y, int from_bezier, float dirn_x, float dirn_y)
830
0
{
831
0
  float ox = s->seg[s->sn].x;
832
0
  float oy = s->seg[s->sn].y;
833
0
  float dx = x - ox;
834
0
  float dy = y - oy;
835
0
  float dlx, dly;
836
837
0
  s->not_just_moves = 1;
838
839
  /* We store the direction (as used for the alignment of caps etc) based on the
840
   * direction we are passed in. */
841
0
  s->dirn_x = dirn_x;
842
0
  s->dirn_y = dirn_y;
843
844
  /* We calculate the normal vectors from the delta that we have just moved. */
845
0
  if (find_normal_vectors(dx, dy, s->linewidth, &dlx, &dly))
846
0
  {
847
0
    return;
848
0
  }
849
850
0
  if (s->sn == 1)
851
0
    fz_add_line_join(ctx, s, s->seg[0].x, s->seg[0].y, ox, oy, x, y, s->from_bezier & from_bezier);
852
853
0
#if 1
854
0
  if (0 && dx == 0)
855
0
  {
856
0
    fz_add_vert_rect(ctx, s, ox - dlx, oy, x + dlx, y);
857
0
  }
858
0
  else if (dy == 0)
859
0
  {
860
0
    fz_add_horiz_rect(ctx, s, ox, oy - dly, x, y + dly);
861
0
  }
862
0
  else
863
0
#endif
864
0
  {
865
866
0
    fz_add_line(ctx, s, ox - dlx, oy - dly, x - dlx, y - dly, 0);
867
0
    fz_add_line(ctx, s, x + dlx, y + dly, ox + dlx, oy + dly, 1);
868
0
  }
869
870
0
  if (s->sn)
871
0
  {
872
0
    s->seg[0] = s->seg[1];
873
0
    s->seg[1].x = x;
874
0
    s->seg[1].y = y;
875
0
  }
876
0
  else
877
0
  {
878
0
    s->seg[1].x = s->beg[1].x = x;
879
0
    s->seg[1].y = s->beg[1].y = y;
880
0
    s->sn = 1;
881
0
  }
882
0
  s->from_bezier = from_bezier;
883
0
}
884
885
static void
886
fz_stroke_lineto(fz_context *ctx, sctx *s, float x, float y, int from_bezier)
887
0
{
888
0
  float ox = s->seg[s->sn].x;
889
0
  float oy = s->seg[s->sn].y;
890
0
  float dx = x - ox;
891
0
  float dy = y - oy;
892
0
  fz_stroke_lineto_aux(ctx, s, x, y, from_bezier, dx, dy);
893
0
}
894
895
static void
896
fz_stroke_closepath(fz_context *ctx, sctx *s)
897
0
{
898
0
  if (s->sn == 1)
899
0
  {
900
0
    fz_stroke_lineto(ctx, s, s->beg[0].x, s->beg[0].y, 0);
901
    /* fz_stroke_lineto will *normally* end up with s->seg[1] being the x,y coords passed in.
902
     * As such, the following line should draw a linejoin between the closing segment of this
903
     * subpath (seg[0]->seg[1]) == (seg[0]->beg[0]) and the first segment of this subpath
904
     * (beg[0]->beg[1]).
905
     * In cases where the line was already at an x,y infinitesimally close to s->beg[0],
906
     * fz_stroke_lineto may exit without doing any processing. This leaves seg[0]->seg[1]
907
     * pointing at the penultimate line segment. Thus this draws a linejoin between that
908
     * penultimate segment and the end segment. This is what we want. */
909
0
    fz_add_line_join(ctx, s, s->seg[0].x, s->seg[0].y, s->beg[0].x, s->beg[0].y, s->beg[1].x, s->beg[1].y, 0);
910
0
  }
911
0
  else if (s->cap == FZ_LINECAP_ROUND)
912
0
  {
913
0
    fz_add_line_dot(ctx, s, s->beg[0].x, s->beg[0].y);
914
0
  }
915
916
0
  s->seg[0] = s->beg[0];
917
0
  s->sn = 0;
918
0
  s->not_just_moves = 0;
919
0
  s->from_bezier = 0;
920
0
  s->dirn_x = 0;
921
0
  s->dirn_y = 0;
922
923
0
  fz_gap_rasterizer(ctx, s->rast);
924
0
}
925
926
static void
927
fz_stroke_bezier(fz_context *ctx, struct sctx *s,
928
  float xa, float ya,
929
  float xb, float yb,
930
  float xc, float yc,
931
  float xd, float yd, int depth)
932
0
{
933
0
  float dmax;
934
0
  float xab, yab;
935
0
  float xbc, ybc;
936
0
  float xcd, ycd;
937
0
  float xabc, yabc;
938
0
  float xbcd, ybcd;
939
0
  float xabcd, yabcd;
940
941
  /* termination check */
942
0
  dmax = fz_abs(xa - xb);
943
0
  dmax = fz_max(dmax, fz_abs(ya - yb));
944
0
  dmax = fz_max(dmax, fz_abs(xd - xc));
945
0
  dmax = fz_max(dmax, fz_abs(yd - yc));
946
0
  if (dmax < s->flatness || depth >= MAX_DEPTH)
947
0
  {
948
0
    fz_stroke_lineto(ctx, s, xd, yd, 1);
949
0
    return;
950
0
  }
951
952
0
  xab = xa + xb;
953
0
  yab = ya + yb;
954
0
  xbc = xb + xc;
955
0
  ybc = yb + yc;
956
0
  xcd = xc + xd;
957
0
  ycd = yc + yd;
958
959
0
  xabc = xab + xbc;
960
0
  yabc = yab + ybc;
961
0
  xbcd = xbc + xcd;
962
0
  ybcd = ybc + ycd;
963
964
0
  xabcd = xabc + xbcd;
965
0
  yabcd = yabc + ybcd;
966
967
0
  xab *= 0.5f; yab *= 0.5f;
968
  /* xbc *= 0.5f; ybc *= 0.5f; */
969
0
  xcd *= 0.5f; ycd *= 0.5f;
970
971
0
  xabc *= 0.25f; yabc *= 0.25f;
972
0
  xbcd *= 0.25f; ybcd *= 0.25f;
973
974
0
  xabcd *= 0.125f; yabcd *= 0.125f;
975
976
0
  fz_stroke_bezier(ctx, s, xa, ya, xab, yab, xabc, yabc, xabcd, yabcd, depth + 1);
977
0
  fz_stroke_bezier(ctx, s, xabcd, yabcd, xbcd, ybcd, xcd, ycd, xd, yd, depth + 1);
978
0
}
979
980
static void
981
fz_stroke_quad(fz_context *ctx, struct sctx *s,
982
  float xa, float ya,
983
  float xb, float yb,
984
  float xc, float yc, int depth)
985
0
{
986
0
  float dmax;
987
0
  float xab, yab;
988
0
  float xbc, ybc;
989
0
  float xabc, yabc;
990
991
  /* termination check */
992
0
  dmax = fz_abs(xa - xb);
993
0
  dmax = fz_max(dmax, fz_abs(ya - yb));
994
0
  dmax = fz_max(dmax, fz_abs(xc - xb));
995
0
  dmax = fz_max(dmax, fz_abs(yc - yb));
996
0
  if (dmax < s->flatness || depth >= MAX_DEPTH)
997
0
  {
998
0
    fz_stroke_lineto(ctx, s, xc, yc, 1);
999
0
    return;
1000
0
  }
1001
1002
0
  xab = xa + xb;
1003
0
  yab = ya + yb;
1004
0
  xbc = xb + xc;
1005
0
  ybc = yb + yc;
1006
1007
0
  xabc = xab + xbc;
1008
0
  yabc = yab + ybc;
1009
1010
0
  xab *= 0.5f; yab *= 0.5f;
1011
0
  xbc *= 0.5f; ybc *= 0.5f;
1012
1013
0
  xabc *= 0.25f; yabc *= 0.25f;
1014
1015
0
  fz_stroke_quad(ctx, s, xa, ya, xab, yab, xabc, yabc, depth + 1);
1016
0
  fz_stroke_quad(ctx, s, xabc, yabc, xbc, ybc, xc, yc, depth + 1);
1017
0
}
1018
1019
static void
1020
stroke_moveto(fz_context *ctx, void *s_, float x, float y)
1021
0
{
1022
0
  sctx *s = (sctx *)s_;
1023
1024
0
  fz_stroke_flush(ctx, s, s->stroke->start_cap, s->stroke->end_cap);
1025
0
  fz_stroke_moveto(ctx, s, x, y);
1026
0
  s->cur.x = x;
1027
0
  s->cur.y = y;
1028
0
}
1029
1030
static void
1031
stroke_lineto(fz_context *ctx, void *s_, float x, float y)
1032
0
{
1033
0
  sctx *s = (sctx *)s_;
1034
1035
0
  fz_stroke_lineto(ctx, s, x, y, 0);
1036
0
  s->cur.x = x;
1037
0
  s->cur.y = y;
1038
0
}
1039
1040
static void
1041
stroke_curveto(fz_context *ctx, void *s_, float x1, float y1, float x2, float y2, float x3, float y3)
1042
0
{
1043
0
  sctx *s = (sctx *)s_;
1044
1045
0
  fz_stroke_bezier(ctx, s, s->cur.x, s->cur.y, x1, y1, x2, y2, x3, y3, 0);
1046
0
  s->cur.x = x3;
1047
0
  s->cur.y = y3;
1048
0
}
1049
1050
static void
1051
stroke_quadto(fz_context *ctx, void *s_, float x1, float y1, float x2, float y2)
1052
0
{
1053
0
  sctx *s = (sctx *)s_;
1054
1055
0
  fz_stroke_quad(ctx, s, s->cur.x, s->cur.y, x1, y1, x2, y2, 0);
1056
0
  s->cur.x = x2;
1057
0
  s->cur.y = y2;
1058
0
}
1059
1060
static void
1061
stroke_close(fz_context *ctx, void *s_)
1062
0
{
1063
0
  sctx *s = (sctx *)s_;
1064
1065
0
  fz_stroke_closepath(ctx, s);
1066
0
}
1067
1068
static const fz_path_walker stroke_proc =
1069
{
1070
  stroke_moveto,
1071
  stroke_lineto,
1072
  stroke_curveto,
1073
  stroke_close,
1074
  stroke_quadto
1075
};
1076
1077
static void
1078
fz_dash_moveto(fz_context *ctx, struct sctx *s, float x, float y)
1079
0
{
1080
0
  s->toggle = 1;
1081
0
  s->offset = 0;
1082
0
  s->phase = s->dash_phase;
1083
1084
0
  while (s->phase > 0 && s->phase >= s->dash_list[s->offset])
1085
0
  {
1086
0
    s->toggle = !s->toggle;
1087
0
    s->phase -= s->dash_list[s->offset];
1088
0
    s->offset ++;
1089
0
    if (s->offset == s->dash_len)
1090
0
      s->offset = 0;
1091
0
  }
1092
1093
0
  s->dash_cur.x = x;
1094
0
  s->dash_cur.y = y;
1095
1096
0
  if (s->toggle)
1097
0
  {
1098
0
    fz_stroke_flush(ctx, s, s->cap, s->stroke->end_cap);
1099
0
    s->cap = s->stroke->start_cap;
1100
0
    fz_stroke_moveto(ctx, s, x, y);
1101
0
  }
1102
0
}
1103
1104
/*
1105
  Performs: a += (b-a) * i/n
1106
  allowing for FP inaccuracies that can cause a to "overrun" b.
1107
*/
1108
static float advance(float a, float b, float i, float n)
1109
0
{
1110
0
  float d = b - a;
1111
0
  float target = a + d * i/n;
1112
1113
0
  if (d < 0 && target < b)
1114
0
    target = b;
1115
0
  else if (d > 0 && target > b)
1116
0
    target = b;
1117
1118
0
  return target;
1119
0
}
1120
1121
static int is_odd(float f)
1122
0
{
1123
0
  float intpart = truncf(f);
1124
0
  return isfinite(f) ? (intpart - 2.0f * truncf(intpart / 2.0f) != 0.0f) : 0;
1125
0
}
1126
1127
static void
1128
fz_dash_lineto(fz_context *ctx, struct sctx *s, float bx, float by, int from_bezier)
1129
0
{
1130
0
  float dx, dy, d;
1131
0
  float total, used, ratio, tail;
1132
0
  float ax, ay;
1133
0
  float mx, my;
1134
0
  float old_bx = 0, old_by = 0;
1135
0
  float n;
1136
0
  int dash_cap = s->stroke->dash_cap;
1137
1138
0
  ax = s->dash_cur.x;
1139
0
  ay = s->dash_cur.y;
1140
0
  dx = bx - ax;
1141
0
  dy = by - ay;
1142
0
  used = 0;
1143
0
  tail = 0;
1144
0
  total = sqrtf(dx * dx + dy * dy);
1145
1146
  /* If a is off screen, bring it onto the screen. First
1147
   * horizontally... */
1148
0
  if ((d = s->rect.x0 - ax) > 0)
1149
0
  {
1150
0
    if (bx < s->rect.x0)
1151
0
    {
1152
      /* Entirely off screen */
1153
0
      tail = total;
1154
0
      old_bx = bx;
1155
0
      old_by = by;
1156
0
      goto adjust_for_tail;
1157
0
    }
1158
0
    ax = s->rect.x0;  /* d > 0, dx > 0 */
1159
0
    goto a_moved_horizontally;
1160
0
  }
1161
0
  else if (d < 0 && (d = (s->rect.x1 - ax)) < 0)
1162
0
  {
1163
0
    if (bx > s->rect.x1)
1164
0
    {
1165
      /* Entirely off screen */
1166
0
      tail = total;
1167
0
      old_bx = bx;
1168
0
      old_by = by;
1169
0
      goto adjust_for_tail;
1170
0
    }
1171
0
    ax = s->rect.x1;  /* d < 0, dx < 0 */
1172
0
a_moved_horizontally: /* d and dx have the same sign */
1173
0
    assert((d > 0 && dx > 0) || (d < 0 && dx < 0));
1174
0
    assert(dx != 0);
1175
0
    ay = advance(ay, by, d, dx);
1176
0
    used = total * d/dx;
1177
0
    total -= used;
1178
0
    dx = bx - ax;
1179
0
    dy = by - ay;
1180
0
  }
1181
  /* Then vertically... */
1182
0
  if ((d = s->rect.y0 - ay) > 0)
1183
0
  {
1184
0
    if (by < s->rect.y0)
1185
0
    {
1186
      /* Entirely off screen */
1187
0
      tail = total;
1188
0
      old_bx = bx;
1189
0
      old_by = by;
1190
0
      goto adjust_for_tail;
1191
0
    }
1192
0
    ay = s->rect.y0;  /* d > 0, dy > 0 */
1193
0
    goto a_moved_vertically;
1194
0
  }
1195
0
  else if (d < 0 && (d = (s->rect.y1 - ay)) < 0)
1196
0
  {
1197
0
    if (by > s->rect.y1)
1198
0
    {
1199
      /* Entirely off screen */
1200
0
      tail = total;
1201
0
      old_bx = bx;
1202
0
      old_by = by;
1203
0
      goto adjust_for_tail;
1204
0
    }
1205
0
    ay = s->rect.y1;  /* d < 0, dy < 0 */
1206
0
a_moved_vertically: /* d and dy have the same sign */
1207
0
    assert((d > 0 && dy > 0) || (d < 0 && dy < 0));
1208
0
    assert(dy != 0);
1209
0
    ax = advance(ax, bx, d, dy);
1210
0
    d = total * d/dy;
1211
0
    total -= d;
1212
0
    used += d;
1213
0
    dx = bx - ax;
1214
0
    dy = by - ay;
1215
0
  }
1216
0
  if (used != 0.0f)
1217
0
  {
1218
    /* Update the position in the dash array */
1219
0
    if (s->toggle)
1220
0
    {
1221
0
      fz_stroke_lineto(ctx, s, ax, ay, from_bezier);
1222
0
    }
1223
0
    else
1224
0
    {
1225
0
      fz_stroke_flush(ctx, s, s->cap, s->stroke->dash_cap);
1226
0
      s->cap = s->stroke->dash_cap;
1227
0
      fz_stroke_moveto(ctx, s, ax, ay);
1228
0
    }
1229
0
    used += s->phase;
1230
0
    n = trunc(used/s->dash_total);
1231
0
    used -= n*s->dash_total;
1232
0
    if (is_odd(n) & s->dash_len & 1)
1233
0
      s->toggle = !s->toggle;
1234
0
    while (used >= s->dash_list[s->offset])
1235
0
    {
1236
0
      used -= s->dash_list[s->offset];
1237
0
      s->offset++;
1238
0
      if (s->offset == s->dash_len)
1239
0
        s->offset = 0;
1240
0
      s->toggle = !s->toggle;
1241
0
    }
1242
0
    if (s->toggle)
1243
0
    {
1244
0
      fz_stroke_lineto(ctx, s, ax, ay, from_bezier);
1245
0
    }
1246
0
    else
1247
0
    {
1248
0
      fz_stroke_flush(ctx, s, s->cap, s->stroke->dash_cap);
1249
0
      s->cap = s->stroke->dash_cap;
1250
0
      fz_stroke_moveto(ctx, s, ax, ay);
1251
0
    }
1252
0
    s->phase = used;
1253
0
    used = 0;
1254
0
  }
1255
1256
  /* Now if bx is off screen, bring it back */
1257
0
  if (dx == 0)
1258
0
  {
1259
    /* Earlier stages can have moved a to be b, while leaving it completely off screen. */
1260
0
  }
1261
0
  else if ((d = bx - s->rect.x0) < 0)
1262
0
  {
1263
0
    old_bx = bx;
1264
0
    old_by = by;
1265
0
    bx = s->rect.x0;  /* d < 0, dx < 0 */
1266
0
    goto b_moved_horizontally;
1267
0
  }
1268
0
  else if (d > 0 && (d = (bx - s->rect.x1)) > 0)
1269
0
  {
1270
0
    old_bx = bx;
1271
0
    old_by = by;
1272
0
    bx = s->rect.x1;  /* d > 0, dx > 0 */
1273
0
b_moved_horizontally: /* d and dx have the same sign */
1274
0
    assert((d > 0 && dx > 0) || (d < 0 && dx < 0));
1275
0
    assert(dx != 0);
1276
0
    by = advance(by, ay, d, dx);
1277
0
    tail = total * d/dx;
1278
0
    total -= tail;
1279
0
    dx = bx - ax;
1280
0
    dy = by - ay;
1281
0
  }
1282
  /* Then vertically... */
1283
0
  if (dy == 0)
1284
0
  {
1285
    /* Earlier stages can have moved a to be b, while leaving it completely off screen. */
1286
0
  }
1287
0
  else if ((d = by - s->rect.y0) < 0)
1288
0
  {
1289
0
    old_bx = bx;
1290
0
    old_by = by;
1291
0
    by = s->rect.y0;  /* d < 0, dy < 0 */
1292
0
    goto b_moved_vertically;
1293
0
  }
1294
0
  else if (d > 0 && (d = (by - s->rect.y1)) > 0)
1295
0
  {
1296
0
    float t;
1297
0
    old_bx = bx;
1298
0
    old_by = by;
1299
0
    by = s->rect.y1;  /* d > 0, dy > 0 */
1300
0
b_moved_vertically: /* d and dy have the same sign */
1301
0
    assert((d > 0 && dy > 0) || (d < 0 && dy < 0));
1302
0
    assert(dy != 0);
1303
0
    bx = advance(bx, ax, d, dy);
1304
0
    t = total * d/dy;
1305
0
    tail += t;
1306
0
    total -= t;
1307
0
    dx = bx - ax;
1308
0
    dy = by - ay;
1309
0
  }
1310
1311
0
  while (total - used > s->dash_list[s->offset] - s->phase)
1312
0
  {
1313
0
    used += s->dash_list[s->offset] - s->phase;
1314
0
    ratio = used / total;
1315
0
    mx = ax + ratio * dx;
1316
0
    my = ay + ratio * dy;
1317
1318
0
    if (s->toggle)
1319
0
    {
1320
0
      fz_stroke_lineto_aux(ctx, s, mx, my, from_bezier, dx, dy);
1321
0
    }
1322
0
    else
1323
0
    {
1324
0
      fz_stroke_flush(ctx, s, s->cap, dash_cap);
1325
0
      s->cap = dash_cap;
1326
0
      fz_stroke_moveto(ctx, s, mx, my);
1327
0
    }
1328
1329
0
    s->toggle = !s->toggle;
1330
0
    s->phase = 0;
1331
0
    s->offset ++;
1332
0
    if (s->offset == s->dash_len)
1333
0
      s->offset = 0;
1334
0
  }
1335
1336
0
  s->phase += total - used;
1337
1338
0
  if (tail == 0.0f)
1339
0
  {
1340
0
    s->dash_cur.x = bx;
1341
0
    s->dash_cur.y = by;
1342
1343
0
    if (s->toggle)
1344
0
    {
1345
0
      fz_stroke_lineto_aux(ctx, s, bx, by, from_bezier, dx, dy);
1346
0
    }
1347
0
  }
1348
0
  else
1349
0
  {
1350
0
adjust_for_tail:
1351
0
    s->dash_cur.x = old_bx;
1352
0
    s->dash_cur.y = old_by;
1353
    /* Update the position in the dash array */
1354
0
    if (s->toggle)
1355
0
    {
1356
0
      fz_stroke_lineto_aux(ctx, s, old_bx, old_by, from_bezier, dx, dy);
1357
0
    }
1358
0
    else
1359
0
    {
1360
0
      fz_stroke_flush(ctx, s, s->cap, dash_cap);
1361
0
      s->cap = dash_cap;
1362
0
      fz_stroke_moveto(ctx, s, old_bx, old_by);
1363
0
    }
1364
0
    tail += s->phase;
1365
0
    n = trunc(tail/s->dash_total);
1366
0
    tail -= n*s->dash_total;
1367
0
    if (is_odd(n) & s->dash_len & 1)
1368
0
      s->toggle = !s->toggle;
1369
0
    while (tail > s->dash_list[s->offset])
1370
0
    {
1371
0
      tail -= s->dash_list[s->offset];
1372
0
      s->offset++;
1373
0
      if (s->offset == s->dash_len)
1374
0
        s->offset = 0;
1375
0
      s->toggle = !s->toggle;
1376
0
    }
1377
0
    if (s->toggle)
1378
0
    {
1379
0
      fz_stroke_lineto_aux(ctx, s, old_bx, old_by, from_bezier, dx, dy);
1380
0
    }
1381
0
    else
1382
0
    {
1383
0
      fz_stroke_flush(ctx, s, s->cap, dash_cap);
1384
0
      s->cap = dash_cap;
1385
0
      fz_stroke_moveto(ctx, s, old_bx, old_by);
1386
0
    }
1387
0
    s->phase = tail;
1388
0
  }
1389
0
}
1390
1391
static void
1392
fz_dash_bezier(fz_context *ctx, struct sctx *s,
1393
  float xa, float ya,
1394
  float xb, float yb,
1395
  float xc, float yc,
1396
  float xd, float yd, int depth)
1397
0
{
1398
0
  float dmax;
1399
0
  float xab, yab;
1400
0
  float xbc, ybc;
1401
0
  float xcd, ycd;
1402
0
  float xabc, yabc;
1403
0
  float xbcd, ybcd;
1404
0
  float xabcd, yabcd;
1405
1406
  /* termination check */
1407
0
  dmax = fz_abs(xa - xb);
1408
0
  dmax = fz_max(dmax, fz_abs(ya - yb));
1409
0
  dmax = fz_max(dmax, fz_abs(xd - xc));
1410
0
  dmax = fz_max(dmax, fz_abs(yd - yc));
1411
0
  if (dmax < s->flatness || depth >= MAX_DEPTH)
1412
0
  {
1413
0
    fz_dash_lineto(ctx, s, xd, yd, 1);
1414
0
    return;
1415
0
  }
1416
1417
0
  xab = xa + xb;
1418
0
  yab = ya + yb;
1419
0
  xbc = xb + xc;
1420
0
  ybc = yb + yc;
1421
0
  xcd = xc + xd;
1422
0
  ycd = yc + yd;
1423
1424
0
  xabc = xab + xbc;
1425
0
  yabc = yab + ybc;
1426
0
  xbcd = xbc + xcd;
1427
0
  ybcd = ybc + ycd;
1428
1429
0
  xabcd = xabc + xbcd;
1430
0
  yabcd = yabc + ybcd;
1431
1432
0
  xab *= 0.5f; yab *= 0.5f;
1433
  /* xbc *= 0.5f; ybc *= 0.5f; */
1434
0
  xcd *= 0.5f; ycd *= 0.5f;
1435
1436
0
  xabc *= 0.25f; yabc *= 0.25f;
1437
0
  xbcd *= 0.25f; ybcd *= 0.25f;
1438
1439
0
  xabcd *= 0.125f; yabcd *= 0.125f;
1440
1441
0
  fz_dash_bezier(ctx, s, xa, ya, xab, yab, xabc, yabc, xabcd, yabcd, depth + 1);
1442
0
  fz_dash_bezier(ctx, s, xabcd, yabcd, xbcd, ybcd, xcd, ycd, xd, yd, depth + 1);
1443
0
}
1444
1445
static void
1446
fz_dash_quad(fz_context *ctx, struct sctx *s,
1447
  float xa, float ya,
1448
  float xb, float yb,
1449
  float xc, float yc, int depth)
1450
0
{
1451
0
  float dmax;
1452
0
  float xab, yab;
1453
0
  float xbc, ybc;
1454
0
  float xabc, yabc;
1455
1456
  /* termination check */
1457
0
  dmax = fz_abs(xa - xb);
1458
0
  dmax = fz_max(dmax, fz_abs(ya - yb));
1459
0
  dmax = fz_max(dmax, fz_abs(xc - xb));
1460
0
  dmax = fz_max(dmax, fz_abs(yc - yb));
1461
0
  if (dmax < s->flatness || depth >= MAX_DEPTH)
1462
0
  {
1463
0
    fz_dash_lineto(ctx, s, xc, yc, 1);
1464
0
    return;
1465
0
  }
1466
1467
0
  xab = xa + xb;
1468
0
  yab = ya + yb;
1469
0
  xbc = xb + xc;
1470
0
  ybc = yb + yc;
1471
1472
0
  xabc = xab + xbc;
1473
0
  yabc = yab + ybc;
1474
1475
0
  xab *= 0.5f; yab *= 0.5f;
1476
0
  xbc *= 0.5f; ybc *= 0.5f;
1477
1478
0
  xabc *= 0.25f; yabc *= 0.25f;
1479
1480
0
  fz_dash_quad(ctx, s, xa, ya, xab, yab, xabc, yabc, depth + 1);
1481
0
  fz_dash_quad(ctx, s, xabc, yabc, xbc, ybc, xc, yc, depth + 1);
1482
0
}
1483
1484
static void
1485
dash_moveto(fz_context *ctx, void *s_, float x, float y)
1486
0
{
1487
0
  sctx *s = (sctx *)s_;
1488
1489
0
  fz_dash_moveto(ctx, s, x, y);
1490
0
  s->dash_beg.x = s->cur.x = x;
1491
0
  s->dash_beg.y = s->cur.y = y;
1492
0
}
1493
1494
static void
1495
dash_lineto(fz_context *ctx, void *s_, float x, float y)
1496
0
{
1497
0
  sctx *s = (sctx *)s_;
1498
1499
0
  fz_dash_lineto(ctx, s, x, y, 0);
1500
0
  s->cur.x = x;
1501
0
  s->cur.y = y;
1502
0
}
1503
1504
static void
1505
dash_curveto(fz_context *ctx, void *s_, float x1, float y1, float x2, float y2, float x3, float y3)
1506
0
{
1507
0
  sctx *s = (sctx *)s_;
1508
1509
0
  fz_dash_bezier(ctx, s, s->cur.x, s->cur.y, x1, y1, x2, y2, x3, y3, 0);
1510
0
  s->cur.x = x3;
1511
0
  s->cur.y = y3;
1512
0
}
1513
1514
static void
1515
dash_quadto(fz_context *ctx, void *s_, float x1, float y1, float x2, float y2)
1516
0
{
1517
0
  sctx *s = (sctx *)s_;
1518
1519
0
  fz_dash_quad(ctx, s, s->cur.x, s->cur.y, x1, y1, x2, y2, 0);
1520
0
  s->cur.x = x2;
1521
0
  s->cur.y = y2;
1522
0
}
1523
1524
static void
1525
dash_close(fz_context *ctx, void *s_)
1526
0
{
1527
0
  sctx *s = (sctx *)s_;
1528
1529
0
  fz_dash_lineto(ctx, s, s->dash_beg.x, s->dash_beg.y, 0);
1530
0
  s->cur.x = s->dash_beg.x;
1531
0
  s->cur.y = s->dash_beg.y;
1532
0
}
1533
1534
static const fz_path_walker dash_proc =
1535
{
1536
  dash_moveto,
1537
  dash_lineto,
1538
  dash_curveto,
1539
  dash_close,
1540
  dash_quadto
1541
};
1542
1543
static int
1544
do_flatten_stroke(fz_context *ctx, fz_rasterizer *rast, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, float flatness, float linewidth)
1545
0
{
1546
0
  struct sctx s;
1547
0
  const fz_path_walker *proc = &stroke_proc;
1548
1549
0
  s.stroke = stroke;
1550
0
  s.rast = rast;
1551
0
  s.ctm = ctm;
1552
0
  s.flatness = flatness;
1553
0
  s.linejoin = stroke->linejoin;
1554
0
  s.linewidth = linewidth * 0.5f; /* hairlines use a different value from the path value */
1555
0
  s.miterlimit = stroke->miterlimit;
1556
0
  s.sn = 0;
1557
0
  s.not_just_moves = 0;
1558
0
  s.toggle = 0;
1559
0
  s.offset = 0;
1560
0
  s.phase = 0;
1561
0
  s.dirn_x = 0;
1562
0
  s.dirn_y = 0;
1563
1564
0
  s.cap = stroke->start_cap;
1565
1566
0
  s.dash_list = NULL;
1567
0
  s.dash_len = stroke->dash_len;
1568
0
  if (s.dash_len > 0)
1569
0
  {
1570
0
    int i;
1571
0
    fz_matrix inv;
1572
0
    float max_expand;
1573
0
    const float *list = stroke->dash_list;
1574
1575
0
    s.dash_total = 0;
1576
0
    for (i = 0; i < s.dash_len; i++)
1577
0
      s.dash_total += list[i];
1578
0
    if (s.dash_total == 0)
1579
0
      return 1;
1580
1581
0
    s.rect = fz_scissor_rasterizer(ctx, rast);
1582
0
    if (fz_try_invert_matrix(&inv, ctm))
1583
0
      return 1;
1584
0
    s.rect = fz_transform_rect(s.rect, inv);
1585
0
    s.rect.x0 -= linewidth;
1586
0
    s.rect.x1 += linewidth;
1587
0
    s.rect.y0 -= linewidth;
1588
0
    s.rect.y1 += linewidth;
1589
1590
0
    max_expand = fz_matrix_max_expansion(ctm);
1591
0
    if (s.dash_total >= 0.01f && s.dash_total * max_expand >= 0.5f)
1592
0
    {
1593
0
      proc = &dash_proc;
1594
0
      s.dash_phase = fmodf(stroke->dash_phase, s.dash_total);
1595
0
      s.dash_list = list;
1596
0
    }
1597
0
  }
1598
1599
0
  s.cur.x = s.cur.y = 0;
1600
0
  fz_walk_path(ctx, path, proc, &s);
1601
0
  fz_stroke_flush(ctx, &s, s.cap, stroke->end_cap);
1602
1603
0
  return fz_is_empty_irect(fz_bound_rasterizer(ctx, rast));
1604
0
}
1605
1606
int
1607
fz_flatten_stroke_path(fz_context *ctx, fz_rasterizer *rast, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, float flatness, float linewidth, fz_irect scissor, fz_irect *bbox)
1608
0
{
1609
0
  int empty;
1610
0
  fz_irect local_bbox;
1611
0
  if (!bbox)
1612
0
    bbox = &local_bbox;
1613
1614
0
  if (fz_reset_rasterizer(ctx, rast, scissor))
1615
0
  {
1616
0
    empty = do_flatten_stroke(ctx, rast, path, stroke, ctm, flatness, linewidth);
1617
0
    if (empty)
1618
0
      return *bbox = fz_empty_irect, 1;
1619
0
    fz_postindex_rasterizer(ctx, rast);
1620
0
  }
1621
1622
0
  empty = do_flatten_stroke(ctx, rast, path, stroke, ctm, flatness, linewidth);
1623
0
  if (empty)
1624
0
    return *bbox = fz_empty_irect, 1;
1625
1626
0
  *bbox = fz_intersect_irect(scissor, fz_bound_rasterizer(ctx, rast));
1627
0
  return fz_is_empty_irect(*bbox);
1628
0
}