SkDebugCanvas.cpp revision 08fc80704d369508276c0bd9d34eb4f8cb2c2f54
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
20class DebugPaintFilterCanvas : public SkPaintFilterCanvas {
21public:
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
32protected:
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
54private:
55    SkAutoTUnref<SkXfermode> fOverdrawXfermode;
56
57    bool fOverrideFilterQuality;
58    SkFilterQuality fFilterQuality;
59
60    typedef SkPaintFilterCanvas INHERITED;
61};
62
63SkDebugCanvas::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
93SkDebugCanvas::~SkDebugCanvas() {
94    fCommandVector.deleteAll();
95}
96
97void SkDebugCanvas::addDrawCommand(SkDrawCommand* command) {
98    fCommandVector.push(command);
99}
100
101void SkDebugCanvas::draw(SkCanvas* canvas) {
102    if (!fCommandVector.isEmpty()) {
103        this->drawTo(canvas, fCommandVector.count() - 1);
104    }
105}
106
107void SkDebugCanvas::applyUserTransform(SkCanvas* canvas) {
108    canvas->concat(fUserMatrix);
109}
110
111int 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
134class SkDebugClipVisitor : public SkCanvas::ClipVisitor {
135public:
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
160protected:
161    SkCanvas* fCanvas;
162
163private:
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
169void 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
191void 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
299void SkDebugCanvas::deleteDrawCommandAt(int index) {
300    SkASSERT(index < fCommandVector.count());
301    delete fCommandVector[index];
302    fCommandVector.remove(index);
303}
304
305SkDrawCommand* SkDebugCanvas::getDrawCommandAt(int index) {
306    SkASSERT(index < fCommandVector.count());
307    return fCommandVector[index];
308}
309
310void SkDebugCanvas::setDrawCommandAt(int index, SkDrawCommand* command) {
311    SkASSERT(index < fCommandVector.count());
312    delete fCommandVector[index];
313    fCommandVector[index] = command;
314}
315
316const SkTDArray<SkString*>* SkDebugCanvas::getCommandInfo(int index) const {
317    SkASSERT(index < fCommandVector.count());
318    return fCommandVector[index]->Info();
319}
320
321bool SkDebugCanvas::getDrawCommandVisibilityAt(int index) {
322    SkASSERT(index < fCommandVector.count());
323    return fCommandVector[index]->isVisible();
324}
325
326const SkTDArray <SkDrawCommand*>& SkDebugCanvas::getDrawCommands() const {
327    return fCommandVector;
328}
329
330SkTDArray <SkDrawCommand*>& SkDebugCanvas::getDrawCommands() {
331    return fCommandVector;
332}
333
334Json::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
345void 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
356void SkDebugCanvas::setOverdrawViz(bool overdrawViz) {
357    if (fOverdrawViz == overdrawViz) {
358        return;
359    }
360
361    fOverdrawViz = overdrawViz;
362    this->updatePaintFilterCanvas();
363}
364
365void 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
375void SkDebugCanvas::onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
376    this->addDrawCommand(new SkClipPathCommand(path, op, kSoft_ClipEdgeStyle == edgeStyle));
377}
378
379void SkDebugCanvas::onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
380    this->addDrawCommand(new SkClipRectCommand(rect, op, kSoft_ClipEdgeStyle == edgeStyle));
381}
382
383void SkDebugCanvas::onClipRRect(const SkRRect& rrect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
384    this->addDrawCommand(new SkClipRRectCommand(rrect, op, kSoft_ClipEdgeStyle == edgeStyle));
385}
386
387void SkDebugCanvas::onClipRegion(const SkRegion& region, SkRegion::Op op) {
388    this->addDrawCommand(new SkClipRegionCommand(region, op));
389}
390
391void SkDebugCanvas::didConcat(const SkMatrix& matrix) {
392    this->addDrawCommand(new SkConcatCommand(matrix));
393    this->INHERITED::didConcat(matrix);
394}
395
396void SkDebugCanvas::onDrawBitmap(const SkBitmap& bitmap, SkScalar left,
397                                 SkScalar top, const SkPaint* paint) {
398    this->addDrawCommand(new SkDrawBitmapCommand(bitmap, left, top, paint));
399}
400
401void 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
407void 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
412void SkDebugCanvas::onDrawImage(const SkImage* image, SkScalar left, SkScalar top,
413                                const SkPaint* paint) {
414    this->addDrawCommand(new SkDrawImageCommand(image, left, top, paint));
415}
416
417void 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
422void SkDebugCanvas::onDrawOval(const SkRect& oval, const SkPaint& paint) {
423    this->addDrawCommand(new SkDrawOvalCommand(oval, paint));
424}
425
426void SkDebugCanvas::onDrawPaint(const SkPaint& paint) {
427    this->addDrawCommand(new SkDrawPaintCommand(paint));
428}
429
430void SkDebugCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) {
431    this->addDrawCommand(new SkDrawPathCommand(path, paint));
432}
433
434void 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
443void 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
448void 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
453void 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
459void 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
464void SkDebugCanvas::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) {
465    this->addDrawCommand(new SkDrawRRectCommand(rrect, paint));
466}
467
468void SkDebugCanvas::onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
469                                 const SkPaint& paint) {
470    this->addDrawCommand(new SkDrawDRRectCommand(outer, inner, paint));
471}
472
473void 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
478void 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
484void SkDebugCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
485                                   const SkPaint& paint) {
486    this->addDrawCommand(new SkDrawTextBlobCommand(blob, x, y, paint));
487}
488
489void 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
495void 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
503void SkDebugCanvas::willRestore() {
504    this->addDrawCommand(new SkRestoreCommand());
505    this->INHERITED::willRestore();
506}
507
508void SkDebugCanvas::willSave() {
509    this->addDrawCommand(new SkSaveCommand());
510    this->INHERITED::willSave();
511}
512
513SkCanvas::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
520void SkDebugCanvas::didSetMatrix(const SkMatrix& matrix) {
521    this->addDrawCommand(new SkSetMatrixCommand(matrix));
522    this->INHERITED::didSetMatrix(matrix);
523}
524
525void SkDebugCanvas::toggleCommand(int index, bool toggle) {
526    SkASSERT(index < fCommandVector.count());
527    fCommandVector[index]->setVisible(toggle);
528}
529
530static const char* gFillTypeStrs[] = {
531    "kWinding_FillType",
532    "kEvenOdd_FillType",
533    "kInverseWinding_FillType",
534    "kInverseEvenOdd_FillType"
535};
536
537static const char* gOpStrs[] = {
538    "kDifference_PathOp",
539    "kIntersect_PathOp",
540    "kUnion_PathOp",
541    "kXor_PathOp",
542    "kReverseDifference_PathOp",
543};
544
545static const char kHTML4SpaceIndent[] = "&nbsp;&nbsp;&nbsp;&nbsp;";
546
547void 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
563void 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
574void SkDebugCanvas::outputPoints(const SkPoint* pts, int count) {
575    this->outputPointsCommon(pts, count);
576    fClipStackData.appendf(");<br>");
577}
578
579void 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
586void 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
627void 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
646bool 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