Home | History | Annotate | Download | only in debugger
      1 /*
      2  * Copyright 2012 Google Inc.
      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 "SkCanvasPriv.h"
      9 #include "SkClipStack.h"
     10 #include "SkDebugCanvas.h"
     11 #include "SkDrawCommand.h"
     12 #include "SkDevice.h"
     13 #include "SkPaintFilterCanvas.h"
     14 #include "SkOverdrawMode.h"
     15 
     16 #define SKDEBUGCANVAS_VERSION            1
     17 #define SKDEBUGCANVAS_ATTRIBUTE_VERSION  "version"
     18 #define SKDEBUGCANVAS_ATTRIBUTE_COMMANDS "commands"
     19 
     20 class DebugPaintFilterCanvas : public SkPaintFilterCanvas {
     21 public:
     22     DebugPaintFilterCanvas(int width,
     23                            int height,
     24                            bool overdrawViz,
     25                            bool overrideFilterQuality,
     26                            SkFilterQuality quality)
     27         : INHERITED(width, height)
     28         , fOverdrawXfermode(overdrawViz ? SkOverdrawMode::Create() : nullptr)
     29         , fOverrideFilterQuality(overrideFilterQuality)
     30         , fFilterQuality(quality) {}
     31 
     32 protected:
     33     bool onFilter(SkTCopyOnFirstWrite<SkPaint>* paint, Type) const override {
     34         if (*paint) {
     35             if (nullptr != fOverdrawXfermode.get()) {
     36                 paint->writable()->setAntiAlias(false);
     37                 paint->writable()->setXfermode(fOverdrawXfermode.get());
     38             }
     39 
     40             if (fOverrideFilterQuality) {
     41                 paint->writable()->setFilterQuality(fFilterQuality);
     42             }
     43         }
     44         return true;
     45     }
     46 
     47     void onDrawPicture(const SkPicture* picture,
     48                        const SkMatrix* matrix,
     49                        const SkPaint* paint) override {
     50         // We need to replay the picture onto this canvas in order to filter its internal paints.
     51         this->SkCanvas::onDrawPicture(picture, matrix, paint);
     52     }
     53 
     54 private:
     55     SkAutoTUnref<SkXfermode> fOverdrawXfermode;
     56 
     57     bool fOverrideFilterQuality;
     58     SkFilterQuality fFilterQuality;
     59 
     60     typedef SkPaintFilterCanvas INHERITED;
     61 };
     62 
     63 SkDebugCanvas::SkDebugCanvas(int width, int height)
     64         : INHERITED(width, height)
     65         , fPicture(nullptr)
     66         , fFilter(false)
     67         , fMegaVizMode(false)
     68         , fOverdrawViz(false)
     69         , fOverrideFilterQuality(false)
     70         , fFilterQuality(kNone_SkFilterQuality)
     71         , fClipVizColor(SK_ColorTRANSPARENT) {
     72     fUserMatrix.reset();
     73 
     74     // SkPicturePlayback uses the base-class' quickReject calls to cull clipped
     75     // operations. This can lead to problems in the debugger which expects all
     76     // the operations in the captured skp to appear in the debug canvas. To
     77     // circumvent this we create a wide open clip here (an empty clip rect
     78     // is not sufficient).
     79     // Internally, the SkRect passed to clipRect is converted to an SkIRect and
     80     // rounded out. The following code creates a nearly maximal rect that will
     81     // not get collapsed by the coming conversions (Due to precision loss the
     82     // inset has to be surprisingly large).
     83     SkIRect largeIRect = SkIRect::MakeLargest();
     84     largeIRect.inset(1024, 1024);
     85     SkRect large = SkRect::Make(largeIRect);
     86 #ifdef SK_DEBUG
     87     SkASSERT(!large.roundOut().isEmpty());
     88 #endif
     89     // call the base class' version to avoid adding a draw command
     90     this->INHERITED::onClipRect(large, SkRegion::kReplace_Op, kHard_ClipEdgeStyle);
     91 }
     92 
     93 SkDebugCanvas::~SkDebugCanvas() {
     94     fCommandVector.deleteAll();
     95 }
     96 
     97 void SkDebugCanvas::addDrawCommand(SkDrawCommand* command) {
     98     fCommandVector.push(command);
     99 }
    100 
    101 void SkDebugCanvas::draw(SkCanvas* canvas) {
    102     if (!fCommandVector.isEmpty()) {
    103         this->drawTo(canvas, fCommandVector.count() - 1);
    104     }
    105 }
    106 
    107 void SkDebugCanvas::applyUserTransform(SkCanvas* canvas) {
    108     canvas->concat(fUserMatrix);
    109 }
    110 
    111 int SkDebugCanvas::getCommandAtPoint(int x, int y, int index) {
    112     SkBitmap bitmap;
    113     bitmap.allocPixels(SkImageInfo::MakeN32Premul(1, 1));
    114 
    115     SkCanvas canvas(bitmap);
    116     canvas.translate(SkIntToScalar(-x), SkIntToScalar(-y));
    117     this->applyUserTransform(&canvas);
    118 
    119     int layer = 0;
    120     SkColor prev = bitmap.getColor(0,0);
    121     for (int i = 0; i < index; i++) {
    122         if (fCommandVector[i]->isVisible()) {
    123             fCommandVector[i]->setUserMatrix(fUserMatrix);
    124             fCommandVector[i]->execute(&canvas);
    125         }
    126         if (prev != bitmap.getColor(0,0)) {
    127             layer = i;
    128         }
    129         prev = bitmap.getColor(0,0);
    130     }
    131     return layer;
    132 }
    133 
    134 class SkDebugClipVisitor : public SkCanvas::ClipVisitor {
    135 public:
    136     SkDebugClipVisitor(SkCanvas* canvas) : fCanvas(canvas) {}
    137 
    138     void clipRect(const SkRect& r, SkRegion::Op, bool doAA) override {
    139         SkPaint p;
    140         p.setColor(SK_ColorRED);
    141         p.setStyle(SkPaint::kStroke_Style);
    142         p.setAntiAlias(doAA);
    143         fCanvas->drawRect(r, p);
    144     }
    145     void clipRRect(const SkRRect& rr, SkRegion::Op, bool doAA) override {
    146         SkPaint p;
    147         p.setColor(SK_ColorGREEN);
    148         p.setStyle(SkPaint::kStroke_Style);
    149         p.setAntiAlias(doAA);
    150         fCanvas->drawRRect(rr, p);
    151     }
    152     void clipPath(const SkPath& path, SkRegion::Op, bool doAA) override {
    153         SkPaint p;
    154         p.setColor(SK_ColorBLUE);
    155         p.setStyle(SkPaint::kStroke_Style);
    156         p.setAntiAlias(doAA);
    157         fCanvas->drawPath(path, p);
    158     }
    159 
    160 protected:
    161     SkCanvas* fCanvas;
    162 
    163 private:
    164     typedef SkCanvas::ClipVisitor INHERITED;
    165 };
    166 
    167 // set up the saveLayer commands so that the active ones
    168 // return true in their 'active' method
    169 void SkDebugCanvas::markActiveCommands(int index) {
    170     fActiveLayers.rewind();
    171 
    172     for (int i = 0; i < fCommandVector.count(); ++i) {
    173         fCommandVector[i]->setActive(false);
    174     }
    175 
    176     for (int i = 0; i < index; ++i) {
    177         SkDrawCommand::Action result = fCommandVector[i]->action();
    178         if (SkDrawCommand::kPushLayer_Action == result) {
    179             fActiveLayers.push(fCommandVector[i]);
    180         } else if (SkDrawCommand::kPopLayer_Action == result) {
    181             fActiveLayers.pop();
    182         }
    183     }
    184 
    185     for (int i = 0; i < fActiveLayers.count(); ++i) {
    186         fActiveLayers[i]->setActive(true);
    187     }
    188 
    189 }
    190 
    191 void SkDebugCanvas::drawTo(SkCanvas* canvas, int index) {
    192     SkASSERT(!fCommandVector.isEmpty());
    193     SkASSERT(index < fCommandVector.count());
    194 
    195     int saveCount = canvas->save();
    196 
    197     SkRect windowRect = SkRect::MakeWH(SkIntToScalar(canvas->getBaseLayerSize().width()),
    198                                        SkIntToScalar(canvas->getBaseLayerSize().height()));
    199 
    200     bool pathOpsMode = getAllowSimplifyClip();
    201     canvas->setAllowSimplifyClip(pathOpsMode);
    202     canvas->clear(SK_ColorWHITE);
    203     canvas->resetMatrix();
    204     if (!windowRect.isEmpty()) {
    205         canvas->clipRect(windowRect, SkRegion::kReplace_Op);
    206     }
    207     this->applyUserTransform(canvas);
    208 
    209     if (fPaintFilterCanvas) {
    210         fPaintFilterCanvas->addCanvas(canvas);
    211         canvas = fPaintFilterCanvas.get();
    212     }
    213 
    214     if (fMegaVizMode) {
    215         this->markActiveCommands(index);
    216     }
    217 
    218     for (int i = 0; i <= index; i++) {
    219         if (i == index && fFilter) {
    220             canvas->clear(0xAAFFFFFF);
    221         }
    222 
    223         if (fCommandVector[i]->isVisible()) {
    224             if (fMegaVizMode && fCommandVector[i]->active()) {
    225                 // "active" commands execute their visualization behaviors:
    226                 //     All active saveLayers get replaced with saves so all draws go to the
    227                 //     visible canvas.
    228                 //     All active culls draw their cull box
    229                 fCommandVector[i]->vizExecute(canvas);
    230             } else {
    231                 fCommandVector[i]->setUserMatrix(fUserMatrix);
    232                 fCommandVector[i]->execute(canvas);
    233             }
    234         }
    235     }
    236 
    237     if (SkColorGetA(fClipVizColor) != 0) {
    238         canvas->save();
    239         #define LARGE_COORD 1000000000
    240         canvas->clipRect(SkRect::MakeLTRB(-LARGE_COORD, -LARGE_COORD, LARGE_COORD, LARGE_COORD),
    241                        SkRegion::kReverseDifference_Op);
    242         SkPaint clipPaint;
    243         clipPaint.setColor(fClipVizColor);
    244         canvas->drawPaint(clipPaint);
    245         canvas->restore();
    246     }
    247 
    248     if (fMegaVizMode) {
    249         canvas->save();
    250         // nuke the CTM
    251         canvas->resetMatrix();
    252         // turn off clipping
    253         if (!windowRect.isEmpty()) {
    254             SkRect r = windowRect;
    255             r.outset(SK_Scalar1, SK_Scalar1);
    256             canvas->clipRect(r, SkRegion::kReplace_Op);
    257         }
    258         // visualize existing clips
    259         SkDebugClipVisitor visitor(canvas);
    260 
    261         canvas->replayClips(&visitor);
    262 
    263         canvas->restore();
    264     }
    265     if (pathOpsMode) {
    266         this->resetClipStackData();
    267         const SkClipStack* clipStack = canvas->getClipStack();
    268         SkClipStack::Iter iter(*clipStack, SkClipStack::Iter::kBottom_IterStart);
    269         const SkClipStack::Element* element;
    270         SkPath devPath;
    271         while ((element = iter.next())) {
    272             SkClipStack::Element::Type type = element->getType();
    273             SkPath operand;
    274             if (type != SkClipStack::Element::kEmpty_Type) {
    275                element->asPath(&operand);
    276             }
    277             SkRegion::Op elementOp = element->getOp();
    278             this->addClipStackData(devPath, operand, elementOp);
    279             if (elementOp == SkRegion::kReplace_Op) {
    280                 devPath = operand;
    281             } else {
    282                 Op(devPath, operand, (SkPathOp) elementOp, &devPath);
    283             }
    284         }
    285         this->lastClipStackData(devPath);
    286     }
    287     fMatrix = canvas->getTotalMatrix();
    288     if (!canvas->getClipDeviceBounds(&fClip)) {
    289         fClip.setEmpty();
    290     }
    291 
    292     canvas->restoreToCount(saveCount);
    293 
    294     if (fPaintFilterCanvas) {
    295         fPaintFilterCanvas->removeAll();
    296     }
    297 }
    298 
    299 void SkDebugCanvas::deleteDrawCommandAt(int index) {
    300     SkASSERT(index < fCommandVector.count());
    301     delete fCommandVector[index];
    302     fCommandVector.remove(index);
    303 }
    304 
    305 SkDrawCommand* SkDebugCanvas::getDrawCommandAt(int index) {
    306     SkASSERT(index < fCommandVector.count());
    307     return fCommandVector[index];
    308 }
    309 
    310 void SkDebugCanvas::setDrawCommandAt(int index, SkDrawCommand* command) {
    311     SkASSERT(index < fCommandVector.count());
    312     delete fCommandVector[index];
    313     fCommandVector[index] = command;
    314 }
    315 
    316 const SkTDArray<SkString*>* SkDebugCanvas::getCommandInfo(int index) const {
    317     SkASSERT(index < fCommandVector.count());
    318     return fCommandVector[index]->Info();
    319 }
    320 
    321 bool SkDebugCanvas::getDrawCommandVisibilityAt(int index) {
    322     SkASSERT(index < fCommandVector.count());
    323     return fCommandVector[index]->isVisible();
    324 }
    325 
    326 const SkTDArray <SkDrawCommand*>& SkDebugCanvas::getDrawCommands() const {
    327     return fCommandVector;
    328 }
    329 
    330 SkTDArray <SkDrawCommand*>& SkDebugCanvas::getDrawCommands() {
    331     return fCommandVector;
    332 }
    333 
    334 Json::Value SkDebugCanvas::toJSON(UrlDataManager& urlDataManager, int n, SkCanvas* canvas) {
    335     Json::Value result = Json::Value(Json::objectValue);
    336     result[SKDEBUGCANVAS_ATTRIBUTE_VERSION] = Json::Value(SKDEBUGCANVAS_VERSION);
    337     Json::Value commands = Json::Value(Json::arrayValue);
    338     for (int i = 0; i < this->getSize() && i <= n; i++) {
    339         commands[i] = this->getDrawCommandAt(i)->drawToAndCollectJSON(canvas, urlDataManager);
    340     }
    341     result[SKDEBUGCANVAS_ATTRIBUTE_COMMANDS] = commands;
    342     return result;
    343 }
    344 
    345 void SkDebugCanvas::updatePaintFilterCanvas() {
    346     if (!fOverdrawViz && !fOverrideFilterQuality) {
    347         fPaintFilterCanvas.reset(nullptr);
    348         return;
    349     }
    350 
    351     const SkImageInfo info = this->imageInfo();
    352     fPaintFilterCanvas.reset(new DebugPaintFilterCanvas(info.width(), info.height(), fOverdrawViz,
    353                                                         fOverrideFilterQuality, fFilterQuality));
    354 }
    355 
    356 void SkDebugCanvas::setOverdrawViz(bool overdrawViz) {
    357     if (fOverdrawViz == overdrawViz) {
    358         return;
    359     }
    360 
    361     fOverdrawViz = overdrawViz;
    362     this->updatePaintFilterCanvas();
    363 }
    364 
    365 void SkDebugCanvas::overrideTexFiltering(bool overrideTexFiltering, SkFilterQuality quality) {
    366     if (fOverrideFilterQuality == overrideTexFiltering && fFilterQuality == quality) {
    367         return;
    368     }
    369 
    370     fOverrideFilterQuality = overrideTexFiltering;
    371     fFilterQuality = quality;
    372     this->updatePaintFilterCanvas();
    373 }
    374 
    375 void SkDebugCanvas::onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
    376     this->addDrawCommand(new SkClipPathCommand(path, op, kSoft_ClipEdgeStyle == edgeStyle));
    377 }
    378 
    379 void SkDebugCanvas::onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
    380     this->addDrawCommand(new SkClipRectCommand(rect, op, kSoft_ClipEdgeStyle == edgeStyle));
    381 }
    382 
    383 void SkDebugCanvas::onClipRRect(const SkRRect& rrect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
    384     this->addDrawCommand(new SkClipRRectCommand(rrect, op, kSoft_ClipEdgeStyle == edgeStyle));
    385 }
    386 
    387 void SkDebugCanvas::onClipRegion(const SkRegion& region, SkRegion::Op op) {
    388     this->addDrawCommand(new SkClipRegionCommand(region, op));
    389 }
    390 
    391 void SkDebugCanvas::didConcat(const SkMatrix& matrix) {
    392     this->addDrawCommand(new SkConcatCommand(matrix));
    393     this->INHERITED::didConcat(matrix);
    394 }
    395 
    396 void SkDebugCanvas::onDrawBitmap(const SkBitmap& bitmap, SkScalar left,
    397                                  SkScalar top, const SkPaint* paint) {
    398     this->addDrawCommand(new SkDrawBitmapCommand(bitmap, left, top, paint));
    399 }
    400 
    401 void SkDebugCanvas::onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst,
    402                                      const SkPaint* paint, SrcRectConstraint constraint) {
    403     this->addDrawCommand(new SkDrawBitmapRectCommand(bitmap, src, dst, paint,
    404                                                      (SrcRectConstraint)constraint));
    405 }
    406 
    407 void SkDebugCanvas::onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
    408                                      const SkRect& dst, const SkPaint* paint) {
    409     this->addDrawCommand(new SkDrawBitmapNineCommand(bitmap, center, dst, paint));
    410 }
    411 
    412 void SkDebugCanvas::onDrawImage(const SkImage* image, SkScalar left, SkScalar top,
    413                                 const SkPaint* paint) {
    414     this->addDrawCommand(new SkDrawImageCommand(image, left, top, paint));
    415 }
    416 
    417 void SkDebugCanvas::onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst,
    418                                     const SkPaint* paint, SrcRectConstraint constraint) {
    419     this->addDrawCommand(new SkDrawImageRectCommand(image, src, dst, paint, constraint));
    420 }
    421 
    422 void SkDebugCanvas::onDrawOval(const SkRect& oval, const SkPaint& paint) {
    423     this->addDrawCommand(new SkDrawOvalCommand(oval, paint));
    424 }
    425 
    426 void SkDebugCanvas::onDrawPaint(const SkPaint& paint) {
    427     this->addDrawCommand(new SkDrawPaintCommand(paint));
    428 }
    429 
    430 void SkDebugCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) {
    431     this->addDrawCommand(new SkDrawPathCommand(path, paint));
    432 }
    433 
    434 void SkDebugCanvas::onDrawPicture(const SkPicture* picture,
    435                                   const SkMatrix* matrix,
    436                                   const SkPaint* paint) {
    437     this->addDrawCommand(new SkBeginDrawPictureCommand(picture, matrix, paint));
    438     SkAutoCanvasMatrixPaint acmp(this, matrix, paint, picture->cullRect());
    439     picture->playback(this);
    440     this->addDrawCommand(new SkEndDrawPictureCommand(SkToBool(matrix) || SkToBool(paint)));
    441 }
    442 
    443 void SkDebugCanvas::onDrawPoints(PointMode mode, size_t count,
    444                                  const SkPoint pts[], const SkPaint& paint) {
    445     this->addDrawCommand(new SkDrawPointsCommand(mode, count, pts, paint));
    446 }
    447 
    448 void SkDebugCanvas::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
    449                                   const SkPaint& paint) {
    450     this->addDrawCommand(new SkDrawPosTextCommand(text, byteLength, pos, paint));
    451 }
    452 
    453 void SkDebugCanvas::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
    454                                    SkScalar constY, const SkPaint& paint) {
    455     this->addDrawCommand(
    456         new SkDrawPosTextHCommand(text, byteLength, xpos, constY, paint));
    457 }
    458 
    459 void SkDebugCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) {
    460     // NOTE(chudy): Messing up when renamed to DrawRect... Why?
    461     addDrawCommand(new SkDrawRectCommand(rect, paint));
    462 }
    463 
    464 void SkDebugCanvas::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) {
    465     this->addDrawCommand(new SkDrawRRectCommand(rrect, paint));
    466 }
    467 
    468 void SkDebugCanvas::onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
    469                                  const SkPaint& paint) {
    470     this->addDrawCommand(new SkDrawDRRectCommand(outer, inner, paint));
    471 }
    472 
    473 void SkDebugCanvas::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
    474                                const SkPaint& paint) {
    475     this->addDrawCommand(new SkDrawTextCommand(text, byteLength, x, y, paint));
    476 }
    477 
    478 void SkDebugCanvas::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path,
    479                                      const SkMatrix* matrix, const SkPaint& paint) {
    480     this->addDrawCommand(
    481         new SkDrawTextOnPathCommand(text, byteLength, path, matrix, paint));
    482 }
    483 
    484 void SkDebugCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
    485                                    const SkPaint& paint) {
    486     this->addDrawCommand(new SkDrawTextBlobCommand(blob, x, y, paint));
    487 }
    488 
    489 void SkDebugCanvas::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
    490                                 const SkPoint texCoords[4], SkXfermode* xmode,
    491                                 const SkPaint& paint) {
    492     this->addDrawCommand(new SkDrawPatchCommand(cubics, colors, texCoords, xmode, paint));
    493 }
    494 
    495 void SkDebugCanvas::onDrawVertices(VertexMode vmode, int vertexCount, const SkPoint vertices[],
    496                                    const SkPoint texs[], const SkColor colors[],
    497                                    SkXfermode*, const uint16_t indices[], int indexCount,
    498                                    const SkPaint& paint) {
    499     this->addDrawCommand(new SkDrawVerticesCommand(vmode, vertexCount, vertices,
    500                          texs, colors, nullptr, indices, indexCount, paint));
    501 }
    502 
    503 void SkDebugCanvas::willRestore() {
    504     this->addDrawCommand(new SkRestoreCommand());
    505     this->INHERITED::willRestore();
    506 }
    507 
    508 void SkDebugCanvas::willSave() {
    509     this->addDrawCommand(new SkSaveCommand());
    510     this->INHERITED::willSave();
    511 }
    512 
    513 SkCanvas::SaveLayerStrategy SkDebugCanvas::getSaveLayerStrategy(const SaveLayerRec& rec) {
    514     this->addDrawCommand(new SkSaveLayerCommand(rec));
    515     (void)this->INHERITED::getSaveLayerStrategy(rec);
    516     // No need for a full layer.
    517     return kNoLayer_SaveLayerStrategy;
    518 }
    519 
    520 void SkDebugCanvas::didSetMatrix(const SkMatrix& matrix) {
    521     this->addDrawCommand(new SkSetMatrixCommand(matrix));
    522     this->INHERITED::didSetMatrix(matrix);
    523 }
    524 
    525 void SkDebugCanvas::toggleCommand(int index, bool toggle) {
    526     SkASSERT(index < fCommandVector.count());
    527     fCommandVector[index]->setVisible(toggle);
    528 }
    529 
    530 static const char* gFillTypeStrs[] = {
    531     "kWinding_FillType",
    532     "kEvenOdd_FillType",
    533     "kInverseWinding_FillType",
    534     "kInverseEvenOdd_FillType"
    535 };
    536 
    537 static const char* gOpStrs[] = {
    538     "kDifference_PathOp",
    539     "kIntersect_PathOp",
    540     "kUnion_PathOp",
    541     "kXor_PathOp",
    542     "kReverseDifference_PathOp",
    543 };
    544 
    545 static const char kHTML4SpaceIndent[] = "&nbsp;&nbsp;&nbsp;&nbsp;";
    546 
    547 void SkDebugCanvas::outputScalar(SkScalar num) {
    548     if (num == (int) num) {
    549         fClipStackData.appendf("%d", (int) num);
    550     } else {
    551         SkString str;
    552         str.printf("%1.9g", num);
    553         int width = (int) str.size();
    554         const char* cStr = str.c_str();
    555         while (cStr[width - 1] == '0') {
    556             --width;
    557         }
    558         str.resize(width);
    559         fClipStackData.appendf("%sf", str.c_str());
    560     }
    561 }
    562 
    563 void SkDebugCanvas::outputPointsCommon(const SkPoint* pts, int count) {
    564     for (int index = 0; index < count; ++index) {
    565         this->outputScalar(pts[index].fX);
    566         fClipStackData.appendf(", ");
    567         this->outputScalar(pts[index].fY);
    568         if (index + 1 < count) {
    569             fClipStackData.appendf(", ");
    570         }
    571     }
    572 }
    573 
    574 void SkDebugCanvas::outputPoints(const SkPoint* pts, int count) {
    575     this->outputPointsCommon(pts, count);
    576     fClipStackData.appendf(");<br>");
    577 }
    578 
    579 void SkDebugCanvas::outputConicPoints(const SkPoint* pts, SkScalar weight) {
    580     this->outputPointsCommon(pts, 2);
    581     fClipStackData.appendf(", ");
    582     this->outputScalar(weight);
    583     fClipStackData.appendf(");<br>");
    584 }
    585 
    586 void SkDebugCanvas::addPathData(const SkPath& path, const char* pathName) {
    587     SkPath::RawIter iter(path);
    588     SkPath::FillType fillType = path.getFillType();
    589     fClipStackData.appendf("%sSkPath %s;<br>", kHTML4SpaceIndent, pathName);
    590     fClipStackData.appendf("%s%s.setFillType(SkPath::%s);<br>", kHTML4SpaceIndent, pathName,
    591             gFillTypeStrs[fillType]);
    592     iter.setPath(path);
    593     uint8_t verb;
    594     SkPoint pts[4];
    595     while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
    596         switch (verb) {
    597             case SkPath::kMove_Verb:
    598                 fClipStackData.appendf("%s%s.moveTo(", kHTML4SpaceIndent, pathName);
    599                 this->outputPoints(&pts[0], 1);
    600                 continue;
    601             case SkPath::kLine_Verb:
    602                 fClipStackData.appendf("%s%s.lineTo(", kHTML4SpaceIndent, pathName);
    603                 this->outputPoints(&pts[1], 1);
    604                 break;
    605             case SkPath::kQuad_Verb:
    606                 fClipStackData.appendf("%s%s.quadTo(", kHTML4SpaceIndent, pathName);
    607                 this->outputPoints(&pts[1], 2);
    608                 break;
    609             case SkPath::kConic_Verb:
    610                 fClipStackData.appendf("%s%s.conicTo(", kHTML4SpaceIndent, pathName);
    611                 this->outputConicPoints(&pts[1], iter.conicWeight());
    612                 break;
    613             case SkPath::kCubic_Verb:
    614                 fClipStackData.appendf("%s%s.cubicTo(", kHTML4SpaceIndent, pathName);
    615                 this->outputPoints(&pts[1], 3);
    616                 break;
    617             case SkPath::kClose_Verb:
    618                 fClipStackData.appendf("%s%s.close();<br>", kHTML4SpaceIndent, pathName);
    619                 break;
    620             default:
    621                 SkDEBUGFAIL("bad verb");
    622                 return;
    623         }
    624     }
    625 }
    626 
    627 void SkDebugCanvas::addClipStackData(const SkPath& devPath, const SkPath& operand,
    628                                      SkRegion::Op elementOp) {
    629     if (elementOp == SkRegion::kReplace_Op) {
    630         if (!lastClipStackData(devPath)) {
    631             fSaveDevPath = operand;
    632         }
    633         fCalledAddStackData = false;
    634     } else {
    635         fClipStackData.appendf("<br>static void test(skiatest::Reporter* reporter,"
    636             " const char* filename) {<br>");
    637         addPathData(fCalledAddStackData ? devPath : fSaveDevPath, "path");
    638         addPathData(operand, "pathB");
    639         fClipStackData.appendf("%stestPathOp(reporter, path, pathB, %s, filename);<br>",
    640             kHTML4SpaceIndent, gOpStrs[elementOp]);
    641         fClipStackData.appendf("}<br>");
    642         fCalledAddStackData = true;
    643     }
    644 }
    645 
    646 bool SkDebugCanvas::lastClipStackData(const SkPath& devPath) {
    647     if (fCalledAddStackData) {
    648         fClipStackData.appendf("<br>");
    649         addPathData(devPath, "pathOut");
    650         return true;
    651     }
    652     return false;
    653 }
    654