< prev index next >

## modules/javafx.graphics/src/main/java/com/sun/marlin/DStroker.java

```*** 1,7 ****
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
--- 1,7 ----
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
*** 25,57 ****

package com.sun.marlin;

import java.util.Arrays;
import com.sun.marlin.DHelpers.PolyStack;

// TODO: some of the arithmetic here is too verbose and prone to hard to
// debug typos. We should consider making a small Point/Vector class that
// has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
public final class DStroker implements DPathConsumer2D, MarlinConst {

private static final int MOVE_TO = 0;
private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
private static final int CLOSE = 2;

!     // pisces used to use fixed point arithmetic with 16 decimal digits. I
!     // didn't want to change the values of the constant below when I converted
!     // it to floating point, so that's why the divisions by 2^16 are there.
!     private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d;

// kappa = (4/3) * (SQRT(2) - 1)
private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);

// SQRT(2)
private static final double SQRT_2 = Math.sqrt(2.0d);

-     private static final int MAX_N_CURVES = 11;
-
private DPathConsumer2D out;

private int capStyle;
private int joinStyle;

--- 25,56 ----

package com.sun.marlin;

import java.util.Arrays;
import com.sun.marlin.DHelpers.PolyStack;
+ import com.sun.marlin.DTransformingPathConsumer2D.CurveBasicMonotonizer;
+ import com.sun.marlin.DTransformingPathConsumer2D.CurveClipSplitter;

// TODO: some of the arithmetic here is too verbose and prone to hard to
// debug typos. We should consider making a small Point/Vector class that
// has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
public final class DStroker implements DPathConsumer2D, MarlinConst {

private static final int MOVE_TO = 0;
private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
private static final int CLOSE = 2;

!     // round join threshold = 1 subpixel
!     private static final double ERR_JOIN = (1.0f / MIN_SUBPIXELS);
!     private static final double ROUND_JOIN_THRESHOLD = ERR_JOIN * ERR_JOIN;

// kappa = (4/3) * (SQRT(2) - 1)
private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);

// SQRT(2)
private static final double SQRT_2 = Math.sqrt(2.0d);

private DPathConsumer2D out;

private int capStyle;
private int joinStyle;

*** 78,93 ****
// would be error prone and hard to read, so we keep these anyway.
private double smx, smy, cmx, cmy;

private final PolyStack reverse;

-     // This is where the curve to be processed is put. We give it
-     // enough room to store all curves.
-     private final double[] middle = new double[MAX_N_CURVES * 6 + 2];
private final double[] lp = new double[8];
private final double[] rp = new double[8];
-     private final double[] subdivTs = new double[MAX_N_CURVES - 1];

final DRendererContext rdrCtx;

// dirty curve
--- 77,88 ----
*** 104,113 ****
--- 99,113 ----

// flag indicating if the path is opened (clipped)
private boolean opened = false;
// flag indicating if the starting point's cap is done
private boolean capStart = false;
+     // flag indicating to monotonize curves
+     private boolean monotonize;
+
+     private boolean subdivide = false;
+     private final CurveClipSplitter curveSplitter;

/**
* Constructs a <code>DStroker</code>.
* @param rdrCtx per-thread renderer context
*/
*** 122,131 ****
--- 122,132 ----
rdrCtx.stats.stat_array_str_polystack_curves,
rdrCtx.stats.stat_array_str_polystack_types)
: new PolyStack(rdrCtx);

this.curve = rdrCtx.curve;
+         this.curveSplitter = rdrCtx.curveClipSplitter;
}

/**
* Inits the <code>DStroker</code>.
*
*** 139,163 ****
* <code>JOIN_BEVEL</code>.
* @param miterLimit the desired miter limit
* @param scale scaling factor applied to clip boundaries
* @param rdrOffX renderer's coordinate offset on X axis
* @param rdrOffY renderer's coordinate offset on Y axis
* @return this instance
*/
public DStroker init(final DPathConsumer2D pc2d,
final double lineWidth,
final int capStyle,
final int joinStyle,
final double miterLimit,
final double scale,
double rdrOffX,
!                          double rdrOffY)
{
this.out = pc2d;

this.lineWidth2 = lineWidth / 2.0d;
this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2);
this.capStyle = capStyle;
this.joinStyle = joinStyle;

final double limit = miterLimit * lineWidth2;
this.miterLimitSq = limit * limit;
--- 140,168 ----
* <code>JOIN_BEVEL</code>.
* @param miterLimit the desired miter limit
* @param scale scaling factor applied to clip boundaries
* @param rdrOffX renderer's coordinate offset on X axis
* @param rdrOffY renderer's coordinate offset on Y axis
+      * @param subdivideCurves true to indicate to subdivide curves, false if dasher does
* @return this instance
*/
public DStroker init(final DPathConsumer2D pc2d,
final double lineWidth,
final int capStyle,
final int joinStyle,
final double miterLimit,
final double scale,
double rdrOffX,
!                          double rdrOffY,
!                          final boolean subdivideCurves)
{
this.out = pc2d;

this.lineWidth2 = lineWidth / 2.0d;
this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2);
+         this.monotonize = subdivideCurves;
+
this.capStyle = capStyle;
this.joinStyle = joinStyle;

final double limit = miterLimit * lineWidth2;
this.miterLimitSq = limit * limit;
*** 190,207 ****
--- 195,227 ----
_clipRect[0] -= margin - rdrOffY;
_clipRect[1] += margin + rdrOffY;
_clipRect[2] -= margin - rdrOffX;
_clipRect[3] += margin + rdrOffX;
this.clipRect = _clipRect;
+
+             // initialize curve splitter here for stroker & dasher:
+             if (DO_CLIP_SUBDIVIDER) {
+                 subdivide = subdivideCurves;
+                 curveSplitter.init();
+             } else {
+                 subdivide = false;
+             }
} else {
this.clipRect = null;
this.cOutCode = 0;
this.sOutCode = 0;
}
return this; // fluent API
}

+     public void disableClipping() {
+         this.clipRect = null;
+         this.cOutCode = 0;
+         this.sOutCode = 0;
+     }
+
/**
* Disposes this stroker:
* clean up before reusing this instance
*/
void dispose() {
*** 214,227 ****
// Force zero-fill dirty arrays:
Arrays.fill(offset0, 0.0d);
Arrays.fill(offset1, 0.0d);
Arrays.fill(offset2, 0.0d);
Arrays.fill(miter, 0.0d);
-             Arrays.fill(middle, 0.0d);
Arrays.fill(lp, 0.0d);
Arrays.fill(rp, 0.0d);
-             Arrays.fill(subdivTs, 0.0d);
}
}

private static void computeOffset(final double lx, final double ly,
final double w, final double[] m)
--- 234,245 ----
*** 249,281 ****
final double dx2, final double dy2)
{
return dx1 * dy2 <= dy1 * dx2;
}

!     private void drawRoundJoin(double x, double y,
!                                double omx, double omy, double mx, double my,
!                                boolean rev,
!                                double threshold)
{
if ((omx == 0.0d && omy == 0.0d) || (mx == 0.0d && my == 0.0d)) {
return;
}

!         double domx = omx - mx;
!         double domy = omy - my;
!         double len = domx*domx + domy*domy;
!         if (len < threshold) {
return;
}

if (rev) {
omx = -omx;
omy = -omy;
mx  = -mx;
my  = -my;
}
!         drawRoundJoin(x, y, omx, omy, mx, my, rev);
}

private void drawRoundJoin(double cx, double cy,
double omx, double omy,
double mx, double my,
--- 267,300 ----
final double dx2, final double dy2)
{
return dx1 * dy2 <= dy1 * dx2;
}

!     private void mayDrawRoundJoin(double cx, double cy,
!                                   double omx, double omy,
!                                   double mx, double my,
!                                   boolean rev)
{
if ((omx == 0.0d && omy == 0.0d) || (mx == 0.0d && my == 0.0d)) {
return;
}

!         final double domx = omx - mx;
!         final double domy = omy - my;
!         final double lenSq = domx*domx + domy*domy;
!
!         if (lenSq < ROUND_JOIN_THRESHOLD) {
return;
}

if (rev) {
omx = -omx;
omy = -omy;
mx  = -mx;
my  = -my;
}
!         drawRoundJoin(cx, cy, omx, omy, mx, my, rev);
}

private void drawRoundJoin(double cx, double cy,
double omx, double omy,
double mx, double my,
*** 286,302 ****
// (ext is the angle between omx,omy and mx,my).
final double cosext = omx * mx + omy * my;
// If it is >=0, we know that abs(ext) is <= 90 degrees, so we only
// need 1 curve to approximate the circle section that joins omx,omy
// and mx,my.
!         final int numCurves = (cosext >= 0.0d) ? 1 : 2;
!
!         switch (numCurves) {
!         case 1:
drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev);
!             break;
!         case 2:
// we need to split the arc into 2 arcs spanning the same angle.
// The point we want will be one of the 2 intersections of the
// perpendicular bisector of the chord (omx,omy)->(mx,my) and the
// circle. We could find this by scaling the vector
// (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies
--- 305,317 ----
// (ext is the angle between omx,omy and mx,my).
final double cosext = omx * mx + omy * my;
// If it is >=0, we know that abs(ext) is <= 90 degrees, so we only
// need 1 curve to approximate the circle section that joins omx,omy
// and mx,my.
!         if (cosext >= 0.0d) {
drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev);
!         } else {
// we need to split the arc into 2 arcs spanning the same angle.
// The point we want will be one of the 2 intersections of the
// perpendicular bisector of the chord (omx,omy)->(mx,my) and the
// circle. We could find this by scaling the vector
// (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies
*** 321,332 ****
mmx = -mmx;
mmy = -mmy;
}
drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev);
drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev);
-             break;
-         default:
}
}

// the input arc defined by omx,omy and mx,my must span <= 90 degrees.
private void drawBezApproxForArc(final double cx, final double cy,
--- 336,345 ----
*** 382,392 ****
// and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
private static void computeMiter(final double x0, final double y0,
final double x1, final double y1,
final double x0p, final double y0p,
final double x1p, final double y1p,
!                                      final double[] m, int off)
{
double x10 = x1 - x0;
double y10 = y1 - y0;
double x10p = x1p - x0p;
double y10p = y1p - y0p;
--- 395,405 ----
// and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
private static void computeMiter(final double x0, final double y0,
final double x1, final double y1,
final double x0p, final double y0p,
final double x1p, final double y1p,
!                                      final double[] m)
{
double x10 = x1 - x0;
double y10 = y1 - y0;
double x10p = x1p - x0p;
double y10p = y1p - y0p;
*** 401,421 ****
// (mx == omx && my == omy) will be true, and drawMiter will return
// immediately).
double den = x10*y10p - x10p*y10;
double t = x10p*(y0-y0p) - y10p*(x0-x0p);
t /= den;
!         m[off++] = x0 + t*x10;
!         m[off]   = y0 + t*y10;
}

// Return the intersection point of the lines (x0, y0) -> (x1, y1)
// and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
private static void safeComputeMiter(final double x0, final double y0,
final double x1, final double y1,
final double x0p, final double y0p,
final double x1p, final double y1p,
!                                          final double[] m, int off)
{
double x10 = x1 - x0;
double y10 = y1 - y0;
double x10p = x1p - x0p;
double y10p = y1p - y0p;
--- 414,434 ----
// (mx == omx && my == omy) will be true, and drawMiter will return
// immediately).
double den = x10*y10p - x10p*y10;
double t = x10p*(y0-y0p) - y10p*(x0-x0p);
t /= den;
!         m[0] = x0 + t*x10;
!         m[1] = y0 + t*y10;
}

// Return the intersection point of the lines (x0, y0) -> (x1, y1)
// and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
private static void safeComputeMiter(final double x0, final double y0,
final double x1, final double y1,
final double x0p, final double y0p,
final double x1p, final double y1p,
!                                          final double[] m)
{
double x10 = x1 - x0;
double y10 = y1 - y0;
double x10p = x1p - x0p;
double y10p = y1p - y0p;
*** 429,452 ****
// miter drawing because it won't be called by drawMiter (because
// (mx == omx && my == omy) will be true, and drawMiter will return
// immediately).
double den = x10*y10p - x10p*y10;
if (den == 0.0d) {
!             m[off++] = (x0 + x0p) / 2.0d;
!             m[off]   = (y0 + y0p) / 2.0d;
!             return;
}
-         double t = x10p*(y0-y0p) - y10p*(x0-x0p);
-         t /= den;
-         m[off++] = x0 + t*x10;
-         m[off] = y0 + t*y10;
}

private void drawMiter(final double pdx, final double pdy,
final double x0, final double y0,
final double dx, final double dy,
!                            double omx, double omy, double mx, double my,
boolean rev)
{
if ((mx == omx && my == omy) ||
(pdx == 0.0d && pdy == 0.0d) ||
(dx == 0.0d && dy == 0.0d))
--- 442,466 ----
// miter drawing because it won't be called by drawMiter (because
// (mx == omx && my == omy) will be true, and drawMiter will return
// immediately).
double den = x10*y10p - x10p*y10;
if (den == 0.0d) {
!             m[2] = (x0 + x0p) / 2.0d;
!             m[3] = (y0 + y0p) / 2.0d;
!         } else {
!             double t = x10p*(y0-y0p) - y10p*(x0-x0p);
!             t /= den;
!             m[2] = x0 + t*x10;
!             m[3] = y0 + t*y10;
}
}

private void drawMiter(final double pdx, final double pdy,
final double x0, final double y0,
final double dx, final double dy,
!                            double omx, double omy,
!                            double mx, double my,
boolean rev)
{
if ((mx == omx && my == omy) ||
(pdx == 0.0d && pdy == 0.0d) ||
(dx == 0.0d && dy == 0.0d))
*** 460,471 ****
mx  = -mx;
my  = -my;
}

computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
!                      (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
!                      miter, 0);

final double miterX = miter[0];
final double miterY = miter[1];
double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);

--- 474,484 ----
mx  = -mx;
my  = -my;
}

computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
!                      (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, miter);

final double miterX = miter[0];
final double miterY = miter[1];
double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);

*** 479,489 ****
}
}

@Override
public void moveTo(final double x0, final double y0) {
!         moveTo(x0, y0, cOutCode);
// update starting point:
this.sx0 = x0;
this.sy0 = y0;
this.sdx = 1.0d;
this.sdy = 0.0d;
--- 492,502 ----
}
}

@Override
public void moveTo(final double x0, final double y0) {
!         _moveTo(x0, y0, cOutCode);
// update starting point:
this.sx0 = x0;
this.sy0 = y0;
this.sdx = 1.0d;
this.sdy = 0.0d;
*** 495,505 ****
this.cOutCode = outcode;
this.sOutCode = outcode;
}
}

!     private void moveTo(final double x0, final double y0,
final int outcode)
{
if (prev == MOVE_TO) {
this.cx0 = x0;
this.cy0 = y0;
--- 508,518 ----
this.cOutCode = outcode;
this.sOutCode = outcode;
}
}

!     private void _moveTo(final double x0, final double y0,
final int outcode)
{
if (prev == MOVE_TO) {
this.cx0 = x0;
this.cy0 = y0;
*** 522,541 ****

private void lineTo(final double x1, final double y1,
final boolean force)
{
final int outcode0 = this.cOutCode;
if (!force && clipRect != null) {
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
-             this.cOutCode = outcode1;

!             // basic rejection criteria
!             if ((outcode0 & outcode1) != 0) {
!                 moveTo(x1, y1, outcode0);
!                 opened = true;
!                 return;
}
}

double dx = x1 - cx0;
double dy = y1 - cy0;
if (dx == 0.0d && dy == 0.0d) {
--- 535,578 ----

private void lineTo(final double x1, final double y1,
final boolean force)
{
final int outcode0 = this.cOutCode;
+
if (!force && clipRect != null) {
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);

!             // Should clip
!             final int orCode = (outcode0 | outcode1);
!             if (orCode != 0) {
!                 final int sideCode = outcode0 & outcode1;
!
!                 // basic rejection criteria:
!                 if (sideCode == 0) {
!                     // ovelap clip:
!                     if (subdivide) {
!                         // avoid reentrance
!                         subdivide = false;
!                         // subdivide curve => callback with subdivided parts:
!                         boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1,
!                                                               orCode, this);
!                         // reentrance is done:
!                         subdivide = true;
!                         if (ret) {
!                             return;
!                         }
!                     }
!                     // already subdivided so render it
!                 } else {
!                     this.cOutCode = outcode1;
!                     _moveTo(x1, y1, outcode0);
!                     opened = true;
!                     return;
!                 }
}
+
+             this.cOutCode = outcode1;
}

double dx = x1 - cx0;
double dy = y1 - cy0;
if (dx == 0.0d && dy == 0.0d) {
*** 753,811 ****
final boolean cw = isCW(pdx, pdy, dx, dy);
if (outcode == 0) {
if (joinStyle == JOIN_MITER) {
drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
} else if (joinStyle == JOIN_ROUND) {
!                     drawRoundJoin(x0, y0,
!                                   omx, omy,
!                                   mx, my, cw,
!                                   ROUND_JOIN_THRESHOLD);
}
}
emitLineTo(x0, y0, !cw);
}
prev = DRAWING_OP_TO;
}

private static boolean within(final double x1, final double y1,
final double x2, final double y2,
!                                   final double ERR)
{
!         assert ERR > 0 : "";
// compare taxicab distance. ERR will always be small, so using
// true distance won't give much benefit
!         return (DHelpers.within(x1, x2, ERR) &&  // we want to avoid calling Math.abs
!                 DHelpers.within(y1, y2, ERR)); // this is just as good.
}

!     private void getLineOffsets(double x1, double y1,
!                                 double x2, double y2,
!                                 double[] left, double[] right) {
computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0);
final double mx = offset0[0];
final double my = offset0[1];
left[0] = x1 + mx;
left[1] = y1 + my;
left[2] = x2 + mx;
left[3] = y2 + my;
right[0] = x1 - mx;
right[1] = y1 - my;
right[2] = x2 - mx;
right[3] = y2 - my;
}

!     private int computeOffsetCubic(double[] pts, final int off,
!                                    double[] leftOff, double[] rightOff)
{
// if p1=p2 or p3=p4 it means that the derivative at the endpoint
// vanishes, which creates problems with computeOffset. Usually
// this happens when this stroker object is trying to widen
// a curve with a cusp. What happens is that curveTo splits
// the input curve at the cusp, and passes it to this function.
// because of inaccuracies in the splitting, we consider points
// equal if they're very close to each other.
!         final double x1 = pts[off + 0], y1 = pts[off + 1];
final double x2 = pts[off + 2], y2 = pts[off + 3];
final double x3 = pts[off + 4], y3 = pts[off + 5];
final double x4 = pts[off + 6], y4 = pts[off + 7];

double dx4 = x4 - x3;
--- 790,848 ----
final boolean cw = isCW(pdx, pdy, dx, dy);
if (outcode == 0) {
if (joinStyle == JOIN_MITER) {
drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
} else if (joinStyle == JOIN_ROUND) {
!                     mayDrawRoundJoin(x0, y0, omx, omy, mx, my, cw);
}
}
emitLineTo(x0, y0, !cw);
}
prev = DRAWING_OP_TO;
}

private static boolean within(final double x1, final double y1,
final double x2, final double y2,
!                                   final double err)
{
!         assert err > 0 : "";
// compare taxicab distance. ERR will always be small, so using
// true distance won't give much benefit
!         return (DHelpers.within(x1, x2, err) && // we want to avoid calling Math.abs
!                 DHelpers.within(y1, y2, err));  // this is just as good.
}

!     private void getLineOffsets(final double x1, final double y1,
!                                 final double x2, final double y2,
!                                 final double[] left, final double[] right)
!     {
computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0);
final double mx = offset0[0];
final double my = offset0[1];
left[0] = x1 + mx;
left[1] = y1 + my;
left[2] = x2 + mx;
left[3] = y2 + my;
+
right[0] = x1 - mx;
right[1] = y1 - my;
right[2] = x2 - mx;
right[3] = y2 - my;
}

!     private int computeOffsetCubic(final double[] pts, final int off,
!                                    final double[] leftOff,
!                                    final double[] rightOff)
{
// if p1=p2 or p3=p4 it means that the derivative at the endpoint
// vanishes, which creates problems with computeOffset. Usually
// this happens when this stroker object is trying to widen
// a curve with a cusp. What happens is that curveTo splits
// the input curve at the cusp, and passes it to this function.
// because of inaccuracies in the splitting, we consider points
// equal if they're very close to each other.
!         final double x1 = pts[off    ], y1 = pts[off + 1];
final double x2 = pts[off + 2], y2 = pts[off + 3];
final double x3 = pts[off + 4], y3 = pts[off + 5];
final double x4 = pts[off + 6], y4 = pts[off + 7];

double dx4 = x4 - x3;
*** 815,824 ****
--- 852,862 ----

// if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
// in which case ignore if p1 == p2
final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2));
final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0d * Math.ulp(y4));
+
if (p1eqp2 && p3eqp4) {
getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
return 4;
} else if (p1eqp2) {
dx1 = x3 - x1;
*** 830,839 ****
--- 868,878 ----

// if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
double dotsq = (dx1 * dx4 + dy1 * dy4);
dotsq *= dotsq;
double l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4;
+
if (DHelpers.within(dotsq, l1sq * l4sq, 4.0d * Math.ulp(dotsq))) {
getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
return 4;
}

*** 943,956 ****
}

// compute offset curves using bezier spline through t=0.5 (i.e.
// ComputedCurve(0.5) == IdealParallelCurve(0.5))
// return the kind of curve in the right and left arrays.
!     private int computeOffsetQuad(double[] pts, final int off,
!                                   double[] leftOff, double[] rightOff)
{
!         final double x1 = pts[off + 0], y1 = pts[off + 1];
final double x2 = pts[off + 2], y2 = pts[off + 3];
final double x3 = pts[off + 4], y3 = pts[off + 5];

final double dx3 = x3 - x2;
final double dy3 = y3 - y2;
--- 982,996 ----
}

// compute offset curves using bezier spline through t=0.5 (i.e.
// ComputedCurve(0.5) == IdealParallelCurve(0.5))
// return the kind of curve in the right and left arrays.
!     private int computeOffsetQuad(final double[] pts, final int off,
!                                   final double[] leftOff,
!                                   final double[] rightOff)
{
!         final double x1 = pts[off    ], y1 = pts[off + 1];
final double x2 = pts[off + 2], y2 = pts[off + 3];
final double x3 = pts[off + 4], y3 = pts[off + 5];

final double dx3 = x3 - x2;
final double dy3 = y3 - y2;
*** 967,985 ****
--- 1007,1027 ----

// if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
// in which case ignore.
final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2));
final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0d * Math.ulp(y3));
+
if (p1eqp2 || p2eqp3) {
getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
return 4;
}

// if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
double dotsq = (dx1 * dx3 + dy1 * dy3);
dotsq *= dotsq;
double l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3;
+
if (DHelpers.within(dotsq, l1sq * l3sq, 4.0d * Math.ulp(dotsq))) {
getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
return 4;
}

*** 991,1163 ****

double x1p = x1 + offset0[0]; // start
double y1p = y1 + offset0[1]; // point
double x3p = x3 + offset1[0]; // end
double y3p = y3 + offset1[1]; // point
!         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2);
leftOff[0] = x1p; leftOff[1] = y1p;
leftOff[4] = x3p; leftOff[5] = y3p;

x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
x3p = x3 - offset1[0]; y3p = y3 - offset1[1];
!         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2);
rightOff[0] = x1p; rightOff[1] = y1p;
rightOff[4] = x3p; rightOff[5] = y3p;
return 6;
}

-     // finds values of t where the curve in pts should be subdivided in order
-     // to get good offset curves a distance of w away from the middle curve.
-     // Stores the points in ts, and returns how many of them there were.
-     private static int findSubdivPoints(final DCurve c, double[] pts, double[] ts,
-                                         final int type, final double w)
-     {
-         final double x12 = pts[2] - pts[0];
-         final double y12 = pts[3] - pts[1];
-         // if the curve is already parallel to either axis we gain nothing
-         // from rotating it.
-         if (y12 != 0.0d && x12 != 0.0d) {
-             // we rotate it so that the first vector in the control polygon is
-             // parallel to the x-axis. This will ensure that rotated quarter
-             // circles won't be subdivided.
-             final double hypot = Math.sqrt(x12 * x12 + y12 * y12);
-             final double cos = x12 / hypot;
-             final double sin = y12 / hypot;
-             final double x1 = cos * pts[0] + sin * pts[1];
-             final double y1 = cos * pts[1] - sin * pts[0];
-             final double x2 = cos * pts[2] + sin * pts[3];
-             final double y2 = cos * pts[3] - sin * pts[2];
-             final double x3 = cos * pts[4] + sin * pts[5];
-             final double y3 = cos * pts[5] - sin * pts[4];
-
-             switch(type) {
-             case 8:
-                 final double x4 = cos * pts[6] + sin * pts[7];
-                 final double y4 = cos * pts[7] - sin * pts[6];
-                 c.set(x1, y1, x2, y2, x3, y3, x4, y4);
-                 break;
-             case 6:
-                 c.set(x1, y1, x2, y2, x3, y3);
-                 break;
-             default:
-             }
-         } else {
-             c.set(pts, type);
-         }
-
-         int ret = 0;
-         // we subdivide at values of t such that the remaining rotated
-         // curves are monotonic in x and y.
-         ret += c.dxRoots(ts, ret);
-         ret += c.dyRoots(ts, ret);
-         // subdivide at inflection points.
-         if (type == 8) {
-             // quadratic curves can't have inflection points
-             ret += c.infPoints(ts, ret);
-         }
-
-         // now we must subdivide at points where one of the offset curves will have
-         // a cusp. This happens at ts where the radius of curvature is equal to w.
-         ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001d);
-
-         ret = DHelpers.filterOutNotInAB(ts, 0, ret, 0.0001d, 0.9999d);
-         DHelpers.isort(ts, 0, ret);
-         return ret;
-     }
-
@Override
public void curveTo(final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3)
{
final int outcode0 = this.cOutCode;
if (clipRect != null) {
final int outcode3 = DHelpers.outcode(x3, y3, clipRect);
-             this.cOutCode = outcode3;

!             if ((outcode0 & outcode3) != 0) {
!                 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
!                 final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
!
!                 // basic rejection criteria
!                 if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
!                     moveTo(x3, y3, outcode0);
opened = true;
return;
}
}
-         }

!         final double[] mid = middle;
!
!         mid[0] = cx0; mid[1] = cy0;
!         mid[2] = x1;  mid[3] = y1;
!         mid[4] = x2;  mid[5] = y2;
!         mid[6] = x3;  mid[7] = y3;

// need these so we can update the state at the end of this method
!         final double xf = x3, yf = y3;
!         double dxs = mid[2] - mid[0];
!         double dys = mid[3] - mid[1];
!         double dxf = mid[6] - mid[4];
!         double dyf = mid[7] - mid[5];
!
!         boolean p1eqp2 = (dxs == 0.0d && dys == 0.0d);
!         boolean p3eqp4 = (dxf == 0.0d && dyf == 0.0d);
!         if (p1eqp2) {
!             dxs = mid[4] - mid[0];
!             dys = mid[5] - mid[1];
!             if (dxs == 0.0d && dys == 0.0d) {
!                 dxs = mid[6] - mid[0];
!                 dys = mid[7] - mid[1];
!             }
!         }
!         if (p3eqp4) {
!             dxf = mid[6] - mid[2];
!             dyf = mid[7] - mid[3];
!             if (dxf == 0.0d && dyf == 0.0d) {
!                 dxf = mid[6] - mid[0];
!                 dyf = mid[7] - mid[1];
}
}
!         if (dxs == 0.0d && dys == 0.0d) {
// this happens if the "curve" is just a point
// fix outcode0 for lineTo() call:
if (clipRect != null) {
this.cOutCode = outcode0;
}
!             lineTo(mid[0], mid[1]);
return;
}

// if these vectors are too small, normalize them, to avoid future
// precision problems.
if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
!             double len = Math.sqrt(dxs*dxs + dys*dys);
dxs /= len;
dys /= len;
}
if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
!             double len = Math.sqrt(dxf*dxf + dyf*dyf);
dxf /= len;
dyf /= len;
}

computeOffset(dxs, dys, lineWidth2, offset0);
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);

!         final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);

!         double prevT = 0.0d;
!         for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
!             final double t = subdivTs[i];
!             DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),
!                                      mid, off, mid, off, mid, off + 6);
!             prevT = t;
!         }

!         final double[] l = lp;
final double[] r = rp;

int kind = 0;
for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
kind = computeOffsetCubic(mid, off, l, r);
--- 1033,1173 ----

double x1p = x1 + offset0[0]; // start
double y1p = y1 + offset0[1]; // point
double x3p = x3 + offset1[0]; // end
double y3p = y3 + offset1[1]; // point
!         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff);
leftOff[0] = x1p; leftOff[1] = y1p;
leftOff[4] = x3p; leftOff[5] = y3p;

x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
x3p = x3 - offset1[0]; y3p = y3 - offset1[1];
!         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff);
rightOff[0] = x1p; rightOff[1] = y1p;
rightOff[4] = x3p; rightOff[5] = y3p;
return 6;
}

@Override
public void curveTo(final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3)
{
final int outcode0 = this.cOutCode;
+
if (clipRect != null) {
+             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
+             final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
final int outcode3 = DHelpers.outcode(x3, y3, clipRect);

!             // Should clip
!             final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
!             if (orCode != 0) {
!                 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
!
!                 // basic rejection criteria:
!                 if (sideCode == 0) {
!                     // ovelap clip:
!                     if (subdivide) {
!                         // avoid reentrance
!                         subdivide = false;
!                         // subdivide curve => callback with subdivided parts:
!                         boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
!                                                                x2, y2, x3, y3,
!                                                                orCode, this);
!                         // reentrance is done:
!                         subdivide = true;
!                         if (ret) {
!                             return;
!                         }
!                     }
!                     // already subdivided so render it
!                 } else {
!                     this.cOutCode = outcode3;
!                     _moveTo(x3, y3, outcode0);
opened = true;
return;
}
}

!             this.cOutCode = outcode3;
!         }
!         _curveTo(x1, y1, x2, y2, x3, y3, outcode0);
!     }

+     private void _curveTo(final double x1, final double y1,
+                           final double x2, final double y2,
+                           final double x3, final double y3,
+                           final int outcode0)
+     {
// need these so we can update the state at the end of this method
!         double dxs = x1 - cx0;
!         double dys = y1 - cy0;
!         double dxf = x3 - x2;
!         double dyf = y3 - y2;
!
!         if ((dxs == 0.0d) && (dys == 0.0d)) {
!             dxs = x2 - cx0;
!             dys = y2 - cy0;
!             if ((dxs == 0.0d) && (dys == 0.0d)) {
!                 dxs = x3 - cx0;
!                 dys = y3 - cy0;
!             }
!         }
!         if ((dxf == 0.0d) && (dyf == 0.0d)) {
!             dxf = x3 - x1;
!             dyf = y3 - y1;
!             if ((dxf == 0.0d) && (dyf == 0.0d)) {
!                 dxf = x3 - cx0;
!                 dyf = y3 - cy0;
}
}
!         if ((dxs == 0.0d) && (dys == 0.0d)) {
// this happens if the "curve" is just a point
// fix outcode0 for lineTo() call:
if (clipRect != null) {
this.cOutCode = outcode0;
}
!             lineTo(cx0, cy0);
return;
}

// if these vectors are too small, normalize them, to avoid future
// precision problems.
if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
!             final double len = Math.sqrt(dxs * dxs + dys * dys);
dxs /= len;
dys /= len;
}
if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
!             final double len = Math.sqrt(dxf * dxf + dyf * dyf);
dxf /= len;
dyf /= len;
}

computeOffset(dxs, dys, lineWidth2, offset0);
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);

!         int nSplits = 0;
!         final double[] mid;
!         final double[] l = lp;

!         if (monotonize) {
!             // monotonize curve:
!             final CurveBasicMonotonizer monotonizer
!                 = rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3);

!             nSplits = monotonizer.nbSplits;
!             mid = monotonizer.middle;
!         } else {
!             mid = l;
!             mid[0] = cx0; mid[1] = cy0;
!             mid[2] = x1;  mid[3] = y1;
!             mid[4] = x2;  mid[5] = y2;
!             mid[6] = x3;  mid[7] = y3;
!         }
final double[] r = rp;

int kind = 0;
for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
kind = computeOffsetCubic(mid, off, l, r);
*** 1177,1188 ****
}
emitLineToRev(r[kind - 2], r[kind - 1]);
}

this.prev = DRAWING_OP_TO;
!         this.cx0 = xf;
!         this.cy0 = yf;
this.cdx = dxf;
this.cdy = dyf;
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
}
--- 1187,1198 ----
}
emitLineToRev(r[kind - 2], r[kind - 1]);
}

this.prev = DRAWING_OP_TO;
!         this.cx0 = x3;
!         this.cy0 = y3;
this.cdx = dxf;
this.cdy = dyf;
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
}
*** 1190,1267 ****
@Override
public void quadTo(final double x1, final double y1,
final double x2, final double y2)
{
final int outcode0 = this.cOutCode;
if (clipRect != null) {
final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
-             this.cOutCode = outcode2;
-
-             if ((outcode0 & outcode2) != 0) {
-                 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);

!                 // basic rejection criteria
!                 if ((outcode0 & outcode1 & outcode2) != 0) {
!                     moveTo(x2, y2, outcode0);
opened = true;
return;
}
}
-         }

!         final double[] mid = middle;
!
!         mid[0] = cx0; mid[1] = cy0;
!         mid[2] = x1;  mid[3] = y1;
!         mid[4] = x2;  mid[5] = y2;

// need these so we can update the state at the end of this method
!         final double xf = x2, yf = y2;
!         double dxs = mid[2] - mid[0];
!         double dys = mid[3] - mid[1];
!         double dxf = mid[4] - mid[2];
!         double dyf = mid[5] - mid[3];
!         if ((dxs == 0.0d && dys == 0.0d) || (dxf == 0.0d && dyf == 0.0d)) {
!             dxs = dxf = mid[4] - mid[0];
!             dys = dyf = mid[5] - mid[1];
}
!         if (dxs == 0.0d && dys == 0.0d) {
// this happens if the "curve" is just a point
// fix outcode0 for lineTo() call:
if (clipRect != null) {
this.cOutCode = outcode0;
}
!             lineTo(mid[0], mid[1]);
return;
}
// if these vectors are too small, normalize them, to avoid future
// precision problems.
if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
!             double len = Math.sqrt(dxs*dxs + dys*dys);
dxs /= len;
dys /= len;
}
if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
!             double len = Math.sqrt(dxf*dxf + dyf*dyf);
dxf /= len;
dyf /= len;
}
-
computeOffset(dxs, dys, lineWidth2, offset0);
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);

!         int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);

!         double prevt = 0.0d;
!         for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
!             final double t = subdivTs[i];
!             DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
!                                     mid, off, mid, off, mid, off + 4);
!             prevt = t;
!         }

!         final double[] l = lp;
final double[] r = rp;

int kind = 0;
for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
kind = computeOffsetQuad(mid, off, l, r);
--- 1200,1304 ----
@Override
public void quadTo(final double x1, final double y1,
final double x2, final double y2)
{
final int outcode0 = this.cOutCode;
+
if (clipRect != null) {
+             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
final int outcode2 = DHelpers.outcode(x2, y2, clipRect);

!             // Should clip
!             final int orCode = (outcode0 | outcode1 | outcode2);
!             if (orCode != 0) {
!                 final int sideCode = outcode0 & outcode1 & outcode2;
!
!                 // basic rejection criteria:
!                 if (sideCode == 0) {
!                     // ovelap clip:
!                     if (subdivide) {
!                         // avoid reentrance
!                         subdivide = false;
!                         // subdivide curve => call lineTo() with subdivided curves:
!                         boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
!                                                               x2, y2, orCode, this);
!                         // reentrance is done:
!                         subdivide = true;
!                         if (ret) {
!                             return;
!                         }
!                     }
!                     // already subdivided so render it
!                 } else {
!                     this.cOutCode = outcode2;
!                     _moveTo(x2, y2, outcode0);
opened = true;
return;
}
}

!             this.cOutCode = outcode2;
!         }
!         _quadTo(x1, y1, x2, y2, outcode0);
!     }

+     private void _quadTo(final double x1, final double y1,
+                          final double x2, final double y2,
+                          final int outcode0)
+     {
// need these so we can update the state at the end of this method
!         double dxs = x1 - cx0;
!         double dys = y1 - cy0;
!         double dxf = x2 - x1;
!         double dyf = y2 - y1;
!
!         if (((dxs == 0.0d) && (dys == 0.0d)) || ((dxf == 0.0d) && (dyf == 0.0d))) {
!             dxs = dxf = x2 - cx0;
!             dys = dyf = y2 - cy0;
}
!         if ((dxs == 0.0d) && (dys == 0.0d)) {
// this happens if the "curve" is just a point
// fix outcode0 for lineTo() call:
if (clipRect != null) {
this.cOutCode = outcode0;
}
!             lineTo(cx0, cy0);
return;
}
// if these vectors are too small, normalize them, to avoid future
// precision problems.
if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
!             final double len = Math.sqrt(dxs * dxs + dys * dys);
dxs /= len;
dys /= len;
}
if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
!             final double len = Math.sqrt(dxf * dxf + dyf * dyf);
dxf /= len;
dyf /= len;
}
computeOffset(dxs, dys, lineWidth2, offset0);
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);

!         int nSplits = 0;
!         final double[] mid;
!         final double[] l = lp;

!         if (monotonize) {
!             final CurveBasicMonotonizer monotonizer
!                 = rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2);

!             nSplits = monotonizer.nbSplits;
!             mid = monotonizer.middle;
!         } else {
!             mid = l;
!             mid[0] = cx0; mid[1] = cy0;
!             mid[2] = x1;  mid[3] = y1;
!             mid[4] = x2;  mid[5] = y2;
!         }
final double[] r = rp;

int kind = 0;
for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
kind = computeOffsetQuad(mid, off, l, r);
*** 1281,1292 ****
}
emitLineToRev(r[kind - 2], r[kind - 1]);
}

this.prev = DRAWING_OP_TO;
!         this.cx0 = xf;
!         this.cy0 = yf;
this.cdx = dxf;
this.cdy = dyf;
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
}
--- 1318,1329 ----
}
emitLineToRev(r[kind - 2], r[kind - 1]);
}

this.prev = DRAWING_OP_TO;
!         this.cx0 = x2;
!         this.cy0 = y2;
this.cdx = dxf;
this.cdy = dyf;
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
}
```
< prev index next >