Path: blob/master/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java
41159 views
/*1* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.java2d.marlin;2627import java.awt.geom.AffineTransform;28import java.awt.geom.Path2D;29import java.util.Arrays;30import sun.java2d.marlin.Helpers.IndexStack;31import sun.java2d.marlin.Helpers.PolyStack;3233final class TransformingPathConsumer2D {3435// smaller uncertainty in double variant36static final double CLIP_RECT_PADDING = 0.25d;3738private final RendererContext rdrCtx;3940// recycled ClosedPathDetector instance from detectClosedPath()41private final ClosedPathDetector cpDetector;4243// recycled PathClipFilter instance from pathClipper()44private final PathClipFilter pathClipper;4546// recycled DPathConsumer2D instance from wrapPath2D()47private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();4849// recycled DPathConsumer2D instances from deltaTransformConsumer()50private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();51private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();5253// recycled DPathConsumer2D instances from inverseDeltaTransformConsumer()54private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();55private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();5657// recycled PathTracer instances from tracer...() methods58private final PathTracer tracerInput = new PathTracer("[Input]");59private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");60private final PathTracer tracerFiller = new PathTracer("Filler");61private final PathTracer tracerStroker = new PathTracer("Stroker");62private final PathTracer tracerDasher = new PathTracer("Dasher");6364TransformingPathConsumer2D(final RendererContext rdrCtx) {65// used by RendererContext66this.rdrCtx = rdrCtx;67this.cpDetector = new ClosedPathDetector(rdrCtx);68this.pathClipper = new PathClipFilter(rdrCtx);69}7071DPathConsumer2D wrapPath2D(Path2D.Double p2d) {72return wp_Path2DWrapper.init(p2d);73}7475DPathConsumer2D traceInput(DPathConsumer2D out) {76return tracerInput.init(out);77}7879DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) {80return tracerCPDetector.init(out);81}8283DPathConsumer2D traceFiller(DPathConsumer2D out) {84return tracerFiller.init(out);85}8687DPathConsumer2D traceStroker(DPathConsumer2D out) {88return tracerStroker.init(out);89}9091DPathConsumer2D traceDasher(DPathConsumer2D out) {92return tracerDasher.init(out);93}9495DPathConsumer2D detectClosedPath(DPathConsumer2D out) {96return cpDetector.init(out);97}9899DPathConsumer2D pathClipper(DPathConsumer2D out) {100return pathClipper.init(out);101}102103DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out,104AffineTransform at)105{106if (at == null) {107return out;108}109final double mxx = at.getScaleX();110final double mxy = at.getShearX();111final double myx = at.getShearY();112final double myy = at.getScaleY();113114if (mxy == 0.0d && myx == 0.0d) {115if (mxx == 1.0d && myy == 1.0d) {116return out;117} else {118// Scale only119if (rdrCtx.doClip) {120// adjust clip rectangle (ymin, ymax, xmin, xmax):121rdrCtx.clipInvScale = adjustClipScale(rdrCtx.clipRect,122mxx, myy);123}124return dt_DeltaScaleFilter.init(out, mxx, myy);125}126} else {127if (rdrCtx.doClip) {128// adjust clip rectangle (ymin, ymax, xmin, xmax):129rdrCtx.clipInvScale = adjustClipInverseDelta(rdrCtx.clipRect,130mxx, mxy, myx, myy);131}132return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);133}134}135136private static double adjustClipScale(final double[] clipRect,137final double mxx, final double myy)138{139// Adjust the clipping rectangle (iv_DeltaScaleFilter):140final double scaleY = 1.0d / myy;141clipRect[0] *= scaleY;142clipRect[1] *= scaleY;143144if (clipRect[1] < clipRect[0]) {145double tmp = clipRect[0];146clipRect[0] = clipRect[1];147clipRect[1] = tmp;148}149150final double scaleX = 1.0d / mxx;151clipRect[2] *= scaleX;152clipRect[3] *= scaleX;153154if (clipRect[3] < clipRect[2]) {155double tmp = clipRect[2];156clipRect[2] = clipRect[3];157clipRect[3] = tmp;158}159160if (MarlinConst.DO_LOG_CLIP) {161MarlinUtils.logInfo("clipRect (ClipScale): "162+ Arrays.toString(clipRect));163}164return 0.5d * (Math.abs(scaleX) + Math.abs(scaleY));165}166167private static double adjustClipInverseDelta(final double[] clipRect,168final double mxx, final double mxy,169final double myx, final double myy)170{171// Adjust the clipping rectangle (iv_DeltaTransformFilter):172final double det = mxx * myy - mxy * myx;173final double imxx = myy / det;174final double imxy = -mxy / det;175final double imyx = -myx / det;176final double imyy = mxx / det;177178double xmin, xmax, ymin, ymax;179double x, y;180// xmin, ymin:181x = clipRect[2] * imxx + clipRect[0] * imxy;182y = clipRect[2] * imyx + clipRect[0] * imyy;183184xmin = xmax = x;185ymin = ymax = y;186187// xmax, ymin:188x = clipRect[3] * imxx + clipRect[0] * imxy;189y = clipRect[3] * imyx + clipRect[0] * imyy;190191if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }192if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }193194// xmin, ymax:195x = clipRect[2] * imxx + clipRect[1] * imxy;196y = clipRect[2] * imyx + clipRect[1] * imyy;197198if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }199if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }200201// xmax, ymax:202x = clipRect[3] * imxx + clipRect[1] * imxy;203y = clipRect[3] * imyx + clipRect[1] * imyy;204205if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }206if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }207208clipRect[0] = ymin;209clipRect[1] = ymax;210clipRect[2] = xmin;211clipRect[3] = xmax;212213if (MarlinConst.DO_LOG_CLIP) {214MarlinUtils.logInfo("clipRect (ClipInverseDelta): "215+ Arrays.toString(clipRect));216}217218final double scaleX = Math.sqrt(imxx * imxx + imxy * imxy);219final double scaleY = Math.sqrt(imyx * imyx + imyy * imyy);220221return 0.5d * (scaleX + scaleY);222}223224DPathConsumer2D inverseDeltaTransformConsumer(DPathConsumer2D out,225AffineTransform at)226{227if (at == null) {228return out;229}230double mxx = at.getScaleX();231double mxy = at.getShearX();232double myx = at.getShearY();233double myy = at.getScaleY();234235if (mxy == 0.0d && myx == 0.0d) {236if (mxx == 1.0d && myy == 1.0d) {237return out;238} else {239return iv_DeltaScaleFilter.init(out, 1.0d / mxx, 1.0d / myy);240}241} else {242final double det = mxx * myy - mxy * myx;243return iv_DeltaTransformFilter.init(out,244myy / det,245-mxy / det,246-myx / det,247mxx / det);248}249}250251static final class DeltaScaleFilter implements DPathConsumer2D {252private DPathConsumer2D out;253private double sx, sy;254255DeltaScaleFilter() {}256257DeltaScaleFilter init(DPathConsumer2D out,258double mxx, double myy)259{260this.out = out;261sx = mxx;262sy = myy;263return this; // fluent API264}265266@Override267public void moveTo(double x0, double y0) {268out.moveTo(x0 * sx, y0 * sy);269}270271@Override272public void lineTo(double x1, double y1) {273out.lineTo(x1 * sx, y1 * sy);274}275276@Override277public void quadTo(double x1, double y1,278double x2, double y2)279{280out.quadTo(x1 * sx, y1 * sy,281x2 * sx, y2 * sy);282}283284@Override285public void curveTo(double x1, double y1,286double x2, double y2,287double x3, double y3)288{289out.curveTo(x1 * sx, y1 * sy,290x2 * sx, y2 * sy,291x3 * sx, y3 * sy);292}293294@Override295public void closePath() {296out.closePath();297}298299@Override300public void pathDone() {301out.pathDone();302}303304@Override305public long getNativeConsumer() {306return 0;307}308}309310static final class DeltaTransformFilter implements DPathConsumer2D {311private DPathConsumer2D out;312private double mxx, mxy, myx, myy;313314DeltaTransformFilter() {}315316DeltaTransformFilter init(DPathConsumer2D out,317double mxx, double mxy,318double myx, double myy)319{320this.out = out;321this.mxx = mxx;322this.mxy = mxy;323this.myx = myx;324this.myy = myy;325return this; // fluent API326}327328@Override329public void moveTo(double x0, double y0) {330out.moveTo(x0 * mxx + y0 * mxy,331x0 * myx + y0 * myy);332}333334@Override335public void lineTo(double x1, double y1) {336out.lineTo(x1 * mxx + y1 * mxy,337x1 * myx + y1 * myy);338}339340@Override341public void quadTo(double x1, double y1,342double x2, double y2)343{344out.quadTo(x1 * mxx + y1 * mxy,345x1 * myx + y1 * myy,346x2 * mxx + y2 * mxy,347x2 * myx + y2 * myy);348}349350@Override351public void curveTo(double x1, double y1,352double x2, double y2,353double x3, double y3)354{355out.curveTo(x1 * mxx + y1 * mxy,356x1 * myx + y1 * myy,357x2 * mxx + y2 * mxy,358x2 * myx + y2 * myy,359x3 * mxx + y3 * mxy,360x3 * myx + y3 * myy);361}362363@Override364public void closePath() {365out.closePath();366}367368@Override369public void pathDone() {370out.pathDone();371}372373@Override374public long getNativeConsumer() {375return 0;376}377}378379static final class Path2DWrapper implements DPathConsumer2D {380private Path2D.Double p2d;381382Path2DWrapper() {}383384Path2DWrapper init(Path2D.Double p2d) {385this.p2d = p2d;386return this;387}388389@Override390public void moveTo(double x0, double y0) {391p2d.moveTo(x0, y0);392}393394@Override395public void lineTo(double x1, double y1) {396p2d.lineTo(x1, y1);397}398399@Override400public void closePath() {401p2d.closePath();402}403404@Override405public void pathDone() {}406407@Override408public void curveTo(double x1, double y1,409double x2, double y2,410double x3, double y3)411{412p2d.curveTo(x1, y1, x2, y2, x3, y3);413}414415@Override416public void quadTo(double x1, double y1, double x2, double y2) {417p2d.quadTo(x1, y1, x2, y2);418}419420@Override421public long getNativeConsumer() {422throw new InternalError("Not using a native peer");423}424}425426static final class ClosedPathDetector implements DPathConsumer2D {427428private final RendererContext rdrCtx;429private final PolyStack stack;430431private DPathConsumer2D out;432433ClosedPathDetector(final RendererContext rdrCtx) {434this.rdrCtx = rdrCtx;435this.stack = (rdrCtx.stats != null) ?436new PolyStack(rdrCtx,437rdrCtx.stats.stat_cpd_polystack_types,438rdrCtx.stats.stat_cpd_polystack_curves,439rdrCtx.stats.hist_cpd_polystack_curves,440rdrCtx.stats.stat_array_cpd_polystack_curves,441rdrCtx.stats.stat_array_cpd_polystack_types)442: new PolyStack(rdrCtx);443}444445ClosedPathDetector init(DPathConsumer2D out) {446this.out = out;447return this; // fluent API448}449450/**451* Disposes this instance:452* clean up before reusing this instance453*/454void dispose() {455stack.dispose();456}457458@Override459public void pathDone() {460// previous path is not closed:461finish(false);462out.pathDone();463464// TODO: fix possible leak if exception happened465// Dispose this instance:466dispose();467}468469@Override470public void closePath() {471// path is closed472finish(true);473out.closePath();474}475476@Override477public void moveTo(double x0, double y0) {478// previous path is not closed:479finish(false);480out.moveTo(x0, y0);481}482483private void finish(final boolean closed) {484rdrCtx.closedPath = closed;485stack.pullAll(out);486}487488@Override489public void lineTo(double x1, double y1) {490stack.pushLine(x1, y1);491}492493@Override494public void curveTo(double x3, double y3,495double x2, double y2,496double x1, double y1)497{498stack.pushCubic(x1, y1, x2, y2, x3, y3);499}500501@Override502public void quadTo(double x2, double y2, double x1, double y1) {503stack.pushQuad(x1, y1, x2, y2);504}505506@Override507public long getNativeConsumer() {508throw new InternalError("Not using a native peer");509}510}511512static final class PathClipFilter implements DPathConsumer2D {513514private DPathConsumer2D out;515516// Bounds of the drawing region, at pixel precision.517private final double[] clipRect;518519private final double[] corners = new double[8];520private boolean init_corners = false;521522private final IndexStack stack;523524// the current outcode of the current sub path525private int cOutCode = 0;526527// the cumulated (and) outcode of the complete path528private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;529530private boolean outside = false;531532// The starting point of the path533private double sx0, sy0;534535// The current point (TODO stupid repeated info)536private double cx0, cy0;537538// The current point OUTSIDE539private double cox0, coy0;540541private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;542private final CurveClipSplitter curveSplitter;543544PathClipFilter(final RendererContext rdrCtx) {545this.clipRect = rdrCtx.clipRect;546this.curveSplitter = rdrCtx.curveClipSplitter;547548this.stack = (rdrCtx.stats != null) ?549new IndexStack(rdrCtx,550rdrCtx.stats.stat_pcf_idxstack_indices,551rdrCtx.stats.hist_pcf_idxstack_indices,552rdrCtx.stats.stat_array_pcf_idxstack_indices)553: new IndexStack(rdrCtx);554}555556PathClipFilter init(final DPathConsumer2D out) {557this.out = out;558559if (MarlinConst.DO_CLIP_SUBDIVIDER) {560// adjust padded clip rectangle:561curveSplitter.init();562}563564this.init_corners = true;565this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;566567return this; // fluent API568}569570/**571* Disposes this instance:572* clean up before reusing this instance573*/574void dispose() {575stack.dispose();576}577578private void finishPath() {579if (outside) {580// criteria: inside or totally outside ?581if (gOutCode == 0) {582finish();583} else {584this.outside = false;585stack.reset();586}587}588}589590private void finish() {591this.outside = false;592593if (!stack.isEmpty()) {594if (init_corners) {595init_corners = false;596597final double[] _corners = corners;598final double[] _clipRect = clipRect;599// Top Left (0):600_corners[0] = _clipRect[2];601_corners[1] = _clipRect[0];602// Bottom Left (1):603_corners[2] = _clipRect[2];604_corners[3] = _clipRect[1];605// Top right (2):606_corners[4] = _clipRect[3];607_corners[5] = _clipRect[0];608// Bottom Right (3):609_corners[6] = _clipRect[3];610_corners[7] = _clipRect[1];611}612stack.pullAll(corners, out);613}614out.lineTo(cox0, coy0);615this.cx0 = cox0;616this.cy0 = coy0;617}618619@Override620public void pathDone() {621finishPath();622623out.pathDone();624625// TODO: fix possible leak if exception happened626// Dispose this instance:627dispose();628}629630@Override631public void closePath() {632finishPath();633634out.closePath();635636// back to starting point:637this.cOutCode = Helpers.outcode(sx0, sy0, clipRect);638this.cx0 = sx0;639this.cy0 = sy0;640}641642@Override643public void moveTo(final double x0, final double y0) {644finishPath();645646out.moveTo(x0, y0);647648// update starting point:649this.cOutCode = Helpers.outcode(x0, y0, clipRect);650this.cx0 = x0;651this.cy0 = y0;652653this.sx0 = x0;654this.sy0 = y0;655}656657@Override658public void lineTo(final double xe, final double ye) {659final int outcode0 = this.cOutCode;660final int outcode1 = Helpers.outcode(xe, ye, clipRect);661662// Should clip663final int orCode = (outcode0 | outcode1);664if (orCode != 0) {665final int sideCode = (outcode0 & outcode1);666667// basic rejection criteria:668if (sideCode == 0) {669// overlap clip:670if (subdivide) {671// avoid reentrance672subdivide = false;673boolean ret;674// subdivide curve => callback with subdivided parts:675if (outside) {676ret = curveSplitter.splitLine(cox0, coy0, xe, ye,677orCode, this);678} else {679ret = curveSplitter.splitLine(cx0, cy0, xe, ye,680orCode, this);681}682// reentrance is done:683subdivide = true;684if (ret) {685return;686}687}688// already subdivided so render it689} else {690this.cOutCode = outcode1;691this.gOutCode &= sideCode;692// keep last point coordinate before entering the clip again:693this.outside = true;694this.cox0 = xe;695this.coy0 = ye;696697clip(sideCode, outcode0, outcode1);698return;699}700}701702this.cOutCode = outcode1;703this.gOutCode = 0;704705if (outside) {706finish();707}708// clipping disabled:709out.lineTo(xe, ye);710this.cx0 = xe;711this.cy0 = ye;712}713714private void clip(final int sideCode,715final int outcode0,716final int outcode1)717{718// corner or cross-boundary on left or right side:719if ((outcode0 != outcode1)720&& ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))721{722// combine outcodes:723final int mergeCode = (outcode0 | outcode1);724final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;725final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;726final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;727728// add corners to outside stack:729switch (tbCode) {730case MarlinConst.OUTCODE_TOP:731stack.push(off); // top732return;733case MarlinConst.OUTCODE_BOTTOM:734stack.push(off + 1); // bottom735return;736default:737// both TOP / BOTTOM:738if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {739// top to bottom740stack.push(off); // top741stack.push(off + 1); // bottom742} else {743// bottom to top744stack.push(off + 1); // bottom745stack.push(off); // top746}747}748}749}750751@Override752public void curveTo(final double x1, final double y1,753final double x2, final double y2,754final double xe, final double ye)755{756final int outcode0 = this.cOutCode;757final int outcode1 = Helpers.outcode(x1, y1, clipRect);758final int outcode2 = Helpers.outcode(x2, y2, clipRect);759final int outcode3 = Helpers.outcode(xe, ye, clipRect);760761// Should clip762final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);763if (orCode != 0) {764final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;765766// basic rejection criteria:767if (sideCode == 0) {768// overlap clip:769if (subdivide) {770// avoid reentrance771subdivide = false;772// subdivide curve => callback with subdivided parts:773boolean ret;774if (outside) {775ret = curveSplitter.splitCurve(cox0, coy0, x1, y1,776x2, y2, xe, ye,777orCode, this);778} else {779ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,780x2, y2, xe, ye,781orCode, this);782}783// reentrance is done:784subdivide = true;785if (ret) {786return;787}788}789// already subdivided so render it790} else {791this.cOutCode = outcode3;792this.gOutCode &= sideCode;793// keep last point coordinate before entering the clip again:794this.outside = true;795this.cox0 = xe;796this.coy0 = ye;797798clip(sideCode, outcode0, outcode3);799return;800}801}802803this.cOutCode = outcode3;804this.gOutCode = 0;805806if (outside) {807finish();808}809// clipping disabled:810out.curveTo(x1, y1, x2, y2, xe, ye);811this.cx0 = xe;812this.cy0 = ye;813}814815@Override816public void quadTo(final double x1, final double y1,817final double xe, final double ye)818{819final int outcode0 = this.cOutCode;820final int outcode1 = Helpers.outcode(x1, y1, clipRect);821final int outcode2 = Helpers.outcode(xe, ye, clipRect);822823// Should clip824final int orCode = (outcode0 | outcode1 | outcode2);825if (orCode != 0) {826final int sideCode = outcode0 & outcode1 & outcode2;827828// basic rejection criteria:829if (sideCode == 0) {830// overlap clip:831if (subdivide) {832// avoid reentrance833subdivide = false;834// subdivide curve => callback with subdivided parts:835boolean ret;836if (outside) {837ret = curveSplitter.splitQuad(cox0, coy0, x1, y1,838xe, ye, orCode, this);839} else {840ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,841xe, ye, orCode, this);842}843// reentrance is done:844subdivide = true;845if (ret) {846return;847}848}849// already subdivided so render it850} else {851this.cOutCode = outcode2;852this.gOutCode &= sideCode;853// keep last point coordinate before entering the clip again:854this.outside = true;855this.cox0 = xe;856this.coy0 = ye;857858clip(sideCode, outcode0, outcode2);859return;860}861}862863this.cOutCode = outcode2;864this.gOutCode = 0;865866if (outside) {867finish();868}869// clipping disabled:870out.quadTo(x1, y1, xe, ye);871this.cx0 = xe;872this.cy0 = ye;873}874875@Override876public long getNativeConsumer() {877throw new InternalError("Not using a native peer");878}879}880881static final class CurveClipSplitter {882883static final double LEN_TH = MarlinProperties.getSubdividerMinLength();884static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0d);885886private static final boolean TRACE = false;887888private static final int MAX_N_CURVES = 3 * 4;889890private final RendererContext rdrCtx;891892// scaled length threshold:893private double minLength;894895// clip rectangle (ymin, ymax, xmin, xmax):896final double[] clipRect;897898// clip rectangle (ymin, ymax, xmin, xmax) including padding:899final double[] clipRectPad = new double[4];900private boolean init_clipRectPad = false;901902// This is where the curve to be processed is put. We give it903// enough room to store all curves.904final double[] middle = new double[MAX_N_CURVES * 8 + 2];905// t values at subdivision points906private final double[] subdivTs = new double[MAX_N_CURVES];907908// dirty curve909private final Curve curve;910911CurveClipSplitter(final RendererContext rdrCtx) {912this.rdrCtx = rdrCtx;913this.clipRect = rdrCtx.clipRect;914this.curve = rdrCtx.curve;915}916917void init() {918this.init_clipRectPad = true;919920if (DO_CHECK_LENGTH) {921this.minLength = (this.rdrCtx.clipInvScale == 0.0d) ? LEN_TH922: (LEN_TH * this.rdrCtx.clipInvScale);923924if (MarlinConst.DO_LOG_CLIP) {925MarlinUtils.logInfo("CurveClipSplitter.minLength = "926+ minLength);927}928}929}930931private void initPaddedClip() {932// bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY933// adjust padded clip rectangle (ymin, ymax, xmin, xmax):934// add a rounding error (curve subdivision ~ 0.1px):935final double[] _clipRect = clipRect;936final double[] _clipRectPad = clipRectPad;937938_clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING;939_clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING;940_clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING;941_clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING;942943if (TRACE) {944MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] "945+ "Y [" + _clipRectPad[0] + " .. " + _clipRectPad[1] +"]");946}947}948949boolean splitLine(final double x0, final double y0,950final double x1, final double y1,951final int outCodeOR,952final DPathConsumer2D out)953{954if (TRACE) {955MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");956}957958if (DO_CHECK_LENGTH && Helpers.fastLineLen(x0, y0, x1, y1) <= minLength) {959return false;960}961962final double[] mid = middle;963mid[0] = x0; mid[1] = y0;964mid[2] = x1; mid[3] = y1;965966return subdivideAtIntersections(4, outCodeOR, out);967}968969boolean splitQuad(final double x0, final double y0,970final double x1, final double y1,971final double x2, final double y2,972final int outCodeOR,973final DPathConsumer2D out)974{975if (TRACE) {976MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")");977}978979if (DO_CHECK_LENGTH && Helpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= minLength) {980return false;981}982983final double[] mid = middle;984mid[0] = x0; mid[1] = y0;985mid[2] = x1; mid[3] = y1;986mid[4] = x2; mid[5] = y2;987988return subdivideAtIntersections(6, outCodeOR, out);989}990991boolean splitCurve(final double x0, final double y0,992final double x1, final double y1,993final double x2, final double y2,994final double x3, final double y3,995final int outCodeOR,996final DPathConsumer2D out)997{998if (TRACE) {999MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")");1000}10011002if (DO_CHECK_LENGTH && Helpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= minLength) {1003return false;1004}10051006final double[] mid = middle;1007mid[0] = x0; mid[1] = y0;1008mid[2] = x1; mid[3] = y1;1009mid[4] = x2; mid[5] = y2;1010mid[6] = x3; mid[7] = y3;10111012return subdivideAtIntersections(8, outCodeOR, out);1013}10141015private boolean subdivideAtIntersections(final int type, final int outCodeOR,1016final DPathConsumer2D out)1017{1018final double[] mid = middle;1019final double[] subTs = subdivTs;10201021if (init_clipRectPad) {1022init_clipRectPad = false;1023initPaddedClip();1024}10251026final int nSplits = Helpers.findClipPoints(curve, mid, subTs, type,1027outCodeOR, clipRectPad);10281029if (TRACE) {1030MarlinUtils.logInfo("nSplits: " + nSplits);1031MarlinUtils.logInfo("subTs: " + Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits)));1032}1033if (nSplits == 0) {1034// only curve support shortcut1035return false;1036}1037double prevT = 0.0d;10381039for (int i = 0, off = 0; i < nSplits; i++, off += type) {1040final double t = subTs[i];10411042Helpers.subdivideAt((t - prevT) / (1.0d - prevT),1043mid, off, mid, off, type);1044prevT = t;1045}10461047for (int i = 0, off = 0; i <= nSplits; i++, off += type) {1048if (TRACE) {1049MarlinUtils.logInfo("Part Curve " + Arrays.toString(Arrays.copyOfRange(mid, off, off + type)));1050}1051emitCurrent(type, mid, off, out);1052}1053return true;1054}10551056static void emitCurrent(final int type, final double[] pts,1057final int off, final DPathConsumer2D out)1058{1059// if instead of switch (perf + most probable cases first)1060if (type == 8) {1061out.curveTo(pts[off + 2], pts[off + 3],1062pts[off + 4], pts[off + 5],1063pts[off + 6], pts[off + 7]);1064} else if (type == 4) {1065out.lineTo(pts[off + 2], pts[off + 3]);1066} else {1067out.quadTo(pts[off + 2], pts[off + 3],1068pts[off + 4], pts[off + 5]);1069}1070}1071}10721073static final class CurveBasicMonotonizer {10741075private static final int MAX_N_CURVES = 11;10761077// squared half line width (for stroker)1078private double lw2;10791080// number of splitted curves1081int nbSplits;10821083// This is where the curve to be processed is put. We give it1084// enough room to store all curves.1085final double[] middle = new double[MAX_N_CURVES * 6 + 2];1086// t values at subdivision points1087private final double[] subdivTs = new double[MAX_N_CURVES - 1];10881089// dirty curve1090private final Curve curve;10911092CurveBasicMonotonizer(final RendererContext rdrCtx) {1093this.curve = rdrCtx.curve;1094}10951096void init(final double lineWidth) {1097this.lw2 = (lineWidth * lineWidth) / 4.0d;1098}10991100CurveBasicMonotonizer curve(final double x0, final double y0,1101final double x1, final double y1,1102final double x2, final double y2,1103final double x3, final double y3)1104{1105final double[] mid = middle;1106mid[0] = x0; mid[1] = y0;1107mid[2] = x1; mid[3] = y1;1108mid[4] = x2; mid[5] = y2;1109mid[6] = x3; mid[7] = y3;11101111final double[] subTs = subdivTs;1112final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 8, lw2);11131114double prevT = 0.0d;1115for (int i = 0, off = 0; i < nSplits; i++, off += 6) {1116final double t = subTs[i];11171118Helpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),1119mid, off, mid, off, off + 6);1120prevT = t;1121}11221123this.nbSplits = nSplits;1124return this;1125}11261127CurveBasicMonotonizer quad(final double x0, final double y0,1128final double x1, final double y1,1129final double x2, final double y2)1130{1131final double[] mid = middle;1132mid[0] = x0; mid[1] = y0;1133mid[2] = x1; mid[3] = y1;1134mid[4] = x2; mid[5] = y2;11351136final double[] subTs = subdivTs;1137final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 6, lw2);11381139double prevt = 0.0d;1140for (int i = 0, off = 0; i < nSplits; i++, off += 4) {1141final double t = subTs[i];1142Helpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),1143mid, off, mid, off, off + 4);1144prevt = t;1145}11461147this.nbSplits = nSplits;1148return this;1149}1150}11511152static final class PathTracer implements DPathConsumer2D {1153private final String prefix;1154private DPathConsumer2D out;11551156PathTracer(String name) {1157this.prefix = name + ": ";1158}11591160PathTracer init(DPathConsumer2D out) {1161this.out = out;1162return this; // fluent API1163}11641165@Override1166public void moveTo(double x0, double y0) {1167log("p.moveTo(" + x0 + ", " + y0 + ");");1168out.moveTo(x0, y0);1169}11701171@Override1172public void lineTo(double x1, double y1) {1173log("p.lineTo(" + x1 + ", " + y1 + ");");1174out.lineTo(x1, y1);1175}11761177@Override1178public void curveTo(double x1, double y1,1179double x2, double y2,1180double x3, double y3)1181{1182log("p.curveTo(" + x1 + ", " + y1 + ", " + x2 + ", " + y2 + ", " + x3 + ", " + y3 + ");");1183out.curveTo(x1, y1, x2, y2, x3, y3);1184}11851186@Override1187public void quadTo(double x1, double y1,1188double x2, double y2) {1189log("p.quadTo(" + x1 + ", " + y1 + ", " + x2 + ", " + y2 + ");");1190out.quadTo(x1, y1, x2, y2);1191}11921193@Override1194public void closePath() {1195log("p.closePath();");1196out.closePath();1197}11981199@Override1200public void pathDone() {1201log("p.pathDone();");1202out.pathDone();1203}12041205private void log(final String message) {1206MarlinUtils.logInfo(prefix + message);1207}12081209@Override1210public long getNativeConsumer() {1211throw new InternalError("Not using a native peer");1212}1213}1214}121512161217