1
2/*
3 * Copyright 2012 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10#include "SkColorPriv.h"
11#include "SkDebugCanvas.h"
12#include "SkDrawCommand.h"
13#include "SkDrawFilter.h"
14#include "SkDevice.h"
15#include "SkXfermode.h"
16
17SkDebugCanvas::SkDebugCanvas(int width, int height)
18        : INHERITED(width, height)
19        , fPicture(NULL)
20        , fWidth(width)
21        , fHeight(height)
22        , fFilter(false)
23        , fMegaVizMode(false)
24        , fIndex(0)
25        , fOverdrawViz(false)
26        , fOverdrawFilter(NULL)
27        , fOverrideTexFiltering(false)
28        , fTexOverrideFilter(NULL)
29        , fOutstandingSaveCount(0) {
30    fUserMatrix.reset();
31
32    // SkPicturePlayback uses the base-class' quickReject calls to cull clipped
33    // operations. This can lead to problems in the debugger which expects all
34    // the operations in the captured skp to appear in the debug canvas. To
35    // circumvent this we create a wide open clip here (an empty clip rect
36    // is not sufficient).
37    // Internally, the SkRect passed to clipRect is converted to an SkIRect and
38    // rounded out. The following code creates a nearly maximal rect that will
39    // not get collapsed by the coming conversions (Due to precision loss the
40    // inset has to be surprisingly large).
41    SkIRect largeIRect = SkIRect::MakeLargest();
42    largeIRect.inset(1024, 1024);
43    SkRect large = SkRect::Make(largeIRect);
44#ifdef SK_DEBUG
45    large.roundOut(&largeIRect);
46    SkASSERT(!largeIRect.isEmpty());
47#endif
48    // call the base class' version to avoid adding a draw command
49    this->INHERITED::onClipRect(large, SkRegion::kReplace_Op, kHard_ClipEdgeStyle);
50}
51
52SkDebugCanvas::~SkDebugCanvas() {
53    fCommandVector.deleteAll();
54    SkSafeUnref(fOverdrawFilter);
55    SkSafeUnref(fTexOverrideFilter);
56}
57
58void SkDebugCanvas::addDrawCommand(SkDrawCommand* command) {
59    command->setOffset(this->getOpID());
60    fCommandVector.push(command);
61}
62
63void SkDebugCanvas::draw(SkCanvas* canvas) {
64    if (!fCommandVector.isEmpty()) {
65        this->drawTo(canvas, fCommandVector.count() - 1);
66    }
67}
68
69void SkDebugCanvas::applyUserTransform(SkCanvas* canvas) {
70    canvas->concat(fUserMatrix);
71}
72
73int SkDebugCanvas::getCommandAtPoint(int x, int y, int index) {
74    SkBitmap bitmap;
75    bitmap.allocPixels(SkImageInfo::MakeN32Premul(1, 1));
76
77    SkCanvas canvas(bitmap);
78    canvas.translate(SkIntToScalar(-x), SkIntToScalar(-y));
79    applyUserTransform(&canvas);
80
81    int layer = 0;
82    SkColor prev = bitmap.getColor(0,0);
83    for (int i = 0; i < index; i++) {
84        if (fCommandVector[i]->isVisible()) {
85            fCommandVector[i]->execute(&canvas);
86        }
87        if (prev != bitmap.getColor(0,0)) {
88            layer = i;
89        }
90        prev = bitmap.getColor(0,0);
91    }
92    return layer;
93}
94
95class OverdrawXfermode : public SkXfermode {
96public:
97    virtual SkPMColor xferColor(SkPMColor src, SkPMColor dst) const SK_OVERRIDE {
98        // This table encodes the color progression of the overdraw visualization
99        static const SkPMColor gTable[] = {
100            SkPackARGB32(0x00, 0x00, 0x00, 0x00),
101            SkPackARGB32(0xFF, 128, 158, 255),
102            SkPackARGB32(0xFF, 170, 185, 212),
103            SkPackARGB32(0xFF, 213, 195, 170),
104            SkPackARGB32(0xFF, 255, 192, 127),
105            SkPackARGB32(0xFF, 255, 185, 85),
106            SkPackARGB32(0xFF, 255, 165, 42),
107            SkPackARGB32(0xFF, 255, 135, 0),
108            SkPackARGB32(0xFF, 255,  95, 0),
109            SkPackARGB32(0xFF, 255,  50, 0),
110            SkPackARGB32(0xFF, 255,  0, 0)
111        };
112
113        for (size_t i = 0; i < SK_ARRAY_COUNT(gTable)-1; ++i) {
114            if (gTable[i] == dst) {
115                return gTable[i+1];
116            }
117        }
118
119        return gTable[SK_ARRAY_COUNT(gTable)-1];
120    }
121
122    virtual Factory getFactory() const SK_OVERRIDE { return NULL; }
123#ifndef SK_IGNORE_TO_STRING
124    virtual void toString(SkString* str) const { str->set("OverdrawXfermode"); }
125#endif
126};
127
128class SkOverdrawFilter : public SkDrawFilter {
129public:
130    SkOverdrawFilter() {
131        fXferMode = SkNEW(OverdrawXfermode);
132    }
133
134    virtual ~SkOverdrawFilter() {
135        delete fXferMode;
136    }
137
138    virtual bool filter(SkPaint* p, Type) SK_OVERRIDE {
139        p->setXfermode(fXferMode);
140        return true;
141    }
142
143protected:
144    SkXfermode* fXferMode;
145
146private:
147    typedef SkDrawFilter INHERITED;
148};
149
150// SkTexOverrideFilter modifies every paint to use the specified
151// texture filtering mode
152class SkTexOverrideFilter : public SkDrawFilter {
153public:
154    SkTexOverrideFilter() : fFilterLevel(SkPaint::kNone_FilterLevel) {
155    }
156
157    void setFilterLevel(SkPaint::FilterLevel filterLevel) {
158        fFilterLevel = filterLevel;
159    }
160
161    virtual bool filter(SkPaint* p, Type) SK_OVERRIDE {
162        p->setFilterLevel(fFilterLevel);
163        return true;
164    }
165
166protected:
167    SkPaint::FilterLevel fFilterLevel;
168
169private:
170    typedef SkDrawFilter INHERITED;
171};
172
173class SkDebugClipVisitor : public SkCanvas::ClipVisitor {
174public:
175    SkDebugClipVisitor(SkCanvas* canvas) : fCanvas(canvas) {}
176
177    virtual void clipRect(const SkRect& r, SkRegion::Op, bool doAA) SK_OVERRIDE {
178        SkPaint p;
179        p.setColor(SK_ColorRED);
180        p.setStyle(SkPaint::kStroke_Style);
181        p.setAntiAlias(doAA);
182        fCanvas->drawRect(r, p);
183    }
184    virtual void clipRRect(const SkRRect& rr, SkRegion::Op, bool doAA) SK_OVERRIDE {
185        SkPaint p;
186        p.setColor(SK_ColorGREEN);
187        p.setStyle(SkPaint::kStroke_Style);
188        p.setAntiAlias(doAA);
189        fCanvas->drawRRect(rr, p);
190    }
191    virtual void clipPath(const SkPath& path, SkRegion::Op, bool doAA) SK_OVERRIDE {
192        SkPaint p;
193        p.setColor(SK_ColorBLUE);
194        p.setStyle(SkPaint::kStroke_Style);
195        p.setAntiAlias(doAA);
196        fCanvas->drawPath(path, p);
197    }
198
199protected:
200    SkCanvas* fCanvas;
201
202private:
203    typedef SkCanvas::ClipVisitor INHERITED;
204};
205
206// set up the saveLayer commands so that the active ones
207// return true in their 'active' method
208void SkDebugCanvas::markActiveCommands(int index) {
209    fActiveLayers.rewind();
210    fActiveCulls.rewind();
211
212    for (int i = 0; i < fCommandVector.count(); ++i) {
213        fCommandVector[i]->setActive(false);
214    }
215
216    for (int i = 0; i < index; ++i) {
217        SkDrawCommand::Action result = fCommandVector[i]->action();
218        if (SkDrawCommand::kPushLayer_Action == result) {
219            fActiveLayers.push(fCommandVector[i]);
220        } else if (SkDrawCommand::kPopLayer_Action == result) {
221            fActiveLayers.pop();
222        } else if (SkDrawCommand::kPushCull_Action == result) {
223            fActiveCulls.push(fCommandVector[i]);
224        } else if (SkDrawCommand::kPopCull_Action == result) {
225            fActiveCulls.pop();
226        }
227    }
228
229    for (int i = 0; i < fActiveLayers.count(); ++i) {
230        fActiveLayers[i]->setActive(true);
231    }
232
233    for (int i = 0; i < fActiveCulls.count(); ++i) {
234        fActiveCulls[i]->setActive(true);
235    }
236}
237
238void SkDebugCanvas::drawTo(SkCanvas* canvas, int index) {
239    SkASSERT(!fCommandVector.isEmpty());
240    SkASSERT(index < fCommandVector.count());
241    int i = 0;
242
243    bool pathOpsMode = getAllowSimplifyClip();
244    canvas->setAllowSimplifyClip(pathOpsMode);
245    // This only works assuming the canvas and device are the same ones that
246    // were previously drawn into because they need to preserve all saves
247    // and restores.
248    // The visibility filter also requires a full re-draw - otherwise we can
249    // end up drawing the filter repeatedly.
250    if (fIndex < index && !fFilter && !fMegaVizMode && !pathOpsMode) {
251        i = fIndex + 1;
252    } else {
253        for (int j = 0; j < fOutstandingSaveCount; j++) {
254            canvas->restore();
255        }
256        canvas->clear(SK_ColorTRANSPARENT);
257        canvas->resetMatrix();
258        SkRect rect = SkRect::MakeWH(SkIntToScalar(fWidth),
259                                     SkIntToScalar(fHeight));
260        canvas->clipRect(rect, SkRegion::kReplace_Op );
261        applyUserTransform(canvas);
262        fOutstandingSaveCount = 0;
263    }
264
265    // The setting of the draw filter has to go here (rather than in
266    // SkRasterWidget) due to the canvas restores this class performs.
267    // Since the draw filter is stored in the layer stack if we
268    // call setDrawFilter on anything but the root layer odd things happen.
269    if (fOverdrawViz) {
270        if (NULL == fOverdrawFilter) {
271            fOverdrawFilter = new SkOverdrawFilter;
272        }
273
274        if (fOverdrawFilter != canvas->getDrawFilter()) {
275            canvas->setDrawFilter(fOverdrawFilter);
276        }
277    } else if (fOverrideTexFiltering) {
278        if (NULL == fTexOverrideFilter) {
279            fTexOverrideFilter = new SkTexOverrideFilter;
280        }
281
282        if (fTexOverrideFilter != canvas->getDrawFilter()) {
283            canvas->setDrawFilter(fTexOverrideFilter);
284        }
285    } else {
286        canvas->setDrawFilter(NULL);
287    }
288
289    if (fMegaVizMode) {
290        this->markActiveCommands(index);
291    }
292
293    for (; i <= index; i++) {
294        if (i == index && fFilter) {
295            SkPaint p;
296            p.setColor(0xAAFFFFFF);
297            canvas->save();
298            canvas->resetMatrix();
299            SkRect mask;
300            mask.set(SkIntToScalar(0), SkIntToScalar(0),
301                    SkIntToScalar(fWidth), SkIntToScalar(fHeight));
302            canvas->clipRect(mask, SkRegion::kReplace_Op, false);
303            canvas->drawRectCoords(SkIntToScalar(0), SkIntToScalar(0),
304                    SkIntToScalar(fWidth), SkIntToScalar(fHeight), p);
305            canvas->restore();
306        }
307
308        if (fCommandVector[i]->isVisible()) {
309            if (fMegaVizMode && fCommandVector[i]->active()) {
310                // "active" commands execute their visualization behaviors:
311                //     All active saveLayers get replaced with saves so all draws go to the
312                //     visible canvas.
313                //     All active culls draw their cull box
314                fCommandVector[i]->vizExecute(canvas);
315            } else {
316                fCommandVector[i]->execute(canvas);
317            }
318
319            fCommandVector[i]->trackSaveState(&fOutstandingSaveCount);
320        }
321    }
322
323    if (fMegaVizMode) {
324        SkRect r = SkRect::MakeWH(SkIntToScalar(fWidth), SkIntToScalar(fHeight));
325        r.outset(SK_Scalar1, SK_Scalar1);
326
327        canvas->save();
328        // nuke the CTM
329        canvas->setMatrix(SkMatrix::I());
330        // turn off clipping
331        canvas->clipRect(r, SkRegion::kReplace_Op);
332
333        // visualize existing clips
334        SkDebugClipVisitor visitor(canvas);
335
336        canvas->replayClips(&visitor);
337
338        canvas->restore();
339    }
340    if (pathOpsMode) {
341        this->resetClipStackData();
342        const SkClipStack* clipStack = canvas->getClipStack();
343        SkClipStack::Iter iter(*clipStack, SkClipStack::Iter::kBottom_IterStart);
344        const SkClipStack::Element* element;
345        SkPath devPath;
346        while ((element = iter.next())) {
347            SkClipStack::Element::Type type = element->getType();
348            SkPath operand;
349            if (type != SkClipStack::Element::kEmpty_Type) {
350               element->asPath(&operand);
351            }
352            SkRegion::Op elementOp = element->getOp();
353            this->addClipStackData(devPath, operand, elementOp);
354            if (elementOp == SkRegion::kReplace_Op) {
355                devPath = operand;
356            } else {
357                Op(devPath, operand, (SkPathOp) elementOp, &devPath);
358            }
359        }
360        this->lastClipStackData(devPath);
361    }
362    fMatrix = canvas->getTotalMatrix();
363    if (!canvas->getClipDeviceBounds(&fClip)) {
364        fClip.setEmpty();
365    }
366    fIndex = index;
367}
368
369void SkDebugCanvas::deleteDrawCommandAt(int index) {
370    SkASSERT(index < fCommandVector.count());
371    delete fCommandVector[index];
372    fCommandVector.remove(index);
373}
374
375SkDrawCommand* SkDebugCanvas::getDrawCommandAt(int index) {
376    SkASSERT(index < fCommandVector.count());
377    return fCommandVector[index];
378}
379
380void SkDebugCanvas::setDrawCommandAt(int index, SkDrawCommand* command) {
381    SkASSERT(index < fCommandVector.count());
382    delete fCommandVector[index];
383    fCommandVector[index] = command;
384}
385
386SkTDArray<SkString*>* SkDebugCanvas::getCommandInfo(int index) {
387    SkASSERT(index < fCommandVector.count());
388    return fCommandVector[index]->Info();
389}
390
391bool SkDebugCanvas::getDrawCommandVisibilityAt(int index) {
392    SkASSERT(index < fCommandVector.count());
393    return fCommandVector[index]->isVisible();
394}
395
396const SkTDArray <SkDrawCommand*>& SkDebugCanvas::getDrawCommands() const {
397    return fCommandVector;
398}
399
400SkTDArray <SkDrawCommand*>& SkDebugCanvas::getDrawCommands() {
401    return fCommandVector;
402}
403
404// TODO(chudy): Free command string memory.
405SkTArray<SkString>* SkDebugCanvas::getDrawCommandsAsStrings() const {
406    SkTArray<SkString>* commandString = new SkTArray<SkString>(fCommandVector.count());
407    if (!fCommandVector.isEmpty()) {
408        for (int i = 0; i < fCommandVector.count(); i ++) {
409            commandString->push_back() = fCommandVector[i]->toString();
410        }
411    }
412    return commandString;
413}
414
415SkTDArray<size_t>* SkDebugCanvas::getDrawCommandOffsets() const {
416    SkTDArray<size_t>* commandOffsets = new SkTDArray<size_t>;
417    if (!fCommandVector.isEmpty()) {
418        for (int i = 0; i < fCommandVector.count(); i ++) {
419            *commandOffsets->push() = fCommandVector[i]->offset();
420        }
421    }
422    return commandOffsets;
423}
424
425void SkDebugCanvas::overrideTexFiltering(bool overrideTexFiltering, SkPaint::FilterLevel level) {
426    if (NULL == fTexOverrideFilter) {
427        fTexOverrideFilter = new SkTexOverrideFilter;
428    }
429
430    fOverrideTexFiltering = overrideTexFiltering;
431    fTexOverrideFilter->setFilterLevel(level);
432}
433
434void SkDebugCanvas::clear(SkColor color) {
435    this->addDrawCommand(new SkClearCommand(color));
436}
437
438void SkDebugCanvas::onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
439    this->addDrawCommand(new SkClipPathCommand(path, op, kSoft_ClipEdgeStyle == edgeStyle));
440}
441
442void SkDebugCanvas::onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
443    this->addDrawCommand(new SkClipRectCommand(rect, op, kSoft_ClipEdgeStyle == edgeStyle));
444}
445
446void SkDebugCanvas::onClipRRect(const SkRRect& rrect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
447    this->addDrawCommand(new SkClipRRectCommand(rrect, op, kSoft_ClipEdgeStyle == edgeStyle));
448}
449
450void SkDebugCanvas::onClipRegion(const SkRegion& region, SkRegion::Op op) {
451    this->addDrawCommand(new SkClipRegionCommand(region, op));
452}
453
454void SkDebugCanvas::didConcat(const SkMatrix& matrix) {
455    switch (matrix.getType()) {
456        case SkMatrix::kTranslate_Mask:
457            this->addDrawCommand(new SkTranslateCommand(matrix.getTranslateX(),
458                                                        matrix.getTranslateY()));
459            break;
460        case SkMatrix::kScale_Mask:
461            this->addDrawCommand(new SkScaleCommand(matrix.getScaleX(),
462                                                    matrix.getScaleY()));
463            break;
464        default:
465            this->addDrawCommand(new SkConcatCommand(matrix));
466            break;
467    }
468
469    this->INHERITED::didConcat(matrix);
470}
471
472void SkDebugCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar left,
473                               SkScalar top, const SkPaint* paint = NULL) {
474    this->addDrawCommand(new SkDrawBitmapCommand(bitmap, left, top, paint));
475}
476
477void SkDebugCanvas::drawBitmapRectToRect(const SkBitmap& bitmap,
478                                         const SkRect* src, const SkRect& dst,
479                                         const SkPaint* paint,
480                                         SkCanvas::DrawBitmapRectFlags flags) {
481    this->addDrawCommand(new SkDrawBitmapRectCommand(bitmap, src, dst, paint, flags));
482}
483
484void SkDebugCanvas::drawBitmapMatrix(const SkBitmap& bitmap,
485                                     const SkMatrix& matrix, const SkPaint* paint) {
486    this->addDrawCommand(new SkDrawBitmapMatrixCommand(bitmap, matrix, paint));
487}
488
489void SkDebugCanvas::drawBitmapNine(const SkBitmap& bitmap,
490        const SkIRect& center, const SkRect& dst, const SkPaint* paint) {
491    this->addDrawCommand(new SkDrawBitmapNineCommand(bitmap, center, dst, paint));
492}
493
494void SkDebugCanvas::drawData(const void* data, size_t length) {
495    this->addDrawCommand(new SkDrawDataCommand(data, length));
496}
497
498void SkDebugCanvas::beginCommentGroup(const char* description) {
499    this->addDrawCommand(new SkBeginCommentGroupCommand(description));
500}
501
502void SkDebugCanvas::addComment(const char* kywd, const char* value) {
503    this->addDrawCommand(new SkCommentCommand(kywd, value));
504}
505
506void SkDebugCanvas::endCommentGroup() {
507    this->addDrawCommand(new SkEndCommentGroupCommand());
508}
509
510void SkDebugCanvas::drawOval(const SkRect& oval, const SkPaint& paint) {
511    this->addDrawCommand(new SkDrawOvalCommand(oval, paint));
512}
513
514void SkDebugCanvas::drawPaint(const SkPaint& paint) {
515    this->addDrawCommand(new SkDrawPaintCommand(paint));
516}
517
518void SkDebugCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
519    this->addDrawCommand(new SkDrawPathCommand(path, paint));
520}
521
522void SkDebugCanvas::onDrawPicture(const SkPicture* picture) {
523    this->addDrawCommand(new SkDrawPictureCommand(picture));
524}
525
526void SkDebugCanvas::drawPoints(PointMode mode, size_t count,
527                               const SkPoint pts[], const SkPaint& paint) {
528    this->addDrawCommand(new SkDrawPointsCommand(mode, count, pts, paint));
529}
530
531void SkDebugCanvas::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
532                                  const SkPaint& paint) {
533    this->addDrawCommand(new SkDrawPosTextCommand(text, byteLength, pos, paint));
534}
535
536void SkDebugCanvas::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
537                                   SkScalar constY, const SkPaint& paint) {
538    this->addDrawCommand(
539        new SkDrawPosTextHCommand(text, byteLength, xpos, constY, paint));
540}
541
542void SkDebugCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
543    // NOTE(chudy): Messing up when renamed to DrawRect... Why?
544    addDrawCommand(new SkDrawRectCommand(rect, paint));
545}
546
547void SkDebugCanvas::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
548    this->addDrawCommand(new SkDrawRRectCommand(rrect, paint));
549}
550
551void SkDebugCanvas::onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
552                                 const SkPaint& paint) {
553    this->addDrawCommand(new SkDrawDRRectCommand(outer, inner, paint));
554}
555
556void SkDebugCanvas::drawSprite(const SkBitmap& bitmap, int left, int top,
557                               const SkPaint* paint = NULL) {
558    this->addDrawCommand(new SkDrawSpriteCommand(bitmap, left, top, paint));
559}
560
561void SkDebugCanvas::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
562                               const SkPaint& paint) {
563    this->addDrawCommand(new SkDrawTextCommand(text, byteLength, x, y, paint));
564}
565
566void SkDebugCanvas::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path,
567                                     const SkMatrix* matrix, const SkPaint& paint) {
568    this->addDrawCommand(
569        new SkDrawTextOnPathCommand(text, byteLength, path, matrix, paint));
570}
571
572void SkDebugCanvas::drawVertices(VertexMode vmode, int vertexCount,
573        const SkPoint vertices[], const SkPoint texs[], const SkColor colors[],
574        SkXfermode*, const uint16_t indices[], int indexCount,
575        const SkPaint& paint) {
576    this->addDrawCommand(new SkDrawVerticesCommand(vmode, vertexCount, vertices,
577                         texs, colors, NULL, indices, indexCount, paint));
578}
579
580void SkDebugCanvas::onPushCull(const SkRect& cullRect) {
581    this->addDrawCommand(new SkPushCullCommand(cullRect));
582}
583
584void SkDebugCanvas::onPopCull() {
585    this->addDrawCommand(new SkPopCullCommand());
586}
587
588void SkDebugCanvas::willRestore() {
589    this->addDrawCommand(new SkRestoreCommand());
590    this->INHERITED::willRestore();
591}
592
593void SkDebugCanvas::willSave(SaveFlags flags) {
594    this->addDrawCommand(new SkSaveCommand(flags));
595    this->INHERITED::willSave(flags);
596}
597
598SkCanvas::SaveLayerStrategy SkDebugCanvas::willSaveLayer(const SkRect* bounds, const SkPaint* paint,
599                                                         SaveFlags flags) {
600    this->addDrawCommand(new SkSaveLayerCommand(bounds, paint, flags));
601    this->INHERITED::willSaveLayer(bounds, paint, flags);
602    // No need for a full layer.
603    return kNoLayer_SaveLayerStrategy;
604}
605
606void SkDebugCanvas::didSetMatrix(const SkMatrix& matrix) {
607    this->addDrawCommand(new SkSetMatrixCommand(matrix));
608    this->INHERITED::didSetMatrix(matrix);
609}
610
611void SkDebugCanvas::toggleCommand(int index, bool toggle) {
612    SkASSERT(index < fCommandVector.count());
613    fCommandVector[index]->setVisible(toggle);
614}
615
616static const char* gFillTypeStrs[] = {
617    "kWinding_FillType",
618    "kEvenOdd_FillType",
619    "kInverseWinding_FillType",
620    "kInverseEvenOdd_FillType"
621};
622
623static const char* gOpStrs[] = {
624    "kDifference_PathOp",
625    "kIntersect_PathOp",
626    "kUnion_PathOp",
627    "kXor_PathOp",
628    "kReverseDifference_PathOp",
629};
630
631static const char kHTML4SpaceIndent[] = "&nbsp;&nbsp;&nbsp;&nbsp;";
632
633void SkDebugCanvas::outputScalar(SkScalar num) {
634    if (num == (int) num) {
635        fClipStackData.appendf("%d", (int) num);
636    } else {
637        SkString str;
638        str.printf("%1.9g", num);
639        int width = (int) str.size();
640        const char* cStr = str.c_str();
641        while (cStr[width - 1] == '0') {
642            --width;
643        }
644        str.resize(width);
645        fClipStackData.appendf("%sf", str.c_str());
646    }
647}
648
649void SkDebugCanvas::outputPointsCommon(const SkPoint* pts, int count) {
650    for (int index = 0; index < count; ++index) {
651        this->outputScalar(pts[index].fX);
652        fClipStackData.appendf(", ");
653        this->outputScalar(pts[index].fY);
654        if (index + 1 < count) {
655            fClipStackData.appendf(", ");
656        }
657    }
658}
659
660void SkDebugCanvas::outputPoints(const SkPoint* pts, int count) {
661    this->outputPointsCommon(pts, count);
662    fClipStackData.appendf(");<br>");
663}
664
665void SkDebugCanvas::outputConicPoints(const SkPoint* pts, SkScalar weight) {
666    this->outputPointsCommon(pts, 2);
667    fClipStackData.appendf(", ");
668    this->outputScalar(weight);
669    fClipStackData.appendf(");<br>");
670}
671
672void SkDebugCanvas::addPathData(const SkPath& path, const char* pathName) {
673    SkPath::RawIter iter(path);
674    SkPath::FillType fillType = path.getFillType();
675    fClipStackData.appendf("%sSkPath %s;<br>", kHTML4SpaceIndent, pathName);
676    fClipStackData.appendf("%s%s.setFillType(SkPath::%s);<br>", kHTML4SpaceIndent, pathName,
677            gFillTypeStrs[fillType]);
678    iter.setPath(path);
679    uint8_t verb;
680    SkPoint pts[4];
681    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
682        switch (verb) {
683            case SkPath::kMove_Verb:
684                fClipStackData.appendf("%s%s.moveTo(", kHTML4SpaceIndent, pathName);
685                this->outputPoints(&pts[0], 1);
686                continue;
687            case SkPath::kLine_Verb:
688                fClipStackData.appendf("%s%s.lineTo(", kHTML4SpaceIndent, pathName);
689                this->outputPoints(&pts[1], 1);
690                break;
691            case SkPath::kQuad_Verb:
692                fClipStackData.appendf("%s%s.quadTo(", kHTML4SpaceIndent, pathName);
693                this->outputPoints(&pts[1], 2);
694                break;
695            case SkPath::kConic_Verb:
696                fClipStackData.appendf("%s%s.conicTo(", kHTML4SpaceIndent, pathName);
697                this->outputConicPoints(&pts[1], iter.conicWeight());
698                break;
699            case SkPath::kCubic_Verb:
700                fClipStackData.appendf("%s%s.cubicTo(", kHTML4SpaceIndent, pathName);
701                this->outputPoints(&pts[1], 3);
702                break;
703            case SkPath::kClose_Verb:
704                fClipStackData.appendf("%s%s.close();<br>", kHTML4SpaceIndent, pathName);
705                break;
706            default:
707                SkDEBUGFAIL("bad verb");
708                return;
709        }
710    }
711}
712
713void SkDebugCanvas::addClipStackData(const SkPath& devPath, const SkPath& operand,
714                                     SkRegion::Op elementOp) {
715    if (elementOp == SkRegion::kReplace_Op) {
716        if (!lastClipStackData(devPath)) {
717            fSaveDevPath = operand;
718        }
719        fCalledAddStackData = false;
720    } else {
721        fClipStackData.appendf("<br>static void test(skiatest::Reporter* reporter,"
722            " const char* filename) {<br>");
723        addPathData(fCalledAddStackData ? devPath : fSaveDevPath, "path");
724        addPathData(operand, "pathB");
725        fClipStackData.appendf("%stestPathOp(reporter, path, pathB, %s, filename);<br>",
726            kHTML4SpaceIndent, gOpStrs[elementOp]);
727        fClipStackData.appendf("}<br>");
728        fCalledAddStackData = true;
729    }
730}
731
732bool SkDebugCanvas::lastClipStackData(const SkPath& devPath) {
733    if (fCalledAddStackData) {
734        fClipStackData.appendf("<br>");
735        addPathData(devPath, "pathOut");
736        return true;
737    }
738    return false;
739}
740