package com.sun.marlin;
import com.sun.javafx.geom.Path2D;
import com.sun.javafx.geom.PathConsumer2D;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.marlin.Helpers.IndexStack;
import com.sun.marlin.Helpers.PolyStack;
import java.util.Arrays;
public final class TransformingPathConsumer2D {
static final float CLIP_RECT_PADDING = 1.0f;
private final RendererContext rdrCtx;
private final ClosedPathDetector cpDetector;
private final PathClipFilter pathClipper;
private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();
private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();
private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
private final PathTracer tracerInput = new PathTracer("[Input]");
private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
private final PathTracer tracerFiller = new PathTracer("Filler");
private final PathTracer tracerStroker = new PathTracer("Stroker");
private final PathTracer tracerDasher = new PathTracer("Dasher");
TransformingPathConsumer2D(final RendererContext rdrCtx) {
this.rdrCtx = rdrCtx;
this.cpDetector = new ClosedPathDetector(rdrCtx);
this.pathClipper = new PathClipFilter(rdrCtx);
}
public PathConsumer2D wrapPath2D(Path2D p2d) {
return wp_Path2DWrapper.init(p2d);
}
public PathConsumer2D traceInput(PathConsumer2D out) {
return tracerInput.init(out);
}
public PathConsumer2D traceClosedPathDetector(PathConsumer2D out) {
return tracerCPDetector.init(out);
}
public PathConsumer2D traceFiller(PathConsumer2D out) {
return tracerFiller.init(out);
}
public PathConsumer2D traceStroker(PathConsumer2D out) {
return tracerStroker.init(out);
}
public PathConsumer2D traceDasher(PathConsumer2D out) {
return tracerDasher.init(out);
}
public PathConsumer2D detectClosedPath(PathConsumer2D out) {
return cpDetector.init(out);
}
public PathConsumer2D pathClipper(PathConsumer2D out,
final float rdrOffX,
final float rdrOffY)
{
return pathClipper.init(out, rdrOffX, rdrOffY);
}
public PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
BaseTransform at,
final float rdrOffX,
final float rdrOffY)
{
if (at == null) {
return out;
}
final float mxx = (float) at.getMxx();
final float mxy = (float) at.getMxy();
final float myx = (float) at.getMyx();
final float myy = (float) at.getMyy();
if (mxy == 0.0f && myx == 0.0f) {
if (mxx == 1.0f && myy == 1.0f) {
return out;
} else {
if (rdrCtx.doClip) {
adjustClipOffset(rdrCtx.clipRect, rdrOffX, rdrOffY);
adjustClipScale(rdrCtx.clipRect, mxx, myy);
}
return dt_DeltaScaleFilter.init(out, mxx, myy);
}
} else {
if (rdrCtx.doClip) {
adjustClipOffset(rdrCtx.clipRect, rdrOffX, rdrOffY);
adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy);
}
return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
}
}
private static void adjustClipOffset(final float[] clipRect,
final float rdrOffX,
final float rdrOffY)
{
clipRect[0] += rdrOffY;
clipRect[1] += rdrOffY;
clipRect[2] += rdrOffX;
clipRect[3] += rdrOffX;
}
private static void adjustClipScale(final float[] clipRect,
final float mxx, final float myy)
{
clipRect[0] /= myy;
clipRect[1] /= myy;
clipRect[2] /= mxx;
clipRect[3] /= mxx;
}
private static void adjustClipInverseDelta(final float[] clipRect,
final float mxx, final float mxy,
final float myx, final float myy)
{
final float det = mxx * myy - mxy * myx;
final float imxx = myy / det;
final float imxy = -mxy / det;
final float imyx = -myx / det;
final float imyy = mxx / det;
float xmin, xmax, ymin, ymax;
float x, y;
x = clipRect[2] * imxx + clipRect[0] * imxy;
y = clipRect[2] * imyx + clipRect[0] * imyy;
xmin = xmax = x;
ymin = ymax = y;
x = clipRect[3] * imxx + clipRect[0] * imxy;
y = clipRect[3] * imyx + clipRect[0] * imyy;
if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
x = clipRect[2] * imxx + clipRect[1] * imxy;
y = clipRect[2] * imyx + clipRect[1] * imyy;
if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
x = clipRect[3] * imxx + clipRect[1] * imxy;
y = clipRect[3] * imyx + clipRect[1] * imyy;
if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
clipRect[0] = ymin;
clipRect[1] = ymax;
clipRect[2] = xmin;
clipRect[3] = xmax;
}
public PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out,
BaseTransform at)
{
if (at == null) {
return out;
}
float mxx = (float) at.getMxx();
float mxy = (float) at.getMxy();
float myx = (float) at.getMyx();
float myy = (float) at.getMyy();
if (mxy == 0.0f && myx == 0.0f) {
if (mxx == 1.0f && myy == 1.0f) {
return out;
} else {
return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy);
}
} else {
final float det = mxx * myy - mxy * myx;
return iv_DeltaTransformFilter.init(out,
myy / det,
-mxy / det,
-myx / det,
mxx / det);
}
}
static final class DeltaScaleFilter implements PathConsumer2D {
private PathConsumer2D out;
private float sx, sy;
DeltaScaleFilter() {}
DeltaScaleFilter init(PathConsumer2D out,
float mxx, float myy)
{
this.out = out;
sx = mxx;
sy = myy;
return this;
}
@Override
public void moveTo(float x0, float y0) {
out.moveTo(x0 * sx, y0 * sy);
}
@Override
public void lineTo(float x1, float y1) {
out.lineTo(x1 * sx, y1 * sy);
}
@Override
public void quadTo(float x1, float y1,
float x2, float y2)
{
out.quadTo(x1 * sx, y1 * sy,
x2 * sx, y2 * sy);
}
@Override
public void curveTo(float x1, float y1,
float x2, float y2,
float x3, float y3)
{
out.curveTo(x1 * sx, y1 * sy,
x2 * sx, y2 * sy,
x3 * sx, y3 * sy);
}
@Override
public void closePath() {
out.closePath();
}
@Override
public void pathDone() {
out.pathDone();
}
}
static final class DeltaTransformFilter implements PathConsumer2D {
private PathConsumer2D out;
private float mxx, mxy, myx, myy;
DeltaTransformFilter() {}
DeltaTransformFilter init(PathConsumer2D out,
float mxx, float mxy,
float myx, float myy)
{
this.out = out;
this.mxx = mxx;
this.mxy = mxy;
this.myx = myx;
this.myy = myy;
return this;
}
@Override
public void moveTo(float x0, float y0) {
out.moveTo(x0 * mxx + y0 * mxy,
x0 * myx + y0 * myy);
}
@Override
public void lineTo(float x1, float y1) {
out.lineTo(x1 * mxx + y1 * mxy,
x1 * myx + y1 * myy);
}
@Override
public void quadTo(float x1, float y1,
float x2, float y2)
{
out.quadTo(x1 * mxx + y1 * mxy,
x1 * myx + y1 * myy,
x2 * mxx + y2 * mxy,
x2 * myx + y2 * myy);
}
@Override
public void curveTo(float x1, float y1,
float x2, float y2,
float x3, float y3)
{
out.curveTo(x1 * mxx + y1 * mxy,
x1 * myx + y1 * myy,
x2 * mxx + y2 * mxy,
x2 * myx + y2 * myy,
x3 * mxx + y3 * mxy,
x3 * myx + y3 * myy);
}
@Override
public void closePath() {
out.closePath();
}
@Override
public void pathDone() {
out.pathDone();
}
}
static final class Path2DWrapper implements PathConsumer2D {
private Path2D p2d;
Path2DWrapper() {}
Path2DWrapper init(Path2D p2d) {
this.p2d = p2d;
return this;
}
@Override
public void moveTo(float x0, float y0) {
p2d.moveTo(x0, y0);
}
@Override
public void lineTo(float x1, float y1) {
p2d.lineTo(x1, y1);
}
@Override
public void closePath() {
p2d.closePath();
}
@Override
public void pathDone() {}
@Override
public void curveTo(float x1, float y1,
float x2, float y2,
float x3, float y3)
{
p2d.curveTo(x1, y1, x2, y2, x3, y3);
}
@Override
public void quadTo(float x1, float y1, float x2, float y2) {
p2d.quadTo(x1, y1, x2, y2);
}
}
static final class ClosedPathDetector implements PathConsumer2D {
private final RendererContext rdrCtx;
private final PolyStack stack;
private PathConsumer2D out;
ClosedPathDetector(final RendererContext rdrCtx) {
this.rdrCtx = rdrCtx;
this.stack = (rdrCtx.stats != null) ?
new PolyStack(rdrCtx,
rdrCtx.stats.stat_cpd_polystack_types,
rdrCtx.stats.stat_cpd_polystack_curves,
rdrCtx.stats.hist_cpd_polystack_curves,
rdrCtx.stats.stat_array_cpd_polystack_curves,
rdrCtx.stats.stat_array_cpd_polystack_types)
: new PolyStack(rdrCtx);
}
ClosedPathDetector init(PathConsumer2D out) {
this.out = out;
return this;
}
void dispose() {
stack.dispose();
}
@Override
public void pathDone() {
finish(false);
out.pathDone();
dispose();
}
@Override
public void closePath() {
finish(true);
out.closePath();
}
@Override
public void moveTo(float x0, float y0) {
finish(false);
out.moveTo(x0, y0);
}
private void finish(final boolean closed) {
rdrCtx.closedPath = closed;
stack.pullAll(out);
}
@Override
public void lineTo(float x1, float y1) {
stack.pushLine(x1, y1);
}
@Override
public void curveTo(float x3, float y3,
float x2, float y2,
float x1, float y1)
{
stack.pushCubic(x1, y1, x2, y2, x3, y3);
}
@Override
public void quadTo(float x2, float y2, float x1, float y1) {
stack.pushQuad(x1, y1, x2, y2);
}
}
static final class PathClipFilter implements PathConsumer2D {
private PathConsumer2D out;
private final float[] clipRect;
private final float[] corners = new float[8];
private boolean init_corners = false;
private final IndexStack stack;
private int cOutCode = 0;
private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
private boolean outside = false;
private float cx0, cy0;
private float cox0, coy0;
private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
private final CurveClipSplitter curveSplitter;
PathClipFilter(final RendererContext rdrCtx) {
this.clipRect = rdrCtx.clipRect;
this.curveSplitter = rdrCtx.curveClipSplitter;
this.stack = (rdrCtx.stats != null) ?
new IndexStack(rdrCtx,
rdrCtx.stats.stat_pcf_idxstack_indices,
rdrCtx.stats.hist_pcf_idxstack_indices,
rdrCtx.stats.stat_array_pcf_idxstack_indices)
: new IndexStack(rdrCtx);
}
PathClipFilter init(final PathConsumer2D out,
final double rdrOffX,
final double rdrOffY)
{
this.out = out;
final float margin = 1e-3f;
final float[] _clipRect = this.clipRect;
_clipRect[0] -= margin - rdrOffY;
_clipRect[1] += margin + rdrOffY;
_clipRect[2] -= margin - rdrOffX;
_clipRect[3] += margin + rdrOffX;
if (MarlinConst.DO_CLIP_SUBDIVIDER) {
curveSplitter.init();
}
this.init_corners = true;
this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
return this;
}
void dispose() {
stack.dispose();
}
private void finishPath() {
if (outside) {
if (gOutCode == 0) {
finish();
} else {
this.outside = false;
stack.reset();
}
}
}
private void finish() {
this.outside = false;
if (!stack.isEmpty()) {
if (init_corners) {
init_corners = false;
final float[] _corners = corners;
final float[] _clipRect = clipRect;
_corners[0] = _clipRect[2];
_corners[1] = _clipRect[0];
_corners[2] = _clipRect[2];
_corners[3] = _clipRect[1];
_corners[4] = _clipRect[3];
_corners[5] = _clipRect[0];
_corners[6] = _clipRect[3];
_corners[7] = _clipRect[1];
}
stack.pullAll(corners, out);
}
out.lineTo(cox0, coy0);
this.cx0 = cox0;
this.cy0 = coy0;
}
@Override
public void pathDone() {
finishPath();
out.pathDone();
dispose();
}
@Override
public void closePath() {
finishPath();
out.closePath();
}
@Override
public void moveTo(final float x0, final float y0) {
finishPath();
this.cOutCode = Helpers.outcode(x0, y0, clipRect);
this.outside = false;
out.moveTo(x0, y0);
this.cx0 = x0;
this.cy0 = y0;
}
@Override
public void lineTo(final float xe, final float ye) {
final int outcode0 = this.cOutCode;
final int outcode1 = Helpers.outcode(xe, ye, clipRect);
final int orCode = (outcode0 | outcode1);
if (orCode != 0) {
final int sideCode = (outcode0 & outcode1);
if (sideCode == 0) {
if (subdivide) {
subdivide = false;
boolean ret;
if (outside) {
ret = curveSplitter.splitLine(cox0, coy0, xe, ye,
orCode, this);
} else {
ret = curveSplitter.splitLine(cx0, cy0, xe, ye,
orCode, this);
}
subdivide = true;
if (ret) {
return;
}
}
} else {
this.cOutCode = outcode1;
this.gOutCode &= sideCode;
this.outside = true;
this.cox0 = xe;
this.coy0 = ye;
clip(sideCode, outcode0, outcode1);
return;
}
}
this.cOutCode = outcode1;
this.gOutCode = 0;
if (outside) {
finish();
}
out.lineTo(xe, ye);
this.cx0 = xe;
this.cy0 = ye;
}
private void clip(final int sideCode,
final int outcode0,
final int outcode1)
{
if ((outcode0 != outcode1)
&& ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))
{
final int mergeCode = (outcode0 | outcode1);
final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
switch (tbCode) {
case MarlinConst.OUTCODE_TOP:
stack.push(off);
return;
case MarlinConst.OUTCODE_BOTTOM:
stack.push(off + 1);
return;
default:
if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
stack.push(off);
stack.push(off + 1);
} else {
stack.push(off + 1);
stack.push(off);
}
}
}
}
@Override
public void curveTo(final float x1, final float y1,
final float x2, final float y2,
final float xe, final float ye)
{
final int outcode0 = this.cOutCode;
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
final int outcode2 = Helpers.outcode(x2, y2, clipRect);
final int outcode3 = Helpers.outcode(xe, ye, clipRect);
final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
if (sideCode == 0) {
if (subdivide) {
subdivide = false;
boolean ret;
if (outside) {
ret = curveSplitter.splitCurve(cox0, coy0, x1, y1,
x2, y2, xe, ye,
orCode, this);
} else {
ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
x2, y2, xe, ye,
orCode, this);
}
subdivide = true;
if (ret) {
return;
}
}
} else {
this.cOutCode = outcode3;
this.gOutCode &= sideCode;
this.outside = true;
this.cox0 = xe;
this.coy0 = ye;
clip(sideCode, outcode0, outcode3);
return;
}
}
this.cOutCode = outcode3;
this.gOutCode = 0;
if (outside) {
finish();
}
out.curveTo(x1, y1, x2, y2, xe, ye);
this.cx0 = xe;
this.cy0 = ye;
}
@Override
public void quadTo(final float x1, final float y1,
final float xe, final float ye)
{
final int outcode0 = this.cOutCode;
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
final int outcode2 = Helpers.outcode(xe, ye, clipRect);
final int orCode = (outcode0 | outcode1 | outcode2);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2;
if (sideCode == 0) {
if (subdivide) {
subdivide = false;
boolean ret;
if (outside) {
ret = curveSplitter.splitQuad(cox0, coy0, x1, y1,
xe, ye, orCode, this);
} else {
ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
xe, ye, orCode, this);
}
subdivide = true;
if (ret) {
return;
}
}
} else {
this.cOutCode = outcode2;
this.gOutCode &= sideCode;
this.outside = true;
this.cox0 = xe;
this.coy0 = ye;
clip(sideCode, outcode0, outcode2);
return;
}
}
this.cOutCode = outcode2;
this.gOutCode = 0;
if (outside) {
finish();
}
out.quadTo(x1, y1, xe, ye);
this.cx0 = xe;
this.cy0 = ye;
}
}
static final class CurveClipSplitter {
static final float LEN_TH = MarlinProperties.getSubdividerMinLength();
static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0f);
private static final boolean TRACE = false;
private static final int MAX_N_CURVES = 3 * 4;
final float[] clipRect;
final float[] clipRectPad = new float[4];
private boolean init_clipRectPad = false;
final float[] middle = new float[MAX_N_CURVES * 8 + 2];
private final float[] subdivTs = new float[MAX_N_CURVES];
private final Curve curve;
CurveClipSplitter(final RendererContext rdrCtx) {
this.clipRect = rdrCtx.clipRect;
this.curve = rdrCtx.curve;
}
void init() {
this.init_clipRectPad = true;
}
private void initPaddedClip() {
final float[] _clipRect = clipRect;
final float[] _clipRectPad = clipRectPad;
_clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING;
_clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING;
_clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING;
_clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING;
if (TRACE) {
MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] "
+ "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]");
}
}
boolean splitLine(final float x0, final float y0,
final float x1, final float y1,
final int outCodeOR,
final PathConsumer2D out)
{
if (TRACE) {
MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");
}
if (DO_CHECK_LENGTH && Helpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) {
return false;
}
final float[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
return subdivideAtIntersections(4, outCodeOR, out);
}
boolean splitQuad(final float x0, final float y0,
final float x1, final float y1,
final float x2, final float y2,
final int outCodeOR,
final PathConsumer2D out)
{
if (TRACE) {
MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")");
}
if (DO_CHECK_LENGTH && Helpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) {
return false;
}
final float[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
return subdivideAtIntersections(6, outCodeOR, out);
}
boolean splitCurve(final float x0, final float y0,
final float x1, final float y1,
final float x2, final float y2,
final float x3, final float y3,
final int outCodeOR,
final PathConsumer2D out)
{
if (TRACE) {
MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")");
}
if (DO_CHECK_LENGTH && Helpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) {
return false;
}
final float[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
mid[6] = x3; mid[7] = y3;
return subdivideAtIntersections(8, outCodeOR, out);
}
private boolean subdivideAtIntersections(final int type, final int outCodeOR,
final PathConsumer2D out)
{
final float[] mid = middle;
final float[] subTs = subdivTs;
if (init_clipRectPad) {
init_clipRectPad = false;
initPaddedClip();
}
final int nSplits = Helpers.findClipPoints(curve, mid, subTs, type,
outCodeOR, clipRectPad);
if (TRACE) {
MarlinUtils.logInfo("nSplits: "+ nSplits);
MarlinUtils.logInfo("subTs: " + Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits)));
}
if (nSplits == 0) {
return false;
}
float prevT = 0.0f;
for (int i = 0, off = 0; i < nSplits; i++, off += type) {
final float t = subTs[i];
Helpers.subdivideAt((t - prevT) / (1.0f - prevT),
mid, off, mid, off, type);
prevT = t;
}
for (int i = 0, off = 0; i <= nSplits; i++, off += type) {
if (TRACE) {
MarlinUtils.logInfo("Part Curve " + Arrays.toString(Arrays.copyOfRange(mid, off, off + type)));
}
emitCurrent(type, mid, off, out);
}
return true;
}
static void emitCurrent(final int type, final float[] pts,
final int off, final PathConsumer2D out)
{
if (type == 8) {
out.curveTo(pts[off + 2], pts[off + 3],
pts[off + 4], pts[off + 5],
pts[off + 6], pts[off + 7]);
} else if (type == 4) {
out.lineTo(pts[off + 2], pts[off + 3]);
} else {
out.quadTo(pts[off + 2], pts[off + 3],
pts[off + 4], pts[off + 5]);
}
}
}
public static final class CurveBasicMonotonizer {
private static final int MAX_N_CURVES = 11;
private float lw2;
int nbSplits;
final float[] middle = new float[MAX_N_CURVES * 6 + 2];
private final float[] subdivTs = new float[MAX_N_CURVES - 1];
private final Curve curve;
CurveBasicMonotonizer(final RendererContext rdrCtx) {
this.curve = rdrCtx.curve;
}
public void init(final float lineWidth) {
this.lw2 = (lineWidth * lineWidth) / 4.0f;
}
CurveBasicMonotonizer curve(final float x0, final float y0,
final float x1, final float y1,
final float x2, final float y2,
final float x3, final float y3)
{
final float[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
mid[6] = x3; mid[7] = y3;
final float[] subTs = subdivTs;
final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 8, lw2);
float prevT = 0.0f;
for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
final float t = subTs[i];
Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT),
mid, off, mid, off, off + 6);
prevT = t;
}
this.nbSplits = nSplits;
return this;
}
CurveBasicMonotonizer quad(final float x0, final float y0,
final float x1, final float y1,
final float x2, final float y2)
{
final float[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
final float[] subTs = subdivTs;
final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 6, lw2);
float prevt = 0.0f;
for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
final float t = subTs[i];
Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt),
mid, off, mid, off, off + 4);
prevt = t;
}
this.nbSplits = nSplits;
return this;
}
}
static final class PathTracer implements PathConsumer2D {
private final String prefix;
private PathConsumer2D out;
PathTracer(String name) {
this.prefix = name + ": ";
}
PathTracer init(PathConsumer2D out) {
this.out = out;
return this;
}
@Override
public void moveTo(float x0, float y0) {
log("moveTo (" + x0 + ", " + y0 + ')');
out.moveTo(x0, y0);
}
@Override
public void lineTo(float x1, float y1) {
log("lineTo (" + x1 + ", " + y1 + ')');
out.lineTo(x1, y1);
}
@Override
public void curveTo(float x1, float y1,
float x2, float y2,
float x3, float y3)
{
log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ')');
out.curveTo(x1, y1, x2, y2, x3, y3);
}
@Override
public void quadTo(float x1, float y1, float x2, float y2) {
log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')');
out.quadTo(x1, y1, x2, y2);
}
@Override
public void closePath() {
log("closePath");
out.closePath();
}
@Override
public void pathDone() {
log("pathDone");
out.pathDone();
}
private void log(final String message) {
MarlinUtils.logInfo(prefix + message);
}
}
}