tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SkDashPathEffect.cpp (13911B)


      1 /*
      2 * Copyright 2006 The Android Open Source Project
      3 *
      4 * Use of this source code is governed by a BSD-style license that can be
      5 * found in the LICENSE file.
      6 */
      7 
      8 #include "include/effects/SkDashPathEffect.h"
      9 
     10 #include "include/core/SkFlattenable.h"
     11 #include "include/core/SkMatrix.h"
     12 #include "include/core/SkPaint.h"
     13 #include "include/core/SkPath.h"
     14 #include "include/core/SkPathEffect.h"
     15 #include "include/core/SkPoint.h"
     16 #include "include/core/SkRect.h"
     17 #include "include/core/SkStrokeRec.h"
     18 #include "include/private/base/SkAlign.h"
     19 #include "include/private/base/SkFloatingPoint.h"
     20 #include "include/private/base/SkTemplates.h"
     21 #include "src/core/SkPathEffectBase.h"
     22 #include "src/core/SkReadBuffer.h"
     23 #include "src/core/SkWriteBuffer.h"
     24 #include "src/effects/SkDashImpl.h"
     25 #include "src/utils/SkDashPathPriv.h"
     26 
     27 #include <algorithm>
     28 #include <cstdint>
     29 #include <cstring>
     30 #include <optional>
     31 
     32 class SkPathBuilder;
     33 
     34 using namespace skia_private;
     35 
     36 SkDashImpl::SkDashImpl(SkSpan<const SkScalar> intervals, SkScalar phase)
     37        : fIntervals(intervals.size())
     38        , fPhase(0)
     39        , fInitialDashLength(-1)
     40        , fIntervalLength(0)
     41        , fInitialDashIndex(0)
     42 {
     43    SkASSERT(intervals.size() > 1 && SkIsAlign2(intervals.size()));
     44 
     45    memcpy(fIntervals.data(), intervals.data(), intervals.size_bytes());
     46 
     47    // set the internal data members
     48    SkDashPath::CalcDashParameters(phase, fIntervals,
     49            &fInitialDashLength, &fInitialDashIndex, &fIntervalLength, &fPhase);
     50 }
     51 
     52 bool SkDashImpl::onFilterPath(SkPathBuilder* builder, const SkPath& src, SkStrokeRec* rec,
     53                              const SkRect* cullRect, const SkMatrix&) const {
     54    return SkDashPath::InternalFilter(builder, src, rec, cullRect, fIntervals,
     55                                      fInitialDashLength, fInitialDashIndex, fIntervalLength,
     56                                      fPhase);
     57 }
     58 
     59 static void outset_for_stroke(SkRect* rect, const SkStrokeRec& rec) {
     60    SkScalar radius = SkScalarHalf(rec.getWidth());
     61    if (0 == radius) {
     62        radius = SK_Scalar1;    // hairlines
     63    }
     64    if (SkPaint::kMiter_Join == rec.getJoin()) {
     65        radius *= rec.getMiter();
     66    }
     67    rect->outset(radius, radius);
     68 }
     69 
     70 // Attempt to trim the line to minimally cover the cull rect (currently
     71 // only works for horizontal and vertical lines).
     72 // Return true if processing should continue; false otherwise.
     73 static bool cull_line(SkPoint* pts, const SkStrokeRec& rec,
     74                      const SkMatrix& ctm, const SkRect* cullRect,
     75                      const SkScalar intervalLength) {
     76    if (nullptr == cullRect) {
     77        SkASSERT(false); // Shouldn't ever occur in practice
     78        return false;
     79    }
     80 
     81    SkScalar dx = pts[1].x() - pts[0].x();
     82    SkScalar dy = pts[1].y() - pts[0].y();
     83 
     84    if ((dx && dy) || (!dx && !dy)) {
     85        return false;
     86    }
     87 
     88    SkRect bounds = *cullRect;
     89    outset_for_stroke(&bounds, rec);
     90 
     91    // cullRect is in device space while pts are in the local coordinate system
     92    // defined by the ctm. We want our answer in the local coordinate system.
     93 
     94    SkASSERT(ctm.rectStaysRect());
     95    SkMatrix inv;
     96    if (!ctm.invert(&inv)) {
     97        return false;
     98    }
     99 
    100    inv.mapRect(&bounds);
    101 
    102    if (dx) {
    103        SkASSERT(dx && !dy);
    104        SkScalar minX = pts[0].fX;
    105        SkScalar maxX = pts[1].fX;
    106 
    107        if (dx < 0) {
    108            using std::swap;
    109            swap(minX, maxX);
    110        }
    111 
    112        SkASSERT(minX < maxX);
    113        if (maxX <= bounds.fLeft || minX >= bounds.fRight) {
    114            return false;
    115        }
    116 
    117        // Now we actually perform the chop, removing the excess to the left and
    118        // right of the bounds (keeping our new line "in phase" with the dash,
    119        // hence the (mod intervalLength).
    120 
    121        if (minX < bounds.fLeft) {
    122            minX = bounds.fLeft - SkScalarMod(bounds.fLeft - minX, intervalLength);
    123        }
    124        if (maxX > bounds.fRight) {
    125            maxX = bounds.fRight + SkScalarMod(maxX - bounds.fRight, intervalLength);
    126        }
    127 
    128        SkASSERT(maxX > minX);
    129        if (dx < 0) {
    130            using std::swap;
    131            swap(minX, maxX);
    132        }
    133        pts[0].fX = minX;
    134        pts[1].fX = maxX;
    135    } else {
    136        SkASSERT(dy && !dx);
    137        SkScalar minY = pts[0].fY;
    138        SkScalar maxY = pts[1].fY;
    139 
    140        if (dy < 0) {
    141            using std::swap;
    142            swap(minY, maxY);
    143        }
    144 
    145        SkASSERT(minY < maxY);
    146        if (maxY <= bounds.fTop || minY >= bounds.fBottom) {
    147            return false;
    148        }
    149 
    150        // Now we actually perform the chop, removing the excess to the top and
    151        // bottom of the bounds (keeping our new line "in phase" with the dash,
    152        // hence the (mod intervalLength).
    153 
    154        if (minY < bounds.fTop) {
    155            minY = bounds.fTop - SkScalarMod(bounds.fTop - minY, intervalLength);
    156        }
    157        if (maxY > bounds.fBottom) {
    158            maxY = bounds.fBottom + SkScalarMod(maxY - bounds.fBottom, intervalLength);
    159        }
    160 
    161        SkASSERT(maxY > minY);
    162        if (dy < 0) {
    163            using std::swap;
    164            swap(minY, maxY);
    165        }
    166        pts[0].fY = minY;
    167        pts[1].fY = maxY;
    168    }
    169 
    170    return true;
    171 }
    172 
    173 // Currently asPoints is more restrictive then it needs to be. In the future
    174 // we need to:
    175 //      allow kRound_Cap capping (could allow rotations in the matrix with this)
    176 //      allow paths to be returned
    177 bool SkDashImpl::onAsPoints(PointData* results, const SkPath& src, const SkStrokeRec& rec,
    178                            const SkMatrix& matrix, const SkRect* cullRect) const {
    179    // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out
    180    if (0 >= rec.getWidth()) {
    181        return false;
    182    }
    183 
    184    // TODO: this next test could be eased up. We could allow any number of
    185    // intervals as long as all the ons match and all the offs match.
    186    // Additionally, they do not necessarily need to be integers.
    187    // We cannot allow arbitrary intervals since we want the returned points
    188    // to be uniformly sized.
    189    if (fIntervals.size() != 2 ||
    190        !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) ||
    191        !SkScalarIsInt(fIntervals[0]) ||
    192        !SkScalarIsInt(fIntervals[1])) {
    193        return false;
    194    }
    195 
    196    SkPoint pts[2];
    197 
    198    if (!src.isLine(pts)) {
    199        return false;
    200    }
    201 
    202    // TODO: this test could be eased up to allow circles
    203    if (SkPaint::kButt_Cap != rec.getCap()) {
    204        return false;
    205    }
    206 
    207    // TODO: this test could be eased up for circles. Rotations could be allowed.
    208    if (!matrix.rectStaysRect()) {
    209        return false;
    210    }
    211 
    212    // See if the line can be limited to something plausible.
    213    if (!cull_line(pts, rec, matrix, cullRect, fIntervalLength)) {
    214        return false;
    215    }
    216 
    217    SkScalar length = SkPoint::Distance(pts[1], pts[0]);
    218 
    219    SkVector tangent = pts[1] - pts[0];
    220    if (tangent.isZero()) {
    221        return false;
    222    }
    223 
    224    tangent.scale(SkScalarInvert(length));
    225 
    226    // TODO: make this test for horizontal & vertical lines more robust
    227    bool isXAxis = true;
    228    if (SkScalarNearlyEqual(SK_Scalar1, tangent.fX) ||
    229        SkScalarNearlyEqual(-SK_Scalar1, tangent.fX)) {
    230        results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth()));
    231    } else if (SkScalarNearlyEqual(SK_Scalar1, tangent.fY) ||
    232               SkScalarNearlyEqual(-SK_Scalar1, tangent.fY)) {
    233        results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0]));
    234        isXAxis = false;
    235    } else if (SkPaint::kRound_Cap != rec.getCap()) {
    236        // Angled lines don't have axis-aligned boxes.
    237        return false;
    238    }
    239 
    240    if (results) {
    241        results->fFlags = 0;
    242        SkScalar clampedInitialDashLength = std::min(length, fInitialDashLength);
    243 
    244        if (SkPaint::kRound_Cap == rec.getCap()) {
    245            results->fFlags |= PointData::kCircles_PointFlag;
    246        }
    247 
    248        results->fNumPoints = 0;
    249        SkScalar len2 = length;
    250        if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
    251            SkASSERT(len2 >= clampedInitialDashLength);
    252            if (0 == fInitialDashIndex) {
    253                if (clampedInitialDashLength > 0) {
    254                    if (clampedInitialDashLength >= fIntervals[0]) {
    255                        ++results->fNumPoints;  // partial first dash
    256                    }
    257                    len2 -= clampedInitialDashLength;
    258                }
    259                len2 -= fIntervals[1];  // also skip first space
    260                if (len2 < 0) {
    261                    len2 = 0;
    262                }
    263            } else {
    264                len2 -= clampedInitialDashLength; // skip initial partial empty
    265            }
    266        }
    267        // Too many midpoints can cause results->fNumPoints to overflow or
    268        // otherwise cause the results->fPoints allocation below to OOM.
    269        // Cap it to a sane value.
    270        SkScalar numIntervals = len2 / fIntervalLength;
    271        if (!SkIsFinite(numIntervals) || numIntervals > SkDashPath::kMaxDashCount) {
    272            return false;
    273        }
    274        int numMidPoints = SkScalarFloorToInt(numIntervals);
    275        results->fNumPoints += numMidPoints;
    276        len2 -= numMidPoints * fIntervalLength;
    277        bool partialLast = false;
    278        if (len2 > 0) {
    279            if (len2 < fIntervals[0]) {
    280                partialLast = true;
    281            } else {
    282                ++numMidPoints;
    283                ++results->fNumPoints;
    284            }
    285        }
    286 
    287        results->fPoints = new SkPoint[results->fNumPoints];
    288 
    289        SkScalar    distance = 0;
    290        int         curPt = 0;
    291 
    292        if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
    293            SkASSERT(clampedInitialDashLength <= length);
    294 
    295            if (0 == fInitialDashIndex) {
    296                if (clampedInitialDashLength > 0) {
    297                    // partial first block
    298                    SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
    299                    SkScalar x = pts[0].fX + tangent.fX * SkScalarHalf(clampedInitialDashLength);
    300                    SkScalar y = pts[0].fY + tangent.fY * SkScalarHalf(clampedInitialDashLength);
    301                    SkScalar halfWidth, halfHeight;
    302                    if (isXAxis) {
    303                        halfWidth = SkScalarHalf(clampedInitialDashLength);
    304                        halfHeight = SkScalarHalf(rec.getWidth());
    305                    } else {
    306                        halfWidth = SkScalarHalf(rec.getWidth());
    307                        halfHeight = SkScalarHalf(clampedInitialDashLength);
    308                    }
    309                    if (clampedInitialDashLength < fIntervals[0]) {
    310                        // This one will not be like the others
    311                        results->fFirst = SkPath::Rect({x - halfWidth, y - halfHeight,
    312                                                        x + halfWidth, y + halfHeight});
    313                    } else {
    314                        SkASSERT(curPt < results->fNumPoints);
    315                        results->fPoints[curPt].set(x, y);
    316                        ++curPt;
    317                    }
    318 
    319                    distance += clampedInitialDashLength;
    320                }
    321 
    322                distance += fIntervals[1];  // skip over the next blank block too
    323            } else {
    324                distance += clampedInitialDashLength;
    325            }
    326        }
    327 
    328        if (0 != numMidPoints) {
    329            distance += SkScalarHalf(fIntervals[0]);
    330 
    331            for (int i = 0; i < numMidPoints; ++i) {
    332                SkScalar x = pts[0].fX + tangent.fX * distance;
    333                SkScalar y = pts[0].fY + tangent.fY * distance;
    334 
    335                SkASSERT(curPt < results->fNumPoints);
    336                results->fPoints[curPt].set(x, y);
    337                ++curPt;
    338 
    339                distance += fIntervalLength;
    340            }
    341 
    342            distance -= SkScalarHalf(fIntervals[0]);
    343        }
    344 
    345        if (partialLast) {
    346            // partial final block
    347            SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
    348            SkScalar temp = length - distance;
    349            SkASSERT(temp < fIntervals[0]);
    350            SkScalar x = pts[0].fX + tangent.fX * (distance + SkScalarHalf(temp));
    351            SkScalar y = pts[0].fY + tangent.fY * (distance + SkScalarHalf(temp));
    352            SkScalar halfWidth, halfHeight;
    353            if (isXAxis) {
    354                halfWidth = SkScalarHalf(temp);
    355                halfHeight = SkScalarHalf(rec.getWidth());
    356            } else {
    357                halfWidth = SkScalarHalf(rec.getWidth());
    358                halfHeight = SkScalarHalf(temp);
    359            }
    360            results->fLast = SkPath::Rect({x - halfWidth, y - halfHeight,
    361                                           x + halfWidth, y + halfHeight});
    362        }
    363 
    364        SkASSERT(curPt == results->fNumPoints);
    365    }
    366 
    367    return true;
    368 }
    369 
    370 std::optional<SkPathEffectBase::DashInfo> SkDashImpl::asADash() const {
    371    return {{fIntervals, fPhase}};
    372 }
    373 
    374 void SkDashImpl::flatten(SkWriteBuffer& buffer) const {
    375    buffer.writeScalar(fPhase);
    376    buffer.writeScalarArray(fIntervals);
    377 }
    378 
    379 sk_sp<SkFlattenable> SkDashImpl::CreateProc(SkReadBuffer& buffer) {
    380    const SkScalar phase = buffer.readScalar();
    381    uint32_t count = buffer.getArrayCount();
    382 
    383    // Don't allocate gigantic buffers if there's not data for them.
    384    if (!buffer.validateCanReadN<SkScalar>(count)) {
    385        return nullptr;
    386    }
    387 
    388    AutoSTArray<32, SkScalar> intervals(count);
    389    if (buffer.readScalarArray(intervals)) {
    390        return SkDashPathEffect::Make(intervals, phase);
    391    }
    392    return nullptr;
    393 }
    394 
    395 //////////////////////////////////////////////////////////////////////////////////////////////////
    396 
    397 sk_sp<SkPathEffect> SkDashPathEffect::Make(SkSpan<const SkScalar> intervals, SkScalar phase) {
    398    if (!SkDashPath::ValidDashPath(phase, intervals)) {
    399        return nullptr;
    400    }
    401    return sk_sp<SkPathEffect>(new SkDashImpl(intervals, phase));
    402 }