| Index: samplecode/SamplePathFuzz.cpp
|
| diff --git a/samplecode/SamplePathFuzz.cpp b/samplecode/SamplePathFuzz.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d6b11c5971012ad51158ab21e6c896a2ba4677e1
|
| --- /dev/null
|
| +++ b/samplecode/SamplePathFuzz.cpp
|
| @@ -0,0 +1,651 @@
|
| +/*
|
| + * Copyright 2015 Google Inc.
|
| + *
|
| + * Use of this source code is governed by a BSD-style license that can be
|
| + * found in the LICENSE file.
|
| + */
|
| +
|
| +#include "SampleCode.h"
|
| +#include "SkView.h"
|
| +#include "SkCanvas.h"
|
| +#include "SkPaint.h"
|
| +#include "SkPath.h"
|
| +#include "SkMatrix.h"
|
| +#include "SkColor.h"
|
| +#include "SkTDArray.h"
|
| +#include "SkRandom.h"
|
| +
|
| +enum RandomAddPath {
|
| + kMoveToPath,
|
| + kRMoveToPath,
|
| + kLineToPath,
|
| + kRLineToPath,
|
| + kQuadToPath,
|
| + kRQuadToPath,
|
| + kConicToPath,
|
| + kRConicToPath,
|
| + kCubicToPath,
|
| + kRCubicToPath,
|
| + kArcToPath,
|
| + kArcTo2Path,
|
| + kClosePath,
|
| + kAddArc,
|
| + kAddRoundRect1,
|
| + kAddRoundRect2,
|
| + kAddRRect,
|
| + kAddPoly,
|
| + kAddPath1,
|
| + kAddPath2,
|
| + kAddPath3,
|
| + kReverseAddPath,
|
| +};
|
| +
|
| +const int kRandomAddPath_Last = kReverseAddPath;
|
| +
|
| +const char* gRandomAddPathNames[] = {
|
| + "kMoveToPath",
|
| + "kRMoveToPath",
|
| + "kLineToPath",
|
| + "kRLineToPath",
|
| + "kQuadToPath",
|
| + "kRQuadToPath",
|
| + "kConicToPath",
|
| + "kRConicToPath",
|
| + "kCubicToPath",
|
| + "kRCubicToPath",
|
| + "kArcToPath",
|
| + "kArcTo2Path",
|
| + "kClosePath",
|
| + "kAddArc",
|
| + "kAddRoundRect1",
|
| + "kAddRoundRect2",
|
| + "kAddRRect",
|
| + "kAddPoly",
|
| + "kAddPath1",
|
| + "kAddPath2",
|
| + "kAddPath3",
|
| + "kReverseAddPath",
|
| +};
|
| +
|
| +enum RandomSetRRect {
|
| + kSetEmpty,
|
| + kSetRect,
|
| + kSetOval,
|
| + kSetRectXY,
|
| + kSetNinePatch,
|
| + kSetRectRadii,
|
| +};
|
| +
|
| +const char* gRandomSetRRectNames[] = {
|
| + "kSetEmpty",
|
| + "kSetRect",
|
| + "kSetOval",
|
| + "kSetRectXY",
|
| + "kSetNinePatch",
|
| + "kSetRectRadii",
|
| +};
|
| +
|
| +int kRandomSetRRect_Last = kSetRectRadii;
|
| +
|
| +enum RandomSetMatrix {
|
| + kSetIdentity,
|
| + kSetTranslate,
|
| + kSetTranslateX,
|
| + kSetTranslateY,
|
| + kSetScale,
|
| + kSetScaleTranslate,
|
| + kSetScaleX,
|
| + kSetScaleY,
|
| + kSetSkew,
|
| + kSetSkewTranslate,
|
| + kSetSkewX,
|
| + kSetSkewY,
|
| + kSetRotate,
|
| + kSetRotateTranslate,
|
| + kSetPerspectiveX,
|
| + kSetPerspectiveY,
|
| + kSetAll,
|
| +};
|
| +
|
| +int kRandomSetMatrix_Last = kSetAll;
|
| +
|
| +const char* gRandomSetMatrixNames[] = {
|
| + "kSetIdentity",
|
| + "kSetTranslate",
|
| + "kSetTranslateX",
|
| + "kSetTranslateY",
|
| + "kSetScale",
|
| + "kSetScaleTranslate",
|
| + "kSetScaleX",
|
| + "kSetScaleY",
|
| + "kSetSkew",
|
| + "kSetSkewTranslate",
|
| + "kSetSkewX",
|
| + "kSetSkewY",
|
| + "kSetRotate",
|
| + "kSetRotateTranslate",
|
| + "kSetPerspectiveX",
|
| + "kSetPerspectiveY",
|
| + "kSetAll",
|
| +};
|
| +
|
| +class FuzzPath {
|
| +public:
|
| + FuzzPath()
|
| + : fFloatMin(0)
|
| + , fFloatMax(800)
|
| + , fAddCount(0)
|
| + , fPrintName(false)
|
| + , fValidate(false)
|
| + {
|
| + fTab = " ";
|
| + }
|
| + void randomize() {
|
| + fPathDepth = 0;
|
| + fPathDepthLimit = fRand.nextRangeU(1, 2);
|
| + fPathContourCount = fRand.nextRangeU(1, 4);
|
| + fPathSegmentLimit = fRand.nextRangeU(1, 8);
|
| + fClip = makePath();
|
| + SkASSERT(!fPathDepth);
|
| + fMatrix = makeMatrix();
|
| + fPaint = makePaint();
|
| + fPathDepthLimit = fRand.nextRangeU(1, 3);
|
| + fPathContourCount = fRand.nextRangeU(1, 6);
|
| + fPathSegmentLimit = fRand.nextRangeU(1, 16);
|
| + fPath = makePath();
|
| + SkASSERT(!fPathDepth);
|
| + }
|
| +
|
| + const SkPath& getClip() const {
|
| + return fClip;
|
| + }
|
| +
|
| + const SkMatrix& getMatrix() const {
|
| + return fMatrix;
|
| + }
|
| +
|
| + const SkPaint& getPaint() const {
|
| + return fPaint;
|
| + }
|
| +
|
| + const SkPath& getPath() const {
|
| + return fPath;
|
| + }
|
| +
|
| +private:
|
| +
|
| +SkPath::AddPathMode makeAddPathMode() {
|
| + return (SkPath::AddPathMode) fRand.nextRangeU(SkPath::kAppend_AddPathMode,
|
| + SkPath::kExtend_AddPathMode);
|
| +}
|
| +
|
| +RandomAddPath makeAddPathType() {
|
| + return (RandomAddPath) fRand.nextRangeU(0, kRandomAddPath_Last);
|
| +}
|
| +
|
| +SkScalar makeAngle() {
|
| + SkScalar angle;
|
| + angle = fRand.nextF();
|
| + return angle;
|
| +}
|
| +
|
| +bool makeBool() {
|
| + return fRand.nextBool();
|
| +}
|
| +
|
| +SkPath::Direction makeDirection() {
|
| + return (SkPath::Direction) fRand.nextRangeU(SkPath::kCW_Direction, SkPath::kCCW_Direction);
|
| +}
|
| +
|
| +SkMatrix makeMatrix() {
|
| + SkMatrix matrix;
|
| + matrix.reset();
|
| + RandomSetMatrix setMatrix = (RandomSetMatrix) fRand.nextRangeU(0, kRandomSetMatrix_Last);
|
| + if (fPrintName) {
|
| + SkDebugf("%.*s%s\n", fPathDepth * 3, fTab, gRandomSetMatrixNames[setMatrix]);
|
| + }
|
| + switch (setMatrix) {
|
| + case kSetIdentity:
|
| + break;
|
| + case kSetTranslateX:
|
| + matrix.setTranslateX(makeScalar());
|
| + break;
|
| + case kSetTranslateY:
|
| + matrix.setTranslateY(makeScalar());
|
| + break;
|
| + case kSetTranslate:
|
| + matrix.setTranslate(makeScalar(), makeScalar());
|
| + break;
|
| + case kSetScaleX:
|
| + matrix.setScaleX(makeScalar());
|
| + break;
|
| + case kSetScaleY:
|
| + matrix.setScaleY(makeScalar());
|
| + break;
|
| + case kSetScale:
|
| + matrix.setScale(makeScalar(), makeScalar());
|
| + break;
|
| + case kSetScaleTranslate:
|
| + matrix.setScale(makeScalar(), makeScalar(), makeScalar(), makeScalar());
|
| + break;
|
| + case kSetSkewX:
|
| + matrix.setSkewX(makeScalar());
|
| + break;
|
| + case kSetSkewY:
|
| + matrix.setSkewY(makeScalar());
|
| + break;
|
| + case kSetSkew:
|
| + matrix.setSkew(makeScalar(), makeScalar());
|
| + break;
|
| + case kSetSkewTranslate:
|
| + matrix.setSkew(makeScalar(), makeScalar(), makeScalar(), makeScalar());
|
| + break;
|
| + case kSetRotate:
|
| + matrix.setRotate(makeScalar());
|
| + break;
|
| + case kSetRotateTranslate:
|
| + matrix.setRotate(makeScalar(), makeScalar(), makeScalar());
|
| + break;
|
| + case kSetPerspectiveX:
|
| + matrix.setPerspX(makeScalar());
|
| + break;
|
| + case kSetPerspectiveY:
|
| + matrix.setPerspY(makeScalar());
|
| + break;
|
| + case kSetAll:
|
| + matrix.setAll(makeScalar(), makeScalar(), makeScalar(),
|
| + makeScalar(), makeScalar(), makeScalar(),
|
| + makeScalar(), makeScalar(), makeScalar());
|
| + break;
|
| + }
|
| + return matrix;
|
| +}
|
| +
|
| +SkPaint makePaint() {
|
| + SkPaint paint;
|
| + bool antiAlias = fRand.nextBool();
|
| + paint.setAntiAlias(antiAlias);
|
| + SkPaint::Style style = (SkPaint::Style) fRand.nextRangeU(SkPaint::kFill_Style,
|
| + SkPaint::kStrokeAndFill_Style);
|
| + paint.setStyle(style);
|
| + SkColor color = (SkColor) fRand.nextU();
|
| + paint.setColor(color);
|
| + SkScalar width = fRand.nextF();
|
| + paint.setStrokeWidth(width);
|
| + SkScalar miter = fRand.nextF();
|
| + paint.setStrokeMiter(miter);
|
| + SkPaint::Cap cap = (SkPaint::Cap) fRand.nextRangeU(SkPaint::kButt_Cap, SkPaint::kSquare_Cap);
|
| + paint.setStrokeCap(cap);
|
| + SkPaint::Join join = (SkPaint::Join) fRand.nextRangeU(SkPaint::kMiter_Join,
|
| + SkPaint::kBevel_Join);
|
| + paint.setStrokeJoin(join);
|
| + return paint;
|
| +}
|
| +
|
| +SkPoint makePoint() {
|
| + SkPoint result;
|
| + makeScalarArray(2, &result.fX);
|
| + return result;
|
| +}
|
| +
|
| +void makePointArray(size_t arrayCount, SkPoint* points) {
|
| + for (size_t index = 0; index < arrayCount; ++index) {
|
| + points[index] = makePoint();
|
| + }
|
| +}
|
| +
|
| +void makePointArray(SkTDArray<SkPoint>* points) {
|
| + size_t arrayCount = fRand.nextRangeU(1, 10);
|
| + for (size_t index = 0; index < arrayCount; ++index) {
|
| + *points->append() = makePoint();
|
| + }
|
| +}
|
| +
|
| +SkRect makeRect() {
|
| + SkRect result;
|
| + makeScalarArray(4, &result.fLeft);
|
| + return result;
|
| +}
|
| +
|
| +SkRRect makeRRect() {
|
| + SkRRect rrect;
|
| + RandomSetRRect rrectType = makeSetRRectType();
|
| + if (fPrintName) {
|
| + SkDebugf("%.*s%s\n", fPathDepth * 3, fTab, gRandomSetRRectNames[rrectType]);
|
| + }
|
| + switch (rrectType) {
|
| + case kSetEmpty:
|
| + rrect.setEmpty();
|
| + break;
|
| + case kSetRect: {
|
| + SkRect rect = makeRect();
|
| + rrect.setRect(rect);
|
| + } break;
|
| + case kSetOval: {
|
| + SkRect oval = makeRect();
|
| + rrect.setOval(oval);
|
| + } break;
|
| + case kSetRectXY: {
|
| + SkRect rect = makeRect();
|
| + SkScalar xRad = makeScalar();
|
| + SkScalar yRad = makeScalar();
|
| + rrect.setRectXY(rect, xRad, yRad);
|
| + } break;
|
| + case kSetNinePatch: {
|
| + SkRect rect = makeRect();
|
| + SkScalar leftRad = makeScalar();
|
| + SkScalar topRad = makeScalar();
|
| + SkScalar rightRad = makeScalar();
|
| + SkScalar bottomRad = makeScalar();
|
| + rrect.setNinePatch(rect, leftRad, topRad, rightRad, bottomRad);
|
| + SkDebugf(""); // keep locals in scope
|
| + } break;
|
| + case kSetRectRadii: {
|
| + SkRect rect = makeRect();
|
| + SkVector radii[4];
|
| + makeVectorArray(SK_ARRAY_COUNT(radii), radii);
|
| + rrect.setRectRadii(rect, radii);
|
| + } break;
|
| + }
|
| + return rrect;
|
| +}
|
| +
|
| +SkPath makePath() {
|
| + SkPath path;
|
| + for (uint32_t cIndex = 0; cIndex < fPathContourCount; ++cIndex) {
|
| + uint32_t segments = makeSegmentCount();
|
| + for (uint32_t sIndex = 0; sIndex < segments; ++sIndex) {
|
| + RandomAddPath addPathType = makeAddPathType();
|
| + ++fAddCount;
|
| + if (fPrintName) {
|
| + SkDebugf("%.*s%s\n", fPathDepth * 3, fTab,
|
| + gRandomAddPathNames[addPathType]);
|
| + }
|
| + switch (addPathType) {
|
| + case kAddArc: {
|
| + SkRect oval = makeRect();
|
| + SkScalar startAngle = makeAngle();
|
| + SkScalar sweepAngle = makeAngle();
|
| + path.addArc(oval, startAngle, sweepAngle);
|
| + validate(path);
|
| + } break;
|
| + case kAddRoundRect1: {
|
| + SkRect rect = makeRect();
|
| + SkScalar rx = makeScalar(), ry = makeScalar();
|
| + SkPath::Direction dir = makeDirection();
|
| + path.addRoundRect(rect, rx, ry, dir);
|
| + validate(path);
|
| + } break;
|
| + case kAddRoundRect2: {
|
| + SkRect rect = makeRect();
|
| + SkScalar radii[8];
|
| + makeScalarArray(SK_ARRAY_COUNT(radii), radii);
|
| + SkPath::Direction dir = makeDirection();
|
| + path.addRoundRect(rect, radii, dir);
|
| + validate(path);
|
| + } break;
|
| + case kAddRRect: {
|
| + SkRRect rrect = makeRRect();
|
| + SkPath::Direction dir = makeDirection();
|
| + path.addRRect(rrect, dir);
|
| + validate(path);
|
| + } break;
|
| + case kAddPoly: {
|
| + SkTDArray<SkPoint> points;
|
| + makePointArray(&points);
|
| + bool close = makeBool();
|
| + path.addPoly(&points[0], points.count(), close);
|
| + validate(path);
|
| + } break;
|
| + case kAddPath1:
|
| + if (fPathDepth < fPathDepthLimit) {
|
| + ++fPathDepth;
|
| + SkPath src = makePath();
|
| + validate(src);
|
| + SkScalar dx = makeScalar();
|
| + SkScalar dy = makeScalar();
|
| + SkPath::AddPathMode mode = makeAddPathMode();
|
| + path.addPath(src, dx, dy, mode);
|
| + --fPathDepth;
|
| + validate(path);
|
| + }
|
| + break;
|
| + case kAddPath2:
|
| + if (fPathDepth < fPathDepthLimit) {
|
| + ++fPathDepth;
|
| + SkPath src = makePath();
|
| + validate(src);
|
| + SkPath::AddPathMode mode = makeAddPathMode();
|
| + path.addPath(src, mode);
|
| + --fPathDepth;
|
| + validate(path);
|
| + }
|
| + break;
|
| + case kAddPath3:
|
| + if (fPathDepth < fPathDepthLimit) {
|
| + ++fPathDepth;
|
| + SkPath src = makePath();
|
| + validate(src);
|
| + SkMatrix matrix = makeMatrix();
|
| + SkPath::AddPathMode mode = makeAddPathMode();
|
| + path.addPath(src, matrix, mode);
|
| + --fPathDepth;
|
| + validate(path);
|
| + }
|
| + break;
|
| + case kReverseAddPath:
|
| + if (fPathDepth < fPathDepthLimit) {
|
| + ++fPathDepth;
|
| + SkPath src = makePath();
|
| + validate(src);
|
| + path.reverseAddPath(src);
|
| + --fPathDepth;
|
| + validate(path);
|
| + }
|
| + break;
|
| + case kMoveToPath: {
|
| + SkScalar x = makeScalar();
|
| + SkScalar y = makeScalar();
|
| + path.moveTo(x, y);
|
| + validate(path);
|
| + } break;
|
| + case kRMoveToPath: {
|
| + SkScalar x = makeScalar();
|
| + SkScalar y = makeScalar();
|
| + path.rMoveTo(x, y);
|
| + validate(path);
|
| + } break;
|
| + case kLineToPath: {
|
| + SkScalar x = makeScalar();
|
| + SkScalar y = makeScalar();
|
| + path.lineTo(x, y);
|
| + validate(path);
|
| + } break;
|
| + case kRLineToPath: {
|
| + SkScalar x = makeScalar();
|
| + SkScalar y = makeScalar();
|
| + path.rLineTo(x, y);
|
| + validate(path);
|
| + } break;
|
| + case kQuadToPath: {
|
| + SkPoint pt[2];
|
| + makePointArray(SK_ARRAY_COUNT(pt), pt);
|
| + path.quadTo(pt[0], pt[1]);
|
| + validate(path);
|
| + } break;
|
| + case kRQuadToPath: {
|
| + SkPoint pt[2];
|
| + makePointArray(SK_ARRAY_COUNT(pt), pt);
|
| + path.rQuadTo(pt[0].fX, pt[0].fY, pt[1].fX, pt[1].fY);
|
| + validate(path);
|
| + } break;
|
| + case kConicToPath: {
|
| + SkPoint pt[2];
|
| + makePointArray(SK_ARRAY_COUNT(pt), pt);
|
| + SkScalar weight = makeScalar();
|
| + path.conicTo(pt[0], pt[1], weight);
|
| + validate(path);
|
| + } break;
|
| + case kRConicToPath: {
|
| + SkPoint pt[2];
|
| + makePointArray(SK_ARRAY_COUNT(pt), pt);
|
| + SkScalar weight = makeScalar();
|
| + path.rConicTo(pt[0].fX, pt[0].fY, pt[1].fX, pt[1].fY, weight);
|
| + validate(path);
|
| + } break;
|
| + case kCubicToPath: {
|
| + SkPoint pt[3];
|
| + makePointArray(SK_ARRAY_COUNT(pt), pt);
|
| + path.cubicTo(pt[0], pt[1], pt[2]);
|
| + validate(path);
|
| + } break;
|
| + case kRCubicToPath: {
|
| + SkPoint pt[3];
|
| + makePointArray(SK_ARRAY_COUNT(pt), pt);
|
| + path.rCubicTo(pt[0].fX, pt[0].fY, pt[1].fX, pt[1].fY, pt[2].fX, pt[2].fY);
|
| + validate(path);
|
| + } break;
|
| + case kArcToPath: {
|
| + SkPoint pt[2];
|
| + makePointArray(SK_ARRAY_COUNT(pt), pt);
|
| + SkScalar radius = makeScalar();
|
| + path.arcTo(pt[0], pt[1], radius);
|
| + validate(path);
|
| + } break;
|
| + case kArcTo2Path: {
|
| + SkRect oval = makeRect();
|
| + SkScalar startAngle = makeAngle();
|
| + SkScalar sweepAngle = makeAngle();
|
| + bool forceMoveTo = makeBool();
|
| + path.arcTo(oval, startAngle, sweepAngle, forceMoveTo);
|
| + validate(path);
|
| + } break;
|
| + case kClosePath:
|
| + path.close();
|
| + validate(path);
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + return path;
|
| +}
|
| +
|
| +uint32_t makeSegmentCount() {
|
| + return fRand.nextRangeU(1, fPathSegmentLimit);
|
| +}
|
| +
|
| +RandomSetRRect makeSetRRectType() {
|
| + return (RandomSetRRect) fRand.nextRangeU(0, kRandomSetRRect_Last);
|
| +}
|
| +
|
| +SkScalar makeScalar() {
|
| + SkScalar scalar;
|
| + scalar = fRand.nextRangeF(fFloatMin, fFloatMax);
|
| + return scalar;
|
| +}
|
| +
|
| +void makeScalarArray(size_t arrayCount, SkScalar* array) {
|
| + for (size_t index = 0; index < arrayCount; ++index) {
|
| + array[index] = makeScalar();
|
| + }
|
| +}
|
| +
|
| +void makeVectorArray(size_t arrayCount, SkVector* array) {
|
| + for (size_t index = 0; index < arrayCount; ++index) {
|
| + array[index] = makeVector();
|
| + }
|
| +}
|
| +
|
| +SkVector makeVector() {
|
| + SkVector result;
|
| + makeScalarArray(2, &result.fX);
|
| + return result;
|
| +}
|
| +
|
| +void validate(const SkPath& path) {
|
| + if (fValidate) {
|
| + SkDEBUGCODE(path.experimentalValidateRef());
|
| + }
|
| +}
|
| +
|
| +SkRandom fRand;
|
| +SkMatrix fMatrix;
|
| +SkPath fClip;
|
| +SkPaint fPaint;
|
| +SkPath fPath;
|
| +SkScalar fFloatMin;
|
| +SkScalar fFloatMax;
|
| +uint32_t fPathContourCount;
|
| +int fPathDepth;
|
| +int fPathDepthLimit;
|
| +uint32_t fPathSegmentLimit;
|
| +int fAddCount;
|
| +bool fPrintName;
|
| +bool fValidate;
|
| +const char* fTab;
|
| +};
|
| +
|
| +//////////////////////////////////////////////////////////////////////////////
|
| +static bool contains_only_moveTo(const SkPath& path) {
|
| + int verbCount = path.countVerbs();
|
| + if (verbCount == 0) {
|
| + return true;
|
| + }
|
| + SkTDArray<uint8_t> verbs;
|
| + verbs.setCount(verbCount);
|
| + SkDEBUGCODE(int getVerbResult = ) path.getVerbs(verbs.begin(), verbCount);
|
| + SkASSERT(getVerbResult == verbCount);
|
| + for (int index = 0; index < verbCount; ++index) {
|
| + if (verbs[index] != SkPath::kMove_Verb) {
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +class PathFuzzView : public SampleView {
|
| +public:
|
| + PathFuzzView() {
|
| + fDots = 0;
|
| + }
|
| +protected:
|
| + // overrides from SkEventSink
|
| + virtual bool onQuery(SkEvent* evt) {
|
| + if (SampleCode::TitleQ(*evt)) {
|
| + SampleCode::TitleR(evt, "PathFuzzer");
|
| + return true;
|
| + }
|
| + return this->INHERITED::onQuery(evt);
|
| + }
|
| +
|
| + virtual void onDrawContent(SkCanvas* canvas) {
|
| + fuzzPath.randomize();
|
| + const SkPath& path = fuzzPath.getPath();
|
| + const SkPaint& paint = fuzzPath.getPaint();
|
| + const SkPath& clip = fuzzPath.getClip();
|
| + const SkMatrix& matrix = fuzzPath.getMatrix();
|
| + if (!contains_only_moveTo(clip)) {
|
| + canvas->clipPath(clip);
|
| + }
|
| + canvas->setMatrix(matrix);
|
| + canvas->drawPath(path, paint);
|
| + this->inval(NULL);
|
| + if (++fDots == 8000) {
|
| + SkDebugf("\n");
|
| + fDots = 0;
|
| + }
|
| + if ((fDots % 100) == 99) {
|
| + SkDebugf(".");
|
| + }
|
| + }
|
| +
|
| +private:
|
| + FuzzPath fuzzPath;
|
| + int fDots;
|
| + typedef SkView INHERITED;
|
| +};
|
| +
|
| +//////////////////////////////////////////////////////////////////////////////
|
| +
|
| +static SkView* MyFactory() { return new PathFuzzView; }
|
| +static SkViewRegister reg(MyFactory);
|
|
|