Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java
41159 views
1
/*
2
* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package sun.java2d.marlin;
27
28
import java.awt.geom.AffineTransform;
29
import java.awt.geom.Path2D;
30
import java.util.Arrays;
31
import sun.java2d.marlin.Helpers.IndexStack;
32
import sun.java2d.marlin.Helpers.PolyStack;
33
34
final class TransformingPathConsumer2D {
35
36
// smaller uncertainty in double variant
37
static final double CLIP_RECT_PADDING = 0.25d;
38
39
private final RendererContext rdrCtx;
40
41
// recycled ClosedPathDetector instance from detectClosedPath()
42
private final ClosedPathDetector cpDetector;
43
44
// recycled PathClipFilter instance from pathClipper()
45
private final PathClipFilter pathClipper;
46
47
// recycled DPathConsumer2D instance from wrapPath2D()
48
private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();
49
50
// recycled DPathConsumer2D instances from deltaTransformConsumer()
51
private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();
52
private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
53
54
// recycled DPathConsumer2D instances from inverseDeltaTransformConsumer()
55
private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
56
private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
57
58
// recycled PathTracer instances from tracer...() methods
59
private final PathTracer tracerInput = new PathTracer("[Input]");
60
private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
61
private final PathTracer tracerFiller = new PathTracer("Filler");
62
private final PathTracer tracerStroker = new PathTracer("Stroker");
63
private final PathTracer tracerDasher = new PathTracer("Dasher");
64
65
TransformingPathConsumer2D(final RendererContext rdrCtx) {
66
// used by RendererContext
67
this.rdrCtx = rdrCtx;
68
this.cpDetector = new ClosedPathDetector(rdrCtx);
69
this.pathClipper = new PathClipFilter(rdrCtx);
70
}
71
72
DPathConsumer2D wrapPath2D(Path2D.Double p2d) {
73
return wp_Path2DWrapper.init(p2d);
74
}
75
76
DPathConsumer2D traceInput(DPathConsumer2D out) {
77
return tracerInput.init(out);
78
}
79
80
DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) {
81
return tracerCPDetector.init(out);
82
}
83
84
DPathConsumer2D traceFiller(DPathConsumer2D out) {
85
return tracerFiller.init(out);
86
}
87
88
DPathConsumer2D traceStroker(DPathConsumer2D out) {
89
return tracerStroker.init(out);
90
}
91
92
DPathConsumer2D traceDasher(DPathConsumer2D out) {
93
return tracerDasher.init(out);
94
}
95
96
DPathConsumer2D detectClosedPath(DPathConsumer2D out) {
97
return cpDetector.init(out);
98
}
99
100
DPathConsumer2D pathClipper(DPathConsumer2D out) {
101
return pathClipper.init(out);
102
}
103
104
DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out,
105
AffineTransform at)
106
{
107
if (at == null) {
108
return out;
109
}
110
final double mxx = at.getScaleX();
111
final double mxy = at.getShearX();
112
final double myx = at.getShearY();
113
final double myy = at.getScaleY();
114
115
if (mxy == 0.0d && myx == 0.0d) {
116
if (mxx == 1.0d && myy == 1.0d) {
117
return out;
118
} else {
119
// Scale only
120
if (rdrCtx.doClip) {
121
// adjust clip rectangle (ymin, ymax, xmin, xmax):
122
rdrCtx.clipInvScale = adjustClipScale(rdrCtx.clipRect,
123
mxx, myy);
124
}
125
return dt_DeltaScaleFilter.init(out, mxx, myy);
126
}
127
} else {
128
if (rdrCtx.doClip) {
129
// adjust clip rectangle (ymin, ymax, xmin, xmax):
130
rdrCtx.clipInvScale = adjustClipInverseDelta(rdrCtx.clipRect,
131
mxx, mxy, myx, myy);
132
}
133
return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
134
}
135
}
136
137
private static double adjustClipScale(final double[] clipRect,
138
final double mxx, final double myy)
139
{
140
// Adjust the clipping rectangle (iv_DeltaScaleFilter):
141
final double scaleY = 1.0d / myy;
142
clipRect[0] *= scaleY;
143
clipRect[1] *= scaleY;
144
145
if (clipRect[1] < clipRect[0]) {
146
double tmp = clipRect[0];
147
clipRect[0] = clipRect[1];
148
clipRect[1] = tmp;
149
}
150
151
final double scaleX = 1.0d / mxx;
152
clipRect[2] *= scaleX;
153
clipRect[3] *= scaleX;
154
155
if (clipRect[3] < clipRect[2]) {
156
double tmp = clipRect[2];
157
clipRect[2] = clipRect[3];
158
clipRect[3] = tmp;
159
}
160
161
if (MarlinConst.DO_LOG_CLIP) {
162
MarlinUtils.logInfo("clipRect (ClipScale): "
163
+ Arrays.toString(clipRect));
164
}
165
return 0.5d * (Math.abs(scaleX) + Math.abs(scaleY));
166
}
167
168
private static double adjustClipInverseDelta(final double[] clipRect,
169
final double mxx, final double mxy,
170
final double myx, final double myy)
171
{
172
// Adjust the clipping rectangle (iv_DeltaTransformFilter):
173
final double det = mxx * myy - mxy * myx;
174
final double imxx = myy / det;
175
final double imxy = -mxy / det;
176
final double imyx = -myx / det;
177
final double imyy = mxx / det;
178
179
double xmin, xmax, ymin, ymax;
180
double x, y;
181
// xmin, ymin:
182
x = clipRect[2] * imxx + clipRect[0] * imxy;
183
y = clipRect[2] * imyx + clipRect[0] * imyy;
184
185
xmin = xmax = x;
186
ymin = ymax = y;
187
188
// xmax, ymin:
189
x = clipRect[3] * imxx + clipRect[0] * imxy;
190
y = clipRect[3] * imyx + clipRect[0] * imyy;
191
192
if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
193
if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
194
195
// xmin, ymax:
196
x = clipRect[2] * imxx + clipRect[1] * imxy;
197
y = clipRect[2] * imyx + clipRect[1] * imyy;
198
199
if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
200
if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
201
202
// xmax, ymax:
203
x = clipRect[3] * imxx + clipRect[1] * imxy;
204
y = clipRect[3] * imyx + clipRect[1] * imyy;
205
206
if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
207
if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
208
209
clipRect[0] = ymin;
210
clipRect[1] = ymax;
211
clipRect[2] = xmin;
212
clipRect[3] = xmax;
213
214
if (MarlinConst.DO_LOG_CLIP) {
215
MarlinUtils.logInfo("clipRect (ClipInverseDelta): "
216
+ Arrays.toString(clipRect));
217
}
218
219
final double scaleX = Math.sqrt(imxx * imxx + imxy * imxy);
220
final double scaleY = Math.sqrt(imyx * imyx + imyy * imyy);
221
222
return 0.5d * (scaleX + scaleY);
223
}
224
225
DPathConsumer2D inverseDeltaTransformConsumer(DPathConsumer2D out,
226
AffineTransform at)
227
{
228
if (at == null) {
229
return out;
230
}
231
double mxx = at.getScaleX();
232
double mxy = at.getShearX();
233
double myx = at.getShearY();
234
double myy = at.getScaleY();
235
236
if (mxy == 0.0d && myx == 0.0d) {
237
if (mxx == 1.0d && myy == 1.0d) {
238
return out;
239
} else {
240
return iv_DeltaScaleFilter.init(out, 1.0d / mxx, 1.0d / myy);
241
}
242
} else {
243
final double det = mxx * myy - mxy * myx;
244
return iv_DeltaTransformFilter.init(out,
245
myy / det,
246
-mxy / det,
247
-myx / det,
248
mxx / det);
249
}
250
}
251
252
static final class DeltaScaleFilter implements DPathConsumer2D {
253
private DPathConsumer2D out;
254
private double sx, sy;
255
256
DeltaScaleFilter() {}
257
258
DeltaScaleFilter init(DPathConsumer2D out,
259
double mxx, double myy)
260
{
261
this.out = out;
262
sx = mxx;
263
sy = myy;
264
return this; // fluent API
265
}
266
267
@Override
268
public void moveTo(double x0, double y0) {
269
out.moveTo(x0 * sx, y0 * sy);
270
}
271
272
@Override
273
public void lineTo(double x1, double y1) {
274
out.lineTo(x1 * sx, y1 * sy);
275
}
276
277
@Override
278
public void quadTo(double x1, double y1,
279
double x2, double y2)
280
{
281
out.quadTo(x1 * sx, y1 * sy,
282
x2 * sx, y2 * sy);
283
}
284
285
@Override
286
public void curveTo(double x1, double y1,
287
double x2, double y2,
288
double x3, double y3)
289
{
290
out.curveTo(x1 * sx, y1 * sy,
291
x2 * sx, y2 * sy,
292
x3 * sx, y3 * sy);
293
}
294
295
@Override
296
public void closePath() {
297
out.closePath();
298
}
299
300
@Override
301
public void pathDone() {
302
out.pathDone();
303
}
304
305
@Override
306
public long getNativeConsumer() {
307
return 0;
308
}
309
}
310
311
static final class DeltaTransformFilter implements DPathConsumer2D {
312
private DPathConsumer2D out;
313
private double mxx, mxy, myx, myy;
314
315
DeltaTransformFilter() {}
316
317
DeltaTransformFilter init(DPathConsumer2D out,
318
double mxx, double mxy,
319
double myx, double myy)
320
{
321
this.out = out;
322
this.mxx = mxx;
323
this.mxy = mxy;
324
this.myx = myx;
325
this.myy = myy;
326
return this; // fluent API
327
}
328
329
@Override
330
public void moveTo(double x0, double y0) {
331
out.moveTo(x0 * mxx + y0 * mxy,
332
x0 * myx + y0 * myy);
333
}
334
335
@Override
336
public void lineTo(double x1, double y1) {
337
out.lineTo(x1 * mxx + y1 * mxy,
338
x1 * myx + y1 * myy);
339
}
340
341
@Override
342
public void quadTo(double x1, double y1,
343
double x2, double y2)
344
{
345
out.quadTo(x1 * mxx + y1 * mxy,
346
x1 * myx + y1 * myy,
347
x2 * mxx + y2 * mxy,
348
x2 * myx + y2 * myy);
349
}
350
351
@Override
352
public void curveTo(double x1, double y1,
353
double x2, double y2,
354
double x3, double y3)
355
{
356
out.curveTo(x1 * mxx + y1 * mxy,
357
x1 * myx + y1 * myy,
358
x2 * mxx + y2 * mxy,
359
x2 * myx + y2 * myy,
360
x3 * mxx + y3 * mxy,
361
x3 * myx + y3 * myy);
362
}
363
364
@Override
365
public void closePath() {
366
out.closePath();
367
}
368
369
@Override
370
public void pathDone() {
371
out.pathDone();
372
}
373
374
@Override
375
public long getNativeConsumer() {
376
return 0;
377
}
378
}
379
380
static final class Path2DWrapper implements DPathConsumer2D {
381
private Path2D.Double p2d;
382
383
Path2DWrapper() {}
384
385
Path2DWrapper init(Path2D.Double p2d) {
386
this.p2d = p2d;
387
return this;
388
}
389
390
@Override
391
public void moveTo(double x0, double y0) {
392
p2d.moveTo(x0, y0);
393
}
394
395
@Override
396
public void lineTo(double x1, double y1) {
397
p2d.lineTo(x1, y1);
398
}
399
400
@Override
401
public void closePath() {
402
p2d.closePath();
403
}
404
405
@Override
406
public void pathDone() {}
407
408
@Override
409
public void curveTo(double x1, double y1,
410
double x2, double y2,
411
double x3, double y3)
412
{
413
p2d.curveTo(x1, y1, x2, y2, x3, y3);
414
}
415
416
@Override
417
public void quadTo(double x1, double y1, double x2, double y2) {
418
p2d.quadTo(x1, y1, x2, y2);
419
}
420
421
@Override
422
public long getNativeConsumer() {
423
throw new InternalError("Not using a native peer");
424
}
425
}
426
427
static final class ClosedPathDetector implements DPathConsumer2D {
428
429
private final RendererContext rdrCtx;
430
private final PolyStack stack;
431
432
private DPathConsumer2D out;
433
434
ClosedPathDetector(final RendererContext rdrCtx) {
435
this.rdrCtx = rdrCtx;
436
this.stack = (rdrCtx.stats != null) ?
437
new PolyStack(rdrCtx,
438
rdrCtx.stats.stat_cpd_polystack_types,
439
rdrCtx.stats.stat_cpd_polystack_curves,
440
rdrCtx.stats.hist_cpd_polystack_curves,
441
rdrCtx.stats.stat_array_cpd_polystack_curves,
442
rdrCtx.stats.stat_array_cpd_polystack_types)
443
: new PolyStack(rdrCtx);
444
}
445
446
ClosedPathDetector init(DPathConsumer2D out) {
447
this.out = out;
448
return this; // fluent API
449
}
450
451
/**
452
* Disposes this instance:
453
* clean up before reusing this instance
454
*/
455
void dispose() {
456
stack.dispose();
457
}
458
459
@Override
460
public void pathDone() {
461
// previous path is not closed:
462
finish(false);
463
out.pathDone();
464
465
// TODO: fix possible leak if exception happened
466
// Dispose this instance:
467
dispose();
468
}
469
470
@Override
471
public void closePath() {
472
// path is closed
473
finish(true);
474
out.closePath();
475
}
476
477
@Override
478
public void moveTo(double x0, double y0) {
479
// previous path is not closed:
480
finish(false);
481
out.moveTo(x0, y0);
482
}
483
484
private void finish(final boolean closed) {
485
rdrCtx.closedPath = closed;
486
stack.pullAll(out);
487
}
488
489
@Override
490
public void lineTo(double x1, double y1) {
491
stack.pushLine(x1, y1);
492
}
493
494
@Override
495
public void curveTo(double x3, double y3,
496
double x2, double y2,
497
double x1, double y1)
498
{
499
stack.pushCubic(x1, y1, x2, y2, x3, y3);
500
}
501
502
@Override
503
public void quadTo(double x2, double y2, double x1, double y1) {
504
stack.pushQuad(x1, y1, x2, y2);
505
}
506
507
@Override
508
public long getNativeConsumer() {
509
throw new InternalError("Not using a native peer");
510
}
511
}
512
513
static final class PathClipFilter implements DPathConsumer2D {
514
515
private DPathConsumer2D out;
516
517
// Bounds of the drawing region, at pixel precision.
518
private final double[] clipRect;
519
520
private final double[] corners = new double[8];
521
private boolean init_corners = false;
522
523
private final IndexStack stack;
524
525
// the current outcode of the current sub path
526
private int cOutCode = 0;
527
528
// the cumulated (and) outcode of the complete path
529
private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
530
531
private boolean outside = false;
532
533
// The starting point of the path
534
private double sx0, sy0;
535
536
// The current point (TODO stupid repeated info)
537
private double cx0, cy0;
538
539
// The current point OUTSIDE
540
private double cox0, coy0;
541
542
private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
543
private final CurveClipSplitter curveSplitter;
544
545
PathClipFilter(final RendererContext rdrCtx) {
546
this.clipRect = rdrCtx.clipRect;
547
this.curveSplitter = rdrCtx.curveClipSplitter;
548
549
this.stack = (rdrCtx.stats != null) ?
550
new IndexStack(rdrCtx,
551
rdrCtx.stats.stat_pcf_idxstack_indices,
552
rdrCtx.stats.hist_pcf_idxstack_indices,
553
rdrCtx.stats.stat_array_pcf_idxstack_indices)
554
: new IndexStack(rdrCtx);
555
}
556
557
PathClipFilter init(final DPathConsumer2D out) {
558
this.out = out;
559
560
if (MarlinConst.DO_CLIP_SUBDIVIDER) {
561
// adjust padded clip rectangle:
562
curveSplitter.init();
563
}
564
565
this.init_corners = true;
566
this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
567
568
return this; // fluent API
569
}
570
571
/**
572
* Disposes this instance:
573
* clean up before reusing this instance
574
*/
575
void dispose() {
576
stack.dispose();
577
}
578
579
private void finishPath() {
580
if (outside) {
581
// criteria: inside or totally outside ?
582
if (gOutCode == 0) {
583
finish();
584
} else {
585
this.outside = false;
586
stack.reset();
587
}
588
}
589
}
590
591
private void finish() {
592
this.outside = false;
593
594
if (!stack.isEmpty()) {
595
if (init_corners) {
596
init_corners = false;
597
598
final double[] _corners = corners;
599
final double[] _clipRect = clipRect;
600
// Top Left (0):
601
_corners[0] = _clipRect[2];
602
_corners[1] = _clipRect[0];
603
// Bottom Left (1):
604
_corners[2] = _clipRect[2];
605
_corners[3] = _clipRect[1];
606
// Top right (2):
607
_corners[4] = _clipRect[3];
608
_corners[5] = _clipRect[0];
609
// Bottom Right (3):
610
_corners[6] = _clipRect[3];
611
_corners[7] = _clipRect[1];
612
}
613
stack.pullAll(corners, out);
614
}
615
out.lineTo(cox0, coy0);
616
this.cx0 = cox0;
617
this.cy0 = coy0;
618
}
619
620
@Override
621
public void pathDone() {
622
finishPath();
623
624
out.pathDone();
625
626
// TODO: fix possible leak if exception happened
627
// Dispose this instance:
628
dispose();
629
}
630
631
@Override
632
public void closePath() {
633
finishPath();
634
635
out.closePath();
636
637
// back to starting point:
638
this.cOutCode = Helpers.outcode(sx0, sy0, clipRect);
639
this.cx0 = sx0;
640
this.cy0 = sy0;
641
}
642
643
@Override
644
public void moveTo(final double x0, final double y0) {
645
finishPath();
646
647
out.moveTo(x0, y0);
648
649
// update starting point:
650
this.cOutCode = Helpers.outcode(x0, y0, clipRect);
651
this.cx0 = x0;
652
this.cy0 = y0;
653
654
this.sx0 = x0;
655
this.sy0 = y0;
656
}
657
658
@Override
659
public void lineTo(final double xe, final double ye) {
660
final int outcode0 = this.cOutCode;
661
final int outcode1 = Helpers.outcode(xe, ye, clipRect);
662
663
// Should clip
664
final int orCode = (outcode0 | outcode1);
665
if (orCode != 0) {
666
final int sideCode = (outcode0 & outcode1);
667
668
// basic rejection criteria:
669
if (sideCode == 0) {
670
// overlap clip:
671
if (subdivide) {
672
// avoid reentrance
673
subdivide = false;
674
boolean ret;
675
// subdivide curve => callback with subdivided parts:
676
if (outside) {
677
ret = curveSplitter.splitLine(cox0, coy0, xe, ye,
678
orCode, this);
679
} else {
680
ret = curveSplitter.splitLine(cx0, cy0, xe, ye,
681
orCode, this);
682
}
683
// reentrance is done:
684
subdivide = true;
685
if (ret) {
686
return;
687
}
688
}
689
// already subdivided so render it
690
} else {
691
this.cOutCode = outcode1;
692
this.gOutCode &= sideCode;
693
// keep last point coordinate before entering the clip again:
694
this.outside = true;
695
this.cox0 = xe;
696
this.coy0 = ye;
697
698
clip(sideCode, outcode0, outcode1);
699
return;
700
}
701
}
702
703
this.cOutCode = outcode1;
704
this.gOutCode = 0;
705
706
if (outside) {
707
finish();
708
}
709
// clipping disabled:
710
out.lineTo(xe, ye);
711
this.cx0 = xe;
712
this.cy0 = ye;
713
}
714
715
private void clip(final int sideCode,
716
final int outcode0,
717
final int outcode1)
718
{
719
// corner or cross-boundary on left or right side:
720
if ((outcode0 != outcode1)
721
&& ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))
722
{
723
// combine outcodes:
724
final int mergeCode = (outcode0 | outcode1);
725
final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
726
final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
727
final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
728
729
// add corners to outside stack:
730
switch (tbCode) {
731
case MarlinConst.OUTCODE_TOP:
732
stack.push(off); // top
733
return;
734
case MarlinConst.OUTCODE_BOTTOM:
735
stack.push(off + 1); // bottom
736
return;
737
default:
738
// both TOP / BOTTOM:
739
if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
740
// top to bottom
741
stack.push(off); // top
742
stack.push(off + 1); // bottom
743
} else {
744
// bottom to top
745
stack.push(off + 1); // bottom
746
stack.push(off); // top
747
}
748
}
749
}
750
}
751
752
@Override
753
public void curveTo(final double x1, final double y1,
754
final double x2, final double y2,
755
final double xe, final double ye)
756
{
757
final int outcode0 = this.cOutCode;
758
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
759
final int outcode2 = Helpers.outcode(x2, y2, clipRect);
760
final int outcode3 = Helpers.outcode(xe, ye, clipRect);
761
762
// Should clip
763
final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
764
if (orCode != 0) {
765
final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
766
767
// basic rejection criteria:
768
if (sideCode == 0) {
769
// overlap clip:
770
if (subdivide) {
771
// avoid reentrance
772
subdivide = false;
773
// subdivide curve => callback with subdivided parts:
774
boolean ret;
775
if (outside) {
776
ret = curveSplitter.splitCurve(cox0, coy0, x1, y1,
777
x2, y2, xe, ye,
778
orCode, this);
779
} else {
780
ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
781
x2, y2, xe, ye,
782
orCode, this);
783
}
784
// reentrance is done:
785
subdivide = true;
786
if (ret) {
787
return;
788
}
789
}
790
// already subdivided so render it
791
} else {
792
this.cOutCode = outcode3;
793
this.gOutCode &= sideCode;
794
// keep last point coordinate before entering the clip again:
795
this.outside = true;
796
this.cox0 = xe;
797
this.coy0 = ye;
798
799
clip(sideCode, outcode0, outcode3);
800
return;
801
}
802
}
803
804
this.cOutCode = outcode3;
805
this.gOutCode = 0;
806
807
if (outside) {
808
finish();
809
}
810
// clipping disabled:
811
out.curveTo(x1, y1, x2, y2, xe, ye);
812
this.cx0 = xe;
813
this.cy0 = ye;
814
}
815
816
@Override
817
public void quadTo(final double x1, final double y1,
818
final double xe, final double ye)
819
{
820
final int outcode0 = this.cOutCode;
821
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
822
final int outcode2 = Helpers.outcode(xe, ye, clipRect);
823
824
// Should clip
825
final int orCode = (outcode0 | outcode1 | outcode2);
826
if (orCode != 0) {
827
final int sideCode = outcode0 & outcode1 & outcode2;
828
829
// basic rejection criteria:
830
if (sideCode == 0) {
831
// overlap clip:
832
if (subdivide) {
833
// avoid reentrance
834
subdivide = false;
835
// subdivide curve => callback with subdivided parts:
836
boolean ret;
837
if (outside) {
838
ret = curveSplitter.splitQuad(cox0, coy0, x1, y1,
839
xe, ye, orCode, this);
840
} else {
841
ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
842
xe, ye, orCode, this);
843
}
844
// reentrance is done:
845
subdivide = true;
846
if (ret) {
847
return;
848
}
849
}
850
// already subdivided so render it
851
} else {
852
this.cOutCode = outcode2;
853
this.gOutCode &= sideCode;
854
// keep last point coordinate before entering the clip again:
855
this.outside = true;
856
this.cox0 = xe;
857
this.coy0 = ye;
858
859
clip(sideCode, outcode0, outcode2);
860
return;
861
}
862
}
863
864
this.cOutCode = outcode2;
865
this.gOutCode = 0;
866
867
if (outside) {
868
finish();
869
}
870
// clipping disabled:
871
out.quadTo(x1, y1, xe, ye);
872
this.cx0 = xe;
873
this.cy0 = ye;
874
}
875
876
@Override
877
public long getNativeConsumer() {
878
throw new InternalError("Not using a native peer");
879
}
880
}
881
882
static final class CurveClipSplitter {
883
884
static final double LEN_TH = MarlinProperties.getSubdividerMinLength();
885
static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0d);
886
887
private static final boolean TRACE = false;
888
889
private static final int MAX_N_CURVES = 3 * 4;
890
891
private final RendererContext rdrCtx;
892
893
// scaled length threshold:
894
private double minLength;
895
896
// clip rectangle (ymin, ymax, xmin, xmax):
897
final double[] clipRect;
898
899
// clip rectangle (ymin, ymax, xmin, xmax) including padding:
900
final double[] clipRectPad = new double[4];
901
private boolean init_clipRectPad = false;
902
903
// This is where the curve to be processed is put. We give it
904
// enough room to store all curves.
905
final double[] middle = new double[MAX_N_CURVES * 8 + 2];
906
// t values at subdivision points
907
private final double[] subdivTs = new double[MAX_N_CURVES];
908
909
// dirty curve
910
private final Curve curve;
911
912
CurveClipSplitter(final RendererContext rdrCtx) {
913
this.rdrCtx = rdrCtx;
914
this.clipRect = rdrCtx.clipRect;
915
this.curve = rdrCtx.curve;
916
}
917
918
void init() {
919
this.init_clipRectPad = true;
920
921
if (DO_CHECK_LENGTH) {
922
this.minLength = (this.rdrCtx.clipInvScale == 0.0d) ? LEN_TH
923
: (LEN_TH * this.rdrCtx.clipInvScale);
924
925
if (MarlinConst.DO_LOG_CLIP) {
926
MarlinUtils.logInfo("CurveClipSplitter.minLength = "
927
+ minLength);
928
}
929
}
930
}
931
932
private void initPaddedClip() {
933
// bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
934
// adjust padded clip rectangle (ymin, ymax, xmin, xmax):
935
// add a rounding error (curve subdivision ~ 0.1px):
936
final double[] _clipRect = clipRect;
937
final double[] _clipRectPad = clipRectPad;
938
939
_clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING;
940
_clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING;
941
_clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING;
942
_clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING;
943
944
if (TRACE) {
945
MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] "
946
+ "Y [" + _clipRectPad[0] + " .. " + _clipRectPad[1] +"]");
947
}
948
}
949
950
boolean splitLine(final double x0, final double y0,
951
final double x1, final double y1,
952
final int outCodeOR,
953
final DPathConsumer2D out)
954
{
955
if (TRACE) {
956
MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");
957
}
958
959
if (DO_CHECK_LENGTH && Helpers.fastLineLen(x0, y0, x1, y1) <= minLength) {
960
return false;
961
}
962
963
final double[] mid = middle;
964
mid[0] = x0; mid[1] = y0;
965
mid[2] = x1; mid[3] = y1;
966
967
return subdivideAtIntersections(4, outCodeOR, out);
968
}
969
970
boolean splitQuad(final double x0, final double y0,
971
final double x1, final double y1,
972
final double x2, final double y2,
973
final int outCodeOR,
974
final DPathConsumer2D out)
975
{
976
if (TRACE) {
977
MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")");
978
}
979
980
if (DO_CHECK_LENGTH && Helpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= minLength) {
981
return false;
982
}
983
984
final double[] mid = middle;
985
mid[0] = x0; mid[1] = y0;
986
mid[2] = x1; mid[3] = y1;
987
mid[4] = x2; mid[5] = y2;
988
989
return subdivideAtIntersections(6, outCodeOR, out);
990
}
991
992
boolean splitCurve(final double x0, final double y0,
993
final double x1, final double y1,
994
final double x2, final double y2,
995
final double x3, final double y3,
996
final int outCodeOR,
997
final DPathConsumer2D out)
998
{
999
if (TRACE) {
1000
MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")");
1001
}
1002
1003
if (DO_CHECK_LENGTH && Helpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= minLength) {
1004
return false;
1005
}
1006
1007
final double[] mid = middle;
1008
mid[0] = x0; mid[1] = y0;
1009
mid[2] = x1; mid[3] = y1;
1010
mid[4] = x2; mid[5] = y2;
1011
mid[6] = x3; mid[7] = y3;
1012
1013
return subdivideAtIntersections(8, outCodeOR, out);
1014
}
1015
1016
private boolean subdivideAtIntersections(final int type, final int outCodeOR,
1017
final DPathConsumer2D out)
1018
{
1019
final double[] mid = middle;
1020
final double[] subTs = subdivTs;
1021
1022
if (init_clipRectPad) {
1023
init_clipRectPad = false;
1024
initPaddedClip();
1025
}
1026
1027
final int nSplits = Helpers.findClipPoints(curve, mid, subTs, type,
1028
outCodeOR, clipRectPad);
1029
1030
if (TRACE) {
1031
MarlinUtils.logInfo("nSplits: " + nSplits);
1032
MarlinUtils.logInfo("subTs: " + Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits)));
1033
}
1034
if (nSplits == 0) {
1035
// only curve support shortcut
1036
return false;
1037
}
1038
double prevT = 0.0d;
1039
1040
for (int i = 0, off = 0; i < nSplits; i++, off += type) {
1041
final double t = subTs[i];
1042
1043
Helpers.subdivideAt((t - prevT) / (1.0d - prevT),
1044
mid, off, mid, off, type);
1045
prevT = t;
1046
}
1047
1048
for (int i = 0, off = 0; i <= nSplits; i++, off += type) {
1049
if (TRACE) {
1050
MarlinUtils.logInfo("Part Curve " + Arrays.toString(Arrays.copyOfRange(mid, off, off + type)));
1051
}
1052
emitCurrent(type, mid, off, out);
1053
}
1054
return true;
1055
}
1056
1057
static void emitCurrent(final int type, final double[] pts,
1058
final int off, final DPathConsumer2D out)
1059
{
1060
// if instead of switch (perf + most probable cases first)
1061
if (type == 8) {
1062
out.curveTo(pts[off + 2], pts[off + 3],
1063
pts[off + 4], pts[off + 5],
1064
pts[off + 6], pts[off + 7]);
1065
} else if (type == 4) {
1066
out.lineTo(pts[off + 2], pts[off + 3]);
1067
} else {
1068
out.quadTo(pts[off + 2], pts[off + 3],
1069
pts[off + 4], pts[off + 5]);
1070
}
1071
}
1072
}
1073
1074
static final class CurveBasicMonotonizer {
1075
1076
private static final int MAX_N_CURVES = 11;
1077
1078
// squared half line width (for stroker)
1079
private double lw2;
1080
1081
// number of splitted curves
1082
int nbSplits;
1083
1084
// This is where the curve to be processed is put. We give it
1085
// enough room to store all curves.
1086
final double[] middle = new double[MAX_N_CURVES * 6 + 2];
1087
// t values at subdivision points
1088
private final double[] subdivTs = new double[MAX_N_CURVES - 1];
1089
1090
// dirty curve
1091
private final Curve curve;
1092
1093
CurveBasicMonotonizer(final RendererContext rdrCtx) {
1094
this.curve = rdrCtx.curve;
1095
}
1096
1097
void init(final double lineWidth) {
1098
this.lw2 = (lineWidth * lineWidth) / 4.0d;
1099
}
1100
1101
CurveBasicMonotonizer curve(final double x0, final double y0,
1102
final double x1, final double y1,
1103
final double x2, final double y2,
1104
final double x3, final double y3)
1105
{
1106
final double[] mid = middle;
1107
mid[0] = x0; mid[1] = y0;
1108
mid[2] = x1; mid[3] = y1;
1109
mid[4] = x2; mid[5] = y2;
1110
mid[6] = x3; mid[7] = y3;
1111
1112
final double[] subTs = subdivTs;
1113
final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 8, lw2);
1114
1115
double prevT = 0.0d;
1116
for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1117
final double t = subTs[i];
1118
1119
Helpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),
1120
mid, off, mid, off, off + 6);
1121
prevT = t;
1122
}
1123
1124
this.nbSplits = nSplits;
1125
return this;
1126
}
1127
1128
CurveBasicMonotonizer quad(final double x0, final double y0,
1129
final double x1, final double y1,
1130
final double x2, final double y2)
1131
{
1132
final double[] mid = middle;
1133
mid[0] = x0; mid[1] = y0;
1134
mid[2] = x1; mid[3] = y1;
1135
mid[4] = x2; mid[5] = y2;
1136
1137
final double[] subTs = subdivTs;
1138
final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 6, lw2);
1139
1140
double prevt = 0.0d;
1141
for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1142
final double t = subTs[i];
1143
Helpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
1144
mid, off, mid, off, off + 4);
1145
prevt = t;
1146
}
1147
1148
this.nbSplits = nSplits;
1149
return this;
1150
}
1151
}
1152
1153
static final class PathTracer implements DPathConsumer2D {
1154
private final String prefix;
1155
private DPathConsumer2D out;
1156
1157
PathTracer(String name) {
1158
this.prefix = name + ": ";
1159
}
1160
1161
PathTracer init(DPathConsumer2D out) {
1162
this.out = out;
1163
return this; // fluent API
1164
}
1165
1166
@Override
1167
public void moveTo(double x0, double y0) {
1168
log("p.moveTo(" + x0 + ", " + y0 + ");");
1169
out.moveTo(x0, y0);
1170
}
1171
1172
@Override
1173
public void lineTo(double x1, double y1) {
1174
log("p.lineTo(" + x1 + ", " + y1 + ");");
1175
out.lineTo(x1, y1);
1176
}
1177
1178
@Override
1179
public void curveTo(double x1, double y1,
1180
double x2, double y2,
1181
double x3, double y3)
1182
{
1183
log("p.curveTo(" + x1 + ", " + y1 + ", " + x2 + ", " + y2 + ", " + x3 + ", " + y3 + ");");
1184
out.curveTo(x1, y1, x2, y2, x3, y3);
1185
}
1186
1187
@Override
1188
public void quadTo(double x1, double y1,
1189
double x2, double y2) {
1190
log("p.quadTo(" + x1 + ", " + y1 + ", " + x2 + ", " + y2 + ");");
1191
out.quadTo(x1, y1, x2, y2);
1192
}
1193
1194
@Override
1195
public void closePath() {
1196
log("p.closePath();");
1197
out.closePath();
1198
}
1199
1200
@Override
1201
public void pathDone() {
1202
log("p.pathDone();");
1203
out.pathDone();
1204
}
1205
1206
private void log(final String message) {
1207
MarlinUtils.logInfo(prefix + message);
1208
}
1209
1210
@Override
1211
public long getNativeConsumer() {
1212
throw new InternalError("Not using a native peer");
1213
}
1214
}
1215
}
1216
1217