bookmaker initial checkin

bookmaker is a tool that generates documentation
backends from a canonical markup. Documentation for
bookmaker itself is evolving at docs/usingBookmaker.bmh,
which is visible online at skia.org/user/api/bmh_usingBookmaker

Change-Id: Ic76ddf29134895b5c2ebfbc84603e40ff08caf09
Reviewed-on: https://skia-review.googlesource.com/28000
Commit-Queue: Cary Clark <caryclark@google.com>
Reviewed-by: Cary Clark <caryclark@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 5b06c17..5956f67 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1213,6 +1213,23 @@
     }
   }
 
+  test_app("bookmaker") {
+    sources = [
+      "tools/bookmaker/bookmaker.cpp",
+      "tools/bookmaker/fiddleParser.cpp",
+      "tools/bookmaker/includeParser.cpp",
+      "tools/bookmaker/includeWriter.cpp",
+      "tools/bookmaker/mdOut.cpp",
+      "tools/bookmaker/parserCommon.cpp",
+      "tools/bookmaker/spellCheck.cpp",
+    ]
+    deps = [
+      ":flags",
+      ":skia",
+      ":tool_utils",
+    ]
+  }
+
   import("gn/samples.gni")
   test_lib("samples") {
     public_include_dirs = [ "samplecode" ]
diff --git a/docs/SkCanvas.bmh b/docs/SkCanvas.bmh
new file mode 100644
index 0000000..aefbae4
--- /dev/null
+++ b/docs/SkCanvas.bmh
@@ -0,0 +1,5721 @@
+#Topic Canvas
+
+Canvas provides an interface for drawing, and how the drawing is clipped and transformed.
+Canvas contains a stack of Matrix and Clip values.
+
+Canvas and Paint together provide the state to draw into Surface or Device.
+Each Canvas draw call transforms the geometry of the object by the concatenation of all Matrix
+values in the stack. 
+The transformed geometry is clipped by the intersection of all of Clip values in the stack.
+The Canvas draw calls take Paint parameter for drawing state.
+Create Paint to supply the drawing state, such as Color,
+Typeface, Paint_Text_Size, Paint_Stroke_Width, Shader and so on.
+
+To draw to a pixel-based destination, create Raster_Surface or GPU_Surface. 
+Request Canvas from Surface to obtain the interface to draw. 
+Canvas generated by Raster_Surface draws to memory visible to the CPU. 
+Canvas generated by GPU_Surface uses Vulkan or OpenGL to draw to the GPU.
+
+Canvas can be constructed to draw to Bitmap without first creating Raster_Surface.
+This approach may be deprecated in the future.  
+
+To draw to a document, obtain Canvas from SVG_Canvas, Document_PDF, or Picture_Recorder.
+Document-based Canvas and other Canvas subclasses reference Device describing the destination.
+
+#Class SkCanvas
+
+#Topic Overview
+
+#Subtopic Subtopics
+#Table
+#Legend
+# topics                 # description                                 ##
+#Legend ##
+#ToDo generate a TOC here ##
+#Table ##
+#Subtopic ##
+
+#Subtopic Constants
+#Table
+#Legend
+# constants                      # description                                 ##
+#Legend ##
+# Lattice::Flags                 # Controls Lattice transparency. ##
+# PointMode                      # Sets drawPoints options. ##
+# SaveLayerFlags                 # Sets SaveLayerRec options. ##
+# SrcRectConstraint              # Sets drawImageRect options. ##
+#Table ##
+#Subtopic ##
+
+#Subtopic Structs
+#Table
+#Legend
+# struct                         # description                                 ##
+#Legend ##
+# Lattice                        # Divides Bitmap, Image into a rectangular grid. ##
+# SaveLayerRec                   # Contains state to create the layer offscreen. ##
+#Table ##
+#Subtopic ##
+
+#Subtopic Constructors
+
+Create the desired type of Surface to obtain its Canvas when possible. Constructors are useful
+when no Surface is required, and some helpers implicitly create Raster_Surface.
+
+#Table
+#Legend
+#                                                               # description                                     ##
+#Legend ##
+# SkCanvas()                                                    # No Surface, no dimensions.                      ##
+# SkCanvas(int width, int height, const SkSurfaceProps* = NULL) # No Surface, set dimensions, Surface_Properties. ##
+# SkCanvas(SkBaseDevice* device)                                # Existing Device. (SkBaseDevice is private.)     ##
+# SkCanvas(const SkBitmap& bitmap)                              # Uses existing Bitmap.                           ##
+# SkCanvas(const SkBitmap& bitmap, const SkSurfaceProps& props) # Uses existing Bitmap and Surface_Properties.    ##
+# MakeRasterDirect                                              # Creates from SkImageInfo and Pixel_Storage.     ##
+# MakeRasterDirectN32                                           # Creates from image data and Pixel_Storage.      ##
+#ToDo incomplete ##
+#Table ##
+#Subtopic ##
+
+#Subtopic Member_Functions
+#Table
+#Legend
+# function                         # description                                                   ##
+#Legend ##
+# accessTopLayerPixels             # Returns writable pixel access if available. ##
+# accessTopRasterHandle            # Returns context that tracks Clip and Matrix. ##
+# clear()                          # Fills Clip with Color. ##
+# clipPath                         # Combines Clip with Path. ##
+# clipRRect                        # Combines Clip with Round_Rect. ##
+# clipRect                         # Combines Clip with Rect. ##
+# clipRegion                       # Combines Clip with Region. ##
+# concat()                         # Multiplies Matrix by Matrix. ##
+# discard()                        # Makes Canvas contents undefined. ##
+# drawAnnotation                   # Associates a Rect with a key-value pair.##
+# drawArc                          # Draws Arc using Clip, Matrix, and Paint.##
+# drawAtlas                        # Draws sprites using Clip, Matrix, and Paint.##
+# drawBitmap                       # Draws Bitmap at (x, y) position. ##
+# drawBitmapLattice                # Draws differentially stretched Bitmap. ##
+# drawBitmapNine                   # Draws Nine_Patch Bitmap. ##
+# drawBitmapRect                   # Draws Bitmap, source Rect to destination Rect. ##
+# drawCircle                       # Draws Circle using Clip, Matrix, and Paint. ##
+# drawColor                        # Fills Clip with Color and Blend_Mode. ##
+# drawDRRect                       # Draws double Round_Rect stroked or filled. ##
+# drawDrawable                     # Draws Drawable, encapsulated drawing commands. ##
+# drawIRect                        # Draws IRect using Clip, Matrix, and Paint. ##
+# drawImage                        # Draws Image at (x, y) position. ##
+# drawImageLattice                 # Draws differentially stretched Image. ##
+# drawImageNine                    # Draws Nine_Patch Image. ##
+# drawImageRect                    # Draws Image, source Rect to destination Rect. ##
+# drawLine                         # Draws line segment between two points.##
+# drawOval                         # Draws Oval using Clip, Matrix, and Paint. ##
+# drawPaint                        # Fills Clip with Paint. ##
+# drawPatch                        # Draws cubic Coons patch. ##
+# drawPath                         # Draws Path using Clip, Matrix, and Paint. ##
+# drawPicture                      # Draws Picture using Clip and Matrix. ##
+# drawPoint                        # Draws point at (x, y) position. ##
+# drawPoints                       # Draws array as points, lines, polygon. ##
+# drawPosText                      # Draws text at array of (x, y) positions. ##
+# drawPosTextH                     # Draws text at x positions with common baseline. ##
+# drawRRect                        # Draws Round_Rect using Clip, Matrix, and Paint. ##
+# drawRect                         # Draws Rect using Clip, Matrix, and Paint. ##
+# drawRegion                       # Draws Region using Clip, Matrix, and Paint. ##
+# drawRoundRect                    # Draws Round_Rect using Clip, Matrix, and Paint. ##
+# drawText                         # Draws text at (x, y), using font advance. ##
+# drawTextBlob                     # Draws text with arrays of positions and Paint. ##
+# drawTextOnPath                   # Draws text following Path contour. ##
+# drawTextOnPathHV                 # Draws text following Path with offsets. ##
+# drawTextRSXform                  # Draws text with array of RSXform. ##
+# drawString                       # Draws null terminated string at (x, y) using font advance. ##
+# drawVertices                     # Draws Vertices, a triangle mesh. ##
+# flush()                          # Triggers execution of all pending draw operations. ##
+# getBaseLayerSize                 # Gets size of base layer in global coordinates. ##
+# getDeviceClipBounds              # Returns IRect bounds of Clip. ##
+# getDrawFilter                    # Legacy; to be deprecated. ##
+# getGrContext                     # Returns GPU_Context of the GPU_Surface. ##
+# getLocalClipBounds               # Returns Clip bounds in source coordinates. ##
+# getMetaData                      # Associates additional data with the canvas. ##
+# getProps                         # Copies Surface_Properties if available. ##
+# getSaveCount                     # Returns depth of stack containing Clip and Matrix. ##
+# getTotalMatrix                   # Returns Matrix. ##
+# imageInfo                        # Returns Image_Info for Canvas. ##
+# isClipEmpty                      # Returns if Clip is empty. ##
+# isClipRect                       # Returns if Clip is Rect and not empty. ##
+# MakeRasterDirect                 # Creates Canvas from SkImageInfo and pixel data. ##
+# MakeRasterDirectN32              # Creates Canvas from image specifications and pixel data. ##
+# makeSurface                      # Creates Surface matching SkImageInfo and SkSurfaceProps. ##
+# peekPixels                       # Returns if Canvas has direct access to its pixels. ##
+# quickReject                      # Returns if Rect is outside Clip. ##
+# readPixels                       # Copies and converts rectangle of pixels from Canvas. ##
+# resetMatrix                      # Resets Matrix to identity. ##
+# restore()                        # Restores changes to Clip and Matrix, pops save stack. ##
+# restoreToCount                   # Restores changes to Clip and Matrix to given depth. ##
+# rotate()                         # Rotates Matrix. ##
+# save()                           # Saves Clip and Matrix on stack. ##
+# saveLayer                        # Saves Clip and Matrix on stack; creates offscreen. ##
+# saveLayerAlpha                   # Saves Clip and Matrix on stack; creates offscreen; sets opacity. ##
+# saveLayerPreserveLCDTextRequests # Saves Clip and Matrix on stack; creates offscreen for LCD text. ##
+# scale()                          # Scales Matrix. ##
+# setAllowSimplifyClip             # Experimental. ##
+# setDrawFilter                    # Legacy; to be deprecated. ##
+# setMatrix                        # Sets Matrix. ##
+# skew()                           # Skews Matrix. #
+# translate()                      # Translates Matrix. ##
+# writePixels                      # Copies and converts rectangle of pixels to Canvas. ##
+#Table ##
+#Subtopic ##
+
+#Topic Overview ##
+
+# ------------------------------------------------------------------------------
+
+#Method static std::unique_ptr<SkCanvas> MakeRasterDirect(const SkImageInfo& info,
+        void* pixels, size_t rowBytes)
+
+Allocates raster canvas that will draw directly into pixels.
+To access pixels after drawing, call flush() or peekPixels.
+
+#Param info  Width, height, Image_Color_Type, Image_Alpha_Type, Color_Space, of Raster_Surface.
+             Width, or height, or both, may be zero.
+##
+#Param pixels  Pointer to destination pixels buffer. Buffer size should be info height
+               times rowBytes times bytes required for Image_Color_Type.
+##
+#Param rowBytes  The interval from one Surface row to the next; equal to or greater than
+                 info width times bytes required for Image_Color_Type.
+##
+
+#Return  Canvas if all parameters are valid; otherwise, nullptr.
+         Valid parameters include: info dimensions must be zero or positive, and other checks;
+         info must contain Image_Color_Type and Image_Alpha_Type supported by Raster_Surface;
+         pixels must be not be nullptr;
+         rowBytes must be zero or large enough to contain width pixels of Image_Color_Type.
+##
+
+#Example
+    #Description
+        Allocates a three by three bitmap, clears it to white, and draws a black pixel
+        in the center.
+    ##
+void draw(SkCanvas* ) {

+    SkImageInfo info = SkImageInfo::MakeN32Premul(3, 3);  // device aligned, 32 bpp, premultipled

+    const size_t minRowBytes = info.minRowBytes();  // bytes used by one bitmap row

+    const size_t size = info.getSafeSize(minRowBytes);  // bytes used by all rows

+    SkAutoTMalloc<SkPMColor> storage(size);  // allocate storage for pixels

+    SkPMColor* pixels = storage.get();  // get pointer to allocated storage

+    // create a SkCanvas backed by a raster device, and delete it when the

+    // function goes out of scope.

+    std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirect(info, pixels, minRowBytes);

+    canvas->clear(SK_ColorWHITE);  // white is unpremultiplied, in ARGB order

+    canvas->flush();  // ensure that pixels are cleared

+    SkPMColor pmWhite = pixels[0];  // the premultiplied format may vary

+    SkPaint paint;  // by default, draws black

+    canvas->drawPoint(1, 1, paint);  // draw in the center

+    canvas->flush();  // ensure that point was drawn

+    for (int y = 0; y < info.height(); ++y) {

+        for (int x = 0; x < info.width(); ++x) {

+            SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');

+        }

+        SkDebugf("\n");

+    }

+}
+    #StdOut
+        ---
+        -x-
+        ---
+    ##
+##
+
+#ToDo incomplete ##
+
+#SeeAlso MakeRasterDirectN32 SkSurface::MakeRasterDirect
+##
+
+# ------------------------------------------------------------------------------
+
+#Method static std::unique_ptr<SkCanvas> MakeRasterDirectN32(int width, int height, SkPMColor* pixels,
+                                                         size_t rowBytes) 
+
+Creates Canvas with Raster_Surface with inline image specification that draws into pixels.
+Image_Color_Type is set to kN32_SkColorType.
+Image_Alpha_Type is set to kPremul_SkAlphaType.
+To access pixels after drawing, call flush() or peekPixels.
+
+#Param width  Pixel column count on Raster_Surface created. Must be zero or greater. ##
+#Param height  Pixel row count on Raster_Surface created. Must be zero or greater. ##
+#Param pixels  Pointer to destination pixels buffer. Buffer size should be height
+               times rowBytes times four.
+##
+#Param rowBytes  The interval from one Surface row to the next; equal to or greater than
+                 width times four.
+##
+
+#Return  Canvas if all parameters are valid; otherwise, nullptr.
+         Valid parameters include: width and height must be zero or positive;
+         pixels must be not be nullptr;
+         rowBytes must be zero or large enough to contain width pixels of Image_Color_Type.
+##
+
+#Example
+    #Description
+        Allocates a three by three bitmap, clears it to white, and draws a black pixel
+        in the center.
+    ##
+void draw(SkCanvas* ) {
+    const int width = 3;
+    const int height = 3;
+    SkPMColor pixels[height][width];  // allocate a 3x3 premultiplied bitmap on the stack
+    // create a SkCanvas backed by a raster device, and delete it when the
+    // function goes out of scope.
+    std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirectN32(
+            width,
+            height,
+            pixels[0],  // top left of the bitmap
+            sizeof(pixels[0]));  // byte width of the each row
+    // write a pre-multiplied value for white into all pixels in the bitmap
+    canvas->clear(SK_ColorWHITE);
+    SkPMColor pmWhite = pixels[0][0];  // the premultiplied format may vary
+    SkPaint paint;  // by default, draws black
+    canvas->drawPoint(1, 1, paint);  // draw in the center
+    canvas->flush();  // ensure that pixels is ready to be read
+    for (int y = 0; y < height; ++y) {
+        for (int x = 0; x < width; ++x) {
+            SkDebugf("%c", pixels[y][x] == pmWhite ? '-' : 'x');
+        }
+        SkDebugf("\n");
+    }
+}
+    #StdOut
+        ---
+        -x-
+        ---
+    ##
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method SkCanvas()
+
+Creates an empty canvas with no backing device/pixels, and zero
+dimensions.
+
+#Return  An empty canvas. ##
+
+#Example
+
+#Description
+Passes a placeholder to a function that requires one.
+##
+
+#Function
+// Returns true if either the canvas rotates the text by 90 degrees, or the paint does.

+static void check_for_up_and_down_text(const SkCanvas* canvas, const SkPaint& paint) {

+    bool paintHasVertical = paint.isVerticalText();

+    const SkMatrix& matrix = canvas->getTotalMatrix();

+    bool matrixIsVertical = matrix.preservesRightAngles() && !matrix.isScaleTranslate();

+    SkDebugf("paint draws text %s\n", paintHasVertical != matrixIsVertical ?

+            "top to bottom" : "left to right");

+}

+

+static void check_for_up_and_down_text(const SkPaint& paint) {

+    SkCanvas canvas;  // placeholder only, does not have an associated device

+    check_for_up_and_down_text(&canvas, paint);

+}

+

+##
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    check_for_up_and_down_text(paint);  // paint draws text left to right

+    paint.setVerticalText(true);

+    check_for_up_and_down_text(paint);  // paint draws text top to bottom

+    paint.setVerticalText(false);

+    canvas->rotate(90);

+    check_for_up_and_down_text(canvas, paint);  // paint draws text top to bottom

+}

+
+    #StdOut
+        paint draws text left to right
+        paint draws text top to bottom
+        paint draws text top to bottom
+    ##
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method SkCanvas(int width, int height, const SkSurfaceProps* props = NULL)
+
+Creates Canvas of the specified dimensions without a Surface.
+Used by subclasses with custom implementations for draw methods.
+
+#Param width  Zero or greater. ##
+#Param height Zero or greater. ##
+#Param props  The LCD striping orientation and setting for device independent fonts.
+                     If nullptr, use Legacy_Font_Host settings. ##
+
+#Return       Canvas placeholder with dimensions. ##
+
+#Example
+    SkCanvas canvas(10, 20);  // 10 units wide, 20 units high
+    canvas.clipRect(SkRect::MakeXYWH(30, 40, 5, 10));  // clip is outside canvas' device
+    SkDebugf("canvas %s empty\n", canvas.getDeviceClipBounds().isEmpty() ? "is" : "is not");
+
+    #StdOut
+        canvas is empty
+    ##
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method explicit SkCanvas(SkBaseDevice* device)
+
+Construct a canvas that draws into device.
+Used by child classes of SkCanvas.
+
+#ToDo  Since SkBaseDevice is private, shouldn't this be private also? ##
+
+#Param device   Specifies a device for the canvas to draw into. ##
+
+#Return         Canvas that can be used to draw into device. ##
+
+#Example
+#Error "Unsure how to create a meaningful example."
+ SkPDFCanvas::SkPDFCanvas(const sk_sp<SkPDFDevice>& dev)
+    : SkCanvas(dev.get()) {}
+##
+
+#ToDo either remove doc of figure out a way to fiddle it ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method explicit SkCanvas(const SkBitmap& bitmap)
+
+Construct a canvas that draws into bitmap.
+Sets SkSurfaceProps::kLegacyFontHost_InitType in constructed Surface. 
+
+#ToDo Should be deprecated? ##
+
+#Param bitmap   Width, height, Image_Color_Type, Image_Alpha_Type, and pixel storage of Raster_Surface. 
+                Bitmap is copied so that subsequently editing bitmap will not affect
+                constructed Canvas.
+##
+
+#Return         Canvas that can be used to draw into bitmap. ##
+
+#Example
+#Description
+The actual output depends on the installed fonts.
+##
+    SkBitmap bitmap;
+    // create a bitmap 5 wide and 11 high
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(5, 11));
+    SkCanvas canvas(bitmap);
+    canvas.clear(SK_ColorWHITE);  // white is unpremultiplied, in ARGB order
+    SkPixmap pixmap;  // provides guaranteed access to the drawn pixels
+    if (!canvas.peekPixels(&pixmap)) {
+        SkDebugf("peekPixels should never fail.\n");
+    }
+    const SkPMColor* pixels = pixmap.addr32();  // points to top left of bitmap
+    SkPMColor pmWhite = pixels[0];  // the premultiplied format may vary
+    SkPaint paint;  // by default, draws black, 12 point text
+    canvas.drawString("!", 1, 10, paint);  // 1 char at baseline (1, 10)
+    for (int y = 0; y < bitmap.height(); ++y) {
+        for (int x = 0; x < bitmap.width(); ++x) {
+            SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
+        }
+        SkDebugf("\n");
+    }
+
+    #StdOut
+    -----
+    --x--
+    --x--
+    --x--
+    --x--
+    --x--
+    --x--
+    -----
+    --x--
+    --x--
+    -----
+    #StdOut ##
+##
+
+#ToDo incomplete ##
+
+##
+
+#Enum ColorBehavior
+
+#ToDo exclude this during build phase
+      (use SK_BUILD_FOR_ANDROID_FRAMEWORK as exclude directive)
+##
+
+#Private
+Android framework only.
+##
+
+#Code
+    enum class ColorBehavior {
+        kLegacy,
+    };
+##
+#Const kLegacy 0
+##
+##
+
+
+# ------------------------------------------------------------------------------
+
+#Method SkCanvas(const SkBitmap& bitmap, const SkSurfaceProps& props)
+
+Construct a canvas that draws into bitmap.
+Use props to match the device characteristics, like LCD striping.
+
+#Param bitmap   Width, height, Image_Color_Type, Image_Alpha_Type, and pixel storage of Raster_Surface. 
+                Bitmap is copied so that subsequently editing bitmap will not affect
+                constructed Canvas.
+##
+#Param props    The order and orientation of RGB striping; and whether to use
+                device independent fonts.
+##
+
+#Return         Canvas that can be used to draw into bitmap. ##
+
+#Example
+#Description
+The actual output depends on the installed fonts.
+##
+    SkBitmap bitmap;
+    // create a bitmap 5 wide and 11 high
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(5, 11));
+    SkCanvas canvas(bitmap, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
+    canvas.clear(SK_ColorWHITE);  // white is unpremultiplied, in ARGB order
+    SkPixmap pixmap;  // provides guaranteed access to the drawn pixels
+    if (!canvas.peekPixels(&pixmap)) {
+        SkDebugf("peekPixels should never fail.\n");
+    }
+    const SkPMColor* pixels = pixmap.addr32();  // points to top left of bitmap
+    SkPMColor pmWhite = pixels[0];  // the premultiplied format may vary
+    SkPaint paint;  // by default, draws black, 12 point text
+    canvas.drawString("!", 1, 10, paint);  // 1 char at baseline (1, 10)
+    for (int y = 0; y < bitmap.height(); ++y) {
+        for (int x = 0; x < bitmap.width(); ++x) {
+            SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
+        }
+        SkDebugf("\n");
+    }
+
+    #StdOut
+    -----
+    ---x-
+    ---x-
+    ---x-
+    ---x-
+    ---x-
+    ---x-
+    -----
+    ---x-
+    ---x-
+    -----
+    #StdOut ##
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method virtual ~SkCanvas()
+
+Draws State_Stack_Layer, if any.
+Free up resources used by Canvas.
+
+#Example
+#Error "Haven't thought of a useful example to put here."
+##
+
+#ToDo create example to show how draw happens when canvas goes out of scope ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method SkMetaData& getMetaData()
+
+Associates additional data with the canvas.
+The storage is freed when Canvas is deleted.
+
+#Return  storage that can be read from and written to. ##
+
+#Example
+    const char* kHelloMetaData = "HelloMetaData";
+    SkCanvas canvas;
+    SkMetaData& metaData = canvas.getMetaData();
+    SkDebugf("before: %s\n", metaData.findString(kHelloMetaData));
+    metaData.setString(kHelloMetaData, "Hello!");
+    SkDebugf("during: %s\n", metaData.findString(kHelloMetaData));
+    metaData.removeString(kHelloMetaData);
+    SkDebugf("after: %s\n", metaData.findString(kHelloMetaData));
+
+    #StdOut
+        before: (null)
+        during: Hello!
+        after: (null)
+    #StdOut ##
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method SkImageInfo imageInfo() const
+
+Returns Image_Info for Canvas. If Canvas is not associated with Raster_Surface or
+GPU_Surface, returns SkImageInfo::SkImageInfo() is returned Image_Color_Type is set to kUnknown_SkColorType.
+
+#Return  dimensions and Image_Color_Type of Canvas. ##
+
+#Example
+    SkCanvas canvas;
+    SkImageInfo canvasInfo = canvas.imageInfo();
+    SkImageInfo emptyInfo;
+    SkDebugf("emptyInfo %c= canvasInfo\n", emptyInfo == canvasInfo ? '=' : '!');
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method bool getProps(SkSurfaceProps* props) const
+
+If Canvas is associated with Raster_Surface or
+GPU_Surface, copies Surface_Properties and returns true. Otherwise,
+return false and leave props unchanged.
+
+#Param props  Pointer to writable SkSurfaceProps. ##
+
+#Return  true if Surface_Properties was copied. ##
+
+#ToDo This seems old style. Deprecate? ##
+
+#Example
+    SkBitmap bitmap;
+    SkCanvas canvas(bitmap, SkSurfaceProps(0, kRGB_V_SkPixelGeometry));
+    SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
+    SkDebugf("isRGB:%d\n", SkPixelGeometryIsRGB(surfaceProps.pixelGeometry()));
+    if (!canvas.getProps(&surfaceProps)) {
+        SkDebugf("getProps failed unexpectedly.\n");
+    }
+    SkDebugf("isRGB:%d\n", SkPixelGeometryIsRGB(surfaceProps.pixelGeometry()));
+
+    #StdOut
+        isRGB:0
+        isRGB:1
+    #StdOut ##
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void flush()
+
+Triggers the immediate execution of all pending draw operations. 
+If Canvas is associated with GPU_Surface, resolve all pending GPU operations.
+
+#Example
+#Error "haven't thought of a useful example to put here"
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method virtual SkISize getBaseLayerSize() const
+
+Gets the size of the base or root layer in global canvas coordinates. The
+origin of the base layer is always (0,0). The current drawable area may be
+smaller (due to clipping or saveLayer).
+
+#Return  Integral width and height of base layer. ##
+
+#Example
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(20, 30));
+    SkCanvas canvas(bitmap, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
+    canvas.clipRect(SkRect::MakeWH(10, 40));
+    SkIRect clipDeviceBounds = canvas.getDeviceClipBounds();
+    if (clipDeviceBounds.isEmpty()) {
+        SkDebugf("Empty clip bounds is unexpected!\n");
+    }
+    SkDebugf("clip=%d,%d\n", clipDeviceBounds.width(), clipDeviceBounds.height());
+    SkISize baseLayerSize = canvas.getBaseLayerSize();
+    SkDebugf("size=%d,%d\n", baseLayerSize.width(), baseLayerSize.height());
+
+    #StdOut
+        clip=10,30
+        size=20,30
+    ##
+##
+
+#ToDo is this the same as the width and height of surface? ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method sk_sp<SkSurface> makeSurface(const SkImageInfo& info, const SkSurfaceProps* props = nullptr)
+
+Creates Surface matching info and props, and associates it with Canvas.
+If Canvas is already associated with Surface, it cannot create a new Surface.
+
+#Param info  Initialize Surface with width, height, Image_Color_Type, Image_Alpha_Type, and Color_Space. ##
+#Param props  Use to match if provided, or use the Surface_Properties in Canvas otherwise. ##
+
+#Return  Surface matching info and props, or nullptr if no match is available. ##
+
+#Example
+    sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(5, 6);
+    SkCanvas* smallCanvas = surface->getCanvas();
+    SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(3, 4);
+    sk_sp<SkSurface> compatible = smallCanvas->makeSurface(imageInfo);
+    SkDebugf("compatible %c= nullptr\n", compatible == nullptr ? '=' : '!');
+    SkDebugf("size = %d, %d\n", compatible->width(), compatible->height());
+
+    #StdOut
+        compatible != nullptr

+        size = 3, 4
+    ##
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method virtual GrContext* getGrContext()
+
+Returns GPU_Context of the GPU_Surface associated with Canvas.
+
+#Return GPU_Context, if available; nullptr otherwise. ##
+
+#Example
+void draw(SkCanvas* canvas) {
+    if (canvas->getGrContext()) {
+         canvas->clear(SK_ColorRED);
+    } else {
+         canvas->clear(SK_ColorBLUE);
+    }
+}
+##
+
+#ToDo fiddle should show both CPU and GPU out ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void* accessTopLayerPixels(SkImageInfo* info, size_t* rowBytes, SkIPoint* origin = NULL)
+
+Returns the pixel base address, Image_Info, rowBytes, and origin if the pixels
+can be read directly.
+The returned address is only valid
+while Canvas is in scope and unchanged. Any Canvas call or Surface call
+may invalidate the returned address and other returned values.
+
+If pixels are inaccessible, info, rowBytes, and origin are unchanged.
+
+#Param info  If not nullptr, copies writable pixels' Image_Info. ##
+#Param rowBytes  If not nullptr, copies writable pixels' row bytes. ##
+#Param origin  If not nullptr, copies Canvas top layer origin, its top left corner. ##
+
+#Return  Address of pixels, or nullptr if inaccessible. ##
+
+#Example
+void draw(SkCanvas* canvas) {
+    if (canvas->accessTopLayerPixels(nullptr, nullptr)) {
+         canvas->clear(SK_ColorRED);
+    } else {
+         canvas->clear(SK_ColorBLUE);
+    }
+}
+##
+
+#Example
+#Description
+Draws "ABC" on the device. Then draws "DEF" in an offscreen layer, and reads the
+offscreen to add a large dotted "DEF". Finally blends the offscreen with the
+device. 
+
+The offscreen and blended result appear on the CPU and GPU but the large dotted
+"DEF" appear only on the CPU.
+##
+void draw(SkCanvas* canvas) {
+  SkPaint paint;

+  paint.setTextSize(100);

+  canvas->drawString("ABC", 20, 160, paint);

+  SkRect layerBounds = SkRect::MakeXYWH(32, 32, 192, 192);

+  canvas->saveLayerAlpha(&layerBounds, 128);

+  canvas->clear(SK_ColorWHITE);

+  canvas->drawString("DEF", 20, 160, paint);

+  SkImageInfo imageInfo;

+  size_t rowBytes;

+  SkIPoint origin;

+  uint32_t* access = (uint32_t*) canvas->accessTopLayerPixels(&imageInfo, &rowBytes, &origin);

+  if (access) {

+    int h = imageInfo.height();

+    int v = imageInfo.width();

+    int rowWords = rowBytes / sizeof(uint32_t);

+    for (int y = 0; y < h; ++y) {

+        int newY = (y - h / 2) * 2 + h / 2;

+        if (newY < 0 || newY >= h) {

+            continue;

+        }

+        for (int x = 0; x < v; ++x) {

+            int newX = (x - v / 2) * 2 + v / 2;

+            if (newX < 0 || newX >= v) {

+                continue;

+            }

+            if (access[y * rowWords + x] == SK_ColorBLACK) {

+                access[newY * rowWords + newX] = SK_ColorGRAY;

+            }

+        }

+    }

+

+  }

+  canvas->restore();
+}
+##
+
+#ToDo there are no callers of this that I can find. Deprecate? ##
+#ToDo fiddle should show both CPU and GPU out ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method SkRasterHandleAllocator::Handle accessTopRasterHandle() const
+
+Returns custom context that tracks the Matrix and Clip.
+
+Use Raster_Handle_Allocator to blend Skia drawing with custom drawing, typically performed
+by the host platform's user interface. This accessor returns the custom context created
+when SkRasterHandleAllocator::MakeCanvas creates a custom canvas with raster storage for
+the drawing destination.
+
+#Return  Context of custom allocator. ##
+
+#Example
+#Description
+#ToDo ##
+##
+#Function
+    static void DeleteCallback(void*, void* context) {
+        delete (char*) context;
+    }
+
+    class CustomAllocator : public SkRasterHandleAllocator {
+    public:
+        bool allocHandle(const SkImageInfo& info, Rec* rec) override {
+            char* context = new char[4]{'s', 'k', 'i', 'a'};
+            rec->fReleaseProc = DeleteCallback;
+            rec->fReleaseCtx = context;
+            rec->fHandle = context;
+            rec->fPixels = context;
+            rec->fRowBytes = 4;
+            return true;
+        }
+
+        void updateHandle(Handle handle, const SkMatrix& ctm, const SkIRect& clip_bounds) override {
+            // apply canvas matrix and clip to custom environment
+        }
+    };
+
+##
+    void draw(SkCanvas* canvas) {
+        const SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
+        std::unique_ptr<SkCanvas> c2 =
+                SkRasterHandleAllocator::MakeCanvas(std::unique_ptr<CustomAllocator>(
+                new CustomAllocator()), info);
+        char* context = (char*) c2->accessTopRasterHandle();
+        SkDebugf("context = %.4s\n", context);
+
+    }
+    #StdOut
+        context = skia
+    ##
+    #ToDo skstd::make_unique could not be used because def is private -- note to fix in c++14? ##
+##
+
+#SeeAlso SkRasterHandleAllocator
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method bool peekPixels(SkPixmap* pixmap)
+
+Returns true if Canvas has direct access to its pixels.
+
+Pixels are readable when Device is raster. Pixels are not readable when SkCanvas is returned from
+GPU_Surface, returned by SkDocument::beginPage, returned by SkPictureRecorder::beginRecording,
+or SkCanvas is the base of a utility class like SkDumpCanvas.
+
+pixmap pixel address is only valid while Canvas is in scope and unchanged. Any Canvas or Surface call may
+invalidate the pixmap values.
+
+#Param pixmap  storage for Canvas pixel state if Canvas pixels are readable; otherwise, ignored. ##
+
+#Return  true if Canvas has direct access to pixels. ##
+
+#Example
+    SkPixmap pixmap;
+    if (canvas->peekPixels(&pixmap)) {
+        SkDebugf("width=%d height=%d\n", pixmap.bounds().width(), pixmap.bounds().height());
+    }
+    #StdOut
+        width=256 height=256
+    ##
+##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
+                    int srcX, int srcY)
+
+Copies rectangle of pixels from Canvas into dstPixels, converting their Image_Color_Type and Image_Alpha_Type.
+Pixels are readable when Device is raster. Pixels are not readable when SkCanvas is returned from
+GPU_Surface, returned by SkDocument::beginPage, returned by SkPictureRecorder::beginRecording,
+or SkCanvas is the base of a utility class like SkDumpCanvas.
+
+Pixel values are converted only if Canvas Image_Color_Type and Image_Alpha_Type does not match dstInfo.
+Only pixels within the rectangle that intersect Canvas pixels are copied.
+dstPixels outside the rectangle intersection are unchanged.
+
+#Table
+#Legend
+# source rectangle # value ##
+##
+# left # srcX ##
+# top # srcY ##
+# width # dstInfo.width() ##
+# height # dstInfo.height() ##
+##
+
+ #Table
+#Legend
+# canvas pixel bounds # value ##
+##
+# left # 0 ##
+# top # 0 ##
+# width # imageInfo().width() ##
+# height # imageInfo().height() ##
+##
+
+Does not copy, and returns false if:
+
+#List
+# Source rectangle and canvas pixel bounds do not intersect. ##
+# Canvas pixels could not be converted to dstInfo Image_Color_Type or dstInfo Image_Alpha_Type. ##
+# Canvas pixels are not readable; for instance, Canvas is not raster, or is document-based. ##
+# dstRowBytes is too small to contain one row of pixels. ##
+##
+
+#Param dstInfo  Dimensions, Image_Color_Type, and Image_Alpha_Type of dstPixels. ##
+#Param dstPixels  Storage for pixels, of size dstInfo.height() times dstRowBytes. ##
+#Param dstRowBytes  Size of one destination row, dstInfo.width() times pixel size. ##
+#Param srcX  Offset into readable pixels in x. ##
+#Param srcY  Offset into readable pixels in y. ##
+
+#Return  true if pixels were copied. ##
+
+#Example
+#Description
+    Canvas returned by Raster_Surface has premultiplied pixel values.
+    clear() takes unpremultiplied input with Color_Alpha equal 0x80
+    and Color_RGB equal 0x55, 0xAA, 0xFF. Color_RGB is multipled by Color_Alpha
+    to generate premultipled value 0x802B5580. readPixels converts pixel back
+    to unpremultipled value 0x8056A9FF, introducing error.
+##
+    canvas->clear(0x8055aaff);
+    for (SkAlphaType alphaType : { kPremul_SkAlphaType, kUnpremul_SkAlphaType } ) {
+        uint32_t pixel = 0;
+        SkImageInfo info = SkImageInfo::Make(1, 1, kBGRA_8888_SkColorType, alphaType);
+        if (canvas->readPixels(info, &pixel, 4, 0, 0)) {
+            SkDebugf("pixel = %08x\n", pixel);
+        }
+    }
+
+    #StdOut
+        pixel = 802b5580
+        pixel = 8056a9ff
+    ##
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method bool readPixels(const SkPixmap& pixmap, int srcX, int srcY)
+
+Copies rectangle of pixels from Canvas into Pixmap, converting their Image_Color_Type and Image_Alpha_Type.
+Pixels are readable when Device is raster. Pixels are not readable when SkCanvas is returned from
+GPU_Surface, returned by SkDocument::beginPage, returned by SkPictureRecorder::beginRecording,
+or SkCanvas is the base of a utility class like SkDumpCanvas.
+
+Pixel values are converted only if Canvas Image_Color_Type and Image_Alpha_Type does not match bitmap Image_Info.
+Only Pixmap pixels within the rectangle that intersect Canvas pixels are copied.
+Pixmap pixels outside the rectangle intersection are unchanged.
+
+#Table
+#Legend
+# source rectangle # value ##
+##
+# left # srcX ##
+# top # srcY ##
+# width # bitmap.width() ##
+# height # bitmap.height() ##
+##
+
+ #Table
+#Legend
+# canvas pixel bounds # value ##
+##
+# left # 0 ##
+# top # 0 ##
+# width # imageInfo().width() ##
+# height # imageInfo().height() ##
+##
+
+Does not copy, and returns false if:
+
+#List
+# Source rectangle and canvas pixel bounds do not intersect. ##
+# Canvas pixels could not be converted to bitmap Image_Color_Type or bitmap Image_Alpha_Type. ##
+# Canvas pixels are not readable; for instance, Canvas is not raster, or is document-based. ##
+# bitmap pixels could not be allocated. ##
+# Bitmap_Row_Bytes is too small to contain one row of pixels. ##
+##
+
+#Param pixmap  Receives pixels copied from Canvas. ##
+#Param srcX  Offset into readable pixels in x. ##
+#Param srcY  Offset into readable pixels in y. ##
+
+#Return  true if pixels were copied. ##
+
+#Example
+void draw(SkCanvas* canvas) {
+    canvas->clear(0x8055aaff);
+    uint32_t pixels[1] = { 0 };
+    SkPixmap pixmap(SkImageInfo::MakeN32Premul(1, 1), pixels, 4);

+    canvas->readPixels(pixmap, 0, 0);
+    SkDebugf("pixel = %08x\n", pixels[0]);
+}
+    #StdOut
+        pixel = 802b5580
+    ##
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method bool readPixels(const SkBitmap& bitmap, int srcX, int srcY)
+
+Copies pixels enclosed by bitmap offset to (x, y) from Canvas into bitmap, converting their Image_Color_Type and Image_Alpha_Type.
+Pixels are readable when Device is raster. Pixels are not readable when SkCanvas is returned from
+GPU_Surface, returned by SkDocument::beginPage, returned by SkPictureRecorder::beginRecording,
+or SkCanvas is the base of a utility class like SkDumpCanvas.
+Allocates pixel storage in bitmap if needed.
+
+Pixel values are converted only if Canvas Image_Color_Type and Image_Alpha_Type does not match bitmap Image_Info.
+Only pixels within the rectangle that intersect Canvas pixels are copied.
+Bitamp pixels outside the rectangle intersection are unchanged.
+
+ #Table
+#Legend
+# canvas pixel bounds # value ##
+##
+# left # 0 ##
+# top # 0 ##
+# width # imageInfo().width() ##
+# height # imageInfo().height() ##
+##
+
+Does not copy, and returns false if:
+
+#List
+# Bounds formed by (x, y) and bitmap (width, height) and canvas pixel bounds do not intersect. ##
+# Canvas pixels could not be converted to bitmap Image_Color_Type or bitmap Image_Alpha_Type. ##
+# Canvas pixels are not readable; for instance, Canvas is not raster, or is document-based. ##
+# bitmap pixels could not be allocated. ##
+# Bitmap_Row_Bytes is too small to contain one row of pixels. ##
+##
+
+#Param bitmap  Receives pixels copied from Canvas. ##
+#Param srcX  Offset into readable pixels in x. ##
+#Param srcY  Offset into readable pixels in y. ##
+
+#Return  true if pixels were copied. ##
+
+#Example
+void draw(SkCanvas* canvas) {
+    canvas->clear(0x8055aaff);
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(1, 1));
+    canvas->readPixels(bitmap, 0, 0);
+    SkDebugf("pixel = %08x\n", bitmap.getAddr32(0, 0)[0]);
+}
+    #StdOut
+        pixel = 802b5580
+    ##
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method bool writePixels(const SkImageInfo& info, const void* pixels, size_t rowBytes, int x, int y)
+
+Copies to Canvas pixels, ignoring the Matrix and Clip, converting to match
+info Image_Color_Type and info Image_Alpha_Type.
+
+Pixel values are converted only if Canvas Image_Color_Type and Image_Alpha_Type does not match info.
+Only pixels within the source rectangle that intersect Canvas pixel bounds are copied.
+Canvas pixels outside the rectangle intersection are unchanged.
+
+#Table
+#Legend
+# source rectangle # value ##
+##
+# left # x ##
+# top # y ##
+# width # info.width() ##
+# height # info.height() ##
+##
+
+ #Table
+#Legend
+# canvas pixel bounds # value ##
+##
+# left # 0 ##
+# top # 0 ##
+# width # imageInfo().width() ##
+# height # imageInfo().height() ##
+##
+
+Does not copy, and returns false if:
+
+#List
+# Source rectangle and canvas pixel bounds do not intersect. ##
+# pixels could not be converted to Canvas Image_Color_Type or Canvas Image_Alpha_Type. ##
+# Canvas pixels are not writable; for instance, Canvas is document-based. ##
+# rowBytes is too small to contain one row of pixels. ##
+##
+
+#Param info    Dimensions, Image_Color_Type, and Image_Alpha_Type of pixels. ##
+#Param pixels  Pixels to copy, of size info.height() times rowBytes. ##
+#Param rowBytes  Offset from one row to the next, usually info.width() times pixel size. ##
+#Param x  Offset into Canvas writable pixels in x. ##
+#Param y  Offset into Canvas writable pixels in y. ##
+
+#Return  true if pixels were written to Canvas. ##
+
+#Example
+    SkImageInfo imageInfo = SkImageInfo::MakeN32(256, 1, kPremul_SkAlphaType);
+    for (int y = 0; y < 256; ++y) {
+        uint32_t pixels[256];
+        for (int x = 0; x < 256; ++x) {
+            pixels[x] = SkColorSetARGB(x, x + y, x, x - y);
+        }
+        canvas->writePixels(imageInfo, &pixels, sizeof(pixels), 0, y);
+    }
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method bool writePixels(const SkBitmap& bitmap, int x, int y)
+
+Writes to Canvas pixels, ignoring the Matrix and Clip, converting to match
+bitmap Image_Color_Type and bitmap Image_Alpha_Type.
+
+Pixel values are converted only if Canvas Image_Color_Type and Image_Alpha_Type does not match bitmap.
+Only pixels within the source rectangle that intersect Canvas pixel bounds are copied.
+Canvas pixels outside the rectangle intersection are unchanged.
+
+#Table
+#Legend
+# source rectangle # value ##
+##
+# left # x ##
+# top # y ##
+# width # bitmap.width() ##
+# height # bitmap.height() ##
+##
+
+ #Table
+#Legend
+# canvas pixel bounds # value ##
+##
+# left # 0 ##
+# top # 0 ##
+# width # imageInfo().width() ##
+# height # imageInfo().height() ##
+##
+
+Does not copy, and returns false if:
+
+#List
+# Source rectangle and Canvas pixel bounds do not intersect. ##
+# bitmap does not have allocated pixels. ##
+# bitmap pixels could not be converted to Canvas Image_Color_Type or Canvas Image_Alpha_Type. ##
+# Canvas pixels are not writable; for instance, Canvas is document-based. ##
+# bitmap pixels are inaccessible; for instance, bitmap wraps a texture. ##
+##
+
+#Param bitmap  Provides pixels copied to Canvas. ##
+#Param x  Offset into Canvas writable pixels in x. ##
+#Param y  Offset into Canvas writable pixels in y. ##
+
+#Return  true if pixels were written to Canvas. ##
+
+#Example
+void draw(SkCanvas* canvas) {
+    SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(2, 2);
+    SkBitmap bitmap;
+    bitmap.setInfo(imageInfo);
+    uint32_t pixels[4];
+    bitmap.setPixels(pixels);
+    for (int y = 0; y < 256; y += 2) {
+        for (int x = 0; x < 256;  x += 2) {
+            pixels[0] = SkColorSetRGB(x, y, x | y);
+            pixels[1] = SkColorSetRGB(x ^ y, y, x);
+            pixels[2] = SkColorSetRGB(x, x & y, y);
+            pixels[3] = SkColorSetRGB(~x, ~y, x);
+            canvas->writePixels(bitmap, x, y);
+        }
+    }
+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+#Topic State_Stack
+
+Canvas maintains a stack of state that allows hierarchical drawing, commonly used
+to implement windows and views. The initial state has an identity matrix and and an infinite clip.
+Even with a wide-open clip, drawing is constrained by the bounds of the
+Canvas Surface or Device.
+
+Canvas savable state consists of Clip, Matrix, and Draw_Filter.
+Clip describes the area that may be drawn to.
+Matrix transforms the geometry.
+Draw_Filter (deprecated on most platforms) modifies the paint before drawing.
+
+save(), saveLayer, saveLayerPreserveLCDTextRequests, and saveLayerAlpha
+save state and return the depth of the stack.
+
+restore() and restoreToCount revert state to its value when saved.
+
+Each state on the stack intersects Clip with the previous Clip,
+and concatenates Matrix with the previous Matrix.
+The intersected Clip makes the drawing area the same or smaller;
+the concatenated Matrix may move the origin and potentially scale or rotate
+the coordinate space.
+
+Canvas does not require balancing the state stack but it is a good idea
+to do so. Calling save() without restore() will eventually cause Skia to fail;
+mismatched save() and restore() create hard to find bugs.
+
+It is not possible to use state to draw outside of the clip defined by the
+previous state.
+
+#Example
+#Description
+Draw to ever smaller clips; then restore drawing to full canvas.
+Note that the second clipRect is not permitted to enlarge Clip.
+##
+#Height 160
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    canvas->save();                             // records stack depth to restore  

+    canvas->clipRect(SkRect::MakeWH(100, 100)); // constrains drawing to clip

+    canvas->clear(SK_ColorRED);                 // draws to limit of clip

+    canvas->save();                             // records stack depth to restore 

+    canvas->clipRect(SkRect::MakeWH(50, 150));  // Rect below 100 is ignored

+    canvas->clear(SK_ColorBLUE);                // draws to smaller clip

+    canvas->restore();                          // enlarges clip

+    canvas->drawLine(20, 20, 150, 150, paint);  // line below 100 is not drawn

+    canvas->restore();                          // enlarges clip

+    canvas->drawLine(150, 20, 50, 120, paint);  // line below 100 is drawn

+}
+## 
+
+Each Clip uses the current Matrix for its coordinates.
+
+#Example
+#Description
+While clipRect is given the same rectangle twice, Matrix makes the second
+clipRect draw at half the size of the first.
+##
+#Height 128
+void draw(SkCanvas* canvas) {

+    canvas->clipRect(SkRect::MakeWH(100, 100));

+    canvas->clear(SK_ColorRED);

+    canvas->scale(.5, .5);

+    canvas->clipRect(SkRect::MakeWH(100, 100));

+    canvas->clear(SK_ColorBLUE);

+}
+##
+
+#SeeAlso save() saveLayer saveLayerPreserveLCDTextRequests saveLayerAlpha restore() restoreToCount
+
+#Method int save()
+
+Saves Matrix, Clip, and Draw_Filter (Draw_Filter deprecated on most platforms).
+Calling restore() discards changes to Matrix, Clip, and Draw_Filter,
+restoring the Matrix, Clip, and Draw_Filter to their state when save() was called.
+
+Matrix may be changed by translate(), scale(), rotate(), skew(), concat(), setMatrix, and resetMatrix.
+Clip may be changed by clipRect, clipRRect, clipPath, clipRegion.
+
+Saved Canvas state is put on a stack; multiple calls to save() should be balance by an equal number of
+calls to restore().
+
+Call restoreToCount with result to restore this and subsequent saves.
+
+#Return Depth of saved stack. ##
+
+#Example
+#Description 
+The black square is translated 50 pixels down and to the right.
+Restoring Canvas state removes translate() from Canvas stack;
+the red square is not translated, and is drawn at the origin.
+##
+#Height 100
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkRect rect = { 0, 0, 25, 25 };
+    canvas->drawRect(rect, paint);
+    canvas->save();
+    canvas->translate(50, 50);
+    canvas->drawRect(rect, paint);
+    canvas->restore();
+    paint.setColor(SK_ColorRED);
+    canvas->drawRect(rect, paint);
+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+#Subtopic Layer
+
+Layer allocates a temporary offscreen Bitmap to draw into. When the drawing is complete,
+the Bitmap is drawn into the Canvas. 
+
+Layer is saved in a stack along with other saved state. When state with a Layer
+is restored, the offscreen Bitmap is drawn into the previous layer.
+
+Layer may be initialized with the contents of the previous layer. When Layer is
+restored, its Bitmap can be modified by Paint passed to Layer to apply Color_Alpha,
+Color_Filter, Image_Filter, and Blend_Mode.
+
+#Method int saveLayer(const SkRect* bounds, const SkPaint* paint)
+
+Saves Matrix, Clip, and Draw_Filter (Draw_Filter deprecated on most platforms),
+and allocates an offscreen Bitmap for subsequent drawing.
+Calling restore() discards changes to Matrix, Clip, and Draw_Filter,
+and draws the offscreen bitmap.
+The Matrix, Clip, and Draw_Filter are restored to their state when save() was called. 
+
+Matrix may be changed by translate(), scale(), rotate(), skew(), concat(), setMatrix, and resetMatrix.
+Clip may be changed by clipRect, clipRRect, clipPath, clipRegion.
+
+Rect bounds suggests but does not define the offscreen size. To clip drawing to a specific rectangle,
+use clipRect.
+
+Optional Paint paint applies Color_Alpha, Color_Filter, Image_Filter, and Blend_Mode when restore() is called.
+
+Call restoreToCount with result to restore this and subsequent saves.
+
+#Param bounds  Used as a hint to limit the size of the offscreen; may be nullptr. ##
+#Param paint  Used when restore() is called to draw the offscreen; may be nullptr. ##
+
+#Return  Depth of saved stack. ##
+
+#Example
+#Description
+Rectangles are blurred by Image_Filter when restore() draws offscreen to main Canvas.
+##
+#Height 128
+void draw(SkCanvas* canvas) {
+    SkPaint paint, blur;
+    blur.setImageFilter(SkImageFilter::MakeBlur(3, 3, nullptr));
+    canvas->saveLayer(nullptr, &blur);
+    SkRect rect = { 25, 25, 50, 50};
+    canvas->drawRect(rect, paint);
+    canvas->translate(50, 50);
+    paint.setColor(SK_ColorRED);
+    canvas->drawRect(rect, paint);
+    canvas->restore();
+}
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method int saveLayer(const SkRect& bounds, const SkPaint* paint) 
+
+Saves Matrix, Clip, and Draw_Filter (Draw_Filter deprecated on most platforms),
+and allocates an offscreen Bitmap for subsequent drawing.
+Calling restore() discards changes to Matrix, Clip, and Draw_Filter,
+and draws the offscreen Bitmap.
+The Matrix, Clip, and Draw_Filter are restored to their state when save() was called. 
+
+Matrix may be changed by translate(), scale(), rotate(), skew(), concat(), setMatrix, and resetMatrix.
+Clip may be changed by clipRect, clipRRect, clipPath, clipRegion.
+
+bounds suggests but does not define the offscreen size. To clip drawing to a specific rectangle,
+use clipRect.
+
+Optional Paint paint applies Color_Alpha, Color_Filter, Image_Filter, and Blend_Mode when restore() is called.
+
+Call restoreToCount with result to restore this and subsequent saves.
+
+#Param bounds  Used as a hint to limit the size of the offscreen; may be nullptr. ##
+#Param paint  Used when restore() is called to draw the offscreen; may be nullptr. ##
+
+#Return  Depth of saved stack. ##
+
+#Example
+#Description
+Rectangles are blurred by Image_Filter when restore() draws offscreen to main Canvas.
+The red rectangle is clipped; it does not fully fit on the offscreen Canvas. 
+Image_Filter blurs past edge of offscreen so red rectangle is blurred on all sides.
+##
+#Height 128
+void draw(SkCanvas* canvas) {
+    SkPaint paint, blur;
+    blur.setImageFilter(SkImageFilter::MakeBlur(3, 3, nullptr));
+    canvas->saveLayer(SkRect::MakeWH(90, 90), &blur);
+    SkRect rect = { 25, 25, 50, 50};
+    canvas->drawRect(rect, paint);
+    canvas->translate(50, 50);
+    paint.setColor(SK_ColorRED);
+    canvas->drawRect(rect, paint);
+    canvas->restore();
+}
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method int saveLayerPreserveLCDTextRequests(const SkRect* bounds, const SkPaint* paint)
+
+Saves Matrix, Clip, and Draw_Filter (Draw_Filter deprecated on most platforms),
+and allocates an offscreen bitmap for subsequent drawing.
+LCD_Text is preserved when the offscreen is drawn to the prior layer.
+
+Draw text on an opaque background so that LCD_Text blends correctly with the prior layer.
+
+Calling restore() discards changes to Matrix, Clip, and Draw_Filter,
+and draws the offscreen bitmap.
+The Matrix, Clip, and Draw_Filter are restored to their state when save() was called. 
+
+Matrix may be changed by translate(), scale(), rotate(), skew(), concat(), setMatrix, and resetMatrix.
+Clip may be changed by clipRect, clipRRect, clipPath, clipRegion.
+  
+Draw LCD_Text on an opaque background to get good results.
+
+bounds suggests but does not define the offscreen size. To clip drawing to a specific rectangle,
+use clipRect.
+
+paint modifies how the offscreen overlays the prior layer. Color_Alpha, Blend_Mode,
+Color_Filter, Draw_Looper, Image_Filter, and Mask_Filter, affect the offscreen draw.
+
+Call restoreToCount with result to restore this and subsequent saves.
+
+#Param bounds  Used as a hint to limit the size of the offscreen; may be nullptr. ##
+#Param paint  Used when restore() is called to draw the offscreen; may be nullptr. ##
+
+#Return  Depth of saved stack. ##
+
+#Example
+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setLCDRenderText(true);

+    paint.setTextSize(20);

+    for (auto preserve : { false, true } ) {

+        preserve ? canvas->saveLayerPreserveLCDTextRequests(nullptr, nullptr)

+                 : canvas->saveLayer(nullptr, nullptr);

+        SkPaint p;

+        p.setColor(SK_ColorWHITE);

+        // Comment out the next line to draw on a non-opaque background.

+        canvas->drawRect(SkRect::MakeLTRB(25, 40, 200, 70), p);

+        canvas->drawString("Hamburgefons", 30, 60, paint);

+

+        p.setColor(0xFFCCCCCC);

+        canvas->drawRect(SkRect::MakeLTRB(25, 70, 200, 100), p);

+        canvas->drawString("Hamburgefons", 30, 90, paint);

+

+        canvas->restore();

+        canvas->translate(0, 80);

+    }
+    ##
+
+#ToDo incomplete ##
+
+##
+
+#Method int saveLayerAlpha(const SkRect* bounds, U8CPU alpha)
+
+Saves Matrix, Clip, and Draw_Filter (Draw_Filter deprecated on most platforms),
+and allocates an offscreen bitmap for subsequent drawing.
+
+Calling restore() discards changes to Matrix, Clip, and Draw_Filter,
+and blends the offscreen bitmap with alpha opacity onto the prior layer.
+The Matrix, Clip, and Draw_Filter are restored to their state when save() was called. 
+
+Matrix may be changed by translate(), scale(), rotate(), skew(), concat(), setMatrix, and resetMatrix.
+Clip may be changed by clipRect, clipRRect, clipPath, clipRegion.
+
+bounds suggests but does not define the offscreen size. To clip drawing to a specific rectangle,
+use clipRect.
+
+Call restoreToCount with result to restore this and subsequent saves.
+
+#Param bounds  Used as a hint to limit the size of the offscreen; may be nullptr. ##
+#Param alpha  The opacity of the offscreen; zero is fully transparent, 255 is fully opaque. ##
+
+#Return  Depth of saved stack. ##
+
+#Example
+    SkPaint paint;

+    paint.setColor(SK_ColorRED);

+    canvas->drawCircle(50, 50, 50, paint);

+    canvas->saveLayerAlpha(nullptr, 128);

+    paint.setColor(SK_ColorBLUE);

+    canvas->drawCircle(100, 50, 50, paint);

+    paint.setColor(SK_ColorGREEN);

+    paint.setAlpha(128);

+    canvas->drawCircle(75, 90, 50, paint);

+    canvas->restore();
+##
+
+#ToDo incomplete ##
+
+##
+
+#Enum SaveLayerFlags
+
+#Code
+    enum {
+        kIsOpaque_SaveLayerFlag = 1 << 0,
+        kPreserveLCDText_SaveLayerFlag = 1 << 1,
+        kInitWithPrevious_SaveLayerFlag = 1 << 2,
+    };
+
+    typedef uint32_t SaveLayerFlags;
+##
+
+SaveLayerFlags provides options that may be used in any combination in SaveLayerRec,
+defining how the offscreen allocated by saveLayer operates.
+
+#Const kIsOpaque_SaveLayerFlag 1
+  Creates offscreen without transparency. Flag is ignored if layer Paint contains
+  Image_Filter or Color_Filter.
+##
+
+#Const kPreserveLCDText_SaveLayerFlag 2
+  Creates offscreen for LCD text. Flag is ignored if layer Paint contains
+  Image_Filter or Color_Filter.
+##
+
+#Const kInitWithPrevious_SaveLayerFlag 4
+  Initializes offscreen with the contents of the previous layer.
+##
+
+#Example
+#Height 160
+#Description
+Canvas layer captures red and blue circles scaled up by four.
+scalePaint blends offscreen back with transparency. 
+##
+void draw(SkCanvas* canvas) {

+    SkPaint redPaint, bluePaint, scalePaint;

+    redPaint.setColor(SK_ColorRED);

+    canvas->drawCircle(21, 21, 8, redPaint);

+    bluePaint.setColor(SK_ColorBLUE);

+    canvas->drawCircle(31, 21, 8, bluePaint);

+    SkMatrix matrix;

+    matrix.setScale(4, 4);

+    scalePaint.setAlpha(0x40);

+    scalePaint.setImageFilter(

+            SkImageFilter::MakeMatrixFilter(matrix, kNone_SkFilterQuality, nullptr));

+    SkCanvas::SaveLayerRec saveLayerRec(nullptr, &scalePaint,

+            SkCanvas::kInitWithPrevious_SaveLayerFlag); 

+    canvas->saveLayer(saveLayerRec);

+    canvas->restore();

+}
+##
+
+#ToDo incomplete ##
+
+#Enum ##
+
+#Struct SaveLayerRec
+
+#Code
+    struct SaveLayerRec {
+        SaveLayerRec*(...
+
+        const SkRect*           fBounds;
+        const SkPaint*          fPaint;
+        const SkImageFilter*    fBackdrop;
+        SaveLayerFlags          fSaveLayerFlags;
+    };
+##
+
+SaveLayerRec contains the state used to create the layer offscreen. 
+
+#Member const SkRect*           fBounds
+    fBounds is used as a hint to limit the size of the offscreen; may be nullptr.
+    fBounds suggests but does not define the offscreen size. To clip drawing to a specific rectangle,
+    use clipRect.
+##
+
+#Member const SkPaint*          fPaint
+    fPaint modifies how the offscreen overlays the prior layer; may be nullptr. Color_Alpha, Blend_Mode,
+    Color_Filter, Draw_Looper, Image_Filter, and Mask_Filter affect the offscreen draw.
+##
+
+#Member const SkImageFilter*    fBackdrop
+    fBackdrop applies Image_Filter to the prior layer when copying to the layer offscreen; may be nullptr.
+    Use kInitWithPrevious_SaveLayerFlag to copy the prior layer without a Image_Filter.
+##
+
+#Member const SkImage*          fClipMask
+#ToDo header documentation is incomplete ##
+    may be nullptr.
+##
+
+#Member const SkMatrix*         fClipMatrix
+#ToDo header documentation is incomplete ##
+   may be nullptr.
+##
+
+#Member SaveLayerFlags          fSaveLayerFlags
+    fSaveLayerFlags are used to create layer offscreen without transparency, create layer offscreen for
+    LCD text, and to create layer offscreen with the contents of the previous layer.
+##
+
+#Example
+#Height 160
+#Description
+Canvas layer captures a red anti-aliased circle and a blue aliased circle scaled up by four.
+After drawing another unscaled red circle on top, the offscreen is transferred to the main canvas. 
+##
+void draw(SkCanvas* canvas) {

+    SkPaint redPaint, bluePaint;

+    redPaint.setAntiAlias(true);

+    redPaint.setColor(SK_ColorRED);

+    canvas->drawCircle(21, 21, 8, redPaint);

+    bluePaint.setColor(SK_ColorBLUE);

+    canvas->drawCircle(31, 21, 8, bluePaint);

+    SkMatrix matrix;

+    matrix.setScale(4, 4);

+    auto scaler = SkImageFilter::MakeMatrixFilter(matrix, kNone_SkFilterQuality, nullptr);

+    SkCanvas::SaveLayerRec saveLayerRec(nullptr, nullptr, scaler.get(), 0); 

+    canvas->saveLayer(saveLayerRec);

+    canvas->drawCircle(125, 85, 8, redPaint);

+    canvas->restore();

+}
+##
+
+#Method SaveLayerRec()
+
+Sets fBounds, fPaint, and fBackdrop to nullptr. Clears fSaveLayerFlags.
+
+#Return  empty SaveLayerRec. ##
+
+#Example
+    SkCanvas::SaveLayerRec rec1;

+    rec1.fSaveLayerFlags = SkCanvas::kIsOpaque_SaveLayerFlag;

+    SkCanvas::SaveLayerRec rec2(nullptr, nullptr, SkCanvas::kIsOpaque_SaveLayerFlag);

+    SkDebugf("rec1 %c= rec2\n", rec1.fBounds == rec2.fBounds

+            && rec1.fPaint == rec2.fPaint

+            && rec1.fBackdrop == rec2.fBackdrop

+            && rec1.fSaveLayerFlags == rec2.fSaveLayerFlags ? '=' : '!');
+    #StdOut
+        rec1 == rec2
+    ##
+##
+
+##
+
+#Method SaveLayerRec(const SkRect* bounds, const SkPaint* paint, SaveLayerFlags saveLayerFlags = 0)
+
+Sets fBounds, fPaint, and fSaveLayerFlags; sets fBackdrop to nullptr.
+
+#Param bounds  Offscreen dimensions; may be nullptr. ##
+#Param paint  Applied to offscreen when overlaying prior layer; may be nullptr. ##
+#Param saveLayerFlags  SaveLayerRec options to modify offscreen. ##
+
+#Return  SaveLayerRec with empty backdrop. ##
+
+#Example
+    SkCanvas::SaveLayerRec rec1;

+    SkCanvas::SaveLayerRec rec2(nullptr, nullptr);

+    SkDebugf("rec1 %c= rec2\n", rec1.fBounds == rec2.fBounds

+            && rec1.fPaint == rec2.fPaint

+            && rec1.fBackdrop == rec2.fBackdrop

+            && rec1.fSaveLayerFlags == rec2.fSaveLayerFlags ? '=' : '!');
+    #StdOut
+        rec1 == rec2
+    ##
+##
+
+##
+
+#Method SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop,
+                     SaveLayerFlags saveLayerFlags)
+
+Sets fBounds, fPaint, fBackdrop, and fSaveLayerFlags.
+
+#Param bounds  Offscreen dimensions; may be nullptr. ##
+#Param paint  Applied to offscreen when overlaying prior layer; may be nullptr. ##
+#Param backdrop  Copies prior layer to offscreen with Image_Filter; may be nullptr. ##
+#Param saveLayerFlags  SaveLayerRec options to modify offscreen. ##
+
+#Return  SaveLayerRec fully specified. ##
+
+#Example
+    SkCanvas::SaveLayerRec rec1;

+    SkCanvas::SaveLayerRec rec2(nullptr, nullptr, nullptr, 0);

+    SkDebugf("rec1 %c= rec2\n", rec1.fBounds == rec2.fBounds

+            && rec1.fPaint == rec2.fPaint

+            && rec1.fBackdrop == rec2.fBackdrop

+            && rec1.fSaveLayerFlags == rec2.fSaveLayerFlags ? '=' : '!');
+    #StdOut
+        rec1 == rec2
+    ##
+##
+
+##
+
+#Method SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop,
+                     const SkImage* clipMask, const SkMatrix* clipMatrix,
+                     SaveLayerFlags saveLayerFlags)
+
+#Experimental
+Not ready for general use.
+##
+
+#Param bounds  Offscreen dimensions; may be nullptr. ##
+#Param paint  Applied to offscreen when overlaying prior layer; may be nullptr. ##
+#Param backdrop  Copies prior layer to offscreen with Image_Filter; may be nullptr. ##
+#Param clipMask  May be nullptr. ##
+#Param clipMatrix  May be nullptr. ##
+#Param saveLayerFlags  SaveLayerRec options to modify offscreen. ##
+
+#Return  SaveLayerRec fully specified. ##
+
+#ToDo  incomplete ##
+
+##
+
+#Struct ##
+
+#Method int saveLayer(const SaveLayerRec& layerRec)
+
+Saves Matrix, Clip, and Draw_Filter (Draw_Filter deprecated on most platforms),
+and allocates an offscreen bitmap for subsequent drawing.
+
+Calling restore() discards changes to Matrix, Clip, and Draw_Filter,
+and blends the offscreen bitmap with alpha opacity onto the prior layer.
+The Matrix, Clip, and Draw_Filter are restored to their state when save() was called. 
+
+Matrix may be changed by translate(), scale(), rotate(), skew(), concat(), setMatrix, and resetMatrix.
+Clip may be changed by clipRect, clipRRect, clipPath, clipRegion.
+
+SaveLayerRec contains the state used to create the layer offscreen.
+
+Call restoreToCount with result to restore this and subsequent saves.
+
+#Param layerRec  offscreen state. ##
+
+#Return          depth of save state stack. ##
+
+#Example
+#Description
+The example draws an image, and saves it into a layer with kInitWithPrevious_SaveLayerFlag.
+Next it punches a hole in the layer and restore with SkBlendMode::kPlus.
+Where the layer was cleared, the original image will draw unchanged.
+Outside of the circle the mandrill is brightened.
+##
+    #Image 3
+    // sk_sp<SkImage> image = GetResourceAsImage("mandrill_256.png");
+    canvas->drawImage(image, 0, 0, nullptr);
+    SkCanvas::SaveLayerRec rec;
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kPlus);
+    rec.fSaveLayerFlags = SkCanvas::kInitWithPrevious_SaveLayerFlag;
+    rec.fPaint = &paint;
+    canvas->saveLayer(rec);
+    paint.setBlendMode(SkBlendMode::kClear);
+    canvas->drawCircle(128, 128, 96, paint);
+    canvas->restore();
+##
+
+#ToDo above example needs to replace GetResourceAsImage with way to select image in fiddle ##
+
+##
+
+#Subtopic Layer ##
+
+# ------------------------------------------------------------------------------
+
+#Method void restore()
+
+Removes changes to Matrix, Clip, and Draw_Filter since Canvas state was
+last saved. The state is removed from the stack. 
+
+Does nothing if the stack is empty. 
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkCanvas simple;

+    SkDebugf("depth = %d\n", simple.getSaveCount());

+    simple.restore();

+    SkDebugf("depth = %d\n", simple.getSaveCount());

+}
+##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method int getSaveCount() const
+
+Returns the number of saved states, each containing: Matrix, Clip, and Draw_Filter.
+Equals the number of save() calls less the number of restore() calls plus one. 
+The save count of a new canvas is one.
+
+#Return  depth of save state stack. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkCanvas simple;

+    SkDebugf("depth = %d\n", simple.getSaveCount());

+    simple.save();

+    SkDebugf("depth = %d\n", simple.getSaveCount());

+    simple.restore();

+    SkDebugf("depth = %d\n", simple.getSaveCount());

+}
+#StdOut
+depth = 1

+depth = 2

+depth = 1
+##
+##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void restoreToCount(int saveCount)
+
+Restores state to Matrix, Clip, and Draw_Filter
+values when save(), saveLayer, saveLayerPreserveLCDTextRequests, or saveLayerAlpha
+returned saveCount.
+
+Does nothing if saveCount is greater than state stack count. 
+Restores state to initial values if saveCount is less than or equal to one.
+
+#Param saveCount    The depth of state stack to restore. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkDebugf("depth = %d\n", canvas->getSaveCount());

+    canvas->save();

+    canvas->save();

+    SkDebugf("depth = %d\n", canvas->getSaveCount());

+    canvas->restoreToCount(0);

+    SkDebugf("depth = %d\n", canvas->getSaveCount());

+}
+#StdOut
+depth = 1

+depth = 3

+depth = 1
+##
+##
+
+##
+
+#Topic State_Stack ##
+
+# ------------------------------------------------------------------------------
+#Topic Matrix
+
+#Method void translate(SkScalar dx, SkScalar dy)
+
+Translate Matrix by dx along the x-axis and dy along the y-axis.
+
+Mathematically, replace Matrix with a translation matrix
+pre-multiplied with Matrix. 
+
+This has the effect of moving the drawing by (dx, dy) before transforming
+the result with Matrix.
+
+#Param  dx   The distance to translate in x. ##
+#Param  dy   The distance to translate in y. ##
+
+#Example
+#Height 128
+#Description
+scale() followed by translate() produces different results from translate() followed
+by scale(). 
+
+The blue stroke follows translate of (50, 50); a black
+fill follows scale of (2, 1/2.f). After restoring the clip, which resets 
+Matrix, a red frame follows the same scale of (2, 1/2.f); a gray fill
+follows translate of (50, 50).
+##
+void draw(SkCanvas* canvas) {

+    SkPaint filledPaint;

+    SkPaint outlinePaint;

+    outlinePaint.setStyle(SkPaint::kStroke_Style);

+    outlinePaint.setColor(SK_ColorBLUE);

+    canvas->save();

+    canvas->translate(50, 50);

+    canvas->drawCircle(28, 28, 15, outlinePaint);  // blue center: (50+28, 50+28)

+    canvas->scale(2, 1/2.f);

+    canvas->drawCircle(28, 28, 15, filledPaint);   // black center: (50+(28*2), 50+(28/2))

+    canvas->restore();

+    filledPaint.setColor(SK_ColorGRAY);

+    outlinePaint.setColor(SK_ColorRED);

+    canvas->scale(2, 1/2.f);

+    canvas->drawCircle(28, 28, 15, outlinePaint);  // red center: (28*2, 28/2)

+    canvas->translate(50, 50);

+    canvas->drawCircle(28, 28, 15, filledPaint);   // gray center: ((50+28)*2, (50+28)/2)

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void scale(SkScalar sx, SkScalar sy)
+
+Scale Matrix by sx on the x-axis and sy on the y-axis.
+
+Mathematically, replace Matrix with a scale matrix
+pre-multiplied with Matrix. 
+
+This has the effect of scaling the drawing by (sx, sy) before transforming
+the result with Matrix.
+
+#Param  sx   The amount to scale in x. ##
+#Param  sy   The amount to scale in y. ##
+
+#Example
+#Height 160
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    SkRect rect = { 10, 20, 60, 120 };

+    canvas->translate(20, 20);

+    canvas->drawRect(rect, paint);

+    canvas->scale(2, .5f);

+    paint.setColor(SK_ColorGRAY);

+    canvas->drawRect(rect, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void rotate(SkScalar degrees)
+
+Rotate Matrix by degrees. Positive degrees rotates clockwise.
+
+Mathematically, replace Matrix with a rotation matrix
+pre-multiplied with Matrix. 
+
+This has the effect of rotating the drawing by degrees before transforming
+the result with Matrix.
+
+#Param  degrees  The amount to rotate, in degrees. ##
+
+#Example
+#Description
+Draw clock hands at time 5:10. The hour hand and minute hand point up and
+are rotated clockwise.
+##
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setStyle(SkPaint::kStroke_Style);

+    canvas->translate(128, 128);

+    canvas->drawCircle(0, 0, 60, paint);

+    canvas->save();

+    canvas->rotate(10 * 360 / 60);   // 10 minutes of 60 scaled to 360 degrees

+    canvas->drawLine(0, 0, 0, -50, paint); 

+    canvas->restore();

+    canvas->rotate((5 + 10.f/60) * 360 / 12); // 5 and 10/60 hours of 12 scaled to 360 degrees

+    canvas->drawLine(0, 0, 0, -30, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void rotate(SkScalar degrees, SkScalar px, SkScalar py)
+
+Rotate Matrix by degrees about a point at (px, py). Positive degrees rotates clockwise.
+
+Mathematically, construct a rotation matrix. Pre-multiply the rotation matrix by
+a translation matrix, then replace Matrix with the resulting matrix
+pre-multiplied with Matrix. 
+
+This has the effect of rotating the drawing about a given point before transforming
+the result with Matrix.
+
+#Param  degrees  The amount to rotate, in degrees. ##
+#Param  px  The x coordinate of the point to rotate about. ##
+#Param  py  The y coordinate of the point to rotate about. ##
+
+#Example
+#Height 192
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setTextSize(96);

+    canvas->drawString("A1", 130, 100, paint);

+    canvas->rotate(180, 130, 100);

+    canvas->drawString("A1", 130, 100, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void skew(SkScalar sx, SkScalar sy)
+
+Skew Matrix by sx on the x-axis and sy on the y-axis. A positive value of sx skews the
+drawing right as y increases; a positive value of sy skews the drawing down as x increases.
+
+Mathematically, replace Matrix with a skew matrix
+pre-multiplied with Matrix. 
+
+Preconcat the current matrix with the specified skew.
+#Param  sx   The amount to skew in x. ##
+#Param  sy   The amount to skew in y. ##
+
+This has the effect of scaling the drawing by (sx, sy) before transforming
+the result with Matrix.
+
+#Example
+    #Description 
+        Black text mimics an oblique text style by using a negative skew in x that
+        shifts the geometry to the right as the y values decrease.
+        Red text uses a positive skew in y to shift the geometry down as the x values
+        increase.
+        Blue text combines x and y skew to rotate and scale.
+    ##
+    SkPaint paint;

+    paint.setTextSize(128);

+    canvas->translate(30, 130);

+    canvas->save();

+    canvas->skew(-.5, 0);

+    canvas->drawString("A1", 0, 0, paint);

+    canvas->restore();

+    canvas->save();

+    canvas->skew(0, .5);

+    paint.setColor(SK_ColorRED);

+    canvas->drawString("A1", 0, 0, paint);

+    canvas->restore();

+    canvas->skew(-.5, .5);

+    paint.setColor(SK_ColorBLUE);

+    canvas->drawString("A1", 0, 0, paint);
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void concat(const SkMatrix& matrix)
+
+Replace Matrix with matrix pre-multiplied with Matrix.
+
+This has the effect of transforming the drawn geometry by matrix, before transforming
+the result with Matrix.
+
+#Param  matrix   Pre-multiply with Matrix. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setTextSize(80);

+    paint.setTextScaleX(.3);

+    SkMatrix matrix;

+    SkRect rect[2] = {{ 10, 20, 90, 110 }, { 40, 130, 140, 180 }};

+    matrix.setRectToRect(rect[0], rect[1], SkMatrix::kFill_ScaleToFit);

+    canvas->drawRect(rect[0], paint);

+    canvas->drawRect(rect[1], paint);

+    paint.setColor(SK_ColorWHITE);

+    canvas->drawString("Here", rect[0].fLeft + 10, rect[0].fBottom - 10, paint);

+    canvas->concat(matrix);

+    canvas->drawString("There", rect[0].fLeft + 10, rect[0].fBottom - 10, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void setMatrix(const SkMatrix& matrix)
+
+Replace Matrix with matrix.
+Unlike concat(), any prior matrix state is overwritten.
+
+#Param  matrix  Copied into Matrix. ##
+
+#Example
+#Height 128
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    canvas->scale(4, 6);

+    canvas->drawString("truth", 2, 10, paint);

+    SkMatrix matrix;

+    matrix.setScale(2.8f, 6);

+    canvas->setMatrix(matrix);

+    canvas->drawString("consequences", 2, 20, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void resetMatrix()
+
+Sets Matrix to the identity matrix.
+Any prior matrix state is overwritten.
+
+#Example
+#Height 128
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    canvas->scale(4, 6);

+    canvas->drawString("truth", 2, 10, paint);

+    canvas->resetMatrix();

+    canvas->scale(2.8f, 6);

+    canvas->drawString("consequences", 2, 20, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method const SkMatrix& getTotalMatrix() const
+
+Returns Matrix.
+This does not account for translation by Device or Surface.
+
+#Return Matrix on Canvas. ##
+
+#Example
+    SkDebugf("isIdentity %s\n", canvas->getTotalMatrix().isIdentity() ? "true" : "false");

+    #StdOut

+        isIdentity true

+    ##

+##
+
+#ToDo incomplete ##
+
+##
+
+#Topic Matrix ##
+
+# ------------------------------------------------------------------------------
+#Topic Clip
+
+Clip is built from a stack of clipping paths. Each Path in the
+stack can be constructed from one or more Path_Contour elements. The 
+Path_Contour may be composed of any number of Path_Verb segments. Each
+Path_Contour forms a closed area; Path_Fill_Type defines the area enclosed
+by Path_Contour.
+
+Clip stack of Path elements successfully restrict the Path area. Each
+Path is transformed by Matrix, then intersected with or subtracted from the 
+prior Clip to form the replacement Clip. Use SkClipOp::kDifference
+to subtract Path from Clip; use SkClipOp::kIntersect to intersect Path
+with Clip.
+
+A clipping Path may be anti-aliased; if Path, after transformation, is
+composed of horizontal and vertical lines, clearing Anti-alias allows whole pixels
+to either be inside or outside the clip. The fastest drawing has a aliased,
+rectanglar clip.
+
+If clipping Path has Anti-alias set, clip may partially clip a pixel, requiring
+that drawing blend partially with the destination along the edge. A rotated 
+rectangular anti-aliased clip looks smoother but draws slower.
+
+Clip can combine with Rect and Round_Rect primitives; like
+Path, these are transformed by Matrix before they are combined with Clip.
+
+Clip can combine with Region. Region is assumed to be in Device coordinates
+and is unaffected by Matrix.
+
+#Example
+#Height 90
+    #Description
+        Draw a red circle with an aliased clip and an anti-aliased clip.
+        Use an image filter to zoom into the pixels drawn.
+        The edge of the aliased clip fully draws pixels in the red circle.
+        The edge of the anti-aliased clip partially draws pixels in the red circle.
+    ##
+    SkPaint redPaint, scalePaint;

+    redPaint.setAntiAlias(true);

+    redPaint.setColor(SK_ColorRED);

+    canvas->save();

+    for (bool antialias : { false, true } ) {

+        canvas->save();

+        canvas->clipRect(SkRect::MakeWH(19.5f, 11.5f), antialias);

+        canvas->drawCircle(17, 11, 8, redPaint);

+        canvas->restore();

+        canvas->translate(16, 0);

+    }

+    canvas->restore();

+    SkMatrix matrix;

+    matrix.setScale(6, 6);

+    scalePaint.setImageFilter(

+            SkImageFilter::MakeMatrixFilter(matrix, kNone_SkFilterQuality, nullptr));

+    SkCanvas::SaveLayerRec saveLayerRec(

+            nullptr, &scalePaint, SkCanvas::kInitWithPrevious_SaveLayerFlag); 

+    canvas->saveLayer(saveLayerRec);

+    canvas->restore();
+##
+
+#Method void clipRect(const SkRect& rect, SkClipOp op, bool doAntiAlias)
+
+Replace Clip with the intersection or difference of Clip and rect,
+with an aliased or anti-aliased clip edge. rect is transformed by Matrix
+before it is combined with Clip.
+
+#Param  rect  Rectangle to combine with Clip. ##
+#Param  op    Clip_Op to apply to Clip. ##
+#Param  doAntiAlias  true if Clip is to be anti-aliased. ##
+
+#Example
+#Height 128
+void draw(SkCanvas* canvas) {

+    canvas->rotate(10);

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    for (auto alias: { false, true } ) {

+        canvas->save();

+        canvas->clipRect(SkRect::MakeWH(90, 80), SkClipOp::kIntersect, alias);

+        canvas->drawCircle(100, 60, 60, paint);

+        canvas->restore();

+        canvas->translate(80, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method void clipRect(const SkRect& rect, SkClipOp op) 
+
+Replace Clip with the intersection or difference of Clip and rect.
+Resulting Clip is aliased; pixels are fully contained by the clip.
+rect is transformed by Matrix
+before it is combined with Clip.
+
+#Param  rect  Rectangle to combine with Clip. ##
+#Param  op    Clip_Op to apply to Clip. ##
+
+#Example
+#Height 192
+#Width 280
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    for (SkClipOp op: { SkClipOp::kIntersect, SkClipOp::kDifference } ) {

+        canvas->save();

+        canvas->clipRect(SkRect::MakeWH(90, 120), op, false);

+        canvas->drawCircle(100, 100, 60, paint);

+        canvas->restore();

+        canvas->translate(80, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method void clipRect(const SkRect& rect, bool doAntiAlias = false) 
+
+Replace Clip with the intersection of Clip and rect.
+Resulting Clip is aliased; pixels are fully contained by the clip.
+rect is transformed by Matrix
+before it is combined with Clip.
+
+#Param  rect  Rectangle to combine with Clip. ##
+#Param  doAntiAlias  true if Clip is to be anti-aliased. ##
+
+#Example
+#Height 133
+    #Description
+        A circle drawn in pieces looks uniform when drawn aliased.
+        The same circle pieces blend with pixels more than once when anti-aliased,
+        visible as a thin pair of lines through the right circle.
+    ##
+void draw(SkCanvas* canvas) {

+    canvas->clear(SK_ColorWHITE);

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setColor(0x8055aaff);

+    SkRect clipRect = { 0, 0, 87.4f, 87.4f };

+    for (auto alias: { false, true } ) {

+        canvas->save();

+        canvas->clipRect(clipRect, SkClipOp::kIntersect, alias);

+        canvas->drawCircle(67, 67, 60, paint);

+        canvas->restore();

+        canvas->save();

+        canvas->clipRect(clipRect, SkClipOp::kDifference, alias);

+        canvas->drawCircle(67, 67, 60, paint);

+        canvas->restore();

+        canvas->translate(120, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method void androidFramework_setDeviceClipRestriction(const SkIRect& rect)
+
+Sets the max clip rectangle, which can be set by clipRect, clipRRect and
+clipPath and intersect the current clip with the specified rect.
+The max clip affects only future ops (it is not retroactive).
+The clip restriction is not recorded in pictures.
+
+#Private
+This is private API to be used only by Android framework.
+##
+
+#Param  rect   The maximum allowed clip in device coordinates. 
+Empty rect means max clip is not enforced. ##
+
+##
+
+#Method void clipRRect(const SkRRect& rrect, SkClipOp op, bool doAntiAlias)
+
+Replace Clip with the intersection or difference of Clip and rrect,
+with an aliased or anti-aliased clip edge.
+rrect is transformed by Matrix
+before it is combined with Clip.
+
+#Param  rrect  Round_Rect to combine with Clip. ##
+#Param  op  Clip_Op to apply to Clip. ##
+#Param  doAntiAlias  true if Clip is to be antialiased. ##
+
+#Example
+#Height 128
+void draw(SkCanvas* canvas) {

+    canvas->clear(SK_ColorWHITE);

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setColor(0x8055aaff);

+    SkRRect oval;

+    oval.setOval({10, 20, 90, 100});

+    canvas->clipRRect(oval, SkClipOp::kIntersect, true);

+    canvas->drawCircle(70, 100, 60, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method void clipRRect(const SkRRect& rrect, SkClipOp op) 
+
+Replace Clip with the intersection or difference of Clip and rrect.
+Resulting Clip is aliased; pixels are fully contained by the clip.
+rrect is transformed by Matrix
+before it is combined with Clip.
+
+#Param  rrect  Round_Rect to combine with Clip. ##
+#Param  op  Clip_Op to apply to Clip. ##
+
+#Example
+#Height 128
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setColor(0x8055aaff);

+    auto oval = SkRRect::MakeOval({10, 20, 90, 100});

+    canvas->clipRRect(oval, SkClipOp::kIntersect);

+    canvas->drawCircle(70, 100, 60, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method void clipRRect(const SkRRect& rrect, bool doAntiAlias = false) 
+
+Replace Clip with the intersection of Clip and rrect,
+with an aliased or anti-aliased clip edge.
+rrect is transformed by Matrix
+before it is combined with Clip.
+
+#Param  rrect  Round_Rect to combine with Clip. ##
+#Param  doAntiAlias  true if Clip is to be antialiased. ##
+
+#Example
+#Height 128
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    auto oval = SkRRect::MakeRectXY({10, 20, 90, 100}, 9, 13);

+    canvas->clipRRect(oval, true);

+    canvas->drawCircle(70, 100, 60, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method void clipPath(const SkPath& path, SkClipOp op, bool doAntiAlias)
+
+Replace Clip with the intersection or difference of Clip and path,
+with an aliased or anti-aliased clip edge. Path_Fill_Type determines if path
+describes the area inside or outside its contours; and if Path_Contour overlaps
+itself or another Path_Contour, whether the overlaps form part of the area.
+path is transformed by Matrix
+before it is combined with Clip.
+
+#Param  path  Path to combine with Clip. ##
+#Param  op  Clip_Op to apply to Clip. ##
+#Param  doAntiAlias  true if Clip is to be antialiased. ##
+
+#Example
+#Description
+Top figure uses SkPath::kInverseWinding_FillType and SkClipOp::kDifference;
+area outside clip is subtracted from circle.
+
+Bottom figure uses SkPath::kWinding_FillType and SkClipOp::kIntersect;
+area inside clip is intersected with circle.
+##
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    SkPath path;

+    path.addRect({20, 30, 100, 110});

+    path.setFillType(SkPath::kInverseWinding_FillType);

+    canvas->save();

+    canvas->clipPath(path, SkClipOp::kDifference, false);

+    canvas->drawCircle(70, 100, 60, paint);

+    canvas->restore();

+    canvas->translate(100, 100);

+    path.setFillType(SkPath::kWinding_FillType);

+    canvas->clipPath(path, SkClipOp::kIntersect, false);

+    canvas->drawCircle(70, 100, 60, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method void clipPath(const SkPath& path, SkClipOp op) 
+
+Replace Clip with the intersection or difference of Clip and path.
+Resulting Clip is aliased; pixels are fully contained by the clip.
+Path_Fill_Type determines if path
+describes the area inside or outside its contours; and if Path_Contour overlaps
+itself or another Path_Contour, whether the overlaps form part of the area.
+path is transformed by Matrix
+before it is combined with Clip.
+
+#Param  path  Path to combine with Clip. ##
+#Param  op  Clip_Op to apply to Clip. ##
+
+#Example
+#Description
+Overlapping Rects form a clip. When clip's Path_Fill_Type is set to
+SkPath::kWinding_FillType, the overlap is included. Set to 
+SkPath::kEvenOdd_FillType, the overlap is excluded and forms a hole.
+##
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    SkPath path;

+    path.addRect({20, 15, 100, 95});

+    path.addRect({50, 65, 130, 135});

+    path.setFillType(SkPath::kWinding_FillType);

+    canvas->save();

+    canvas->clipPath(path, SkClipOp::kIntersect);

+    canvas->drawCircle(70, 85, 60, paint);

+    canvas->restore();

+    canvas->translate(100, 100);

+    path.setFillType(SkPath::kEvenOdd_FillType);

+    canvas->clipPath(path, SkClipOp::kIntersect);

+    canvas->drawCircle(70, 85, 60, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method void clipPath(const SkPath& path, bool doAntiAlias = false) 
+
+Replace Clip with the intersection of Clip and path.
+Resulting Clip is aliased; pixels are fully contained by the clip.
+Path_Fill_Type determines if path
+describes the area inside or outside its contours; and if Path_Contour overlaps
+itself or another Path_Contour, whether the overlaps form part of the area.
+path is transformed by Matrix
+before it is combined with Clip.
+
+#Param  path  Path to combine with Clip. ##
+#Param  doAntiAlias  true if Clip is to be antialiased. ##
+
+#Example
+#Height 212
+#Description
+Clip loops over itself covering its center twice. When clip's Path_Fill_Type 
+is set to SkPath::kWinding_FillType, the overlap is included. Set to 
+SkPath::kEvenOdd_FillType, the overlap is excluded and forms a hole.
+##
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    SkPath path;

+    SkPoint poly[] = {{20, 20}, { 80, 20}, { 80,  80}, {40,  80},

+                      {40, 40}, {100, 40}, {100, 100}, {20, 100}};

+    path.addPoly(poly, SK_ARRAY_COUNT(poly), true);

+    path.setFillType(SkPath::kWinding_FillType);

+    canvas->save();

+    canvas->clipPath(path, SkClipOp::kIntersect);

+    canvas->drawCircle(50, 50, 45, paint);

+    canvas->restore();

+    canvas->translate(100, 100);

+    path.setFillType(SkPath::kEvenOdd_FillType);

+    canvas->clipPath(path, SkClipOp::kIntersect);

+    canvas->drawCircle(50, 50, 45, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void setAllowSimplifyClip(bool allow) 
+
+#Experimental
+Only used for testing.
+##
+
+Set to simplify clip stack using path ops.
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void clipRegion(const SkRegion& deviceRgn, SkClipOp op = SkClipOp::kIntersect)
+
+Replace Clip with the intersection or difference of Clip and Region deviceRgn.
+Resulting Clip is aliased; pixels are fully contained by the clip.
+deviceRgn is unaffected by Matrix.
+
+#Param  deviceRgn    Region to combine with Clip. ##
+#Param  op  Clip_Op to apply to Clip. ##
+
+#Example
+#Description

+    region is unaffected by canvas rotation; rect is affected by canvas rotation.

+    Both clips are aliased; this is unnoticable on Region clip because it

+    aligns to pixel boundaries.

+##

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    SkIRect iRect = {30, 40, 120, 130 };

+    SkRegion region(iRect);

+    canvas->rotate(10);

+    canvas->save();

+    canvas->clipRegion(region, SkClipOp::kIntersect);

+    canvas->drawCircle(50, 50, 45, paint);

+    canvas->restore();

+    canvas->translate(100, 100);

+    canvas->clipRect(SkRect::Make(iRect), SkClipOp::kIntersect);

+    canvas->drawCircle(50, 50, 45, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method bool quickReject(const SkRect& rect) const
+
+Return true if Rect rect, transformed by Matrix, can be quickly determined to be
+outside of Clip. May return false even though rect is outside of Clip.
+
+Use to check if an area to be drawn is clipped out, to skip subsequent draw calls.
+
+#Param  rect  Rect to compare with Clip. ##
+
+#Return  true if rect, transformed by Matrix, does not intersect Clip. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkRect testRect = {30, 30, 120, 129 }; 

+    SkRect clipRect = {30, 130, 120, 230 }; 

+    canvas->save();

+    canvas->clipRect(clipRect);

+    SkDebugf("quickReject %s\n", canvas->quickReject(testRect) ? "true" : "false");

+    canvas->restore();

+    canvas->rotate(10);

+    canvas->clipRect(clipRect);

+    SkDebugf("quickReject %s\n", canvas->quickReject(testRect) ? "true" : "false");

+}
+    #StdOut
+        quickReject true

+        quickReject false
+    ##
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method bool quickReject(const SkPath& path) const
+
+Return true if path, transformed by Matrix, can be quickly determined to be
+outside of Clip. May return false even though path is outside of Clip.
+
+Use to check if an area to be drawn is clipped out, to skip subsequent draw calls.
+
+#Param path  Path to compare with Clip. ##
+
+#Return  true if path, transformed by Matrix, does not intersect Clip. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkPoint testPoints[] = {{30,  30}, {120,  30}, {120, 129} }; 

+    SkPoint clipPoints[] = {{30, 130}, {120, 130}, {120, 230} }; 

+    SkPath testPath, clipPath;

+    testPath.addPoly(testPoints, SK_ARRAY_COUNT(testPoints), true);

+    clipPath.addPoly(clipPoints, SK_ARRAY_COUNT(clipPoints), true);

+    canvas->save();

+    canvas->clipPath(clipPath);

+    SkDebugf("quickReject %s\n", canvas->quickReject(testPath) ? "true" : "false");

+    canvas->restore();

+    canvas->rotate(10);

+    canvas->clipPath(clipPath);

+    SkDebugf("quickReject %s\n", canvas->quickReject(testPath) ? "true" : "false");

+    #StdOut
+        quickReject true

+        quickReject false
+    ##
+}
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method SkRect getLocalClipBounds() const 
+
+Return bounds of Clip, transformed by inverse of Matrix. If Clip is empty,
+return SkRect::MakeEmpty, where all Rect sides equal zero.
+
+Rect returned is outset by one to account for partial pixel coverage if Clip
+is anti-aliased.
+
+#Return  bounds of Clip in local coordinates. ##
+
+#Example
+    #Description 
+        Initial bounds is device bounds outset by 1 on all sides.
+        Clipped bounds is clipPath bounds outset by 1 on all sides.
+        Scaling the canvas by two in x and y scales the local bounds by 1/2 in x and y.
+    ##
+    SkCanvas local(256, 256);

+    canvas = &local;

+    SkRect bounds = canvas->getLocalClipBounds();

+    SkDebugf("left:%g  top:%g  right:%g  bottom:%g\n",

+            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);

+    SkPoint clipPoints[]  = {{30, 130}, {120, 130}, {120, 230} }; 

+    SkPath clipPath;

+    clipPath.addPoly(clipPoints, SK_ARRAY_COUNT(clipPoints), true);

+    canvas->clipPath(clipPath);

+    bounds = canvas->getLocalClipBounds();

+    SkDebugf("left:%g  top:%g  right:%g  bottom:%g\n",

+            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);

+    canvas->scale(2, 2);

+    bounds = canvas->getLocalClipBounds();

+    SkDebugf("left:%g  top:%g  right:%g  bottom:%g\n",

+            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);

+    #StdOut

+        left:-1  top:-1  right:257  bottom:257

+        left:29  top:129  right:121  bottom:231

+        left:14.5  top:64.5  right:60.5  bottom:115.5

+    ##

+##
+
+# local canvas in example works around bug in fiddle ##
+#Bug 6524  ##
+
+##
+
+#Method bool getLocalClipBounds(SkRect* bounds) const 
+
+Return bounds of Clip, transformed by inverse of Matrix. If Clip is empty,
+return false, and set bounds to SkRect::MakeEmpty, where all Rect sides equal zero.
+
+bounds is outset by one to account for partial pixel coverage if Clip
+is anti-aliased.
+
+#Param bounds  Rect of Clip in local coordinates. ##
+
+#Return  true if Clip bounds is not empty. ##
+
+#Example
+    void draw(SkCanvas* canvas) {

+        SkCanvas local(256, 256);

+        canvas = &local;

+        SkRect bounds;

+        SkDebugf("local bounds empty = %s\n", canvas->getLocalClipBounds(&bounds)

+                 ? "false" : "true");

+        SkPath path;

+        canvas->clipPath(path);

+        SkDebugf("local bounds empty = %s\n", canvas->getLocalClipBounds(&bounds)

+                 ? "false" : "true");

+    }
+    #StdOut
+        local bounds empty = false

+        local bounds empty = true
+    ##
+##
+
+# local canvas in example works around bug in fiddle ##
+#Bug 6524  ##
+
+##
+
+#Method SkIRect getDeviceClipBounds() const 
+
+Return IRect bounds of Clip, unaffected by Matrix. If Clip is empty,
+return SkRect::MakeEmpty, where all Rect sides equal zero.
+
+Unlike getLocalClipBounds, returned IRect is not outset. 
+
+#Return  bounds of Clip in Device coordinates. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    #Description

+        Initial bounds is device bounds, not outset.
+        Clipped bounds is clipPath bounds, not outset.
+        Scaling the canvas by 1/2 in x and y scales the device bounds by 1/2 in x and y.
+    ##

+    SkCanvas device(256, 256);

+    canvas = &device;

+    SkIRect bounds = canvas->getDeviceClipBounds();

+    SkDebugf("left:%d  top:%d  right:%d  bottom:%d\n",

+            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);

+    SkPoint clipPoints[]  = {{30, 130}, {120, 130}, {120, 230} }; 

+    SkPath clipPath;

+    clipPath.addPoly(clipPoints, SK_ARRAY_COUNT(clipPoints), true);

+    canvas->save();

+    canvas->clipPath(clipPath);

+    bounds = canvas->getDeviceClipBounds();

+    SkDebugf("left:%d  top:%d  right:%d  bottom:%d\n",

+            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);

+    canvas->restore();

+    canvas->scale(1.f/2, 1.f/2);

+    canvas->clipPath(clipPath);

+    bounds = canvas->getDeviceClipBounds();

+    SkDebugf("left:%d  top:%d  right:%d  bottom:%d\n",

+            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);

+    #StdOut
+        left:0  top:0  right:256  bottom:256

+        left:30  top:130  right:120  bottom:230

+        left:15  top:65  right:60  bottom:115
+    ##
+}
+##
+
+#ToDo some confusion on why with an identity Matrix local and device are different ##
+
+# device canvas in example works around bug in fiddle ##
+#Bug 6524  ##
+
+##
+
+#Method bool getDeviceClipBounds(SkIRect* bounds) const 
+
+Return IRect bounds of Clip, unaffected by Matrix. If Clip is empty,
+return false, and set bounds to SkRect::MakeEmpty, where all Rect sides equal zero.
+
+Unlike getLocalClipBounds, bounds is not outset. 
+
+#Param bounds  Rect of Clip in device coordinates. ##
+
+#Return  true if Clip bounds is not empty. ##
+
+#Example
+    void draw(SkCanvas* canvas) {

+        SkIRect bounds;

+        SkDebugf("device bounds empty = %s\n", canvas->getDeviceClipBounds(&bounds)

+                 ? "false" : "true");

+        SkPath path;

+        canvas->clipPath(path);

+        SkDebugf("device bounds empty = %s\n", canvas->getDeviceClipBounds(&bounds)

+                 ? "false" : "true");

+    }
+    #StdOut
+        device bounds empty = false

+        device bounds empty = true
+    ##
+##
+
+#ToDo incomplete ##
+
+##
+
+#Topic Clip ##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawColor(SkColor color, SkBlendMode mode = SkBlendMode::kSrcOver)
+
+Fill Clip with Color color.
+mode determines how Color_ARGB is combined with destination.
+
+#Param color    Unpremultiplied Color_ARGB. ##
+#Param mode  SkBlendMode used to combine source color and destination. ##
+
+#Example
+    canvas->drawColor(SK_ColorRED);

+    canvas->clipRect(SkRect::MakeWH(150, 150));

+    canvas->drawColor(SkColorSetARGB(0x80, 0x00, 0xFF, 0x00), SkBlendMode::kPlus);

+    canvas->clipRect(SkRect::MakeWH(75, 75));

+    canvas->drawColor(SkColorSetARGB(0x80, 0x00, 0x00, 0xFF), SkBlendMode::kPlus);

+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void clear(SkColor color) 
+
+Fill Clip with Color color using SkBlendMode::kSrc. 
+This has the effect of replacing all pixels contained by Clip with color.
+
+#Param color    Unpremultiplied Color_ARGB. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    canvas->save();

+    canvas->clipRect(SkRect::MakeWH(256, 128));

+    canvas->clear(SkColorSetARGB(0x80, 0xFF, 0x00, 0x00)); 

+    canvas->restore();

+    canvas->save();

+    canvas->clipRect(SkRect::MakeWH(150, 192));

+    canvas->clear(SkColorSetARGB(0x80, 0x00, 0xFF, 0x00));

+    canvas->restore();

+    canvas->clipRect(SkRect::MakeWH(75, 256));

+    canvas->clear(SkColorSetARGB(0x80, 0x00, 0x00, 0xFF));

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void discard() 
+
+Make Canvas contents undefined. Subsequent calls that read Canvas pixels,
+such as drawing with SkBlendMode, return undefined results. discard() does
+not change Clip or Matrix.
+
+discard() may do nothing, depending on the implementation of Surface or Device
+that created Canvas.
+
+discard() allows optimized performance on subsequent draws by removing
+cached data associated with Surface or Device.
+It is not necessary to call discard() once done with Canvas;
+any cached data is deleted when owning Surface or Device is deleted.
+
+#ToDo example? not sure how to make this meaningful w/o more implementation detail ##
+
+#NoExample 
+##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawPaint(const SkPaint& paint)
+
+Fill Clip with Paint paint. drawPaint is affected by  Paint components  
+Rasterizer, Mask_Filter, Shader, Color_Filter, Image_Filter, and Blend_Mode; but not by
+Path_Effect.
+
+# can Path_Effect in paint ever alter drawPaint?
+
+#Param  paint    Used to fill the canvas. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkColor     colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };

+    SkScalar    pos[] = { 0, SK_Scalar1/2, SK_Scalar1 };

+    SkPaint     paint;

+    paint.setShader(SkGradientShader::MakeSweep(256, 256, colors, pos, SK_ARRAY_COUNT(colors)));

+    canvas->drawPaint(paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Enum PointMode
+
+#Code
+    enum PointMode {
+        kPoints_PointMode,
+        kLines_PointMode,
+        kPolygon_PointMode 
+    };
+##
+
+Selects if an array of points are drawn as discrete points, as lines, or as
+an open polygon.
+
+#Const kPoints_PointMode 0
+    Draw each point separately.
+##
+
+#Const kLines_PointMode 1
+    Draw each pair of points as a line segment.
+##
+
+#Const kPolygon_PointMode 2
+    Draw the array of points as a open polygon.
+##
+
+#Example
+    #Description 
+        The upper left corner shows three squares when drawn as points.
+        The upper right corner shows one line; when drawn as lines, two points are required per line.
+        The lower right corner shows two lines; when draw as polygon, no miter is drawn at the corner.
+        The lower left corner shows two lines with a miter when path contains polygon.
+    ##
+void draw(SkCanvas* canvas) {

+  SkPaint paint;

+  paint.setStyle(SkPaint::kStroke_Style);

+  paint.setStrokeWidth(10);

+  SkPoint points[] = {{64, 32}, {96, 96}, {32, 96}};

+  canvas->drawPoints(SkCanvas::kPoints_PointMode, 3, points, paint);

+  canvas->translate(128, 0);

+  canvas->drawPoints(SkCanvas::kLines_PointMode, 3, points, paint);

+  canvas->translate(0, 128);

+  canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, points, paint);

+  SkPath path;

+  path.addPoly(points, 3, false);

+  canvas->translate(-128, 0);

+  canvas->drawPath(path, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint)
+
+Draw pts using Clip, Matrix and Paint paint.
+count is the number of points; if count is less than one, drawPoints has no effect.
+mode may be one of: kPoints_PointMode, kLines_PointMode, or kPolygon_PointMode.
+
+If mode is kPoints_PointMode, the shape of point drawn depends on paint Paint_Stroke_Cap.
+If paint is set to SkPaint::kRound_Cap, each point draws a circle of diameter Paint_Stroke_Width.
+If paint is set to SkPaint::kSquare_Cap or SkPaint::kButt_Cap, 
+each point draws a square of width and height Paint_Stroke_Width.
+
+If mode is kLines_PointMode, each pair of points draws a line segment.
+One line is drawn for every two points; each point is used once. If count is odd,
+the final point is ignored. 
+
+If mode is kPolygon_PointMode, each adjacent pair of points draws a line segment.
+count minus one lines are drawn; the first and last point are used once.
+
+Each line segment respects paint Paint_Stroke_Cap and Paint_Stroke_Width.
+Paint_Style is ignored, as if were set to SkPaint::kStroke_Style.
+
+drawPoints always draws each element one at a time; drawPoints is not affected by
+Paint_Stroke_Join, and unlike drawPath, does not create a mask from all points and lines
+before drawing. 
+
+#Param  mode     Whether pts draws points or lines. ##
+#Param  count    The number of points in the array. ##
+#Param  pts      Array of points to draw. ##
+#Param  paint    Stroke, blend, color, and so on, used to draw. ##
+
+#Example
+#Height 200
+    #Description 
+    #List
+    # The first column draws points. ##
+    # The second column draws points as lines. ##
+    # The third column draws points as a polygon. ##
+    # The fourth column draws points as a polygonal path. ##
+    # The first row uses a round cap and round join. ##
+    # The second row uses a square cap and a miter join. ##
+    # The third row uses a butt cap and a bevel join. ##
+    ##
+    The transparent color makes multiple line draws visible;
+    the path is drawn all at once.
+    ##
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setStrokeWidth(10);

+    paint.setColor(0x80349a45);

+    const SkPoint points[] = {{32, 16}, {48, 48}, {16, 32}};

+    const SkPaint::Join join[] = { SkPaint::kRound_Join, 

+                                   SkPaint::kMiter_Join,

+                                   SkPaint::kBevel_Join };

+    int joinIndex = 0;

+    SkPath path;

+    path.addPoly(points, 3, false);

+    for (const auto cap : { SkPaint::kRound_Cap, SkPaint::kSquare_Cap, SkPaint::kButt_Cap } ) {

+        paint.setStrokeCap(cap);

+        paint.setStrokeJoin(join[joinIndex++]);

+        for (const auto mode : { SkCanvas::kPoints_PointMode,

+                                 SkCanvas::kLines_PointMode,

+                                 SkCanvas::kPolygon_PointMode } ) {

+            canvas->drawPoints(mode, 3, points, paint);

+            canvas->translate(64, 0);

+        }

+        canvas->drawPath(path, paint);

+        canvas->translate(-192, 64);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawPoint(SkScalar x, SkScalar y, const SkPaint& paint)
+
+Draw point at (x, y) using Clip, Matrix and Paint paint.
+
+The shape of point drawn depends on paint Paint_Stroke_Cap.
+If paint is set to SkPaint::kRound_Cap, draw a circle of diameter Paint_Stroke_Width.
+If paint is set to SkPaint::kSquare_Cap or SkPaint::kButt_Cap, 
+draw a square of width and height Paint_Stroke_Width.
+Paint_Style is ignored, as if were set to SkPaint::kStroke_Style.
+
+#Param  x        Left edge of circle or square. ##
+#Param  y        Top edge of circle or square. ##
+#Param  paint    Stroke, blend, color, and so on, used to draw. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+  SkPaint paint;

+  paint.setAntiAlias(true);

+  paint.setColor(0x80349a45);

+  paint.setStyle(SkPaint::kStroke_Style);

+  paint.setStrokeWidth(100);

+  paint.setStrokeCap(SkPaint::kRound_Cap);

+  canvas->scale(1, 1.2f);

+  canvas->drawPoint(64, 96, paint);

+  canvas->scale(.6f, .8f);

+  paint.setColor(SK_ColorWHITE);

+  canvas->drawPoint(106, 120, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint& paint)
+
+Draw line segment from (x0, y0) to (x1, y1) using Clip, Matrix, and Paint paint.
+In paint: Paint_Stroke_Width describes the line thickness; Paint_Stroke_Cap draws the end rounded or square;
+Paint_Style is ignored, as if were set to SkPaint::kStroke_Style.
+
+#Param  x0    Start of line segment on x-axis. ##
+#Param  y0    Start of line segment on y-axis. ##
+#Param  x1    End of line segment on x-axis. ##
+#Param  y1    End of line segment on y-axis. ##
+#Param  paint  Stroke, blend, color, and so on, used to draw. ##
+
+#Example
+  SkPaint paint;

+  paint.setAntiAlias(true);

+  paint.setColor(0xFF9a67be);

+  paint.setStrokeWidth(20);

+  canvas->skew(1, 0);

+  canvas->drawLine(32, 96, 32, 160, paint);

+  canvas->skew(-2, 0);

+  canvas->drawLine(288, 96, 288, 160, paint);

+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawRect(const SkRect& rect, const SkPaint& paint)
+
+Draw Rect rect using Clip, Matrix, and Paint paint.
+In paint: Paint_Style determines if rectangle is stroked or filled; 
+if stroked, Paint_Stroke_Width describes the line thickness, and
+Paint_Stroke_Join draws the corners rounded or square.
+
+#Param  rect     The rectangle to be drawn. ##
+#Param  paint    Stroke or fill, blend, color, and so on, used to draw. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkPoint rectPts[] = { {64, 48}, {192, 160} };

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setStrokeWidth(20);

+    paint.setStrokeJoin(SkPaint::kRound_Join);

+    SkMatrix rotator;

+    rotator.setRotate(30, 128, 128);

+    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorMAGENTA } ) {

+        paint.setColor(color);

+        SkRect rect;

+        rect.set(rectPts[0], rectPts[1]);

+        canvas->drawRect(rect, paint);

+        rotator.mapPoints(rectPts, 2);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawIRect(const SkIRect& rect, const SkPaint& paint) 
+
+Draw IRect rect using Clip, Matrix, and Paint paint.
+In paint: Paint_Style determines if rectangle is stroked or filled; 
+if stroked, Paint_Stroke_Width describes the line thickness, and
+Paint_Stroke_Join draws the corners rounded or square.
+
+#Param  rect     The rectangle to be drawn. ##
+#Param  paint    Stroke or fill, blend, color, and so on, used to draw. ##
+
+#Example
+    SkIRect rect = { 64, 48, 192, 160 };

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setStrokeWidth(20);

+    paint.setStrokeJoin(SkPaint::kRound_Join);

+    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorMAGENTA } ) {

+        paint.setColor(color);

+        canvas->drawIRect(rect, paint);

+        canvas->rotate(30, 128, 128);

+    }

+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawRegion(const SkRegion& region, const SkPaint& paint)
+
+Draw Region region using Clip, Matrix, and Paint paint.
+In paint: Paint_Style determines if rectangle is stroked or filled; 
+if stroked, Paint_Stroke_Width describes the line thickness, and
+Paint_Stroke_Join draws the corners rounded or square.
+
+#Param  region   The region to be drawn. ##
+#Param  paint    Paint stroke or fill, blend, color, and so on, used to draw. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkRegion region;

+    region.op( 10, 10, 50, 50, SkRegion::kUnion_Op);

+    region.op( 10, 50, 90, 90, SkRegion::kUnion_Op);

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setStrokeWidth(20);

+    paint.setStrokeJoin(SkPaint::kRound_Join);

+    canvas->drawRegion(region, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawOval(const SkRect& oval, const SkPaint& paint)
+
+Draw Oval oval using Clip, Matrix, and Paint.
+In paint: Paint_Style determines if Oval is stroked or filled; 
+if stroked, Paint_Stroke_Width describes the line thickness.
+
+#Param  oval     Rect bounds of Oval. ##
+#Param  paint    Paint stroke or fill, blend, color, and so on, used to draw. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    canvas->clear(0xFF3f5f9f);

+    SkColor  kColor1 = SkColorSetARGB(0xff, 0xff, 0x7f, 0);

+    SkColor  g1Colors[] = { kColor1, SkColorSetA(kColor1, 0x20) };

+    SkPoint  g1Points[] = { { 0, 0 }, { 0, 100 } };

+    SkScalar pos[] = { 0.2f, 1.0f };

+    SkRect bounds = SkRect::MakeWH(80, 70);

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setShader(SkGradientShader::MakeLinear(g1Points, g1Colors, pos, SK_ARRAY_COUNT(g1Colors),

+            SkShader::kClamp_TileMode));

+    canvas->drawOval(bounds , paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawRRect(const SkRRect& rrect, const SkPaint& paint)
+
+Draw Round_Rect rrect using Clip, Matrix, and Paint paint.
+In paint: Paint_Style determines if rrect is stroked or filled; 
+if stroked, Paint_Stroke_Width describes the line thickness.
+
+rrect may represent a rectangle, circle, oval, uniformly rounded rectangle, or may have
+any combination of positive non-square radii for the four corners.
+
+#Param  rrect    Round_Rect with up to eight corner radii to draw. ##
+#Param  paint    Paint stroke or fill, blend, color, and so on, used to draw. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    SkRect outer = {30, 40, 210, 220};

+    SkRect radii = {30, 50, 70, 90 };

+    SkRRect rRect;

+    rRect.setNinePatch(outer, radii.fLeft, radii.fTop, radii.fRight, radii.fBottom);

+    canvas->drawRRect(rRect, paint);

+    paint.setColor(SK_ColorWHITE);

+    canvas->drawLine(outer.fLeft + radii.fLeft, outer.fTop,

+                     outer.fLeft + radii.fLeft, outer.fBottom, paint);

+    canvas->drawLine(outer.fRight - radii.fRight, outer.fTop, 

+                     outer.fRight - radii.fRight, outer.fBottom, paint);

+    canvas->drawLine(outer.fLeft,  outer.fTop + radii.fTop, 

+                     outer.fRight, outer.fTop + radii.fTop, paint);

+    canvas->drawLine(outer.fLeft,  outer.fBottom - radii.fBottom, 

+                     outer.fRight, outer.fBottom - radii.fBottom, paint);

+}

+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint)
+
+Draw Round_Rect outer and inner
+using Clip, Matrix, and Paint paint. 
+outer must contain inner or the drawing is undefined.
+In paint: Paint_Style determines if rrect is stroked or filled; 
+if stroked, Paint_Stroke_Width describes the line thickness.
+If stroked and Round_Rect corner has zero length radii, Paint_Stroke_Join can draw
+corners rounded or square. 
+
+GPU-backed platforms take advantage of drawDRRect since both outer and inner are
+concave and outer contains inner. These platforms may not be able to draw
+Path built with identical data as fast. 
+
+#Param  outer    Round_Rect outer bounds to draw. ##
+#Param  inner    Round_Rect inner bounds to draw. ##
+#Param  paint    Paint stroke or fill, blend, color, and so on, used to draw. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+   SkRRect outer = SkRRect::MakeRect({20, 40, 210, 200});

+   SkRRect inner = SkRRect::MakeOval({60, 70, 170, 160});

+   SkPaint paint;

+   canvas->drawDRRect(outer, inner, paint);

+}
+##
+
+#Example
+#Description
+    Outer Rect has no corner radii, but stroke join is rounded.
+    Inner Round_Rect has corner radii; outset stroke increases radii of corners.
+    Stroke join does not affect inner Round_Rect since it has no sharp corners.
+##
+void draw(SkCanvas* canvas) {

+   SkRRect outer = SkRRect::MakeRect({20, 40, 210, 200});

+   SkRRect inner = SkRRect::MakeRectXY({60, 70, 170, 160}, 10, 10);

+   SkPaint paint;

+   paint.setAntiAlias(true);

+   paint.setStyle(SkPaint::kStroke_Style);

+   paint.setStrokeWidth(20);

+   paint.setStrokeJoin(SkPaint::kRound_Join);

+   canvas->drawDRRect(outer, inner, paint);

+   paint.setStrokeWidth(1);

+   paint.setColor(SK_ColorWHITE);

+   canvas->drawDRRect(outer, inner, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawCircle(SkScalar cx, SkScalar cy, SkScalar radius, const SkPaint& paint)
+
+Draw Circle at (cx, cy) with radius using Clip, Matrix, and Paint paint.
+If radius is zero or less, nothing is drawn.
+In paint: Paint_Style determines if Circle is stroked or filled; 
+if stroked, Paint_Stroke_Width describes the line thickness.
+
+#Param  cx       Circle center on the x-axis. ##
+#Param  cy       Circle center on the y-axis. ##
+#Param  radius   Half the diameter of Circle. ##
+#Param  paint    Paint stroke or fill, blend, color, and so on, used to draw. ##
+
+#Example
+    void draw(SkCanvas* canvas) {

+        SkPaint paint;

+        paint.setAntiAlias(true);

+        canvas->drawCircle(128, 128, 90, paint);

+        paint.setColor(SK_ColorWHITE);

+        canvas->drawCircle(86, 86, 20, paint);

+        canvas->drawCircle(160, 76, 20, paint);

+        canvas->drawCircle(140, 150, 35, paint);

+    }

+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
+                 bool useCenter, const SkPaint& paint)
+
+Draw Arc using Clip, Matrix, and Paint paint.
+Arc is part of Oval bounded by oval, sweeping from startAngle to startAngle plus
+sweepAngle. startAngle and sweepAngle are in degrees.
+startAngle of zero places start point at the right middle edge of oval.
+A positive sweepAngle places Arc end point clockwise from start point;
+a negative sweepAngle places Arc end point counterclockwise from start point.
+sweepAngle may exceed 360 degrees, a full circle.
+If useCenter is true, draw a wedge that includes lines from oval
+center to Arc end points. If useCenter is false, draw Arc between end points.
+
+If Rect oval is empty or sweepAngle is zero, nothing is drawn.
+
+#Param  oval     Rect bounds of Oval containing Arc to draw. ##
+#Param  startAngle Angle in degrees where Arc begins. ##
+#Param  sweepAngle Sweep angle in degrees; positive is clockwise. ##
+#Param  useCenter If true include the center of the oval. ##
+#Param  paint    Paint stroke or fill, blend, color, and so on, used to draw. ##
+
+#Example
+    void draw(SkCanvas* canvas) {

+        SkPaint paint;

+        paint.setAntiAlias(true);

+        SkRect oval = { 4, 4, 60, 60};

+        for (auto useCenter : { false, true } ) {

+            for (auto style : { SkPaint::kFill_Style, SkPaint::kStroke_Style } ) {

+                paint.setStyle(style);

+                for (auto degrees : { 45, 90, 180, 360} ) {

+                    canvas->drawArc(oval, 0, degrees , useCenter, paint);

+                    canvas->translate(64, 0);

+                }

+                canvas->translate(-256, 64);

+            }

+        }

+    }
+##
+
+#Example
+#Height 64
+    void draw(SkCanvas* canvas) {

+        SkPaint paint;

+        paint.setAntiAlias(true);

+        paint.setStyle(SkPaint::kStroke_Style);

+        paint.setStrokeWidth(4);

+        SkRect oval = { 4, 4, 60, 60};

+        float intervals[] = { 5, 5 };

+        paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 2.5f));

+        for (auto degrees : { 270, 360, 540, 720 } ) {

+            canvas->drawArc(oval, 0, degrees, false, paint);

+            canvas->translate(64, 0);

+        }

+    }
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry, const SkPaint& paint)
+
+Draw Round_Rect bounded by Rect rect, with corner radii (rx, ry) using Clip, Matrix,
+and Paint paint.
+In paint: Paint_Style determines if Round_Rect is stroked or filled; 
+if stroked, Paint_Stroke_Width describes the line thickness.
+If rx or ry are less than zero, they are treated as if they are zero.
+If rx plus ry exceeds rect width or rect height, radii are scaled down to fit.
+If rx and ry are zero, Round_Rect is drawn as Rect and if stroked is affected by Paint_Stroke_Join.
+
+#Param  rect     Rect bounds of Round_Rect to draw. ##
+#Param  rx       Semiaxis length in x of oval describing rounded corners. ##
+#Param  ry       Semiaxis length in y of oval describing rounded corners. ##
+#Param  paint    Stroke, blend, color, and so on, used to draw. ##
+
+#Example
+#Description
+    Top row has a zero radius a generates a rectangle.
+    Second row radii sum to less than sides.
+    Third row radii sum equals sides.
+    Fourth row radii sum exceeds sides; radii are scaled to fit.
+##
+    void draw(SkCanvas* canvas) {

+        SkVector radii[] = { {0, 20}, {10, 10}, {10, 20}, {10, 40} };

+        SkPaint paint;

+        paint.setStrokeWidth(15);

+        paint.setStrokeJoin(SkPaint::kRound_Join);

+        paint.setAntiAlias(true);

+        for (auto style : { SkPaint::kStroke_Style, SkPaint::kFill_Style  } ) {

+            paint.setStyle(style );

+            for (size_t i = 0; i < SK_ARRAY_COUNT(radii); ++i) {

+               canvas->drawRoundRect({10, 10, 60, 40}, radii[i].fX, radii[i].fY, paint);

+               canvas->translate(0, 60);

+            }

+            canvas->translate(80, -240);

+        }

+    }
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawPath(const SkPath& path, const SkPaint& paint)
+
+Draw Path path using Clip, Matrix, and Paint paint.
+Path contains an array of Path_Contour, each of which may be open or closed.
+
+In paint: Paint_Style determines if Round_Rect is stroked or filled:
+if filled, Path_Fill_Type determines whether Path_Contour describes inside or outside of fill;
+if stroked, Paint_Stroke_Width describes the line thickness, Paint_Stroke_Cap describes line ends,
+and Paint_Stroke_Join describes how corners are drawn.
+
+#Param  path     Path to draw. ##
+#Param  paint    Stroke, blend, color, and so on, used to draw. ##
+
+#Example
+#Description
+    Top rows draw stroked path with combinations of joins and caps. The open contour
+    is affected by caps; the closed contour is affected by joins.
+    Bottom row draws fill the same for open and closed contour. 
+    First bottom column shows winding fills overlap.
+    Second bottom column shows even odd fills exclude overlap.
+    Third bottom column shows inverse winding fills area outside both contours.
+##
+void draw(SkCanvas* canvas) {

+    SkPath path;

+    path.moveTo(20, 20);

+    path.quadTo(60, 20, 60, 60);

+    path.close();

+    path.moveTo(60, 20);

+    path.quadTo(60, 60, 20, 60);

+    SkPaint paint;

+    paint.setStrokeWidth(10);

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    for (auto join: { SkPaint::kBevel_Join, SkPaint::kRound_Join, SkPaint::kMiter_Join } ) {

+        paint.setStrokeJoin(join);

+        for (auto cap: { SkPaint::kButt_Cap, SkPaint::kSquare_Cap, SkPaint::kRound_Cap  } ) {

+            paint.setStrokeCap(cap);

+            canvas->drawPath(path, paint);

+            canvas->translate(80, 0);

+        }

+        canvas->translate(-240, 60);

+    }

+    paint.setStyle(SkPaint::kFill_Style);

+    for (auto fill : { SkPath::kWinding_FillType, 

+                       SkPath::kEvenOdd_FillType, 

+                       SkPath::kInverseWinding_FillType } ) {

+        path.setFillType(fill);

+        canvas->save();

+        canvas->clipRect({0, 10, 80, 70});

+        canvas->drawPath(path, paint);

+        canvas->restore();

+        canvas->translate(80, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+#Topic Draw_Image
+
+drawImage, drawImageRect, and drawImageNine can be called with a bare pointer or a smart pointer as a convenience.
+The pairs of calls are otherwise identical.
+
+
+#Method void drawImage(const SkImage* image, SkScalar left, SkScalar top, const SkPaint* paint = NULL)
+
+Draw Image image, with its top-left corner at (left, top),
+using Clip, Matrix, and optional Paint paint.
+
+If paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If image is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from image bounds. If generated mask extends
+beyond image bounds, replicate image edge colors, just as Shader made from 
+SkImage::makeShader with SkShader::kClamp_TileMode set replicates the image's edge
+color when it samples outside of its bounds. 
+
+#Param  image    Uncompressed rectangular map of pixels. ##
+#Param  left     Left side of image. ##
+#Param  top      Top side of image. ##
+#Param  paint    Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+
+#Example
+#Height 64
+#Image 4
+void draw(SkCanvas* canvas) {

+   // sk_sp<SkImage> image;

+   SkImage* imagePtr = image.get();

+   canvas->drawImage(imagePtr, 0, 0);

+   SkPaint paint;

+   canvas->drawImage(imagePtr, 80, 0, &paint);

+   paint.setAlpha(0x80);

+   canvas->drawImage(imagePtr, 160, 0, &paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawImage(const sk_sp<SkImage>& image, SkScalar left, SkScalar top,
+                   const SkPaint* paint = NULL) 
+
+Draw Image image, with its top-left corner at (left, top),
+using Clip, Matrix, and optional Paint paint.
+
+If Paint paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If image is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from image bounds. If generated mask extends
+beyond image bounds, replicate image edge colors, just as Shader made from 
+SkImage::makeShader with SkShader::kClamp_TileMode set replicates the image's edge
+color when it samples outside of its bounds. 
+
+#Param  image    Uncompressed rectangular map of pixels. ##
+#Param  left     Left side of image. ##
+#Param  top      Top side of image. ##
+#Param  paint    Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+
+#Example
+#Height 64
+#Image 4
+void draw(SkCanvas* canvas) {

+   // sk_sp<SkImage> image;

+   canvas->drawImage(image, 0, 0);

+   SkPaint paint;

+   canvas->drawImage(image, 80, 0, &paint);

+   paint.setAlpha(0x80);

+   canvas->drawImage(image, 160, 0, &paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Enum SrcRectConstraint
+
+#Code
+    enum SrcRectConstraint {
+        kStrict_SrcRectConstraint, 
+        kFast_SrcRectConstraint, 
+    };
+##
+
+SrcRectConstraint controls the behavior at the edge of the Rect src, provided to drawImageRect,
+trading off speed for precision.
+
+Image_Filter in Paint may sample multiple pixels in the image.
+Rect src restricts the bounds of pixels that may be read. Image_Filter may slow
+down if it cannot read outside the bounds, when sampling near the edge of Rect src. 
+SrcRectConstraint specifies whether an Image_Filter is allowed to read pixels
+outside Rect src.
+
+#Const kStrict_SrcRectConstraint
+    Requires Image_Filter to respect Rect src,
+    sampling only inside of its bounds, possibly with a performance penalty.
+##
+
+#Const kFast_SrcRectConstraint
+    Permits Image_Filter to sample outside of Rect src
+    by half the width of Image_Filter, permitting it to run faster but with
+    error at the image edges.
+##
+
+#Example
+#Height 64
+#Description
+    redBorder contains a black and white checkerboard bordered by red.
+    redBorder is drawn scaled by 16 on the left.
+    The middle and right bitmaps are filtered checkboards.
+    Drawing the checkerboard with kStrict_SrcRectConstraint shows only a blur of black and white.
+    Drawing the checkerboard with kFast_SrcRectConstraint allows red to bleed in the corners.
+##
+void draw(SkCanvas* canvas) {

+    SkBitmap redBorder;

+    redBorder.allocPixels(SkImageInfo::MakeN32Premul(4, 4));

+    SkCanvas checkRed(redBorder);

+    checkRed.clear(SK_ColorRED);

+    uint32_t checkers[][2] = { { SK_ColorBLACK, SK_ColorWHITE },

+                               { SK_ColorWHITE, SK_ColorBLACK } };

+    checkRed.writePixels(

+            SkImageInfo::MakeN32Premul(2, 2), (void*) checkers, sizeof(checkers[0]), 1, 1);

+    canvas->scale(16, 16);

+    canvas->drawBitmap(redBorder, 0, 0, nullptr);

+    canvas->resetMatrix();

+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(redBorder);

+    SkPaint lowPaint;

+    lowPaint.setFilterQuality(kLow_SkFilterQuality);

+    for (auto constraint : { SkCanvas::kStrict_SrcRectConstraint,

+                             SkCanvas::kFast_SrcRectConstraint } ) {

+        canvas->translate(80, 0);

+        canvas->drawImageRect(image.get(), SkRect::MakeLTRB(1, 1, 3, 3),

+                SkRect::MakeLTRB(16, 16, 48, 48), &lowPaint, constraint);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawImageRect(const SkImage* image, const SkRect& src, const SkRect& dst,
+                       const SkPaint* paint,
+                       SrcRectConstraint constraint = kStrict_SrcRectConstraint)
+
+Draw Rect src of Image image, scaled and translated to fill Rect dst.
+Additionally transform draw using Clip, Matrix, and optional Paint paint.
+If Paint paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If image is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from image bounds. If generated mask extends
+beyond image bounds, replicate image edge colors, just as Shader made from 
+SkImage::makeShader with SkShader::kClamp_TileMode set replicates the image's edge
+color when it samples outside of its bounds. 
+constraint set to kStrict_SrcRectConstraint limits Paint Filter_Quality to sample within src;
+set to kFast_SrcRectConstraint allows sampling outside to improve performance.
+
+#Param  image      Image containing pixels, dimensions, and format. ##
+#Param  src        Source Rect of image to draw from. ##
+#Param  dst        Destination Rect of image to draw to. ##
+#Param  paint      Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+#Param  constraint Filter strictly within src or draw faster. ##
+
+#Example
+#Height 64
+#Description
+    The left bitmap draws with Paint default kNone_SkFilterQuality, and stays within
+    its bounds; there's no bleeding with kFast_SrcRectConstraint.
+    the middle and right bitmaps draw with kLow_SkFilterQuality; with
+    kStrict_SrcRectConstraint, the filter remains within the checkerboard, and
+    with kFast_SrcRectConstraint red bleeds on the edges.
+##
+void draw(SkCanvas* canvas) {

+    uint32_t pixels[][4] = { 

+            { 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000 },

+            { 0xFFFF0000, 0xFF000000, 0xFFFFFFFF, 0xFFFF0000 },

+            { 0xFFFF0000, 0xFFFFFFFF, 0xFF000000, 0xFFFF0000 },

+            { 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000 } };

+    SkBitmap redBorder;

+    redBorder.installPixels(SkImageInfo::MakeN32Premul(4, 4), 

+            (void*) pixels, sizeof(pixels[0]));

+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(redBorder);

+    SkPaint lowPaint;

+    for (auto constraint : {

+            SkCanvas::kFast_SrcRectConstraint,

+            SkCanvas::kStrict_SrcRectConstraint,

+            SkCanvas::kFast_SrcRectConstraint } ) {

+        canvas->drawImageRect(image.get(), SkRect::MakeLTRB(1, 1, 3, 3),

+                SkRect::MakeLTRB(16, 16, 48, 48), &lowPaint, constraint);

+        lowPaint.setFilterQuality(kLow_SkFilterQuality);

+        canvas->translate(80, 0);

+    }

+}

+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawImageRect(const SkImage* image, const SkIRect& isrc, const SkRect& dst,
+                       const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint)
+
+Draw IRect isrc of Image image, scaled and translated to fill Rect dst.
+Note that isrc is on integer pixel boundaries; dst may include fractional boundaries.
+Additionally transform draw using Clip, Matrix, and optional Paint paint.
+If Paint paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If image is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from image bounds. If generated mask extends
+beyond image bounds, replicate image edge colors, just as Shader made from 
+SkImage::makeShader with SkShader::kClamp_TileMode set replicates the image's edge
+color when it samples outside of its bounds. 
+constraint set to kStrict_SrcRectConstraint limits Paint Filter_Quality to sample within src;
+set to kFast_SrcRectConstraint allows sampling outside to improve performance.
+
+#Param  image      Image containing pixels, dimensions, and format. ##
+#Param  isrc       Source IRect of image to draw from. ##
+#Param  dst        Destination Rect of image to draw to. ##
+#Param  paint      Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+#Param  constraint Filter strictly within src or draw faster. ##
+
+#Example
+#Image 4
+void draw(SkCanvas* canvas) {

+    // sk_sp<SkImage> image;

+    for (auto i : { 1, 2, 4, 8 } ) {

+        canvas->drawImageRect(image.get(), SkIRect::MakeLTRB(0, 0, 100, 100), 

+                SkRect::MakeXYWH(i * 20, i * 20, i * 20, i * 20), nullptr);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawImageRect(const SkImage* image, const SkRect& dst, const SkPaint* paint,
+                       SrcRectConstraint constraint = kStrict_SrcRectConstraint)
+
+Draw Image image, scaled and translated to fill Rect dst,
+using Clip, Matrix, and optional Paint paint.
+If Paint paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If image is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from image bounds. If generated mask extends
+beyond image bounds, replicate image edge colors, just as Shader made from 
+SkImage::makeShader with SkShader::kClamp_TileMode set replicates the image's edge
+color when it samples outside of its bounds. 
+Use constaint to choose kStrict_SrcRectConstraint or kFast_SrcRectConstraint.
+
+#Param  image      Image containing pixels, dimensions, and format. ##
+#Param  dst        Destination Rect of image to draw to. ##
+#Param  paint      Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+#Param  constraint Filter strictly within src or draw faster. ##
+
+#Example
+#Image 4
+void draw(SkCanvas* canvas) {

+    // sk_sp<SkImage> image;

+    for (auto i : { 20, 40, 80, 160 } ) {

+        canvas->drawImageRect(image.get(), SkRect::MakeXYWH(i, i, i, i), nullptr);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawImageRect(const sk_sp<SkImage>& image, const SkRect& src, const SkRect& dst,
+                       const SkPaint* paint,
+                       SrcRectConstraint constraint = kStrict_SrcRectConstraint) 
+
+Draw Rect src of Image image, scaled and translated to fill Rect dst.
+Additionally transform draw using Clip, Matrix, and optional Paint paint.
+If Paint paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If image is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from image bounds. If generated mask extends
+beyond image bounds, replicate image edge colors, just as Shader made from 
+SkImage::makeShader with SkShader::kClamp_TileMode set replicates the image's edge
+color when it samples outside of its bounds. 
+constraint set to kStrict_SrcRectConstraint limits Paint Filter_Quality to sample within src;
+set to kFast_SrcRectConstraint allows sampling outside to improve performance.
+
+#Param  image      Image containing pixels, dimensions, and format. ##
+#Param  src        Source Rect of image to draw from. ##
+#Param  dst        Destination Rect of image to draw to. ##
+#Param  paint      Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+#Param  constraint Filter strictly within src or draw faster. ##
+
+#Example
+#Height 64
+#Description
+    Canvas scales and translates; transformation from src to dst also scales.
+    The two matrices are concatenated to create the final transformation.
+##
+void draw(SkCanvas* canvas) {

+    uint32_t pixels[][2] = { { SK_ColorBLACK, SK_ColorWHITE },

+                             { SK_ColorWHITE, SK_ColorBLACK } };

+    SkBitmap bitmap;

+    bitmap.installPixels(SkImageInfo::MakeN32Premul(2, 2), 

+            (void*) pixels, sizeof(pixels[0]));

+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);

+    SkPaint paint;

+    canvas->scale(4, 4);

+    for (auto alpha : { 50, 100, 150, 255 } ) {

+        paint.setAlpha(alpha);

+        canvas->drawImageRect(image, SkRect::MakeWH(2, 2), SkRect::MakeWH(8, 8), &paint);

+        canvas->translate(8, 0);

+    }

+}

+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawImageRect(const sk_sp<SkImage>& image, const SkIRect& isrc, const SkRect& dst,
+                       const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint) 
+
+Draw IRect isrc of Image image, scaled and translated to fill Rect dst.
+Note that isrc is on integer pixel boundaries; dst may include fractional boundaries.
+Additionally transform draw using Clip, Matrix, and optional Paint paint.
+If Paint paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If image is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from image bounds. If generated mask extends
+beyond image bounds, replicate image edge colors, just as Shader made from 
+SkShader::MakeBitmapShader with SkShader::kClamp_TileMode set replicates the image's edge
+color when it samples outside of its bounds. 
+cons set to kStrict_SrcRectConstraint limits Paint Filter_Quality to sample within src;
+set to kFast_SrcRectConstraint allows sampling outside to improve performance.
+
+#Param  image      Image containing pixels, dimensions, and format. ##
+#Param  isrc       Source IRect of image to draw from. ##
+#Param  dst        Destination Rect of image to draw to. ##
+#Param  paint      Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+#Param  constraint Filter strictly within src or draw faster. ##
+
+#Example
+#Height 64
+void draw(SkCanvas* canvas) {

+    uint32_t pixels[][2] = { { 0x00000000, 0x55555555},

+                             { 0xAAAAAAAA, 0xFFFFFFFF} };

+    SkBitmap bitmap;

+    bitmap.installPixels(SkImageInfo::MakeN32Premul(2, 2), 

+            (void*) pixels, sizeof(pixels[0]));

+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);

+    SkPaint paint;

+    canvas->scale(4, 4);

+    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {

+        paint.setColorFilter(SkColorFilter::MakeModeFilter(color, SkBlendMode::kPlus));

+        canvas->drawImageRect(image, SkIRect::MakeWH(2, 2), SkRect::MakeWH(8, 8), &paint);

+        canvas->translate(8, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawImageRect(const sk_sp<SkImage>& image, const SkRect& dst, const SkPaint* paint,
+                       SrcRectConstraint constraint = kStrict_SrcRectConstraint) 
+
+Draw Image image, scaled and translated to fill Rect dst,
+using Clip, Matrix, and optional Paint paint.
+If Paint paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If image is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from image bounds. If generated mask extends
+beyond image bounds, replicate image edge colors, just as Shader made from 
+SkImage::makeShader with SkShader::kClamp_TileMode set replicates the image's edge
+color when it samples outside of its bounds. 
+constraint set to kStrict_SrcRectConstraint limits Paint Filter_Quality to sample within src;
+set to kFast_SrcRectConstraint allows sampling outside to improve performance.
+
+#Param  image      Image containing pixels, dimensions, and format. ##
+#Param  dst        Destination Rect of image to draw to. ##
+#Param  paint      Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+#Param  constraint Filter strictly within src or draw faster. ##
+
+#Example
+#Height 64
+void draw(SkCanvas* canvas) {

+    uint32_t pixels[][2] = { { 0x00000000, 0x55550000},

+                             { 0xAAAA0000, 0xFFFF0000} };

+    SkBitmap bitmap;

+    bitmap.installPixels(SkImageInfo::MakeN32Premul(2, 2), 

+            (void*) pixels, sizeof(pixels[0]));

+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);

+    SkPaint paint;

+    canvas->scale(4, 4);

+    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {

+        paint.setColorFilter(SkColorFilter::MakeModeFilter(color, SkBlendMode::kPlus));

+        canvas->drawImageRect(image, SkRect::MakeWH(8, 8), &paint);

+        canvas->translate(8, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawImageNine(const SkImage* image, const SkIRect& center, const SkRect& dst,
+                       const SkPaint* paint = nullptr)
+
+Draw Image image stretched differentially to fit into Rect dst.
+IRect center divides the image into nine sections: four sides, four corners, and the center.
+corners are unscaled or scaled down proportionately if their sides are larger than dst;
+center and four sides are scaled to fit remaining space, if any.
+Additionally transform draw using Clip, Matrix, and optional Paint paint.
+If paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If image is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from image bounds. If generated mask extends
+beyond image bounds, replicate image edge colors, just as Shader made from 
+SkShader::MakeBitmapShader with SkShader::kClamp_TileMode set replicates the image's edge
+color when it samples outside of its bounds. 
+
+#Param  image      Image containing pixels, dimensions, and format. ##
+#Param  center     IRect edge of image corners and sides. ##
+#Param  dst        Destination Rect of image to draw to. ##
+#Param  paint      Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+
+#Example
+#Height 128
+#Description
+    The leftmost image is smaller than center; only corners are drawn, all scaled to fit.
+    The second image equals the size of center; only corners are drawn, unscaled.
+    The remaining images are larger than center. All corners draw unscaled. The sides
+    and center are scaled if needed to take up the remaining space.
+##
+void draw(SkCanvas* canvas) {

+    SkIRect center = { 20, 10, 50, 40 };

+    SkBitmap bitmap;

+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));

+    SkCanvas bitCanvas(bitmap);

+    SkPaint paint;

+    SkColor gray = 0xFF000000;

+    int left = 0;

+    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {

+        int top = 0;

+        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {

+            paint.setColor(gray);

+            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);

+            gray += 0x001f1f1f;

+            top = bottom;

+        }

+        left = right; 

+    }

+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);

+    SkImage* imagePtr = image.get();

+    for (auto dest: { 20, 30, 40, 60, 90 } ) {

+        canvas->drawImageNine(imagePtr, center, SkRect::MakeWH(dest, dest), nullptr);

+        canvas->translate(dest + 4, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawImageNine(const sk_sp<SkImage>& image, const SkIRect& center, const SkRect& dst,
+                       const SkPaint* paint = nullptr) 
+
+Draw Image image stretched differentially to fit into Rect dst.
+IRect center divides the image into nine sections: four sides, four corners, and the center.
+corners are unscaled or scaled down proportionately if their sides are larger than dst;
+center and four sides are scaled to fit remaining space, if any.
+Additionally transform draw using Clip, Matrix, and optional Paint paint.
+If Paint paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If image is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from image bounds. If generated mask extends
+beyond image bounds, replicate image edge colors, just as Shader made from 
+SkShader::MakeBitmapShader with SkShader::kClamp_TileMode set replicates the image's edge
+color when it samples outside of its bounds. 
+
+#Param  image      Image containing pixels, dimensions, and format. ##
+#Param  center     IRect edge of image corners and sides. ##
+#Param  dst        Destination Rect of image to draw to. ##
+#Param  paint      Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+
+#Example
+#Height 128
+#Description
+    The two leftmost images has four corners and sides to the left and right of center.
+    The leftmost image scales the width of corners proportionately to fit.
+    The third and fourth image corners are unscaled; the sides and center are scaled to 
+    fill the remaining space.
+    The rightmost image has four corners scaled vertically to fit, and uses sides above
+    and below center to fill the remaining space.
+##
+void draw(SkCanvas* canvas) {

+    SkIRect center = { 20, 10, 50, 40 };

+    SkBitmap bitmap;

+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));

+    SkCanvas bitCanvas(bitmap);

+    SkPaint paint;

+    SkColor gray = 0xFF000000;

+    int left = 0;

+    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {

+        int top = 0;

+        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {

+            paint.setColor(gray);

+            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);

+            gray += 0x001f1f1f;

+            top = bottom;

+        }

+        left = right; 

+    }

+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);

+    for (auto dest: { 20, 30, 40, 60, 90 } ) {

+        canvas->drawImageNine(image, center, SkRect::MakeWH(dest, 110 - dest), nullptr);

+        canvas->translate(dest + 4, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
+                    const SkPaint* paint = NULL)
+
+Draw Bitmap bitmap, with its top-left corner at (left, top),
+using Clip, Matrix, and optional Paint paint.
+If paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If bitmap is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from bitmap bounds. If generated mask extends
+beyond bitmap bounds, replicate bitmap edge colors, just as Shader made from
+SkShader::MakeBitmapShader with SkShader::kClamp_TileMode set replicates the bitmap's edge
+color when it samples outside of its bounds. 
+
+#Param  bitmap   Bitmap containing pixels, dimensions, and format. ##
+#Param  left     Left side of bitmap. ##
+#Param  top      Top side of bitmap. ##
+#Param  paint    Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+
+#Example
+#Height 64
+void draw(SkCanvas* canvas) {

+    uint8_t pixels[][8] = { { 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00},

+                            { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00},

+                            { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00},

+                            { 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF},

+                            { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},

+                            { 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00},

+                            { 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00},

+                            { 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF} };

+    SkBitmap bitmap;

+    bitmap.installPixels(SkImageInfo::MakeA8(8, 8), 

+            (void*) pixels, sizeof(pixels[0]));

+    SkPaint paint;

+    canvas->scale(4, 4);

+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xFF007F00} ) {

+        paint.setColor(color);

+        canvas->drawBitmap(bitmap, 0, 0, &paint);

+        canvas->translate(12, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawBitmapRect(const SkBitmap& bitmap, const SkRect& src, const SkRect& dst,
+                        const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint)
+
+Draw Rect src of Bitmap bitmap, scaled and translated to fill Rect dst.
+Additionally transform draw using Clip, Matrix, and optional Paint paint.
+If paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If bitmap is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from bitmap bounds. If generated mask extends
+beyond bitmap bounds, replicate bitmap edge colors, just as Shader made from 
+SkShader::MakeBitmapShader with SkShader::kClamp_TileMode set replicates the bitmap's edge
+color when it samples outside of its bounds. 
+constraint set to kStrict_SrcRectConstraint limits Paint Filter_Quality to sample within src;
+set to kFast_SrcRectConstraint allows sampling outside to improve performance.
+
+#Param  bitmap   Bitmap containing pixels, dimensions, and format. ##
+#Param  src      Source Rect of image to draw from. ##
+#Param  dst      Destination Rect of image to draw to. ##
+#Param  paint    Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+#Param  constraint Filter strictly within src or draw faster. ##
+
+#Example
+#Height 64
+void draw(SkCanvas* canvas) {

+    uint8_t pixels[][8] = { { 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00},

+                            { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00},

+                            { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00},

+                            { 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF},

+                            { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},

+                            { 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00},

+                            { 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00},

+                            { 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00} };

+    SkBitmap bitmap;

+    bitmap.installPixels(SkImageInfo::MakeA8(8, 8), 

+            (void*) pixels, sizeof(pixels[0]));

+    SkPaint paint;

+    paint.setMaskFilter(SkBlurMaskFilter::Make(kSolid_SkBlurStyle, 6));

+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xFF007F00} ) {

+        paint.setColor(color);

+        canvas->drawBitmapRect(bitmap, SkRect::MakeWH(8, 8), SkRect::MakeWH(32, 32), &paint);

+        canvas->translate(48, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawBitmapRect(const SkBitmap& bitmap, const SkIRect& isrc, const SkRect& dst,
+                        const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint)
+
+Draw IRect isrc of Bitmap bitmap, scaled and translated to fill Rect dst.
+Note that isrc is on integer pixel boundaries; dst may include fractional boundaries.
+Additionally transform draw using Clip, Matrix, and optional Paint paint.
+If paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If bitmap is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from bitmap bounds. If generated mask extends
+beyond bitmap bounds, replicate bitmap edge colors, just as Shader made from 
+SkShader::MakeBitmapShader with SkShader::kClamp_TileMode set replicates the bitmap's edge
+color when it samples outside of its bounds. 
+constraint set to kStrict_SrcRectConstraint limits Paint Filter_Quality to sample within src;
+set to kFast_SrcRectConstraint allows sampling outside to improve performance.
+
+#Param  bitmap   Bitmap containing pixels, dimensions, and format. ##
+#Param  isrc     Source IRect of image to draw from. ##
+#Param  dst      Destination Rect of image to draw to. ##
+#Param  paint    Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+#Param  constraint Filter strictly within src or draw faster. ##
+
+#Example
+#Height 64
+void draw(SkCanvas* canvas) {

+    uint8_t pixels[][8] = { { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00},

+                            { 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00},

+                            { 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF},

+                            { 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF},

+                            { 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF},

+                            { 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF},

+                            { 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00},

+                            { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00} };

+    SkBitmap bitmap;

+    bitmap.installPixels(SkImageInfo::MakeA8(8, 8), 

+            (void*) pixels, sizeof(pixels[0]));

+    SkPaint paint;

+    paint.setFilterQuality(kHigh_SkFilterQuality);

+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xFF007F00, 0xFF7f007f} ) {

+        paint.setColor(color);

+        canvas->drawBitmapRect(bitmap, SkIRect::MakeWH(8, 8), SkRect::MakeWH(32, 32), &paint);

+        canvas->translate(48.25f, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawBitmapRect(const SkBitmap& bitmap, const SkRect& dst, const SkPaint* paint,
+                        SrcRectConstraint constraint = kStrict_SrcRectConstraint)
+
+Draw Bitmap bitmap, scaled and translated to fill Rect dst.
+Note that isrc is on integer pixel boundaries; dst may include fractional boundaries.
+Additionally transform draw using Clip, Matrix, and optional Paint paint.
+If paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If bitmap is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from bitmap bounds. If generated mask extends
+beyond bitmap bounds, replicate bitmap edge colors, just as Shader made from 
+SkShader::MakeBitmapShader with SkShader::kClamp_TileMode set replicates the bitmap's edge
+color when it samples outside of its bounds. 
+constraint set to kStrict_SrcRectConstraint limits Paint Filter_Quality to sample within src;
+set to kFast_SrcRectConstraint allows sampling outside to improve performance.
+
+#Param  bitmap   Bitmap containing pixels, dimensions, and format. ##
+#Param  dst      Destination Rect of image to draw to. ##
+#Param  paint    Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+#Param  constraint Filter strictly within src or draw faster. ##
+
+#Example
+#Height 64
+void draw(SkCanvas* canvas) {

+    uint32_t pixels[][2] = { { 0x00000000, 0x55550000},

+                             { 0xAAAA0000, 0xFFFF0000} };

+    SkBitmap bitmap;

+    bitmap.installPixels(SkImageInfo::MakeN32Premul(2, 2), 

+            (void*) pixels, sizeof(pixels[0]));

+    SkPaint paint;

+    canvas->scale(4, 4);

+    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {

+        paint.setColorFilter(SkColorFilter::MakeModeFilter(color, SkBlendMode::kPlus));

+        canvas->drawBitmapRect(bitmap, SkRect::MakeWH(8, 8), &paint);

+        canvas->translate(8, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, const SkRect& dst,
+                        const SkPaint* paint = NULL)
+
+Draw Bitmap bitmap stretched differentially to fit into Rect dst.
+IRect center divides the bitmap into nine sections: four sides, four corners, and the center.
+corners are unscaled or scaled down proportionately if their sides are larger than dst;
+center and four sides are scaled to fit remaining space, if any.
+Additionally transform draw using Clip, Matrix, and optional Paint paint.
+If Paint paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If bitmap is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from bitmap bounds. If generated mask extends
+beyond bitmap bounds, replicate bitmap edge colors, just as Shader made from 
+SkShader::MakeBitmapShader with SkShader::kClamp_TileMode set replicates the bitmap's edge
+color when it samples outside of its bounds. 
+
+#Param  bitmap     Bitmap containing pixels, dimensions, and format. ##
+#Param  center     IRect edge of image corners and sides. ##
+#Param  dst        Destination Rect of image to draw to. ##
+#Param  paint      Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+
+#Example
+#Height 128
+#Description
+    The two leftmost bitmap draws has four corners and sides to the left and right of center.
+    The leftmost bitmap draw scales the width of corners proportionately to fit.
+    The third and fourth draw corners are unscaled; the sides and center are scaled to 
+    fill the remaining space.
+    The rightmost bitmap draw has four corners scaled vertically to fit, and uses sides above
+    and below center to fill the remaining space.
+##
+void draw(SkCanvas* canvas) {

+    SkIRect center = { 20, 10, 50, 40 };

+    SkBitmap bitmap;

+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));

+    SkCanvas bitCanvas(bitmap);

+    SkPaint paint;

+    SkColor gray = 0xFF000000;

+    int left = 0;

+    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {

+        int top = 0;

+        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {

+            paint.setColor(gray);

+            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);

+            gray += 0x001f1f1f;

+            top = bottom;

+        }

+        left = right; 

+    }

+    for (auto dest: { 20, 30, 40, 60, 90 } ) {

+        canvas->drawBitmapNine(bitmap, center, SkRect::MakeWH(dest, 110 - dest), nullptr);

+        canvas->translate(dest + 4, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+#Struct Lattice
+
+    Lattice divides Bitmap or Image into a rectangular grid.
+    Grid entries on even columns and even rows are fixed; these entries are
+    always drawn at their original size if the destination is large enough.
+    If the destination side is too small to hold the fixed entries, all fixed
+    entries are proportionately scaled down to fit.
+    The grid entries not on even columns and rows are scaled to fit the
+    remaining space, if any.
+
+#Code
+    struct Lattice {
+        enum Flags {...
+
+        const int*     fXDivs;
+        const int*     fYDivs;
+        const Flags*   fFlags;
+        int            fXCount;
+        int            fYCount;
+        const SkIRect* fBounds;
+    };
+##
+
+    #Enum Flags
+        #Code
+            enum Flags : uint8_t {
+                kTransparent_Flags = 1 << 0,
+            };
+        ##
+
+        Optional setting per rectangular grid entry to make it transparent.
+
+        #Const kTransparent_Flags 1
+            Set to skip lattice rectangle by making it transparent.
+        ##
+    ##
+
+    #Member const int*   fXDivs
+        Array of x-coordinates that divide the bitmap vertically.
+        Array entries must be unique, increasing, greater than or equal to fBounds left edge,
+        and less than fBounds right edge.
+        Set the first element to fBounds left to collapse the left column of fixed grid entries.
+    ##
+
+    #Member const int*   fYDivs
+        Array of y-coordinates that divide the bitmap horizontally.
+        Array entries must be unique, increasing, greater than or equal to fBounds top edge, 
+        and less than fBounds bottom edge.
+        Set the first element to fBounds top to collapse the top row of fixed grid entries.
+    ##
+
+    #Member const Flags*  fFlags
+        Optional array of Flags, one per rectangular grid entry:
+        array length must be (fXCount + 1) * (fYCount + 1).
+        Array entries correspond to the rectangular grid entries, ascending
+        left to right and then top to bottom.
+    ##
+
+    #Member int   fXCount
+        Number of entries in fXDivs array; one less than the number of horizontal divisions.
+    ##
+
+    #Member int   fYCount
+        Number of entries in fYDivs array; one less than the number of vertical divisions.
+    ##
+
+    #Member const SkIRect*   fBounds
+       Optional subset IRect source to draw from.
+       If nullptr, source bounds is dimensions of Bitmap or Image.
+    ##
+
+#Struct Lattice ##
+
+#Method void drawBitmapLattice(const SkBitmap& bitmap, const Lattice& lattice, const SkRect& dst,
+                           const SkPaint* paint = nullptr)
+
+Draw Bitmap bitmap stretched differentially to fit into Rect dst.
+
+Lattice lattice divides bitmap into a rectangular grid.
+Each intersection of an even-numbered row and column is fixed; like the corners
+of drawBitmapNine, fixed lattice elements never scale larger than their initial size
+and shrink proportionately when all fixed elements exceed the bitmap's dimension.
+All other grid elements scale to fill the available space, if any.
+
+Additionally transform draw using Clip, Matrix, and optional Paint paint.
+If Paint paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If bitmap is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from bitmap bounds. If generated mask extends
+beyond bitmap bounds, replicate bitmap edge colors, just as Shader made from 
+SkShader::MakeBitmapShader with SkShader::kClamp_TileMode set replicates the bitmap's edge
+color when it samples outside of its bounds. 
+
+#Param  bitmap     Bitmap containing pixels, dimensions, and format. ##
+#Param  lattice    Division of bitmap into fixed and variable rectangles. ##
+#Param  dst        Destination Rect of image to draw to. ##
+#Param  paint      Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+
+#Example
+#Height 128
+#Description
+    The two leftmost bitmap draws has four corners and sides to the left and right of center.
+    The leftmost bitmap draw scales the width of corners proportionately to fit.
+    The third and fourth draw corners are unscaled; the sides are scaled to 
+    fill the remaining space; the center is transparent.
+    The rightmost bitmap draw has four corners scaled vertically to fit, and uses sides above
+    and below center to fill the remaining space.
+##
+void draw(SkCanvas* canvas) {

+    SkIRect center = { 20, 10, 50, 40 };

+    SkBitmap bitmap;

+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));

+    SkCanvas bitCanvas(bitmap);

+    SkPaint paint;

+    SkColor gray = 0xFF000000;

+    int left = 0;

+    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {

+        int top = 0;

+        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {

+            paint.setColor(gray);

+            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);

+            gray += 0x001f1f1f;

+            top = bottom;

+        }

+        left = right; 

+    }

+    const int xDivs[] = { center.fLeft, center.fRight };

+    const int yDivs[] = { center.fTop, center.fBottom };

+    SkCanvas::Lattice::Flags flags[3][3];

+    memset(flags, 0, sizeof(flags));  

+    flags[1][1] = SkCanvas::Lattice::kTransparent_Flags;

+    SkCanvas::Lattice lattice = { xDivs, yDivs, flags[0], SK_ARRAY_COUNT(xDivs),

+         SK_ARRAY_COUNT(yDivs), nullptr };

+    for (auto dest: { 20, 30, 40, 60, 90 } ) {

+        canvas->drawBitmapLattice(bitmap, lattice , SkRect::MakeWH(dest, 110 - dest), nullptr);

+        canvas->translate(dest + 4, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawImageLattice(const SkImage* image, const Lattice& lattice, const SkRect& dst,
+                          const SkPaint* paint = nullptr)
+
+Draw Image image stretched differentially to fit into Rect dst.
+
+Lattice lattice divides image into a rectangular grid.
+Each intersection of an even-numbered row and column is fixed; like the corners
+of drawImageNine, fixed lattice elements never scale larger than their initial size
+and shrink proportionately when all fixed elements exceed the bitmap's dimension.
+All other grid elements scale to fill the available space, if any.
+
+Additionally transform draw using Clip, Matrix, and optional Paint paint.
+If Paint paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter, Blend_Mode, and Draw_Looper.
+If image is kAlpha_8_SkColorType, apply Shader.
+if paint contains Mask_Filter, generate mask from image bounds. If generated mask extends
+beyond image bounds, replicate image edge colors, just as Shader made from 
+SkShader::MakeBitmapShader with SkShader::kClamp_TileMode set replicates the image's edge
+color when it samples outside of its bounds. 
+
+#Param  image      Image containing pixels, dimensions, and format. ##
+#Param  lattice    Division of bitmap into fixed and variable rectangles. ##
+#Param  dst        Destination Rect of image to draw to. ##
+#Param  paint      Paint containing Blend_Mode, Color_Filter, Image_Filter, and so on; or nullptr. ##
+
+#Example
+#Height 128
+#Description
+    The leftmost image is smaller than center; only corners are drawn, all scaled to fit.
+    The second image equals the size of center; only corners are drawn, unscaled.
+    The remaining images are larger than center. All corners draw unscaled. The sides
+    are scaled if needed to take up the remaining space; the center is transparent.
+##
+void draw(SkCanvas* canvas) {

+    SkIRect center = { 20, 10, 50, 40 };

+    SkBitmap bitmap;

+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));

+    SkCanvas bitCanvas(bitmap);

+    SkPaint paint;

+    SkColor gray = 0xFF000000;

+    int left = 0;

+    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {

+        int top = 0;

+        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {

+            paint.setColor(gray);

+            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);

+            gray += 0x001f1f1f;

+            top = bottom;

+        }

+        left = right; 

+    }

+    const int xDivs[] = { center.fLeft, center.fRight };

+    const int yDivs[] = { center.fTop, center.fBottom };

+    SkCanvas::Lattice::Flags flags[3][3];

+    memset(flags, 0, sizeof(flags));  

+    flags[1][1] = SkCanvas::Lattice::kTransparent_Flags;

+    SkCanvas::Lattice lattice = { xDivs, yDivs, flags[0], SK_ARRAY_COUNT(xDivs),

+         SK_ARRAY_COUNT(yDivs), nullptr };

+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);

+    SkImage* imagePtr = image.get();

+    for (auto dest: { 20, 30, 40, 60, 90 } ) {

+        canvas->drawImageNine(imagePtr, center, SkRect::MakeWH(dest, dest), nullptr);

+        canvas->translate(dest + 4, 0);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+#Topic Draw_Image ##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
+                  const SkPaint& paint)
+
+Draw text, with origin at (x, y), using Clip, Matrix, and Paint paint.
+text's meaning depends on Paint_Text_Encoding; by default, text encoding is UTF-8.
+x and y meaning depends on Paint_Text_Align and Paint_Vertical_Text; by default text
+draws left to right, positioning the first glyph's left side bearing at x and its
+baseline at y. Text size is affected by Matrix and Paint_Text_Size. 
+
+All elements of paint: Path_Effect, Rasterizer, Mask_Filter, Shader, Color_Filter, 
+Image_Filter, and Draw_Looper; apply to text. By default, drawText draws filled 12 point black
+glyphs.
+
+#Param  text     Character code points or glyphs drawn. ##
+#Param  byteLength   Byte length of text array. ##
+#Param  x        Start of text on x-axis. ##
+#Param  y        Start of text on y-axis. ##
+#Param  paint    Text size, blend, color, and so on, used to draw. ##
+
+#Example
+#Height 200
+#Description
+    The same text is drawn varying Paint_Text_Size and varying
+    Matrix. 
+##
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    float textSizes[] = { 12, 18, 24, 36 };

+    for (auto size: textSizes ) {

+        paint.setTextSize(size);

+        canvas->drawText("Aa", 2, 10, 20, paint);

+        canvas->translate(0, size * 2);

+    }

+    paint.reset();

+    paint.setAntiAlias(true);

+    float yPos = 20;

+    for (auto size: textSizes ) {

+        float scale = size / 12.f;

+        canvas->resetMatrix();

+        canvas->translate(100, 0);

+        canvas->scale(scale, scale);

+        canvas->drawText("Aa", 2, 10 / scale, yPos / scale, paint);

+        yPos += size * 2; 

+    }

+}

+##
+
+#ToDo incomplete ##
+
+##
+
+#Method void drawString(const char* string, SkScalar x, SkScalar y, const SkPaint& paint)
+
+Draw null terminated string, with origin at (x, y), using Clip, Matrix, and Paint paint.
+string's meaning depends on Paint_Text_Encoding; by default, string encoding is UTF-8.
+Other values of Paint_Text_Encoding are unlikely to produce the desired results, since
+zero bytes may be embedded in the string.
+x and y meaning depends on Paint_Text_Align and Paint_Vertical_Text; by default string
+draws left to right, positioning the first glyph's left side bearing at x and its
+baseline at y. Text size is affected by Matrix and Paint_Text_Size. 
+
+All elements of paint: Path_Effect, Rasterizer, Mask_Filter, Shader, Color_Filter, 
+Image_Filter, and Draw_Looper; apply to string. By default, drawString draws filled 12 point black
+glyphs.
+
+#Param  string   Character code points or glyphs drawn, ending with a char value of zero. ##
+#Param  x        Start of string on x-axis. ##
+#Param  y        Start of string on y-axis. ##
+#Param  paint    Text size, blend, color, and so on, used to draw. ##
+
+#Example
+   SkPaint paint;
+   canvas->drawString("a small hello", 20, 20, paint);
+##
+
+#SeeAlso drawText
+
+##
+
+#Method void drawString(const SkString& string, SkScalar x, SkScalar y, const SkPaint& paint)
+
+Draw null terminated string, with origin at (x, y), using Clip, Matrix, and Paint paint.
+string's meaning depends on Paint_Text_Encoding; by default, string encoding is UTF-8.
+Other values of Paint_Text_Encoding are unlikely to produce the desired results, since
+zero bytes may be embedded in the string.
+x and y meaning depends on Paint_Text_Align and Paint_Vertical_Text; by default string
+draws left to right, positioning the first glyph's left side bearing at x and its
+baseline at y. Text size is affected by Matrix and Paint_Text_Size. 
+
+All elements of paint: Path_Effect, Rasterizer, Mask_Filter, Shader, Color_Filter, 
+Image_Filter, and Draw_Looper; apply to string. By default, drawString draws filled 12 point black
+glyphs.
+
+#Param  string   Character code points or glyphs drawn, ending with a char value of zero. ##
+#Param  x        Start of string on x-axis. ##
+#Param  y        Start of string on y-axis. ##
+#Param  paint    Text size, blend, color, and so on, used to draw. ##
+
+#Example
+   SkPaint paint;
+   SkString string("a small hello");
+   canvas->drawString(string, 20, 20, paint);
+##
+
+#SeeAlso drawText
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawPosText(const void* text, size_t byteLength, const SkPoint pos[],
+                     const SkPaint& paint)
+
+Draw each glyph in text with the origin in pos array, using Clip, Matrix, and Paint paint.
+The number of entries in pos array must match the number of glyphs described by byteLength of text.
+text's meaning depends on Paint_Text_Encoding; by default, text encoding is UTF-8.
+pos elements' meaning depends on Paint_Text_Align and Paint_Vertical_Text; by default each
+glyph's left side bearing is positioned at x and its
+baseline is positioned at y. Text size is affected by Matrix and Paint_Text_Size. 
+
+All elements of paint: Path_Effect, Rasterizer, Mask_Filter, Shader, Color_Filter, 
+Image_Filter, and Draw_Looper; apply to text. By default, drawPosText draws filled 12 point black
+glyphs.
+
+Layout engines such as Harfbuzz typically use drawPosText to position each glyph
+rather than using the font's advance widths.
+
+#Param  text     Character code points or glyphs drawn. ##
+#Param  byteLength   Byte length of text array. ##
+#Param  pos      Array of glyph origins. ##
+#Param  paint    Text size, blend, color, and so on, used to draw. ##
+
+#Example
+#Height 120
+void draw(SkCanvas* canvas) {

+  const char hello[] = "HeLLo!";

+  const SkPoint pos[] = { {40, 100}, {82, 95}, {115, 110}, {130, 95}, {145, 85},

+    {172, 100} };

+  SkPaint paint;

+  paint.setTextSize(60);

+  canvas->drawPosText(hello, strlen(hello), pos, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], SkScalar constY,
+                      const SkPaint& paint)
+
+Draw each glyph in text with its (x, y) origin composed from xpos array and constY, using Clip, Matrix, and Paint paint.
+The number of entries in xpos array must match the number of glyphs described by byteLength of text.
+text's meaning depends on Paint_Text_Encoding; by default, text encoding is UTF-8.
+pos elements' meaning depends on Paint_Text_Align and Paint_Vertical_Text; by default each
+glyph's left side bearing is positioned at an xpos element and its
+baseline is positioned at constY. Text size is affected by Matrix and Paint_Text_Size. 
+
+All elements of paint: Path_Effect, Rasterizer, Mask_Filter, Shader, Color_Filter, 
+Image_Filter, and Draw_Looper; apply to text. By default, drawPosTextH draws filled 12 point black
+glyphs.
+
+Layout engines such as Harfbuzz typically use drawPosTextH to position each glyph
+rather than using the font's advance widths if all glyphs share the same baseline.
+
+#Param  text     Character code points or glyphs drawn. ##
+#Param  byteLength   Byte length of text array. ##
+#Param  xpos     Array of x positions, used to position each glyph. ##
+#Param  constY   Shared y coordinate for all of x positions. ##
+#Param  paint    Text size, blend, color, and so on, used to draw. ##
+
+#Example
+#Height 40
+    void draw(SkCanvas* canvas) {

+        SkScalar xpos[] = { 20, 40, 80, 160 };

+        SkPaint paint;

+        canvas->drawPosTextH("XXXX", 4, xpos, 20, paint);

+    }

+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawTextOnPathHV(const void* text, size_t byteLength, const SkPath& path, SkScalar hOffset,
+                          SkScalar vOffset, const SkPaint& paint)
+
+Draw text on Path path, using Clip, Matrix, and Paint paint.
+Origin of text is at distance hOffset along the path, offset by a perpendicular vector of
+length vOffset. If the path section corresponding the glyph advance is curved, the glyph
+is drawn curved to match; control points in the glyph are mapped to projected points parallel
+to the path. If the text's advance is larger than the path length, the excess text is clipped.
+
+text's meaning depends on Paint_Text_Encoding; by default, text encoding is UTF-8.
+Origin meaning depends on Paint_Text_Align and Paint_Vertical_Text; by default text
+positions the first glyph's left side bearing at origin x and its
+baseline at origin y. Text size is affected by Matrix and Paint_Text_Size. 
+
+All elements of paint: Path_Effect, Rasterizer, Mask_Filter, Shader, Color_Filter, 
+Image_Filter, and Draw_Looper; apply to text. By default, drawTextOnPathHV draws filled 12 point black
+glyphs.
+
+#Param  text         Character code points or glyphs drawn. ##
+#Param  byteLength   Byte length of text array. ##
+#Param  path         Path providing text baseline. ##
+#Param  hOffset      Distance along path to offset origin. ##
+#Param  vOffset      Offset of text above (if negative) or below (if positive) the path. ##
+#Param  paint        Text size, blend, color, and so on, used to draw. ##
+
+#Example
+    void draw(SkCanvas* canvas) { 

+        const char aero[] = "correo a" "\xC3" "\xA9" "reo";

+        const size_t len = sizeof(aero) - 1;

+        SkPath path;

+        path.addOval({43-26, 43-26, 43+26, 43+26}, SkPath::kCW_Direction, 3);

+        SkPaint paint;

+        paint.setTextSize(24);

+        for (auto offset : { 0, 10, 20 } ) {

+            canvas->drawTextOnPathHV(aero, len, path, 0, -offset, paint);

+            canvas->translate(70 + offset, 70 + offset);

+        }

+    }

+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawTextOnPath(const void* text, size_t byteLength, const SkPath& path,
+                        const SkMatrix* matrix, const SkPaint& paint)
+
+Draw text on Path path, using Clip, Matrix, and Paint paint.
+Origin of text is at beginning of path offset by matrix, if provided, before it is mapped to path.
+If the path section corresponding the glyph advance is curved, the glyph
+is drawn curved to match; control points in the glyph are mapped to projected points parallel
+to the path. If the text's advance is larger than the path length, the excess text is clipped.
+
+text's meaning depends on Paint_Text_Encoding; by default, text encoding is UTF-8.
+Origin meaning depends on Paint_Text_Align and Paint_Vertical_Text; by default text
+positions the first glyph's left side bearing at origin x and its
+baseline at origin y. Text size is affected by Matrix and Paint_Text_Size. 
+
+All elements of paint: Path_Effect, Rasterizer, Mask_Filter, Shader, Color_Filter, 
+Image_Filter, and Draw_Looper; apply to text. By default, drawTextOnPath draws filled 12 point black
+glyphs.
+
+#Param  text         Character code points or glyphs drawn. ##
+#Param  byteLength   Byte length of text array. ##
+#Param  path         Path providing text baseline. ##
+#Param  matrix       Optional transform of glyphs before mapping to path; or nullptr. ##
+#Param  paint        Text size, blend, color, and so on, used to draw. ##
+
+#Example
+    void draw(SkCanvas* canvas) { 

+        const char roller[] = "rollercoaster";

+        const size_t len = sizeof(roller) - 1;

+        SkPath path;

+        path.cubicTo(40, -80, 120, 80, 160, -40);

+        SkPaint paint;

+        paint.setTextSize(32);

+        paint.setStyle(SkPaint::kStroke_Style);

+        SkMatrix matrix;

+        matrix.setIdentity();

+        for (int i = 0; i < 3; ++i) {

+            canvas->translate(25, 60);

+            canvas->drawPath(path, paint);

+            canvas->drawTextOnPath(roller, len, path, &matrix, paint);

+            matrix.preTranslate(0, 10);

+        }

+    }

+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
+                         const SkRect* cullRect, const SkPaint& paint)
+
+Draw text, transforming each glyph by the corresponding SkRSXform,
+using Clip, Matrix, and Paint paint.
+RSXform array specifies a separate square scale, rotation, and translation for 
+each glyph.
+Optional Rect cullRect is a conservative bounds of text,
+taking into account RSXform and paint. If cullrect is outside of Clip, canvas can
+skip drawing.
+
+All elements of paint: Path_Effect, Rasterizer, Mask_Filter, Shader, Color_Filter, 
+Image_Filter, and Draw_Looper; apply to text. By default, drawTextRSXform draws filled 12 point black
+glyphs.
+
+#Param  text         Character code points or glyphs drawn. ##
+#Param  byteLength   Byte length of text array. ##
+#Param  xform        RSXform rotates, scales, and translates each glyph individually. ##
+#Param  cullRect     Rect bounds of text for efficient clipping; or nullptr. ##
+#Param  paint        Text size, blend, color, and so on, used to draw. ##
+
+#Example
+void draw(SkCanvas* canvas) {  

+    const int iterations = 26;

+    SkRSXform transforms[iterations];

+    char alphabet[iterations];

+    SkScalar angle = 0;

+    SkScalar scale = 1;

+    for (size_t i = 0; i < SK_ARRAY_COUNT(transforms); ++i) {

+        const SkScalar s = SkScalarSin(angle) * scale;

+        const SkScalar c = SkScalarCos(angle) * scale;

+        transforms[i] = SkRSXform::Make(-c, -s, -s * 16, c * 16);

+        angle += .45;

+        scale += .2;

+        alphabet[i] = 'A' + i;

+    }

+    SkPaint paint;

+    paint.setTextAlign(SkPaint::kCenter_Align);

+    canvas->translate(110, 138);

+    canvas->drawTextRSXform(alphabet, sizeof(alphabet), transforms, nullptr, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint)
+
+Draw Text_Blob blob at (x, y), using Clip, Matrix, and Paint paint.
+blob contains glyphs, their positions, and paint attributes specific to text:
+Typeface, Paint_Text_Size, Paint_Text_Scale_X, Paint_Text_Skew_X, Paint_Text_Align,
+Paint_Hinting, Anti-alias, Paint_Fake_Bold, Font_Embedded_Bitmaps, Full_Hinting_Spacing, 
+LCD_Text, Linear_Text, Subpixel_Text, and Paint_Vertical_Text.
+
+Elements of paint: Path_Effect, Rasterizer, Mask_Filter, Shader, Color_Filter, 
+Image_Filter, and Draw_Looper; apply to blob.
+
+#Param  blob     Glyphs, positions, and their paints' text size, typeface, and so on. ##
+#Param  x        Horizontal offset applied to blob. ##
+#Param  y        Vertical offset applied to blob. ##
+#Param  paint    Blend, color, stroking, and so on, used to draw. ##
+
+#Example
+#Height 120
+    void draw(SkCanvas* canvas) {

+        SkTextBlobBuilder textBlobBuilder;

+        const char bunny[] = "/(^x^)\\";

+        const int len = sizeof(bunny) - 1;

+        uint16_t glyphs[len];

+        SkPaint paint;

+        paint.textToGlyphs(bunny, len, glyphs);

+        int runs[] = { 3, 1, 3 };

+        SkPoint textPos = { 20, 100 };

+        int glyphIndex = 0;

+        for (auto runLen : runs) {

+            paint.setTextSize(1 == runLen ? 20 : 50);

+            const SkTextBlobBuilder::RunBuffer& run = 

+                    textBlobBuilder.allocRun(paint, runLen, textPos.fX, textPos.fY);

+            memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);

+            textPos.fX += paint.measureText(&bunny[glyphIndex], runLen, nullptr);

+            glyphIndex += runLen;

+        }

+        sk_sp<const SkTextBlob> blob = textBlobBuilder.make();

+        paint.reset();

+        canvas->drawTextBlob(blob.get(), 0, 0, paint);

+    }
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawTextBlob(const sk_sp<SkTextBlob>& blob, SkScalar x, SkScalar y, const SkPaint& paint) 
+
+Draw Text_Blob blob at (x, y), using Clip, Matrix, and Paint paint.
+blob contains glyphs, their positions, and paint attributes specific to text:
+Typeface, Paint_Text_Size, Paint_Text_Scale_X, Paint_Text_Skew_X, Paint_Text_Align,
+Paint_Hinting, Anti-alias, Paint_Fake_Bold, Font_Embedded_Bitmaps, Full_Hinting_Spacing, 
+LCD_Text, Linear_Text, Subpixel_Text, and Paint_Vertical_Text.
+
+Elements of paint: Path_Effect, Rasterizer, Mask_Filter, Shader, Color_Filter, 
+Image_Filter, and Draw_Looper; apply to blob.
+
+#Param  blob     Glyphs, positions, and their paints' text size, typeface, and so on. ##
+#Param  x        Horizontal offset applied to blob. ##
+#Param  y        Vertical offset applied to blob. ##
+#Param  paint    Blend, color, stroking, and so on, used to draw. ##
+
+#Example
+#Height 120
+#Description
+Paint attributes unrelated to text, like color, have no effect on paint in allocated Text_Blob.
+Paint attributes related to text, like text size, have no effect on paint passed to drawTextBlob.
+##
+    void draw(SkCanvas* canvas) {

+        SkTextBlobBuilder textBlobBuilder;

+        SkPaint paint;

+        paint.setTextSize(50);

+        paint.setColor(SK_ColorRED);

+        const SkTextBlobBuilder::RunBuffer& run = 

+                textBlobBuilder.allocRun(paint, 1, 20, 100);

+        run.glyphs[0] = 20;

+        sk_sp<const SkTextBlob> blob = textBlobBuilder.make();

+        paint.setTextSize(10);

+        paint.setColor(SK_ColorBLUE);

+        canvas->drawTextBlob(blob.get(), 0, 0, paint);

+    }

+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawPicture(const SkPicture* picture) 
+
+Draw Picture picture, using Clip and Matrix.
+Clip and Matrix are unchanged by picture contents, as if
+save() was called before and restore() was called after drawPicture.
+
+Picture records a series of draw commands for later playback.
+
+#Param  picture  Recorded drawing commands to play. ##
+
+#Example
+void draw(SkCanvas* canvas) {  

+    SkPictureRecorder recorder;

+    SkCanvas* recordingCanvas = recorder.beginRecording(50, 50);

+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xff007f00 } ) {

+        SkPaint paint;

+        paint.setColor(color);

+        recordingCanvas->drawRect({10, 10, 30, 40}, paint);

+        recordingCanvas->translate(10, 10);

+        recordingCanvas->scale(1.2f, 1.4f);

+    }

+    sk_sp<SkPicture> playback = recorder.finishRecordingAsPicture();

+    const SkPicture* playbackPtr = playback.get();

+    canvas->drawPicture(playback);

+    canvas->scale(2, 2);

+    canvas->translate(50, 0);

+    canvas->drawPicture(playback);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawPicture(const sk_sp<SkPicture>& picture) 
+
+Draw Picture picture, using Clip and Matrix.
+Clip and Matrix are unchanged by picture contents, as if
+save() was called before and restore() was called after drawPicture.
+
+Picture records a series of draw commands for later playback.
+
+#Param  picture  Recorded drawing commands to play. ##
+
+#Example
+void draw(SkCanvas* canvas) {  

+    SkPictureRecorder recorder;

+    SkCanvas* recordingCanvas = recorder.beginRecording(50, 50);

+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xff007f00 } ) {

+        SkPaint paint;

+        paint.setColor(color);

+        recordingCanvas->drawRect({10, 10, 30, 40}, paint);

+        recordingCanvas->translate(10, 10);

+        recordingCanvas->scale(1.2f, 1.4f);

+    }

+    sk_sp<SkPicture> playback = recorder.finishRecordingAsPicture();

+    canvas->drawPicture(playback);

+    canvas->scale(2, 2);

+    canvas->translate(50, 0);

+    canvas->drawPicture(playback);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint)
+
+Draw Picture picture, using Clip and Matrix;
+transforming picture with Matrix matrix, if provided;
+and use Paint paint Color_Alpha, Color_Filter, Image_Filter, and Blend_Mode, if provided.
+
+matrix transformation is equivalent to: save(), concat(), drawPicture, restore().
+paint use is equivalent to: saveLayer, drawPicture, restore().
+
+#Param  picture  Recorded drawing commands to play. ##
+#Param  matrix   Optional Matrix to rotate, scale, translate, and so on; or nullptr. ##
+#Param  paint    Optional Paint to apply transparency, filtering, and so on; or nullptr. ##
+
+#Example
+void draw(SkCanvas* canvas) {  

+    SkPaint paint;

+    SkPictureRecorder recorder;

+    SkCanvas* recordingCanvas = recorder.beginRecording(50, 50);

+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xff007f00 } ) {

+        paint.setColor(color);

+        recordingCanvas->drawRect({10, 10, 30, 40}, paint);

+        recordingCanvas->translate(10, 10);

+        recordingCanvas->scale(1.2f, 1.4f);

+    }

+    sk_sp<SkPicture> playback = recorder.finishRecordingAsPicture();

+    const SkPicture* playbackPtr = playback.get();

+    SkMatrix matrix;

+    matrix.reset();

+    for (auto alpha : { 70, 140, 210 } ) {

+    paint.setAlpha(alpha);

+    canvas->drawPicture(playbackPtr, &matrix, &paint);

+    matrix.preTranslate(70, 70);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawPicture(const sk_sp<SkPicture>& picture, const SkMatrix* matrix, const SkPaint* paint) 
+
+Draw Picture picture, using Clip and Matrix;
+transforming picture with Matrix matrix, if provided;
+and use Paint paint Color_Alpha, Color_Filter, Image_Filter, and Blend_Mode, if provided.
+
+matrix transformation is equivalent to: save(), concat(), drawPicture, restore().
+paint use is equivalent to: saveLayer, drawPicture, restore().
+
+#Param  picture  Recorded drawing commands to play. ##
+#Param  matrix   Optional Matrix to rotate, scale, translate, and so on; or nullptr. ##
+#Param  paint    Optional Paint to apply transparency, filtering, and so on; or nullptr. ##
+
+#Example
+void draw(SkCanvas* canvas) {  

+    SkPaint paint;

+    SkPictureRecorder recorder;

+    SkCanvas* recordingCanvas = recorder.beginRecording(50, 50);

+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xff007f00 } ) {

+        paint.setColor(color);

+        recordingCanvas->drawRect({10, 10, 30, 40}, paint);

+        recordingCanvas->translate(10, 10);

+        recordingCanvas->scale(1.2f, 1.4f);

+    }

+    sk_sp<SkPicture> playback = recorder.finishRecordingAsPicture();

+    SkMatrix matrix;

+    matrix.reset();

+    for (auto alpha : { 70, 140, 210 } ) {

+    paint.setAlpha(alpha);

+    canvas->drawPicture(playback, &matrix, &paint);

+    matrix.preTranslate(70, 70);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint)
+
+Draw Vertices vertices, a triangle mesh, using Clip and Matrix.
+If Vertices_Texs and Vertices_Colors are defined in vertices, and Paint paint contains Shader,
+Blend_Mode mode combines Vertices_Colors with Shader.
+
+#Param  vertices  The triangle mesh to draw. ##
+#Param  mode      Combines Vertices_Colors with Shader, if both are present. ##
+#Param  paint     Specifies the Shader, used as Vertices texture, if present. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    SkPoint points[] = { { 0, 0 }, { 250, 0 }, { 100, 100 }, { 0, 250 } };

+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorCYAN };

+    auto vertices = SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode,

+            SK_ARRAY_COUNT(points), points, nullptr, colors);

+    canvas->drawVertices(vertices.get(), SkBlendMode::kSrc, paint);

+}

+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawVertices(const sk_sp<SkVertices>& vertices, SkBlendMode mode, const SkPaint& paint)
+
+Draw Vertices vertices, a triangle mesh, using Clip and Matrix.
+If Vertices_Texs and Vertices_Colors are defined in vertices, and Paint paint contains Shader,
+Blend_Mode mode combines Vertices_Colors with Shader.
+
+#Param  vertices  The triangle mesh to draw. ##
+#Param  mode      Combines Vertices_Colors with Shader, if both are present. ##
+#Param  paint     Specifies the Shader, used as Vertices texture, if present. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    SkPoint points[] = { { 0, 0 }, { 250, 0 }, { 100, 100 }, { 0, 250 } };

+    SkPoint texs[] = { { 0, 0 }, { 0, 250 }, { 250, 250 }, { 250, 0 } };

+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorCYAN };

+    paint.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 4,

+            SkShader::kClamp_TileMode));

+    auto vertices = SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode,

+            SK_ARRAY_COUNT(points), points, texs, colors);

+    canvas->drawVertices(vertices.get(), SkBlendMode::kDarken, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawPatch(const SkPoint cubics[12], const SkColor colors[4],
+                   const SkPoint texCoords[4], SkBlendMode mode, const SkPaint& paint)
+
+Draw a cubic Coons patch: the interpolation of four cubics with shared corners, 
+associating a color, and optionally a texture coordinate, with each corner.
+
+#ToDo can patch use image filter? ##
+
+The Coons patch uses Clip and Matrix, Paint paint's Shader, Color_Filter, Color_Alpha,
+Image_Filter, and Blend_Mode. If Shader is provided it is treated as the Coons
+patch texture; Blend_Mode mode combines Color colors and Shader if both are provided.
+
+#Param cubics     Point array cubics specifying the four cubics starting at the top left corner, 
+in clockwise order, sharing every fourth point. The last cubic ends at the first point. ##
+#Param colors     Color array color associating colors with corners in top left, top right, bottom right,
+bottom left order. ##
+#Param texCoords  Point array texCoords mapping Shader as texture to corners in same order, if paint
+contains Shader; or nullptr. ##
+#Param mode       Blend_Mode for colors and Shader if present. ##
+#Param paint      Shader, Color_Filter, Blend_Mode, used to draw. ##
+
+#Example
+#Image 5
+void draw(SkCanvas* canvas) {

+    // SkBitmap source = cmbkygk;

+    SkPaint paint;

+    paint.setFilterQuality(kLow_SkFilterQuality);

+    paint.setAntiAlias(true);

+    SkPoint cubics[] = { { 3, 1 },    { 4, 2 }, { 5, 1 },    { 7, 3 },

+                      /* { 7, 3 }, */ { 6, 4 }, { 7, 5 },    { 5, 7 },

+                      /* { 5, 7 }, */ { 4, 6 }, { 3, 7 },    { 1, 5 },

+                      /* { 1, 5 }, */ { 2, 4 }, { 1, 3 }, /* { 3, 1 } */ };

+    SkColor colors[] = { 0xbfff0000, 0xbf0000ff, 0xbfff00ff, 0xbf00ffff };

+    SkPoint texCoords[] = { { -30, -30 }, { 162, -30}, { 162, 162}, { -30, 162} };

+    paint.setShader(SkShader::MakeBitmapShader(source, SkShader::kClamp_TileMode,

+                                                       SkShader::kClamp_TileMode, nullptr));

+    canvas->scale(15, 15);

+    for (auto blend : { SkBlendMode::kSrcOver, SkBlendMode::kModulate, SkBlendMode::kXor } ) {

+        canvas->drawPatch(cubics, colors, texCoords, blend, paint);

+        canvas->translate(4, 4);

+    }

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawPatch(const SkPoint cubics[12], const SkColor colors[4],
+                   const SkPoint texCoords[4], const SkPaint& paint) 
+
+Draw a cubic Coons patch: the interpolation of four cubics with shared corners, 
+associating a color, a texture coordinate, or both, with each corner.
+
+The Coons patch uses Clip and Matrix, Paint paint's Shader, Color_Filter, Color_Alpha,
+Image_Filter, (?) and Blend_Mode. If Shader is provided it is treated as the Coons
+patch texture.
+
+#Param cubics     Point array cubics specifying the four cubics starting at the top left corner, 
+in clockwise order, sharing every fourth point. The last cubic ends at the first point. ##
+#Param colors     Color array color associating colors with corners in top left, top right, bottom right,
+bottom left order; or nullptr. ##
+#Param texCoords  Point array texCoords mapping Shader as texture to corners in same order, if paint
+contains Shader; or nullptr. ##
+#Param paint      Shader, Color_Filter, Blend_Mode, used to draw. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    SkPoint cubics[] = { { 3, 1 },    { 4, 2 }, { 5, 1 },    { 7, 3 },

+                      /* { 7, 3 }, */ { 6, 4 }, { 7, 5 },    { 5, 7 },

+                      /* { 5, 7 }, */ { 4, 6 }, { 3, 7 },    { 1, 5 },

+                      /* { 1, 5 }, */ { 2, 4 }, { 1, 3 }, /* { 3, 1 } */ };

+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorCYAN };

+    canvas->scale(30, 30);

+    canvas->drawPatch(cubics, colors, nullptr, paint);

+    SkPoint text[] = { {3,0.9f}, {4,2.5f}, {5,0.9f}, {7.5f,3.2f}, {5.5f,4.2f},

+            {7.5f,5.2f}, {5,7.5f}, {4,5.9f}, {3,7.5f}, {0.5f,5.2f}, {2.5f,4.2f},

+            {0.5f,3.2f} };

+    paint.setTextSize(18.f / 30);

+    paint.setTextAlign(SkPaint::kCenter_Align);

+    for (int i = 0; i< 10; ++i) {

+       char digit = '0' + i;

+       canvas->drawText(&digit, 1, text[i].fX, text[i].fY, paint);

+    }

+    canvas->drawString("10", text[10].fX, text[10].fY, paint);

+    canvas->drawString("11", text[11].fX, text[11].fY, paint);

+    paint.setStyle(SkPaint::kStroke_Style);

+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, 12, cubics, paint);

+    canvas->drawLine(cubics[11].fX, cubics[11].fY, cubics[0].fX, cubics[0].fY, paint);

+}
+##
+
+#Example
+#Image 6
+void draw(SkCanvas* canvas) {

+    // SkBitmap source = checkerboard;

+    SkPaint paint;

+    paint.setFilterQuality(kLow_SkFilterQuality);

+    paint.setAntiAlias(true);

+    SkPoint cubics[] = { { 3, 1 },    { 4, 2 }, { 5, 1 },    { 7, 3 },

+                      /* { 7, 3 }, */ { 6, 4 }, { 7, 5 },    { 5, 7 },

+                      /* { 5, 7 }, */ { 4, 6 }, { 3, 7 },    { 1, 5 },

+                      /* { 1, 5 }, */ { 2, 4 }, { 1, 3 }, /* { 3, 1 } */ };

+    SkPoint texCoords[] = { { 0, 0 }, { 0, 62}, { 62, 62}, { 62, 0 } };

+    paint.setShader(SkShader::MakeBitmapShader(source, SkShader::kClamp_TileMode,

+                                                       SkShader::kClamp_TileMode, nullptr));

+    canvas->scale(30, 30);

+    canvas->drawPatch(cubics, nullptr, texCoords, paint);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[],
+                   const SkColor colors[], int count, SkBlendMode mode, const SkRect* cullRect,
+                   const SkPaint* paint)
+
+Draw a set of sprites from atlas, using Clip, Matrix, and optional Paint paint.
+paint uses Anti-alias, Color_Alpha, Color_Filter, Image_Filter, and Blend_Mode to draw, if present.
+For each entry in the array, Rect tex locates sprite in atlas, and RSXform xform transforms it
+into destination space.
+xform, text, and colors if present, must contain count entries.
+Optional colors is applied for each sprite using Blend_Mode.
+Optional cullRect is a conservative bounds of all transformed sprites. 
+If cullrect is outside of Clip, canvas can skip drawing.
+
+#Param atlas  Image containing sprites. ##
+#Param xform  RSXform mappings for sprites in atlas. ##
+#Param tex    Rect locations of sprites in atlas. ##
+#Param colors  Color, one per sprite, blended with sprite using Blend_Mode; or nullptr. ##
+#Param count  Number of sprites to draw. ##
+#Param mode   Blend_Mode combining colors and sprites. ##
+#Param cullRect  Rect bounds of transformed sprites for efficient clipping; or nullptr. ##
+#Param paint  Paint Color_Filter, Image_Filter, Blend_Mode, and so on; or nullptr. ##
+
+#Example
+#Image 3
+void draw(SkCanvas* canvas) {

+  // SkBitmap source = mandrill;

+  SkRSXform xforms[] = { { .5f, 0, 0, 0 }, {0, .5f, 200, 100 } };

+  SkRect tex[] = { { 0, 0, 250, 250 }, { 0, 0, 250, 250 } };

+  SkColor colors[] = { 0x7f55aa00, 0x7f3333bf };

+  const SkImage* imagePtr = image.get();

+  canvas->drawAtlas(imagePtr, xforms, tex, colors, 2, SkBlendMode::kSrcOver, nullptr, nullptr);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawAtlas(const sk_sp<SkImage>& atlas, const SkRSXform xform[], const SkRect tex[],
+                   const SkColor colors[], int count, SkBlendMode mode, const SkRect* cullRect,
+                   const SkPaint* paint) 
+
+Draw a set of sprites from atlas, using Clip, Matrix, and optional Paint paint.
+paint uses Anti-alias, Color_Alpha, Color_Filter, Image_Filter, and Blend_Mode to draw, if present.
+For each entry in the array, Rect tex locates sprite in atlas, and RSXform xform transforms it
+into destination space.
+xform, text, and colors if present, must contain count entries.
+Optional colors is applied for each sprite using Blend_Mode.
+Optional cullRect is a conservative bounds of all transformed sprites. 
+If cullrect is outside of Clip, canvas can skip drawing.
+
+#Param atlas  Image containing sprites. ##
+#Param xform  RSXform mappings for sprites in atlas. ##
+#Param tex    Rect locations of sprites in atlas. ##
+#Param colors  Color, one per sprite, blended with sprite using Blend_Mode; or nullptr. ##
+#Param count  Number of sprites to draw. ##
+#Param mode   Blend_Mode combining colors and sprites. ##
+#Param cullRect  Rect bounds of transformed sprites for efficient clipping; or nullptr. ##
+#Param paint  Paint Color_Filter, Image_Filter, Blend_Mode, and so on; or nullptr. ##
+
+#Example
+#Image 3
+void draw(SkCanvas* canvas) {

+  // SkBitmap source = mandrill;

+  SkRSXform xforms[] = { { .5f, 0, 0, 0 }, {0, .5f, 200, 100 } };

+  SkRect tex[] = { { 0, 0, 250, 250 }, { 0, 0, 250, 250 } };

+  SkColor colors[] = { 0x7f55aa00, 0x7f3333bf };

+  SkPaint paint;

+  paint.setAlpha(127);

+  canvas->drawAtlas(image, xforms, tex, colors, 2, SkBlendMode::kPlus, nullptr, &paint);

+}
+##
+
+#ToDo bug in example on cpu side, gpu looks ok ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[], int count,
+                   const SkRect* cullRect, const SkPaint* paint) 
+
+Draw a set of sprites from atlas, using Clip, Matrix, and optional Paint paint.
+paint uses Anti-alias, Color_Alpha, Color_Filter, Image_Filter, and Blend_Mode to draw, if present.
+For each entry in the array, Rect tex locates sprite in atlas, and RSXform xform transforms it
+into destination space.
+xform and text must contain count entries.
+Optional cullRect is a conservative bounds of all transformed sprites. 
+If cullrect is outside of Clip, canvas can skip drawing.
+
+#Param atlas  Image containing sprites. ##
+#Param xform  RSXform mappings for sprites in atlas. ##
+#Param tex    Rect locations of sprites in atlas. ##
+#Param count  Number of sprites to draw. ##
+#Param cullRect  Rect bounds of transformed sprites for efficient clipping; or nullptr. ##
+#Param paint  Paint Color_Filter, Image_Filter, Blend_Mode, and so on; or nullptr. ##
+
+#Example
+#Image 3
+void draw(SkCanvas* canvas) {

+  // sk_sp<SkImage> image = mandrill;

+  SkRSXform xforms[] = { { .5f, 0, 0, 0 }, {0, .5f, 200, 100 } };

+  SkRect tex[] = { { 0, 0, 250, 250 }, { 0, 0, 250, 250 } };

+  const SkImage* imagePtr = image.get();

+  canvas->drawAtlas(imagePtr, xforms, tex, 2, nullptr, nullptr);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawAtlas(const sk_sp<SkImage>& atlas, const SkRSXform xform[], const SkRect tex[],
+                   int count, const SkRect* cullRect, const SkPaint* paint) 
+
+Draw a set of sprites from atlas, using Clip, Matrix, and optional Paint paint.
+paint uses Anti-alias, Color_Alpha, Color_Filter, Image_Filter, and Blend_Mode to draw, if present.
+For each entry in the array, Rect tex locates sprite in atlas, and RSXform xform transforms it
+into destination space.
+xform and text must contain count entries.
+Optional cullRect is a conservative bounds of all transformed sprites. 
+If cullrect is outside of Clip, canvas can skip drawing.
+
+#Param atlas  Image containing sprites. ##
+#Param xform  RSXform mappings for sprites in atlas. ##
+#Param tex    Rect locations of sprites in atlas. ##
+#Param count  Number of sprites to draw. ##
+#Param cullRect  Rect bounds of transformed sprites for efficient clipping; or nullptr. ##
+#Param paint  Paint Color_Filter, Image_Filter, Blend_Mode, and so on; or nullptr. ##
+
+#Example
+#Image 3
+void draw(SkCanvas* canvas) {

+  // sk_sp<SkImage> image = mandrill;

+  SkRSXform xforms[] = { { 1, 0, 0, 0 }, {0, 1, 300, 100 } };

+  SkRect tex[] = { { 0, 0, 200, 200 }, { 200, 0, 400, 200 } };

+  canvas->drawAtlas(image, xforms, tex, 2, nullptr, nullptr);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawDrawable(SkDrawable* drawable, const SkMatrix* matrix = NULL)
+
+Draw Drawable drawable using Clip and Matrix, concatenated with 
+optional matrix.
+
+If Canvas has an asynchronous implementation, as is the case 
+when it is recording into Picture, then drawable will be referenced,
+so that SkDrawable::draw() can be called when the operation is finalized. To force
+immediate drawing, call SkDrawable::draw() instead.
+
+#Param drawable  Custom struct encapsulating drawing commands. ##
+#Param matrix    Transformation applied to drawing; or nullptr.  ##
+
+#Example
+#Height 100
+#Function
+struct MyDrawable : public SkDrawable {

+    SkRect onGetBounds() override { return SkRect::MakeWH(50, 100);  }

+

+    void onDraw(SkCanvas* canvas) override {

+       SkPath path;

+       path.conicTo(10, 90, 50, 90, 0.9f);

+       SkPaint paint;

+       paint.setColor(SK_ColorBLUE);

+       canvas->drawRect(path.getBounds(), paint);

+       paint.setAntiAlias(true);

+       paint.setColor(SK_ColorWHITE);

+       canvas->drawPath(path, paint);

+    }

+};

+

+#Function ##

+void draw(SkCanvas* canvas) {

+    sk_sp<SkDrawable> drawable(new MyDrawable);

+  SkMatrix matrix;

+  matrix.setTranslate(10, 10);

+  canvas->drawDrawable(drawable.get(), &matrix);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawDrawable(SkDrawable* drawable, SkScalar x, SkScalar y)
+
+Draw Drawable drawable using Clip and Matrix, offset by (x, y).
+
+If Canvas has an asynchronous implementation, as is the case 
+when it is recording into Picture, then drawable will be referenced,
+so that SkDrawable::draw() can be called when the operation is finalized. To force
+immediate drawing, call SkDrawable::draw() instead.
+
+#Param drawable  Custom struct encapsulating drawing commands. ##
+#Param x  Offset into Canvas writable pixels in x. ##
+#Param y  Offset into Canvas writable pixels in y. ##
+
+#Example
+#Height 100
+#Function
+struct MyDrawable : public SkDrawable {

+    SkRect onGetBounds() override { return SkRect::MakeWH(50, 100);  }

+

+    void onDraw(SkCanvas* canvas) override {

+       SkPath path;

+       path.conicTo(10, 90, 50, 90, 0.9f);

+       SkPaint paint;

+       paint.setColor(SK_ColorBLUE);

+       canvas->drawRect(path.getBounds(), paint);

+       paint.setAntiAlias(true);

+       paint.setColor(SK_ColorWHITE);

+       canvas->drawPath(path, paint);

+    }

+};

+

+#Function ##

+void draw(SkCanvas* canvas) {

+    sk_sp<SkDrawable> drawable(new MyDrawable);

+  canvas->drawDrawable(drawable.get(), 10, 10);

+}
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawAnnotation(const SkRect& rect, const char key[], SkData* value)
+
+Associate Rect on Canvas when an annotation; a key-value pair, where the key is
+a null-terminated utf8 string, and optional value is stored as Data.
+
+Only some canvas implementations, such as recording to Picture, or drawing to 
+Document_PDF, use annotations.
+
+#Param rect    Rect extent of canvas to annotate. ##
+#Param key     String used for lookup. ##
+#Param value   Data holding value stored in annotation. ##
+
+#Example
+    #Height 1
+    const char text[] = "Click this link!";
+    SkRect bounds;
+    SkPaint paint;
+    paint.setTextSize(40);
+    (void)paint.measureText(text, strlen(text), &bounds);
+    const char url[] = "https://www.google.com/";
+    sk_sp<SkData> urlData(SkData::MakeWithCString(url));
+    canvas->drawAnnotation(bounds, "url_key", urlData.get());
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void drawAnnotation(const SkRect& rect, const char key[], const sk_sp<SkData>& value) 
+
+Associate Rect on Canvas when an annotation; a key-value pair, where the key is
+a null-terminated utf8 string, and optional value is stored as Data.
+
+Only some canvas implementations, such as recording to Picture, or drawing to 
+Document_PDF, use annotations.
+
+#Param rect    Rect extent of canvas to annotate. ##
+#Param key     String used for lookup. ##
+#Param value   Data holding value stored in annotation. ##
+
+#Example
+#Height 1
+    const char text[] = "Click this link!";
+    SkRect bounds;
+    SkPaint paint;
+    paint.setTextSize(40);
+    (void)paint.measureText(text, strlen(text), &bounds);
+    const char url[] = "https://www.google.com/";
+    sk_sp<SkData> urlData(SkData::MakeWithCString(url));
+    canvas->drawAnnotation(bounds, "url_key", urlData.get());
+##
+
+#ToDo incomplete ##
+
+##
+
+#Method SkDrawFilter* getDrawFilter() const
+
+Legacy call to be deprecated.
+
+#Deprecated 
+##
+
+##
+
+#Method virtual SkDrawFilter* setDrawFilter(SkDrawFilter* filter)
+
+Legacy call to be deprecated.
+
+#Deprecated 
+##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method virtual bool isClipEmpty() const
+
+Returns true if Clip is empty; that is, nothing will draw.
+
+isClipEmpty may do work when called; it should not be called
+more often than needed. However, once called, subsequent calls perform no
+work until Clip changes.
+
+#Return  true if Clip is empty. ##
+
+#Example
+    void draw(SkCanvas* canvas) {

+        SkDebugf("clip is%s empty\n", canvas->isClipEmpty() ? "" : " not");

+        SkPath path;

+        canvas->clipPath(path);

+        SkDebugf("clip is%s empty\n", canvas->isClipEmpty() ? "" : " not");

+    }
+    #StdOut
+        clip is not empty

+        clip is empty
+    ##
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method virtual bool isClipRect() const
+
+Returns true if Clip is Rect and not empty.
+Returns false if the clip is empty, or if it is not Rect.
+
+#Return  true if Clip is Rect and not empty. ##
+
+#Example
+    void draw(SkCanvas* canvas) {

+        SkDebugf("clip is%s rect\n", canvas->isClipRect() ? "" : " not");

+        canvas->clipRect({0, 0, 0, 0});

+        SkDebugf("clip is%s rect\n", canvas->isClipRect() ? "" : " not");

+    }
+    #StdOut
+        clip is rect

+        clip is not rect
+    ##
+##
+
+#ToDo incomplete ##
+
+##
+
+#Class SkCanvas ##
+#Topic Canvas ##
diff --git a/docs/SkPaint.bmh b/docs/SkPaint.bmh
new file mode 100644
index 0000000..4adb6da
--- /dev/null
+++ b/docs/SkPaint.bmh
@@ -0,0 +1,5280 @@
+#Topic Paint
+
+Paint controls options applied when drawing and measuring. Paint collects all
+options outside of the Canvas_Clip and Canvas_Matrix.
+
+Various options apply to text, strokes and fills, and images. 
+
+Some options may not be implemented on all platforms; in these cases, setting
+the option has no effect. Some options are conveniences that duplicate Canvas
+functionality; for instance, text size is identical to matrix scale.
+
+Paint options are rarely exclusive; each option modifies a stage of the drawing
+pipeline and multiple pipeline stages may be affected by a single Paint.
+
+Paint collects effects and filters that describe single-pass and multiple-pass 
+algorithms that alter the drawing geometry, color, and transparency. For instance,
+Paint does not directly implement dashing or blur, but contains the objects that do so. 
+
+The objects contained by Paint are opaque, and cannot be edited outside of the Paint
+to affect it. The implementation is free to defer computations associated with the
+Paint, or ignore them altogether. For instance, some GPU implementations draw all
+Path geometries with anti-aliasing, regardless of SkPaint::kAntiAlias_Flag setting.
+
+Paint describes a single color, a single font, a single image quality, and so on.
+Multiple colors are drawn either by using multiple paints or with objects like
+Shader attached to Paint.
+
+#Class SkPaint
+
+#Topic Overview
+
+#Subtopic Subtopics
+#ToDo not all methods are in topics ##
+#ToDo subtopics are not in topics ##
+#Table
+#Legend
+# topics                 # description                                 ##
+#Legend ##
+# Initializers           # Constructors and initialization.            ##
+# Destructor             # Paint termination.                          ##
+# Management             # Paint copying, moving, comparing.           ##
+# Hinting                # Glyph outline adjustment.                   ##
+# Flags                  # Attributes represented by single bits.      ##
+# Anti-alias             # Approximating coverage with transparency.   ##
+# Dither                 # Distributing color error.                   ##
+# Device_Text            # Increase precision of glyph position.       ##
+# Font_Embedded_Bitmaps  # Custom-sized bitmap glyphs.                 ##
+# Automatic_Hinting      # Always adjust glyph paths.                  ##
+# Vertical_Text          # Orient text from top to bottom.             ##
+# Fake_Bold              # Approximate font styles.                    ##
+# Full_Hinting_Spacing   # Glyph spacing affected by hinting.          ##
+# Filter_Quality_Methods # Get and set Filter_Quality.                 ##
+# Color_Methods          # Get and set Color.                          ##
+# Style                  # Geometry filling, stroking.                 ##
+# Stroke_Width           # Thickness perpendicular to geometry.        ##
+# Miter_Limit            # Maximum length of stroked corners.          ##
+# Stroke_Cap             # Decorations at ends of open strokes.        ##
+# Stroke_Join            # Decoration at corners of strokes.           ##
+# Fill_Path              # Make Path from Path_Effect, stroking.       ##
+# Shader_Methods         # Get and set Shader.                         ##
+# Color_Filter_Methods   # Get and set Color_Filter.                   ##
+# Blend_Mode_Methods     # Get and set Blend_Mode.                     ##
+# Path_Effect_Methods    # Get and set Path_Effect.                    ##
+# Mask_Filter_Methods    # Get and set Mask_Filter.                    ##
+# Typeface_Methods       # Get and set Typeface.                       ##
+# Rasterizer_Methods     # Get and set Rasterizer.                     ##
+# Image_Filter_Methods   # Get and set Image_Filter.                   ##
+# Draw_Looper_Methods    # Get and set Draw_Looper.                    ##
+# Text_Align             # Text placement relative to position.        ##
+# Text_Size              # Overall height in points.                   ##
+# Text_Scale_X           # Text horizontal scale.                      ##
+# Text_Skew_X            # Text horizontal slant.                      ##
+# Text_Encoding          # Text encoded as characters or glyphs.       ##
+# Font_Metrics           # Common glyph dimensions.                    ##
+# Measure_Text           # Width, height, bounds of text.              ##
+# Text_Path              # Geometry of glyphs.                         ##
+# Text_Intercepts        # Advanced underline, strike through.         ##
+# Fast_Bounds            # Appproxiate area required by Paint.         ##
+#Table ##
+#Subtopic ##
+
+#Subtopic Constants
+#Table
+#Legend
+# constants                      # description                                 ##
+#Legend ##
+# Align                          # Glyph locations relative to text position.  ##
+# Cap                            # Start and end geometry on stroked shapes.   ##
+# Flags                          # Values described by bits and masks.         ##
+# FontMetrics::FontMetricsFlags  # Valid Font_Metrics.                         ##
+# Hinting                        # Level of glyph outline adjustment.          ##
+# Join                           # Corner geometry on stroked shapes.          ##
+# Style                          # Stroke, fill, or both.                      ##
+# TextEncoding                   # Character or glyph encoding size.           ##
+#Table ##
+#Subtopic ##
+
+#Subtopic Structs
+#Table
+#Legend
+# struct                         # description                                 ##
+#Legend ##
+# FontMetrics                    # Typeface values.                            ##
+#Table ##
+#Subtopic ##
+
+#Subtopic Constructors
+#Table
+#Legend
+#                                # description                                 ##
+#Legend ##
+# SkPaint()                      # Constructs with default values.             ##
+# SkPaint(const SkPaint& paint)  # Makes a shallow copy.                       ##
+# SkPaint(SkPaint&& paint)       # Moves paint without copying it.             ##
+# ~SkPaint()                     # Decreases Reference_Count of owned objects. ##
+#Table ##
+#Subtopic ##
+
+#Subtopic Operators
+#Table
+#Legend
+# operator                                       # description                      ##
+#Legend ##
+# operator=(const SkPaint& paint)                # Makes a shallow copy.            ##
+# operator=(SkPaint&& paint)                     # Moves paint without copying it.  ##
+# operator==(const SkPaint& a, const SkPaint& b) # Compares paints for equality.    ##
+# operator!=(const SkPaint& a, const SkPaint& b) # Compares paints for inequality.  ##
+#Table ##
+#Subtopic ##
+
+#Subtopic Member_Functions
+#Table
+#Legend
+# function              # description                                           ##
+#Legend ##
+# breakText             # Returns text that fits in a width.                    ##
+# canComputeFastBounds  # Returns true if settings allow for fast bounds computation. ##
+# computeFastBounds     # Returns fill bounds for quick reject tests.           ##
+# computeFastStrokeBounds # Returns stroke bounds for quick reject tests.       ##
+# containsText          # Returns if all text corresponds to glyphs.            ##
+# countText             # Returns number of glyphs in text.                     ##
+# doComputeFastBounds   # Returns bounds for quick reject tests.                ##
+# flatten()             # Serializes into a buffer.                             ##
+# getAlpha              # Returns Color_Alpha, color opacity.                   ##
+# getBlendMode          # Returns Blend_Mode, how colors combine with dest.     ##
+# getColor              # Returns Color_Alpha and Color_RGB, one drawing color. ##
+# getColorFilter        # Returns Color_Filter, how colors are altered.         ##
+# getDrawLooper         # Returns Draw_Looper, multiple layers.                 ##
+# getFillPath           # Returns fill path equivalent to stroke.               ##
+# getFilterQuality      # Returns Filter_Quality, image filtering level.        ##
+# getFlags              # Returns Flags stored in a bit field.                  ##
+# getFontBounds         # Returns union all glyph bounds.                       ##
+# getFontMetrics        # Returns Typeface metrics scaled by text size.         ##
+# getFontSpacing        # Returns recommended spacing between lines.            ##
+# getHash               # Returns a shallow hash for equality checks.           ##
+# getHinting            # Returns Hinting, glyph outline adjustment level.      ##
+# getImageFilter        # Returns Image_Filter, alter pixels; blur.             ##
+# getMaskFilter         # Returns Mask_Filter, alterations to Mask_Alpha.       ##
+# getPathEffect         # Returns Path_Effect, modifications to path geometry; dashing. ##
+# getPosTextPath        # Returns Path equivalent to positioned text.           ##
+# getPosTextIntercepts  # Returns where lines intersect positioned text; underlines. ##
+# getPosTextHIntercepts # Returns where lines intersect horizontally positioned text; underlines. ##
+# getRasterizer         # Returns Rasterizer, Mask_Alpha generation from Path.  ##
+# getShader             # Returns Shader, multiple drawing colors; gradients.   ##  
+# getStrokeCap          # Returns Cap, the area drawn at path ends.             ##
+# getStrokeJoin         # Returns Join, geometry on path corners.               ##
+# getStrokeMiter        # Returns Miter_Limit, angles with sharp corners.       ##
+# getStrokeWidth        # Returns thickness of the stroke.                      ##
+# getStyle              # Returns Style: stroke, fill, or both.                 ##
+# getTextAlign          # Returns Align: left, center, or right.                ##
+# getTextBlobIntercepts # Returns where lines intersect Text_Blob; underlines.  ##
+# getTextEncoding       # Returns character or glyph encoding size.             ##
+# getTextIntercepts     # Returns where lines intersect text; underlines.       ##
+# getTextPath           # Returns Path equivalent to text.                      ##
+# getTextScaleX         # Returns the text horizontal scale; condensed text.    ##
+# getTextSkewX          # Returns the text horizontal skew; oblique text.       ##
+# getTextSize           # Returns text size in points.                          ##
+# getTextWidths         # Returns advance and bounds for each glyph in text.    ##
+# getTypeface           # Returns Typeface, font description.                   ##
+# glyphsToUnichars      # Converts glyphs into text.                            ##
+# isAntiAlias           # Returns true if Anti-alias is set.                    ##
+# isAutohinted          # Returns true if glyphs are always hinted.             ##
+# isDevKernText         # Returns true if Full_Hinting_Spacing is set.          ##
+# isDither              # Returns true if Dither is set.                        ##
+# isEmbeddedBitmapText  # Returns true if Font_Embedded_Bitmaps is set.         ##
+# isFakeBoldText        # Returns true if Fake_Bold is set.                     ##
+# isLCDRenderText       # Returns true if LCD_Text is set.                      ##
+# isSrcOver             # Returns true if Blend_Mode is SkBlendMode::kSrcOver.   ##
+# isSubpixelText        # Returns true if Subpixel_Text is set.                 ##
+# isVerticalText        # Returns true if Vertical_Text is set.                 ##
+# measureText           # Returns advance width and bounds of text.             ##
+# nothingToDraw         # Returns true if Paint prevents all drawing.           ##
+# refColorFilter        # References Color_Filter, how colors are altered.      ##
+# refDrawLooper         # References Draw_Looper, multiple layers.              ##
+# refImageFilter        # References Image_Filter, alter pixels; blur.          ##
+# refMaskFilter         # References Mask_Filter, alterations to Mask_Alpha.    ##
+# refPathEffect         # References Path_Effect, modifications to path geometry; dashing. ##
+# refRasterizer         # References Rasterizer, mask generation from path.     ##
+# refShader             # References Shader, multiple drawing colors; gradients. ##  
+# refTypeface           # References Typeface, font description.                ##
+# reset()               # Sets to default values.                               ##
+# setAlpha              # Sets Color_Alpha, color opacity.                      ##
+# setAntiAlias          # Sets or clears Anti-alias.                            ##
+# setARGB               # Sets color by component.                              ##
+# setAutohinted         # Sets glyphs to always be hinted.                      ##
+# setBlendMode          # Sets Blend_Mode, how colors combine with destination. ##
+# setColor              # Sets Color_Alpha and Color_RGB, one drawing color.    ##
+# setColorFilter        # Sets Color_Filter, alters color.                      ##
+# setDevKernText        # Sets or clears Full_Hinting_Spacing.                  ##
+# setDither             # Sets or clears Dither.                                ##
+# setDrawLooper         # Sets Draw_Looper, multiple layers.                    ##
+# setEmbeddedBitmapText # Sets or clears Font_Embedded_Bitmaps.                 ##
+# setFakeBoldText       # Sets or clears Fake_Bold.                             ##
+# setFilterQuality      # Sets Filter_Quality, the image filtering level.       ##
+# setFlags              # Sets multiple Flags in a bit field.                   ##
+# setHinting            # Sets Hinting, glyph outline adjustment level.         ##
+# setLCDRenderText      # Sets or clears LCD_Text.                              ##
+# setMaskFilter         # Sets Mask_Filter, alterations to Mask_Alpha.          ##
+# setPathEffect         # Sets Path_Effect, modifications to path geometry; dashing. ##
+# setRasterizer         # Sets Rasterizer, Mask_Alpha generation from Path.     ##
+# setImageFilter        # Sets Image_Filter, alter pixels; blur.                ##
+# setShader             # Sets Shader, multiple drawing colors; gradients.      ##  
+# setStrokeCap          # Sets Cap, the area drawn at path ends.                ##
+# setStrokeJoin         # Sets Join, geometry on path corners.                  ##
+# setStrokeMiter        # Sets Miter_Limit, angles with sharp corners.          ##
+# setStrokeWidth        # Sets thickness of the stroke.                         ##
+# setStyle              # Sets Style: stroke, fill, or both.                    ##
+# setSubpixelText       # Sets or clears Subpixel_Text.                         ##
+# setTextAlign          # Sets Align: left, center, or right.                   ##
+# setTextEncoding       # Sets character or glyph encoding size.                ##
+# setTextScaleX         # Sets the text horizontal scale; condensed text.       ##
+# setTextSkewX          # Sets the text horizontal skew; oblique text.          ##
+# setTextSize           # Sets text size in points.                             ##
+# setTypeface           # Sets Typeface, font description.                      ##
+# setVerticalText       # Sets or clears Vertical_Text.                         ##
+# textToGlyphs          # Converts text into glyph indices.                     ##
+# toString              # Converts Paint to machine parsable form (Developer_Mode) ##
+# unflatten()           # Populates from a serialized stream.                   ##
+#Table ##
+#Subtopic ##
+
+#Topic Overview ##
+
+# ------------------------------------------------------------------------------
+#Topic Initializers
+
+#Method SkPaint()
+
+Constructs Paint with default values.
+
+#Table
+#Legend
+# attribute              # default value            ##
+#Legend ##
+# Anti-alias             # false                    ##
+# Blend_Mode             # SkBlendMode::kSrcOver     ##
+# Color                  # SK_ColorBLACK            ##
+# Color_Alpha            # 255                      ##
+# Color_Filter           # nullptr                  ##
+# Dither                 # false                    ##
+# Draw_Looper            # nullptr                  ##
+# Fake_Bold              # false                    ##
+# Filter_Quality         # kNone_SkFilterQuality    ##
+# Font_Embedded_Bitmaps  # false                    ##
+# Automatic_Hinting      # false                    ##
+# Full_Hinting_Spacing   # false                    ##
+# Hinting                # kNormal_Hinting          ##
+# Image_Filter           # nullptr                  ##
+# LCD_Text               # false                    ##
+# Linear_Text            # false                    ##
+# Miter_Limit            # 4                        ##
+# Mask_Filter            # nullptr                  ##
+# Path_Effect            # nullptr                  ##
+# Rasterizer             # nullptr                  ##
+# Shader                 # nullptr                  ##
+# Style                  # kFill_Style              ##
+# Text_Align             # kLeft_Align              ##
+# Text_Encoding          # kUTF8_TextEncoding       ##
+# Text_Scale_X           # 1                        ##
+# Text_Size              # 12                       ##
+# Text_Skew_X            # 0                        ##
+# Typeface               # nullptr                  ##
+# Stroke_Cap             # kButt_Cap                ##
+# Stroke_Join            # kMiter_Join              ##
+# Stroke_Width           # 0                        ##
+# Subpixel_Text          # false                    ##
+# Vertical_Text          # false                    ##
+#Table ##
+
+The flags, text size, hinting, and miter limit may be overridden at compile time by defining
+paint default values. The overrides may be included in SkUserConfig.h or predefined by the 
+build system.
+
+#Return  default initialized Paint ##
+
+#Example
+#ToDo mark this as no output ##
+#Height 1
+###$  $ redefine markup character so preprocessor commands appear normally
+#ifndef SkUserConfig_DEFINED
+#define SkUserConfig_DEFINED
+
+#define SkPaintDefaults_Flags      0x01   // always enable antialiasing
+#define SkPaintDefaults_TextSize   24.f   // double default font size
+#define SkPaintDefaults_Hinting    3      // use full hinting
+#define SkPaintDefaults_MiterLimit 10.f   // use HTML Canvas miter limit setting
+
+#endif
+$$$#  # restore original markup character
+##
+
+
+##
+
+#Method SkPaint(const SkPaint& paint)
+
+Makes a shallow copy of Paint. Typeface, Path_Effect, Shader,
+Mask_Filter, Color_Filter, Rasterizer, Draw_Looper, and Image_Filter are shared
+between the original paint and the copy. These objects' Reference_Count are increased.
+
+The referenced objects Path_Effect, Shader, Mask_Filter, Color_Filter, Rasterizer,
+Draw_Looper, and Image_Filter cannot be modified after they are created.
+This prevents objects with Reference_Count from being modified once Paint refers to them.
+
+#Param paint  original to copy ##
+
+#Return  shallow copy of paint ##
+
+#Example
+#ToDo why is this double-spaced on Fiddle? ##
+    SkPaint paint1;
+    paint1.setColor(SK_ColorRED);
+    SkPaint paint2(paint1);
+    paint2.setColor(SK_ColorBLUE);
+    SkDebugf("SK_ColorRED %c= paint1.getColor()\n", SK_ColorRED == paint1.getColor() ? '=' : '!');
+    SkDebugf("SK_ColorBLUE %c= paint2.getColor()\n", SK_ColorBLUE == paint2.getColor() ? '=' : '!');
+
+    #StdOut
+        SK_ColorRED == paint1.getColor()
+        SK_ColorBLUE == paint2.getColor()
+    ##
+##
+
+##
+
+#Method SkPaint(SkPaint&& paint)
+
+    Implements a move constructor to avoid incrementing the reference counts
+    of objects referenced by the paint.
+
+    After the call, paint is undefined, and can be safely destructed.
+
+    #Param paint  original to move ##
+
+    #Return  content of paint ##
+
+    #Example
+        SkPaint paint;
+        float intervals[] = { 5, 5 };
+        paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 2.5f));
+        SkPaint dashed(std::move(paint));
+        SkDebugf("path effect unique: %s\n", dashed.getPathEffect()->unique() ? "true" : "false");
+
+        #StdOut
+            path effect unique: true
+        ##
+    ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void reset()
+
+Sets all paint's contents to their initial values. This is equivalent to replacing
+the paint with the result of SkPaint().
+
+#Example
+    SkPaint paint1, paint2;
+    paint1.setColor(SK_ColorRED);
+    paint1.reset();
+    SkDebugf("paint1 %c= paint2", paint1 == paint2 ? '=' : '!');
+
+    #StdOut
+        paint1 == paint2
+    ##
+##
+
+##
+
+#Topic ##
+
+# ------------------------------------------------------------------------------
+#Topic Destructor
+
+#Method ~SkPaint()
+
+Decreases Paint Reference_Count of owned objects: Typeface, Path_Effect, Shader,
+Mask_Filter, Color_Filter, Rasterizer, Draw_Looper, and Image_Filter. If the
+objects' reference count goes to zero, they are deleted.
+
+#NoExample 
+##
+
+##
+
+##
+# ------------------------------------------------------------------------------
+#Topic Management
+
+#Method SkPaint& operator=(const SkPaint& paint)
+
+Makes a shallow copy of Paint. Typeface, Path_Effect, Shader,
+Mask_Filter, Color_Filter, Rasterizer, Draw_Looper, and Image_Filter are shared
+between the original paint and the copy. The objects' Reference_Count are in the
+prior destination are decreased by one, and the referenced objects are deleted if the
+resulting count is zero. The objects' Reference_Count in the parameter paint are increased
+by one. paint is unmodified.
+
+#Param paint  original to copy ##
+
+#Return  content of paint ##
+
+#Example
+    SkPaint paint1, paint2;
+    paint1.setColor(SK_ColorRED);
+    paint2 = paint1;
+    SkDebugf("SK_ColorRED %c= paint1.getColor()\n", SK_ColorRED == paint1.getColor() ? '=' : '!');
+    SkDebugf("SK_ColorRED %c= paint2.getColor()\n", SK_ColorRED == paint2.getColor() ? '=' : '!');
+
+    #StdOut
+        SK_ColorRED == paint1.getColor()
+        SK_ColorRED == paint2.getColor()
+    ##
+##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method SkPaint& operator=(SkPaint&& paint)
+
+Moves the paint to avoid incrementing the reference counts
+of objects referenced by the paint parameter. The objects' Reference_Count are in the
+prior destination are decreased by one, and the referenced objects are deleted if the
+resulting count is zero.
+
+After the call, paint is undefined, and can be safely destructed.
+
+    #Param paint  original to move ##
+
+    #Return  content of paint ##
+
+#Example
+    SkPaint paint1, paint2;
+    paint1.setColor(SK_ColorRED);
+    paint2 = std::move(paint1);
+    SkDebugf("SK_ColorRED == paint2.getColor()\n", SK_ColorRED == paint2.getColor() ? '=' : '!');
+
+    #StdOut
+        SK_ColorRED == paint2.getColor()
+    ##
+##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method bool operator==(const SkPaint& a, const SkPaint& b)
+
+    Compares a and b, and returns true if a and b are equivalent. May return false
+    if Typeface, Path_Effect, Shader, Mask_Filter, Color_Filter, Rasterizer,
+    Draw_Looper, or Image_Filter have identical contents but different pointers.
+
+    #Param a  Paint to compare ##
+    #Param b  Paint to compare ##
+
+    #Return  true if Paint pair are equivalent ##
+
+    #Example
+        SkPaint paint1, paint2;
+        paint1.setColor(SK_ColorRED);
+        paint2.setColor(0xFFFF0000);
+        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+        float intervals[] = { 5, 5 };
+        paint1.setPathEffect(SkDashPathEffect::Make(intervals, 2, 2.5f));
+        paint2.setPathEffect(SkDashPathEffect::Make(intervals, 2, 2.5f));
+        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+
+        #StdOut
+            paint1 == paint2
+            paint1 != paint2
+        ##
+    ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method bool operator!=(const SkPaint& a, const SkPaint& b)
+
+    Compares a and b, and returns true if a and b are not equivalent. May return true
+    if Typeface, Path_Effect, Shader, Mask_Filter, Color_Filter, Rasterizer,
+    Draw_Looper, or Image_Filter have identical contents but different pointers.
+
+    #Param a  Paint to compare ##
+    #Param b  Paint to compare ##
+
+    #Return true if Paint pair are not equivalent ##
+
+#Example
+    SkPaint paint1, paint2;
+    paint1.setColor(SK_ColorRED);
+    paint2.setColor(0xFFFF0000);
+    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+    SkDebugf("paint1 %c= paint2\n", paint1 != paint2 ? '!' : '=');
+
+    #StdOut
+        paint1 == paint2
+        paint1 == paint2
+    ##
+##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method uint32_t getHash() const
+
+Returns a hash generated from Paint values and pointers.
+Identical hashes guarantee that the paints are
+equivalent, but differing hashes do not guarantee that the paints have differing
+contents.
+
+If operator==(const SkPaint& a, const SkPaint& b) returns true for two paints,
+their hashes are also equal.
+
+The hash returned is platform and implementation specific.
+
+#Return  a shallow hash ##
+
+#Example
+    SkPaint paint1, paint2;
+    paint1.setColor(SK_ColorRED);
+    paint2.setColor(0xFFFF0000);
+    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+    SkDebugf("paint1.getHash() %c= paint2.getHash()\n",
+             paint1.getHash() == paint2.getHash() ? '=' : '!');
+
+    #StdOut
+        paint1 == paint2
+        paint1.getHash() == paint2.getHash()
+    ##
+##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void flatten(SkWriteBuffer& buffer) const
+
+Serializes Paint into a buffer. A companion unflatten() call
+can reconstitute the paint at a later time.
+
+#Param buffer  Write_Buffer receiving the flattened Paint data ##
+
+#Example
+    class PaintDumper : public SkWriteBuffer {
+    public:
+        bool isCrossProcess() const override { return false; };
+        void writeByteArray(const void* data, size_t size) override {}
+        void writeBool(bool value) override {}
+        void writeScalar(SkScalar value) override {}
+        void writeScalarArray(const SkScalar* value, uint32_t count) override {}
+        void writeInt(int32_t value) override {}
+        void writeIntArray(const int32_t* value, uint32_t count) override {}
+        void writeUInt(uint32_t value) override {}
+        void writeString(const char* value) override {}
+        void writeFlattenable(const SkFlattenable* flattenable) override {}
+        void writeColorArray(const SkColor* color, uint32_t count) override {}
+        void writeColor4f(const SkColor4f& color) override {}
+        void writeColor4fArray(const SkColor4f* color, uint32_t count) override {}
+        void writePoint(const SkPoint& point) override {}
+        void writePointArray(const SkPoint* point, uint32_t count) override {}
+        void writeMatrix(const SkMatrix& matrix) override {}
+        void writeIRect(const SkIRect& rect) override {}
+        void writeRect(const SkRect& rect) override {}
+        void writeRegion(const SkRegion& region) override {}
+        void writePath(const SkPath& path) override {}
+        size_t writeStream(SkStream* stream, size_t length) override { return 0; }
+        void writeBitmap(const SkBitmap& bitmap) override {}
+        void writeImage(const SkImage*) override {}
+        void writeTypeface(SkTypeface* typeface) override {}
+        void writePaint(const SkPaint& paint) override {}
+
+        void writeColor(SkColor color) override {
+            SkDebugf("color = 0x%08x\n", color);
+        }
+    } dumper;
+
+    SkPaint paint;
+    paint.setColor(SK_ColorRED);
+    paint.flatten(dumper);
+
+    #StdOut
+        color = 0xffff0000
+    ##
+##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void unflatten(SkReadBuffer& buffer)
+
+Populates Paint, typically from a serialized stream, created by calling
+flatten() at an earlier time.
+
+SkReadBuffer class is not public, so unflatten() cannot be meaningfully called
+by the client.
+
+#Param buffer  serialized data to unflatten ##
+
+# why is unflatten() public? 
+#Bug 6172 ##
+
+#NoExample 
+##
+
+#ToDo incomplete ##
+
+##
+
+#Topic Management ##
+
+# ------------------------------------------------------------------------------
+#Topic Hinting
+
+#Enum Hinting
+
+#Code
+    enum Hinting {
+        kNo_Hinting            = 0,
+        kSlight_Hinting        = 1,
+        kNormal_Hinting        = 2,
+        kFull_Hinting          = 3
+    };
+##
+
+Hinting adjusts the glyph outlines so that the shape provides a uniform
+look at a given point size on font engines that support it. Hinting may have a
+muted effect or no effect at all depending on the platform.
+
+The four levels roughly control corresponding features on platforms that use FreeType
+as the Font_Engine.
+
+#Const kNo_Hinting   0
+    Leaves glyph outlines unchanged from their native representation.
+    With FreeType, this is equivalent to the FT_LOAD_NO_HINTING
+    bit-field constant supplied to FT_Load_Glyph, which indicates that the vector
+    outline being loaded should not be fitted to the pixel grid but simply scaled
+    to 26.6 fractional pixels.
+##
+#Const kSlight_Hinting 1
+    Modifies glyph outlines minimally to improve constrast.
+    With FreeType, this is equivalent in spirit to the
+    FT_LOAD_TARGET_LIGHT value supplied to FT_Load_Glyph. It chooses a 
+    lighter hinting algorithm for non-monochrome modes.
+    Generated glyphs may be fuzzy but better resemble their original shape.
+##
+#Const kNormal_Hinting 2
+    Modifies glyph outlines to improve constrast. This is the default.
+    With FreeType, this supplies FT_LOAD_TARGET_NORMAL to FT_Load_Glyph,

+    choosing the default hinting algorithm, which is optimized for standard 
+    gray-level rendering. 
+##
+#Const kFull_Hinting 3
+    Modifies glyph outlines for maxiumum constrast. With FreeType, this selects

+    FT_LOAD_TARGET_LCD or FT_LOAD_TARGET_LCD_V if kLCDRenderText_Flag is set. 

+    FT_LOAD_TARGET_LCD is a variant of FT_LOAD_TARGET_NORMAL optimized for 

+    horizontally decimated LCD displays; FT_LOAD_TARGET_LCD_V is a 

+    variant of FT_LOAD_TARGET_NORMAL optimized for vertically decimated LCD displays.
+##
+
+#Track
+#File SkFontHost_mac.cpp:1777,1806
+#Time 2013-03-03 07:16:29 +0000
+#Bug 915 ##
+On OS_X and iOS, hinting controls whether Core_Graphics dilates the font outlines
+to account for LCD text. No hinting uses Core_Text gray scale output.
+Normal hinting uses Core_Text LCD output. If kLCDRenderText_Flag is clear,
+the LCD output is reduced to a single grayscale channel.
+#Track ##
+
+On Windows with DirectWrite, Hinting has no effect.
+
+Hinting defaults to kNormal_Hinting.
+Set SkPaintDefaults_Hinting at compile time to change the default setting.
+
+#ToDo add an illustration? linux running GM:typefacerendering is best for this
+      the hinting variations are every other character horizontally
+#ToDo ##
+
+#Enum ##
+
+#Method Hinting getHinting() const
+
+    Returns level of glyph outline adjustment.
+
+    #Return  one of: kNo_Hinting, kSlight_Hinting, kNormal_Hinting, kFull_Hinting ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("SkPaint::kNormal_Hinting %c= paint.getHinting()\n",
+                SkPaint::kNormal_Hinting == paint.getHinting() ? '=' : ':');
+
+        #StdOut
+            SkPaint::kNormal_Hinting == paint.getHinting()
+        ##
+    ##
+##
+
+#Method void setHinting(Hinting hintingLevel)
+
+    Sets level of glyph outline adjustment.
+    Does not check for valid values of hintingLevel.
+
+    #Table
+    #Legend
+    # Hinting # value # effect on generated glyph outlines ##
+    ##
+    # kNo_Hinting     # 0 # leaves glyph outlines unchanged from their native representation ##
+    # kSlight_Hinting # 1 # modifies glyph outlines minimally to improve constrast ##
+    # kNormal_Hinting # 2 # modifies glyph outlines to improve constrast ##
+    # kFull_Hinting   # 3 # modifies glyph outlines for maxiumum constrast ## 
+    ##
+
+    #Param hintingLevel  one of: kNo_Hinting, kSlight_Hinting, kNormal_Hinting, kFull_Hinting ##
+
+    #Example
+        SkPaint paint1, paint2;
+        paint2.setHinting(SkPaint::kNormal_Hinting);
+        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : ':');
+
+        #StdOut
+        paint1 == paint2
+        ##
+    ##
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Flags
+
+#Enum Flags
+
+#Code
+    enum Flags {
+        kAntiAlias_Flag       = 0x01,
+        kDither_Flag          = 0x04,
+        kFakeBoldText_Flag    = 0x20,
+        kLinearText_Flag      = 0x40,
+        kSubpixelText_Flag    = 0x80,
+        kDevKernText_Flag     = 0x100,
+        kLCDRenderText_Flag   = 0x200,
+        kEmbeddedBitmapText_Flag = 0x400,
+        kAutoHinting_Flag     = 0x800,
+        kVerticalText_Flag    = 0x1000,
+        kGenA8FromLCD_Flag    = 0x2000,
+
+        kAllFlags = 0xFFFF,
+    };
+
+##
+
+The bit values stored in Flags.
+The default value for Flags, normally zero, can be changed at compile time
+with a custom definition of SkPaintDefaults_Flags.
+All flags can be read and written explicitly; Flags allows manipulating
+multiple settings at once.
+
+    #Const kAntiAlias_Flag          0x0001 
+        mask for setting Anti-alias
+    ##
+    #Const kDither_Flag             0x0004
+        mask for setting Dither
+    ##
+
+    #Const kFakeBoldText_Flag       0x0020
+        mask for setting Fake_Bold
+    ##
+    #Const kLinearText_Flag         0x0040
+        mask for setting Linear_Text
+    ##
+    #Const kSubpixelText_Flag       0x0080
+        mask for setting Subpixel_Text                 
+    ##
+    #Const kDevKernText_Flag        0x0100
+        mask for setting Full_Hinting_Spacing
+    ##
+    #Const kLCDRenderText_Flag      0x0200
+        mask for setting LCD_Text
+    ##
+    #Const kEmbeddedBitmapText_Flag 0x0400
+        mask for setting Font_Embedded_Bitmaps             
+    ##
+    #Const kAutoHinting_Flag        0x0800
+        mask for setting Automatic_Hinting
+    ##
+    #Const kVerticalText_Flag       0x1000
+        mask for setting Vertical_Text
+    ##
+    #Const kGenA8FromLCD_Flag       0x2000
+        #Private
+        Hack for GDI -- do not use if you can help it
+        ##
+        not intended for public use                           
+    ##
+    #Const kAllFlags                0xFFFF
+        mask of all Flags, including private flags and flags reserved for future use
+    ##
+
+Flags default to all flags clear, disabling the associated feature.
+
+#Enum ##
+
+#Enum ReserveFlags
+
+#Private 
+To be deprecated; only valid for Android framework.
+##
+
+#Code
+    enum ReserveFlags {
+        kUnderlineText_ReserveFlag   = 0x08,
+        kStrikeThruText_ReserveFlag  = 0x10,
+    };
+##
+
+    #Const kUnderlineText_ReserveFlag   0x0008
+        mask for underline text
+    ##
+    #Const kStrikeThruText_ReserveFlag  0x0010
+        mask for strike-thru text
+    ##
+
+#ToDo incomplete ##
+
+#Enum ##
+
+#Method uint32_t getFlags() const
+
+Returns paint settings described by Flags. Each setting uses one
+bit, and can be tested with Flags members.
+
+#Return  zero, one, or more bits described by Flags ##
+
+#Example
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkDebugf("(SkPaint::kAntiAlias_Flag & paint.getFlags()) %c= 0\n",
+        SkPaint::kAntiAlias_Flag & paint.getFlags() ? '!' : '=');
+
+    #StdOut
+        (SkPaint::kAntiAlias_Flag & paint.getFlags()) != 0
+    ##
+##
+
+##
+
+#Method void setFlags(uint32_t flags)
+
+Replaces Flags with flags, the union of the Flags members.
+All Flags members may be cleared, or one or more may be set.
+
+#Param flags  union of Flags for Paint ##
+
+#Example
+    SkPaint paint;
+    paint.setFlags((uint32_t) (SkPaint::kAntiAlias_Flag | SkPaint::kDither_Flag));
+    SkDebugf("paint.isAntiAlias()\n", paint.isAntiAlias() ? '!' : '=');
+    SkDebugf("paint.isDither()\n", paint.isDither() ? '!' : '=');
+
+    #StdOut
+        paint.isAntiAlias()
+        paint.isDither()
+    ##
+##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Anti-alias
+#Alias Anti-alias  # permit hyphen in topic name, should probably not substitute hyphen with _
+
+Anti-alias drawing approximates partial pixel coverage with transparency.
+If kAntiAlias_Flag is clear, pixel centers contained by the shape edge are drawn opaque.
+If kAntiAlias_Flag is set, pixels are drawn with Color_Alpha equal to their coverage.
+
+The rule for aliased pixels is inconsistent across platforms. A shape edge 
+passing through the pixel center may, but is not required to, draw the pixel.
+
+Raster_Engine draws aliased pixels whose centers are on or to the right of the start of an
+active Path edge, and whose center is to the left of the end of the active Path edge.
+
+#ToDo  add illustration of raster pixels ##
+
+A platform may only support anti-aliased drawing. Some GPU-backed platforms use
+supersampling to anti-alias all drawing, and have no mechanism to selectively
+alias.
+
+The amount of coverage computed for anti-aliased pixels also varies across platforms.
+
+Anti-alias is disabled by default.
+Anti-alias can be enabled by default by setting SkPaintDefaults_Flags to kAntiAlias_Flag
+at compile time.
+
+    #Example
+    #Width 512
+    #Description
+        A red line is drawn with transparency on the edges to make it look smoother.
+        A blue line draws only where the pixel centers are contained.
+        The lines are drawn into an offscreen bitmap, then drawn magified to make the
+        aliasing easier to see.
+    ##
+
+    void draw(SkCanvas* canvas) {
+        SkBitmap bitmap;
+        bitmap.allocN32Pixels(50, 50);
+        SkCanvas offscreen(bitmap);
+        SkPaint paint;
+        paint.setStyle(SkPaint::kStroke_Style);
+        paint.setStrokeWidth(10);
+        for (bool antialias : { false, true }) {
+            paint.setColor(antialias ? SK_ColorRED : SK_ColorBLUE);
+            paint.setAntiAlias(antialias);
+            bitmap.eraseColor(0);
+            offscreen.drawLine(5, 5, 15, 30, paint);
+            canvas->drawLine(5, 5, 15, 30, paint);
+            canvas->save();
+            canvas->scale(10, 10);
+            canvas->drawBitmap(bitmap, antialias ? 12 : 0, 0);
+            canvas->restore();
+            canvas->translate(15, 0);
+        }
+    }
+    ##
+
+#Method bool isAntiAlias() const
+
+    If true, pixels on the active edges of Path may be drawn with partial transparency.
+
+    Equivalent to getFlags masked with kAntiAlias_Flag.
+
+    #Return  kAntiAlias_Flag state ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("paint.isAntiAlias() %c= !!(paint.getFlags() & SkPaint::kAntiAlias_Flag)\n",
+                paint.isAntiAlias() == !!(paint.getFlags() & SkPaint::kAntiAlias_Flag) ? '=' : '!');
+        paint.setAntiAlias(true);
+        SkDebugf("paint.isAntiAlias() %c= !!(paint.getFlags() & SkPaint::kAntiAlias_Flag)\n",
+                paint.isAntiAlias() == !!(paint.getFlags() & SkPaint::kAntiAlias_Flag) ? '=' : '!');
+
+    #StdOut
+        paint.isAntiAlias() == !!(paint.getFlags() & SkPaint::kAntiAlias_Flag)
+        paint.isAntiAlias() == !!(paint.getFlags() & SkPaint::kAntiAlias_Flag)
+    ##
+    ##
+##
+
+#Method void setAntiAlias(bool aa)
+
+    Requests, but does not require, that Path edge pixels draw opaque or with
+    partial transparency.
+
+    Sets kAntiAlias_Flag if aa is true.
+    Clears kAntiAlias_Flag if aa is false.
+
+    #Param aa  setting for kAntiAlias_Flag ##
+
+    #Example
+        SkPaint paint1, paint2;
+        paint1.setAntiAlias(true);
+        paint2.setFlags(paint2.getFlags() | SkPaint::kAntiAlias_Flag);
+        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+
+        #StdOut
+            paint1 == paint2
+        ##
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Dither
+
+Dither increases fidelity by adjusting the color of adjcent pixels. 
+This can help to smooth color transitions and reducing banding in gradients.
+Dithering lessens visible banding from kRGB_565_SkColorType
+and kRGBA_8888_SkColorType gradients, 
+and improves rendering into a kRGB_565_SkColorType Surface.
+
+Dithering is always enabled for linear gradients drawing into
+kRGB_565_SkColorType Surface and kRGBA_8888_SkColorType Surface.
+Dither cannot be enabled for kAlpha_8_SkColorType Surface and
+kRGBA_F16_SkColorType Surface.
+
+Dither is disabled by default.
+Dither can be enabled by default by setting SkPaintDefaults_Flags to kDither_Flag
+at compile time.
+
+Some platform implementations may ignore dithering. Set
+
+#Define SK_IGNORE_GPU_DITHER
+
+to ignore Dither on GPU_Surface.
+
+#Example
+#Description 
+Dithering in the bottom half more closely approximates the requested color by
+alternating nearby colors from pixel to pixel.
+##
+void draw(SkCanvas* canvas) {
+    SkBitmap bm16;
+    bm16.allocPixels(SkImageInfo::Make(32, 32, kRGB_565_SkColorType, kOpaque_SkAlphaType));
+    SkCanvas c16(bm16);
+    SkPaint colorPaint;
+    for (auto dither : { false, true } ) {
+        colorPaint.setDither(dither);
+        for (auto colors : { 0xFF333333, 0xFF666666, 0xFF999999, 0xFFCCCCCC } ) {
+            for (auto mask : { 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFFFFFFFF } ) {
+                 colorPaint.setColor(colors & mask);
+                 c16.drawRect({0, 0, 8, 4}, colorPaint);
+                 c16.translate(8, 0);
+            }
+            c16.translate(-32, 4);
+        }
+    }
+    canvas->scale(8, 8);
+    canvas->drawBitmap(bm16, 0, 0);
+}
+##
+
+#Example
+#Description 
+Dithering introduces subtle adjustments to color to smooth gradients.
+Drawing the gradient repeatedly with SkBlendMode::kPlus exaggerates the
+dither, making it easier to see.
+##
+void draw(SkCanvas* canvas) {
+    canvas->clear(0);
+    SkBitmap bm32;
+    bm32.allocPixels(SkImageInfo::Make(20, 10, kN32_SkColorType, kPremul_SkAlphaType));
+    SkCanvas c32(bm32);
+    SkPoint points[] = {{0, 0}, {20, 0}};
+    SkColor colors[] = {0xFF334455, 0xFF662211 };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(
+                     points, colors, nullptr, SK_ARRAY_COUNT(colors),
+                     SkShader::kClamp_TileMode, 0, nullptr));
+    paint.setDither(true);
+    c32.drawPaint(paint);
+    canvas->scale(12, 12);
+    canvas->drawBitmap(bm32, 0, 0);
+    paint.setBlendMode(SkBlendMode::kPlus);
+    canvas->drawBitmap(bm32, 0, 11, &paint);
+    canvas->drawBitmap(bm32, 0, 11, &paint);
+    canvas->drawBitmap(bm32, 0, 11, &paint);
+}
+##
+
+#Method bool isDither() const
+
+    If true, color error may be distributed to smooth color transition.
+     
+    Equivalent to getFlags masked with kDither_Flag.
+
+    #Return  kDither_Flag state ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("paint.isDither() %c= !!(paint.getFlags() & SkPaint::kDither_Flag)\n",
+                paint.isDither() == !!(paint.getFlags() & SkPaint::kDither_Flag) ? '=' : '!');
+        paint.setDither(true);
+        SkDebugf("paint.isDither() %c= !!(paint.getFlags() & SkPaint::kDither_Flag)\n",
+                paint.isDither() == !!(paint.getFlags() & SkPaint::kDither_Flag) ? '=' : '!');
+
+    #StdOut
+        paint.isDither() == !!(paint.getFlags() & SkPaint::kDither_Flag)
+        paint.isDither() == !!(paint.getFlags() & SkPaint::kDither_Flag)
+    ##
+    ##
+
+##
+
+#Method void setDither(bool dither)
+
+    Requests, but does not require, to distribute color error.
+
+    Sets kDither_Flag if dither is true.
+    Clears kDither_Flag if dither is false.
+
+    #Param dither  setting for kDither_Flag ##
+
+    #Example
+        SkPaint paint1, paint2;
+        paint1.setDither(true);
+        paint2.setFlags(paint2.getFlags() | SkPaint::kDither_Flag);
+        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+
+        #StdOut
+            paint1 == paint2
+        ##
+    ##
+
+    #SeeAlso kRGB_565_SkColorType
+
+##
+
+#SeeAlso Gradient Color_RGB-565
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Device_Text
+
+LCD_Text and Subpixel_Text increase the precision of glyph position.
+
+When set, Flags kLCDRenderText_Flag takes advantage of the organization of Color_RGB stripes that 
+create a color, and relies
+on the small size of the stripe and visual perception to make the color fringing inperceptible.
+LCD_Text can be enabled on devices that orient stripes horizontally or vertically, and that order
+the color components as Color_RGB or Color_RBG.
+
+Flags kSubpixelText_Flag uses the pixel transparency to represent a fractional offset. 
+As the opaqueness
+of the color increases, the edge of the glyph appears to move towards the outside of the pixel.
+
+Either or both techniques can be enabled.
+kLCDRenderText_Flag and kSubpixelText_Flag are clear by default.
+LCD_Text or Subpixel_Text can be enabled by default by setting SkPaintDefaults_Flags to 
+kLCDRenderText_Flag or kSubpixelText_Flag (or both) at compile time.
+
+#Example
+    #Description
+        Four commas are drawn normally and with combinations of LCD_Text and Subpixel_Text.
+        When Subpixel_Text is disabled, the comma glyphs are indentical, but not evenly spaced.
+        When Subpixel_Text is enabled, the comma glyphs are unique, but appear evenly spaced.
+    ##
+
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(24, 33);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorWHITE);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(20);
+    for (bool lcd : { false, true }) {
+        paint.setLCDRenderText(lcd);
+        for (bool subpixel : { false, true }) {
+            paint.setSubpixelText(subpixel);
+            offscreen.drawString(",,,,", 0, 4, paint);
+            offscreen.translate(0, 7);
+        }
+    }
+    canvas->drawBitmap(bitmap, 4, 12);
+    canvas->scale(9, 9);
+    canvas->drawBitmap(bitmap, 4, -1);
+##
+
+#Subtopic Linear_Text
+#Alias Linear_Text # makes this a top level name, since it is under subtopic Device_Text
+
+Linear_Text selects whether text is rendered as a Glyph or as a Path.
+If kLinearText_Flag is set, it has the same effect as setting Hinting to kNormal_Hinting.
+If kLinearText_Flag is clear, it's the same as setting Hinting to kNo_Hinting.
+
+#Method bool isLinearText() const
+
+    If true, text is converted to Path before drawing and measuring.
+
+    Equivalent to getFlags masked with kLinearText_Flag.
+
+    #Return  kLinearText_Flag state ##
+
+    #Example
+    #Height 128
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        const char testStr[] = "xxxx xxxx";
+        for (auto linearText : { false, true } ) {
+            paint.setLinearText(linearText);
+            paint.setTextSize(24);
+            canvas->drawString(paint.isLinearText() ? "linear" : "hinted", 128, 30, paint);
+            for (SkScalar textSize = 8; textSize < 30; textSize *= 1.22f) {
+                paint.setTextSize(textSize);
+                canvas->translate(0, textSize);
+                canvas->drawString(testStr, 10, 0, paint);
+            }
+        }
+    }
+    ##
+
+    #SeeAlso setLinearText Hinting
+##
+
+#Method void setLinearText(bool linearText)
+
+    If true, text is converted to Path before drawing and measuring.
+    By default, kLinearText_Flag is clear.
+
+    Sets kLinearText_Flag if linearText is true.
+    Clears kLinearText_Flag if linearText is false.
+
+    #Param linearText  setting for kLinearText_Flag ##
+
+    #Example
+    #Height 128
+      void draw(SkCanvas* canvas) {
+          SkPaint paint;
+          paint.setAntiAlias(true);
+          const char testStr[] = "abcd efgh";
+          for (int textSize : { 12, 24 } ) {
+              paint.setTextSize(textSize);
+              for (auto linearText : { false, true } ) {
+                  paint.setLinearText(linearText);
+                  SkString width;
+                  width.appendScalar(paint.measureText(testStr, SK_ARRAY_COUNT(testStr), nullptr));
+                  canvas->translate(0, textSize + 4);
+                  canvas->drawString(testStr, 10, 0, paint);
+                  canvas->drawString(width, 128, 0, paint);
+              }
+           }
+        } 
+    ##
+
+    #SeeAlso isLinearText Hinting
+##
+
+#Subtopic ##
+
+#Subtopic Subpixel_Text
+#Alias Subpixel_Text # makes this a top level name, since it is under subtopic Device_Text
+
+Flags kSubpixelText_Flag uses the pixel transparency to represent a fractional offset. 
+As the opaqueness
+of the color increases, the edge of the glyph appears to move towards the outside of the pixel.
+
+#Method bool isSubpixelText() const
+    
+    If true, glyphs at different sub-pixel positions may differ on pixel edge coverage.
+
+    Equivalent to getFlags masked with kSubpixelText_Flag.
+
+    #Return  kSubpixelText_Flag state ##
+
+    #Example
+SkPaint paint;
+SkDebugf("paint.isSubpixelText() %c= !!(paint.getFlags() & SkPaint::kSubpixelText_Flag)\n",
+    paint.isSubpixelText() == !!(paint.getFlags() & SkPaint::kSubpixelText_Flag) ? '=' : '!');
+paint.setSubpixelText(true);
+SkDebugf("paint.isSubpixelText() %c= !!(paint.getFlags() & SkPaint::kSubpixelText_Flag)\n",
+    paint.isSubpixelText() == !!(paint.getFlags() & SkPaint::kSubpixelText_Flag) ? '=' : '!');
+
+        #StdOut
+            paint.isSubpixelText() == !!(paint.getFlags() & SkPaint::kSubpixelText_Flag)
+            paint.isSubpixelText() == !!(paint.getFlags() & SkPaint::kSubpixelText_Flag)
+        ##
+    ##
+
+##
+
+#Method void setSubpixelText(bool subpixelText)
+
+    Requests, but does not require, that glyphs respect sub-pixel positioning.
+
+    Sets kSubpixelText_Flag if subpixelText is true.
+    Clears kSubpixelText_Flag if subpixelText is false.
+
+    #Param subpixelText  setting for kSubpixelText_Flag ##
+
+    #Example
+        SkPaint paint1, paint2;
+        paint1.setSubpixelText(true);
+        paint2.setFlags(paint2.getFlags() | SkPaint::kSubpixelText_Flag);
+        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+
+        #StdOut
+            paint1 == paint2
+        ##
+    ##
+
+##
+
+#Subtopic ##
+
+#Subtopic LCD_Text
+#Alias LCD_Text # makes this a top level name, since it is under subtopic Device_Text
+
+When set, Flags kLCDRenderText_Flag takes advantage of the organization of Color_RGB stripes that 
+create a color, and relies
+on the small size of the stripe and visual perception to make the color fringing inperceptible.
+LCD_Text can be enabled on devices that orient stripes horizontally or vertically, and that order
+the color components as Color_RGB or Color_RBG.
+
+#Method bool isLCDRenderText() const
+
+    If true, glyphs may use LCD striping to improve glyph edges.
+
+    Returns true if Flags kLCDRenderText_Flag is set.
+
+    #Return  kLCDRenderText_Flag state ##
+
+    #Example
+SkPaint paint;
+SkDebugf("paint.isLCDRenderText() %c= !!(paint.getFlags() & SkPaint::kLCDRenderText_Flag)\n",
+    paint.isLCDRenderText() == !!(paint.getFlags() & SkPaint::kLCDRenderText_Flag) ? '=' : '!');
+paint.setLCDRenderText(true);
+SkDebugf("paint.isLCDRenderText() %c= !!(paint.getFlags() & SkPaint::kLCDRenderText_Flag)\n",
+    paint.isLCDRenderText() == !!(paint.getFlags() & SkPaint::kLCDRenderText_Flag) ? '=' : '!');
+
+        #StdOut
+            paint.isLCDRenderText() == !!(paint.getFlags() & SkPaint::kLCDRenderText_Flag)
+            paint.isLCDRenderText() == !!(paint.getFlags() & SkPaint::kLCDRenderText_Flag)
+        ##
+    ##
+
+##
+
+#Method void setLCDRenderText(bool lcdText)
+
+    Requests, but does not require, that glyphs use LCD striping for glyph edges.
+
+    Sets kLCDRenderText_Flag if lcdText is true.
+    Clears kLCDRenderText_Flag if lcdText is false.
+
+    #Param lcdText  setting for kLCDRenderText_Flag ##
+
+    #Example
+        SkPaint paint1, paint2;
+        paint1.setLCDRenderText(true);
+        paint2.setFlags(paint2.getFlags() | SkPaint::kLCDRenderText_Flag);
+        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+
+        #StdOut
+            paint1 == paint2
+        ##
+    ##
+
+
+##
+
+#Subtopic ##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Font_Embedded_Bitmaps
+#Alias Font_Embedded_Bitmaps # long-winded enough, alias so I don't type Paint_Font_...
+
+Font_Embedded_Bitmaps allows selecting custom-sized bitmap glyphs.
+Flags kEmbeddedBitmapText_Flag when set chooses an embedded bitmap glyph over an outline contained
+in a font if the platform supports this option. 
+
+FreeType selects the bitmap glyph if available when kEmbeddedBitmapText_Flag is set, and selects
+the outline glyph if kEmbeddedBitmapText_Flag is clear.
+Windows may select the bitmap glyph but is not required to do so.
+OS_X and iOS do not support this option.
+
+Font_Embedded_Bitmaps is disabled by default.
+Font_Embedded_Bitmaps can be enabled by default by setting SkPaintDefaults_Flags to
+kEmbeddedBitmapText_Flag at compile time.
+
+#Example
+    #ToDo image will only output on Ubuntu ... how to handle that in fiddle? ##
+    #Platform !fiddle
+    #Description
+        The hintgasp TrueType font in the Skia resources/fonts directory includes an embedded
+        bitmap glyph at odd font sizes. This example works on platforms that use FreeType
+        as their Font_Engine.
+        Windows may, but is not required to, return a bitmap glyph if kEmbeddedBitmapText_Flag is set.
+    ##
+    #Image  embeddedbitmap.png
+
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(30, 15);
+    bitmap.eraseColor(0);
+    SkCanvas offscreen(bitmap);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(13);
+    paint.setTypeface(MakeResourceAsTypeface("/fonts/hintgasp.ttf"));
+    for (bool embedded : { false, true}) {
+        paint.setEmbeddedBitmapText(embedded);
+        offscreen.drawString("A", embedded ? 5 : 15, 15, paint);
+    }
+    canvas->drawBitmap(bitmap, 0, 0);
+    canvas->scale(10, 10);
+    canvas->drawBitmap(bitmap, -2, 1);
+##
+
+#Method bool isEmbeddedBitmapText() const
+
+    If true, Font_Engine may return glyphs from font bitmaps instead of from outlines.
+
+    Equivalent to getFlags masked with kEmbeddedBitmapText_Flag.
+
+    #Return  kEmbeddedBitmapText_Flag state ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("paint.isEmbeddedBitmapText() %c="
+                " !!(paint.getFlags() & SkPaint::kEmbeddedBitmapText_Flag)\n",
+                paint.isEmbeddedBitmapText() ==
+                !!(paint.getFlags() & SkPaint::kEmbeddedBitmapText_Flag) ? '=' : '!');
+        paint.setEmbeddedBitmapText(true);
+        SkDebugf("paint.isEmbeddedBitmapText() %c="
+                " !!(paint.getFlags() & SkPaint::kEmbeddedBitmapText_Flag)\n",
+                paint.isEmbeddedBitmapText() ==
+                !!(paint.getFlags() & SkPaint::kEmbeddedBitmapText_Flag) ? '=' : '!');
+
+        #StdOut
+            paint.isEmbeddedBitmapText() == !!(paint.getFlags() & SkPaint::kEmbeddedBitmapText_Flag)
+            paint.isEmbeddedBitmapText() == !!(paint.getFlags() & SkPaint::kEmbeddedBitmapText_Flag)
+        ##
+    ##
+
+##
+
+#Method void setEmbeddedBitmapText(bool useEmbeddedBitmapText)
+
+    Requests, but does not require, to use bitmaps in fonts instead of outlines.
+
+    Sets kEmbeddedBitmapText_Flag if useEmbeddedBitmapText is true.
+    Clears kEmbeddedBitmapText_Flag if useEmbeddedBitmapText is false.
+
+    #Param useEmbeddedBitmapText  setting for kEmbeddedBitmapText_Flag ##
+
+    #Example
+        SkPaint paint1, paint2;
+        paint1.setEmbeddedBitmapText(true);
+        paint2.setFlags(paint2.getFlags() | SkPaint::kEmbeddedBitmapText_Flag);
+        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+
+        #StdOut
+            paint1 == paint2
+        ##
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Automatic_Hinting
+#Substitute auto-hinting
+
+If Hinting is set to kNormal_Hinting or kFull_Hinting, Automatic_Hinting
+instructs the Font_Manager to always hint Glyphs.
+Automatic_Hinting has no effect if Hinting is set to kNo_Hinting or
+kSlight_Hinting.
+
+Automatic_Hinting only affects platforms that use FreeType as the Font_Manager.
+
+#Method bool isAutohinted() const
+
+    If true, and if Hinting is set to kNormal_Hinting or kFull_Hinting, and if
+    platform uses FreeType as the Font_Manager, instruct the Font_Manager to always hint
+    Glyphs.
+
+    Equivalent to getFlags masked with kAutoHinting_Flag.
+
+    #Return  kAutoHinting_Flag state ##
+
+    #Example
+            SkPaint paint;
+            for (auto forceAutoHinting : { false, true} ) {
+            paint.setAutohinted(forceAutoHinting);
+            SkDebugf("paint.isAutohinted() %c="
+                    " !!(paint.getFlags() & SkPaint::kAutoHinting_Flag)\n",
+                    paint.isAutohinted() ==
+                    !!(paint.getFlags() & SkPaint::kAutoHinting_Flag) ? '=' : '!');
+            }
+            #StdOut
+                paint.isAutohinted() == !!(paint.getFlags() & SkPaint::kAutoHinting_Flag)
+                paint.isAutohinted() == !!(paint.getFlags() & SkPaint::kAutoHinting_Flag)
+            ##
+    ##
+
+    #SeeAlso setAutohinted Hinting
+
+##
+
+#Method void setAutohinted(bool useAutohinter)
+
+    If Hinting is set to kNormal_Hinting or kFull_Hinting and useAutohinter is set,
+    instruct the Font_Manager to always hint Glyphs.
+    Automatic_Hinting has no effect if Hinting is set to kNo_Hinting or
+    kSlight_Hinting.
+
+    setAutohinted only affects platforms that use FreeType as the Font_Manager.
+
+    Sets kAutoHinting_Flag if useAutohinter is true.
+    Clears kAutoHinting_Flag if useAutohinter is false.
+
+    #Param useAutohinter  setting for kAutoHinting_Flag ##
+
+    #Example
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        const char testStr[] = "xxxx xxxx";
+            for (auto forceAutoHinting : { false, true} ) {
+            paint.setAutohinted(forceAutoHinting);
+            paint.setTextSize(24);
+            canvas->drawString(paint.isAutohinted() ? "auto-hinted" : "default", 108, 30, paint);
+            for (SkScalar textSize = 8; textSize < 30; textSize *= 1.22f) {
+                paint.setTextSize(textSize);
+                canvas->translate(0, textSize);
+                canvas->drawString(testStr, 10, 0, paint);
+            }
+        }
+    }
+    ##
+
+    #SeeAlso isAutohinted Hinting
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Vertical_Text
+
+Text may be drawn by positioning each glyph, or by positioning the first glyph and
+using Font_Advance to position subsequent glyphs. By default, each successive glyph
+is positioned to the right of the preceeding glyph. Vertical_Text sets successive
+glyphs to position below the preceeding glyph.
+
+Skia can translate text character codes as a series of glyphs, but does not implement
+font substitution, 
+textual substitution, line layout, or contextual spacing like kerning pairs. Use
+a text shaping engine like #A HarfBuzz # http://harfbuzz.org/ ## to translate text runs
+into glyph series.
+
+Vertical_Text is clear if text is drawn left to right or set if drawn from top to bottom.
+
+Flags kVerticalText_Flag if clear draws text left to right.
+Flags kVerticalText_Flag if set draws text top to bottom.
+
+Vertical_Text is clear by default.
+Vertical_Text can be set by default by setting SkPaintDefaults_Flags to
+kVerticalText_Flag at compile time.
+
+#Example
+
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(50);
+    for (bool vertical : { false, true } ) {
+        paint.setVerticalText(vertical);
+        canvas->drawString("aAlL", 25, 50, paint);
+    }
+}
+
+##
+
+#Method bool isVerticalText() const
+
+    If true, glyphs are drawn top to bottom instead of left to right.
+
+    Equivalent to getFlags masked with kVerticalText_Flag.
+
+    #Return  kVerticalText_Flag state ##
+
+    #Example
+    SkPaint paint;
+    SkDebugf("paint.isVerticalText() %c= !!(paint.getFlags() & SkPaint::kVerticalText_Flag)\n",
+        paint.isVerticalText() == !!(paint.getFlags() & SkPaint::kVerticalText_Flag) ? '=' : '!');
+    paint.setVerticalText(true);
+    SkDebugf("paint.isVerticalText() %c= !!(paint.getFlags() & SkPaint::kVerticalText_Flag)\n",
+        paint.isVerticalText() == !!(paint.getFlags() & SkPaint::kVerticalText_Flag) ? '=' : '!');
+
+        #StdOut
+            paint.isVerticalText() == !!(paint.getFlags() & SkPaint::kVerticalText_Flag)
+            paint.isVerticalText() == !!(paint.getFlags() & SkPaint::kVerticalText_Flag)
+        ##
+    ##
+
+##
+
+#Method void setVerticalText(bool verticalText)
+
+    If true, text advance positions the next glyph below the previous glyph instead of to the
+    right of previous glyph.
+
+    Sets kVerticalText_Flag if vertical is true.
+    Clears kVerticalText_Flag if vertical is false.
+
+    #Param verticalText  setting for kVerticalText_Flag ##
+
+    #Example
+        SkPaint paint1, paint2;
+        paint1.setVerticalText(true);
+        paint2.setFlags(paint2.getFlags() | SkPaint::kVerticalText_Flag);
+        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+
+        #StdOut
+            paint1 == paint2
+        ##
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+
+#Topic Fake_Bold
+
+Fake_Bold approximates the bold font style accompanying a normal font when a bold font face
+is not available. Skia does not provide font substitution; it is up to the client to find the
+bold font face using the platform's Font_Manager.
+
+Use Text_Skew_X to approximate an italic font style when the italic font face 
+is not available.
+
+A FreeType-based port may define SK_USE_FREETYPE_EMBOLDEN at compile time to direct
+the font engine to create the bold glyphs. Otherwise, the extra bold is computed
+by increasing the stroke width and setting the Style to kStrokeAndFill_Style as needed.  
+
+Fake_Bold is disabled by default.
+
+#Example
+#Height 128
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(40);
+    canvas->drawString("OjYy_-", 10, 35, paint);
+    paint.setFakeBoldText(true);
+    canvas->drawString("OjYy_-", 10, 75, paint);
+    // create a custom fake bold by varying the stroke width
+    paint.setFakeBoldText(false);
+    paint.setStyle(SkPaint::kStrokeAndFill_Style);
+    paint.setStrokeWidth(40.f / 48);
+    canvas->drawString("OjYy_-", 10, 115, paint);
+}
+##
+
+#Method bool isFakeBoldText() const
+
+    If true, approximate bold by increasing the stroke width when creating glyph bitmaps
+    from outlines.
+
+    Equivalent to getFlags masked with kFakeBoldText_Flag.
+
+    #Return  kFakeBoldText_Flag state ##
+
+    #Example
+    SkPaint paint;
+    SkDebugf("paint.isFakeBoldText() %c= !!(paint.getFlags() & SkPaint::kFakeBoldText_Flag)\n",
+        paint.isFakeBoldText() == !!(paint.getFlags() & SkPaint::kFakeBoldText_Flag) ? '=' : '!');
+    paint.setFakeBoldText(true);
+    SkDebugf("paint.isFakeBoldText() %c= !!(paint.getFlags() & SkPaint::kFakeBoldText_Flag)\n",
+        paint.isFakeBoldText() == !!(paint.getFlags() & SkPaint::kFakeBoldText_Flag) ? '=' : '!');
+
+        #StdOut
+            paint.isFakeBoldText() == !!(paint.getFlags() & SkPaint::kFakeBoldText_Flag)
+            paint.isFakeBoldText() == !!(paint.getFlags() & SkPaint::kFakeBoldText_Flag)
+        ##
+    ##
+
+##
+
+#Method void setFakeBoldText(bool fakeBoldText)
+
+    Use increased stroke width when creating glyph bitmaps to approximate bolding.
+
+    Sets kFakeBoldText_Flag if fakeBoldText is true.
+    Clears kFakeBoldText_Flag if fakeBoldText is false.
+
+    #Param fakeBoldText  setting for kFakeBoldText_Flag ##
+
+    #Example
+        SkPaint paint1, paint2;
+        paint1.setFakeBoldText(true);
+        paint2.setFlags(paint2.getFlags() | SkPaint::kFakeBoldText_Flag);
+        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+
+        #StdOut
+            paint1 == paint2
+        ##
+    ##
+
+##
+
+#Topic ##
+
+# ------------------------------------------------------------------------------
+#Topic Full_Hinting_Spacing
+#Alias Full_Hinting_Spacing # long winded enough -- maybe things with two underscores auto-aliased?
+
+Full_Hinting_Spacing adjusts the character spacing by the difference of the 
+hinted and unhinted left and right side bearings, 
+if Hinting is set to kFull_Hinting. Full_Hinting_Spacing only
+applies to platforms that use FreeType as their Font_Engine.
+
+Full_Hinting_Spacing is not related to text kerning, where the space between
+a specific pair of characters is adjusted using data in the font's kerning tables.
+
+#Method bool isDevKernText() const
+
+    Returns if character spacing may be adjusted by the hinting difference.
+
+    Equivalent to getFlags masked with kDevKernText_Flag.
+
+    #Return  kDevKernText_Flag state ##
+
+    #Example
+    SkPaint paint;
+    SkDebugf("paint.isDevKernText() %c= !!(paint.getFlags() & SkPaint::kDevKernText_Flag)\n",
+        paint.isDevKernText() == !!(paint.getFlags() & SkPaint::kDevKernText_Flag) ? '=' : '!');
+    paint.setDevKernText(true);
+    SkDebugf("paint.isDevKernText() %c= !!(paint.getFlags() & SkPaint::kDevKernText_Flag)\n",
+        paint.isDevKernText() == !!(paint.getFlags() & SkPaint::kDevKernText_Flag) ? '=' : '!');
+    ##
+
+##
+
+#Method void setDevKernText(bool devKernText)
+
+    Requests, but does not require, to use hinting to adjust glyph spacing.
+
+    Sets kDevKernText_Flag if devKernText is true.
+    Clears kDevKernText_Flag if devKernText is false.
+
+    #Param devKernText  setting for devKernText ##
+
+    #Example
+        SkPaint paint1, paint2;
+        paint1.setDevKernText(true);
+        paint2.setFlags(paint2.getFlags() | SkPaint::kDevKernText_Flag);
+        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+
+        #StdOut
+            paint1 == paint2
+        ##
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Filter_Quality_Methods
+
+Filter_Quality trades speed for image filtering when the image is scaled.
+A lower Filter_Quality draws faster, but has less fidelity.
+A higher Filter_Quality draws slower, but looks better.
+If the image is unscaled, the Filter_Quality choice will not result in a noticable
+difference.
+
+Filter_Quality is used in Paint passed as a parameter to
+#List
+# SkCanvas::drawBitmap ##
+# SkCanvas::drawBitmapRect ##
+# SkCanvas::drawImage ##
+# SkCanvas::drawImageRect ##
+  #ToDo probably more... ##
+#List ##
+and when Paint has a Shader specialization that uses Image or Bitmap.
+
+Filter_Quality is kNone_SkFilterQuality by default.
+
+#Example
+#Image 3
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    canvas->scale(.2f, .2f);
+    for (SkFilterQuality q : { kNone_SkFilterQuality, kLow_SkFilterQuality, 
+                               kMedium_SkFilterQuality, kHigh_SkFilterQuality } ) {
+        paint.setFilterQuality(q);
+        canvas->drawImage(image.get(), 0, 0, &paint);
+        canvas->translate(550, 0);
+        if (kLow_SkFilterQuality == q) canvas->translate(-1100, 550);
+    }
+}
+##
+
+#Method SkFilterQuality getFilterQuality() const
+
+Returns Filter_Quality, the image filtering level. A lower setting
+draws faster; a higher setting looks better when the image is scaled.
+
+#Return  one of: kNone_SkFilterQuality, kLow_SkFilterQuality, 
+                 kMedium_SkFilterQuality, kHigh_SkFilterQuality
+#Return ##
+
+#Example
+    SkPaint paint;
+    SkDebugf("kNone_SkFilterQuality %c= paint.getFilterQuality()\n",
+            kNone_SkFilterQuality == paint.getFilterQuality() ? '=' : '!');
+
+    #StdOut
+        kNone_SkFilterQuality == paint.getFilterQuality()
+    ##
+##
+
+##
+
+
+#Method void setFilterQuality(SkFilterQuality quality)
+
+Sets Filter_Quality, the image filtering level. A lower setting
+draws faster; a higher setting looks better when the image is scaled.
+setFilterQuality does not check to see if quality is valid. 
+
+#Param  quality  one of: kNone_SkFilterQuality, kLow_SkFilterQuality, 
+                 kMedium_SkFilterQuality, kHigh_SkFilterQuality
+##
+
+#Example
+    SkPaint paint;
+    paint.setFilterQuality(kHigh_SkFilterQuality);
+    SkDebugf("kHigh_SkFilterQuality %c= paint.getFilterQuality()\n",
+            kHigh_SkFilterQuality == paint.getFilterQuality() ? '=' : '!');
+
+    #StdOut
+        kHigh_SkFilterQuality == paint.getFilterQuality()
+    ##
+##
+
+#SeeAlso SkFilterQuality Image_Scaling
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Color_Methods
+
+Color specifies the Color_RGB_Red, Color_RGB_Blue, Color_RGB_Green, and Color_Alpha values used to draw a filled
+or stroked shape in a
+32-bit value. Each component occupies 8-bits, ranging from zero: no contribution;
+to 255: full intensity. All values in any combination are valid.
+
+Color is not premultiplied;
+Color_Alpha sets the transparency independent of Color_RGB: Color_RGB_Red, Color_RGB_Blue, and Color_RGB_Green.
+
+The bit positions of Color_Alpha and Color_RGB are independent of the bit positions
+on the output device, which may have more or fewer bits, and may have a different arrangement.
+
+#Table
+#Legend
+# bit positions # Color_Alpha # Color_RGB_Red # Color_RGB_Blue # Color_RGB_Green ##
+#Legend ##
+#               # 31 - 24     # 23 - 16       # 15 - 8         # 7 - 0           ##
+#Table ##
+
+#Example
+#Height 128
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setColor(0x8000FF00);  // transparent green
+        canvas->drawCircle(50, 50, 40, paint);
+        paint.setARGB(128, 255, 0, 0); // transparent red
+        canvas->drawCircle(80, 50, 40, paint);
+        paint.setColor(SK_ColorBLUE);
+        paint.setAlpha(0x80);
+        canvas->drawCircle(65, 65, 40, paint);
+    }
+##
+
+#Method SkColor getColor() const
+
+    Retrieves Color_Alpha and Color_RGB, unpremultiplied, packed into 32 bits.
+    Use helpers SkColorGetA, SkColorGetR, SkColorGetG, and SkColorGetB to extract
+    a color component.
+
+    #Return  Unpremultiplied Color_ARGB ##
+
+    #Example
+        SkPaint paint;
+        paint.setColor(SK_ColorYELLOW);
+        SkColor y = paint.getColor();
+        SkDebugf("Yellow is %d%% red, %d%% green, and %d%% blue.\n", (int) (SkColorGetR(y) / 2.55f),
+                (int) (SkColorGetG(y) / 2.55f), (int) (SkColorGetB(y) / 2.55f));
+
+        #StdOut
+            Yellow is 100% red, 100% green, and 0% blue.
+        ##
+    ##
+
+    #SeeAlso SkColor
+
+##
+
+#Method void setColor(SkColor color)
+
+    Sets Color_Alpha and Color_RGB used when stroking and filling. The color is a 32-bit value,
+    unpremutiplied, packing 8-bit components for Color_Alpha, Color_RGB_Red, Color_RGB_Blue, and Color_RGB_Green. 
+
+    #Param color    Unpremultiplied Color_ARGB ##
+        
+    #Example
+        SkPaint green1, green2;
+        unsigned a = 255;
+        unsigned r = 0;
+        unsigned g = 255;
+        unsigned b = 0;
+        green1.setColor((a << 24) + (r << 16) + (g << 8) + (b << 0));
+        green2.setColor(0xFF00FF00);
+        SkDebugf("green1 %c= green2\n", green1 == green2 ? '=' : '!');
+
+        #StdOut
+            green1 == green2
+        ##
+    ##
+
+    #SeeAlso SkColor setARGB SkColorSetARGB
+
+##
+
+#Subtopic Alpha_Methods
+
+Color_Alpha sets the transparency independent of Color_RGB: Color_RGB_Red, Color_RGB_Blue, and Color_RGB_Green.
+
+#Method uint8_t getAlpha() const
+
+    Retrieves Color_Alpha from the Color used when stroking and filling.
+
+    #Return  Color_Alpha ranging from zero, fully transparent, to 255, fully opaque ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("255 %c= paint.getAlpha()\n", 255 == paint.getAlpha() ? '=' : '!');
+
+        #StdOut
+            255 == paint.getAlpha()
+        ##
+    ##
+
+##
+
+#Method void setAlpha(U8CPU a)
+
+    Replaces Color_Alpha, leaving Color_RGB 
+    unchanged. An out of range value triggers an assert in the debug
+    build. a is a value from zero to 255.
+    a set to zero makes Color fully transparent; a set to 255 makes Color
+    fully opaque.
+
+    #Param a    Color_Alpha component of Color ##
+
+    #Example
+        SkPaint paint;
+        paint.setColor(0x00112233);
+        paint.setAlpha(0x44);
+        SkDebugf("0x44112233 %c= paint.getColor()\n", 0x44112233 == paint.getColor() ? '=' : '!');
+
+        #StdOut
+            0x44112233 == paint.getColor()
+        ##
+    ##
+
+##
+
+#Subtopic ##
+
+#Method void setARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b)
+
+    Sets Color used when drawing solid fills. The color components range from 0 to 255.
+    The color is unpremultiplied;
+    Color_Alpha sets the transparency independent of Color_RGB.
+
+    #Param a    amount of Color_Alpha, from fully transparent (0) to fully opaque (255) ##
+    #Param r    amount of Color_RGB_Red, from no red (0) to full red (255) ##
+    #Param g    amount of Color_RGB_Green, from no green (0) to full green (255) ##
+    #Param b    amount of Color_RGB_Blue, from no blue (0) to full blue (255) ##
+        
+    #Example
+        SkPaint transRed1, transRed2;
+        transRed1.setARGB(255 / 2, 255, 0, 0);
+        transRed2.setColor(SkColorSetARGB(255 / 2, 255, 0, 0));
+        SkDebugf("transRed1 %c= transRed2", transRed1 == transRed2 ? '=' : '!');
+
+        #StdOut
+            transRed1 == transRed2
+        ##
+    ##
+
+    #SeeAlso setColor SkColorSetARGB
+
+##
+
+#Topic Color_Methods ##
+
+# ------------------------------------------------------------------------------
+#Topic Style
+
+Style specifies if the geometry is filled, stroked, or both filled and stroked.
+Some shapes ignore Style and are always drawn filled or stroked.
+
+Set Style to kFill_Style to fill the shape.
+The fill covers the area inside the geometry for most shapes.
+
+Set Style to kStroke_Style to stroke the shape.
+
+# ------------------------------------------------------------------------------
+#Subtopic Fill
+
+#ToDo write up whatever generalities make sense to describe filling  ##
+
+#SeeAlso Path_Fill_Type
+#Subtopic ##
+
+#Subtopic Stroke
+The stroke covers the area described by following the shape's edge with a pen or brush of
+Stroke_Width. The area covered where the shape starts and stops is described by Stroke_Cap.
+The area covered where the shape turns a corner is described by Stroke_Join.
+The stroke is centered on the shape; it extends equally on either side of the shape's edge.
+
+As Stroke_Width gets smaller, the drawn path frame is thinner. Stroke_Width less than one
+may have gaps, and if kAntiAlias_Flag is set, Color_Alpha will increase to visually decrease coverage.
+#Subtopic ##
+
+#Subtopic Hairline
+#Alias Hairline # maybe should be Stroke_Hairline ?
+
+Stroke_Width of zero has a special meaning and switches drawing to use Hairline.
+Hairline draws the thinnest continuous frame. If kAntiAlias_Flag is clear, adjacent pixels 
+flow horizontally, vertically,or diagonally. 
+
+#ToDo  what is the description of anti-aliased hairlines? ##
+
+Path drawing with Hairline may hit the same pixel more than once. For instance, Path containing
+two lines in one Path_Contour will draw the corner point once, but may both lines may draw the adjacent
+pixel. If kAntiAlias_Flag is set, transparency is applied twice, resulting in a darker pixel. Some
+GPU-backed implementations apply transparency at a later drawing stage, avoiding double hit pixels
+while stroking.
+
+#Subtopic ##
+
+#Enum Style
+
+#Code
+    enum Style {
+        kFill_Style,
+        kStroke_Style,
+        kStrokeAndFill_Style,
+    };
+##
+
+Set Style to fill, stroke, or both fill and stroke geometry.
+The stroke and fill
+share all paint attributes; for instance, they are drawn with the same color.
+
+Use kStrokeAndFill_Style to avoid hitting the same pixels twice with a stroke draw and
+a fill draw.
+
+#Const  kFill_Style 0
+    Set to fill geometry.
+    Applies to Rect, Region, Round_Rect, Circle, Oval, Path, and Text. 
+    Bitmap, Image, Patch, Region, Sprite, and Vertices are painted as if
+    kFill_Style is set, and ignore the set Style.
+    The Path_Fill_Type specifies additional rules to fill the area outside the path edge,
+    and to create an unfilled hole inside the shape.
+    Style is set to kFill_Style by default.
+##
+
+#Const kStroke_Style 1
+    Set to stroke geometry.
+    Applies to Rect, Region, Round_Rect, Arc, Circle, Oval,
+    Path, and Text. 
+    Arc, Line, Point, and Point_Array are always drawn as if kStroke_Style is set,
+    and ignore the set Style.
+    The stroke construction is unaffected by the Path_Fill_Type.
+##
+
+#Const kStrokeAndFill_Style 2
+    Set to stroke and fill geometry.
+    Applies to Rect, Region, Round_Rect, Circle, Oval, Path, and Text.
+    Path is treated as if it is set to SkPath::kWinding_FillType,
+    and the set Path_Fill_Type is ignored. 
+##
+
+#Enum ##
+
+#Enum
+
+#Code
+    enum {
+        kStyleCount = kStrokeAndFill_Style + 1
+    };
+##
+
+#Const kStyleCount 3
+The number of different Style values defined.
+May be used to verify that Style is a legal value.
+##
+
+#Enum ##
+
+#Method Style getStyle() const
+
+    Whether the geometry is filled, stroked, or filled and stroked.
+
+    #Return  one of:kFill_Style, kStroke_Style, kStrokeAndFill_Style ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("SkPaint::kFill_Style %c= paint.getStyle()\n",
+                SkPaint::kFill_Style == paint.getStyle() ? '=' : '!');
+
+        #StdOut
+            SkPaint::kFill_Style == paint.getStyle()
+        ##
+    ##
+
+#SeeAlso Style setStyle
+##
+
+#Method void setStyle(Style style)
+
+    Sets whether the geometry is filled, stroked, or filled and stroked.
+    Has no effect if style is not a legal Style value.
+
+    #Param style  one of: kFill_Style, kStroke_Style, kStrokeAndFill_Style
+    ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setStrokeWidth(5);
+            SkRegion region;
+            region.op(140, 10, 160, 30, SkRegion::kUnion_Op);
+            region.op(170, 40, 190, 60, SkRegion::kUnion_Op);
+            SkBitmap bitmap;
+            bitmap.setInfo(SkImageInfo::MakeA8(50, 50), 50);
+            uint8_t pixels[50][50];
+            for (int x = 0; x < 50; ++x) {
+                for (int y = 0; y < 50; ++y) {
+                    pixels[y][x] = (x + y) % 5 ? 0xFF : 0x00;
+                }
+            }
+            bitmap.setPixels(pixels);
+            for (auto style : { SkPaint::kFill_Style,
+                                SkPaint::kStroke_Style,
+                                SkPaint::kStrokeAndFill_Style }) {
+                paint.setStyle(style);
+                canvas->drawLine(10, 10, 60, 60, paint);
+                canvas->drawRect({80, 10, 130, 60}, paint);
+                canvas->drawRegion(region, paint);
+                canvas->drawBitmap(bitmap, 200, 10, &paint);
+                canvas->translate(0, 80);
+            }
+        }
+    ##
+
+#SeeAlso Style getStyle
+##
+
+#SeeAlso Path_Fill_Type Path_Effect Style_Fill Style_Stroke
+#Topic Style ##
+
+# ------------------------------------------------------------------------------
+#Topic Stroke_Width
+
+Stroke_Width sets the width for stroking. The width is the thickness
+of the stroke perpendicular to the path's direction when the paint's style is 
+set to kStroke_Style or kStrokeAndFill_Style.
+
+When width is greater than zero, the stroke encompasses as many pixels partially
+or fully as needed. When the width equals zero, the paint enables hairlines;
+the stroke is always one pixel wide. 
+
+The stroke's dimensions are scaled by the canvas matrix, but Hairline stroke
+remains one pixel wide regardless of scaling.
+
+The default width for the paint is zero.
+
+#Example
+#Height 170
+    #Platform raster gpu
+    #Description
+        The pixels hit to represent thin lines vary with the angle of the 
+        line and the platform's implementation.
+    ##
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        for (bool antialias : { false, true }) { 
+            paint.setAntiAlias(antialias);
+            for (int width = 0; width <= 4; ++width) {
+                SkScalar offset = antialias * 100 + width * 20;
+                paint.setStrokeWidth(width * 0.25f);
+                canvas->drawLine(10 + offset,  10, 20 + offset,  60, paint);
+                canvas->drawLine(10 + offset, 110, 60 + offset, 160, paint);
+            }
+        }
+    }
+##
+
+#Method SkScalar getStrokeWidth() const
+
+    Returns the thickness of the pen used by Paint to
+    outline the shape.
+
+    #Return  zero for Hairline, greater than zero for pen thickness ##
+     
+    #Example
+        SkPaint paint;
+        SkDebugf("0 %c= paint.getStrokeWidth()\n", 0 == paint.getStrokeWidth() ? '=' : '!');
+
+        #StdOut
+            0 == paint.getStrokeWidth()
+        ##
+    ##
+
+##
+
+#Method void setStrokeWidth(SkScalar width)
+
+    Sets the thickness of the pen used by the paint to
+    outline the shape. 
+    Has no effect if width is less than zero. 
+
+    #Param width  zero thickness for Hairline; greater than zero for pen thickness 
+    ##
+
+    #Example
+        SkPaint paint;
+        paint.setStrokeWidth(5);
+        paint.setStrokeWidth(-1);
+        SkDebugf("5 %c= paint.getStrokeWidth()\n", 5 == paint.getStrokeWidth() ? '=' : '!');
+
+        #StdOut
+            5 == paint.getStrokeWidth()
+        ##
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Miter_Limit
+
+Miter_Limit specifies the maximum miter length,
+relative to the stroke width.
+
+Miter_Limit is used when the Stroke_Join
+is set to kMiter_Join, and the Style is either kStroke_Style
+or kStrokeAndFill_Style.
+
+If the miter at a corner exceeds this limit, kMiter_Join
+is replaced with kBevel_Join.
+
+Miter_Limit can be computed from the corner angle:
+
+#Formula
+    miter limit = 1 / sin ( angle / 2 )
+#Formula ##
+
+Miter_Limit default value is 4.
+The default may be changed at compile time by setting SkPaintDefaults_MiterLimit
+in SkUserConfig.h or as a define supplied by the build environment.
+
+Here are some miter limits and the angles that triggers them.
+#Table
+#Legend
+    # miter limit    # angle in degrees ##
+#Legend ##
+    # 10             # 11.48            ##
+    # 9              # 12.76            ##
+    # 8              # 14.36            ##
+    # 7              # 16.43            ##
+    # 6              # 19.19            ##
+    # 5              # 23.07            ##
+    # 4              # 28.96            ##
+    # 3              # 38.94            ##
+    # 2              # 60               ##
+    # 1              # 180              ##
+#Table ##
+
+#Example
+    #Height 170
+    #Width 384
+    #Description
+        This example draws a stroked corner and the miter length beneath.
+        When the miter limit is decreased slightly, the miter join is replaced
+        by a bevel join.
+    ##
+    void draw(SkCanvas* canvas) {
+        SkPoint pts[] = {{ 10, 50 }, { 110, 80 }, { 10, 110 }};
+        SkVector v[] = { pts[0] - pts[1], pts[2] - pts[1] };
+        SkScalar angle1 = SkScalarATan2(v[0].fY, v[0].fX);
+        SkScalar angle2 = SkScalarATan2(v[1].fY, v[1].fX);
+        const SkScalar strokeWidth = 20;
+        SkScalar miterLimit = 1 / SkScalarSin((angle2 - angle1) / 2);
+        SkScalar miterLength = strokeWidth * miterLimit;
+        SkPath path;
+        path.moveTo(pts[0]);
+        path.lineTo(pts[1]);
+        path.lineTo(pts[2]);
+        SkPaint paint;  // set to default kMiter_Join
+        paint.setAntiAlias(true);
+        paint.setStyle(SkPaint::kStroke_Style);
+        paint.setStrokeMiter(miterLimit);
+        paint.setStrokeWidth(strokeWidth);
+        canvas->drawPath(path, paint);
+        paint.setStrokeWidth(1);
+        canvas->drawLine(pts[1].fX - miterLength / 2, pts[1].fY + 50,
+                         pts[1].fX + miterLength / 2, pts[1].fY + 50, paint);
+        canvas->translate(200, 0);
+        miterLimit *= 0.99f;
+        paint.setStrokeMiter(miterLimit);
+        paint.setStrokeWidth(strokeWidth);
+        canvas->drawPath(path, paint);
+        paint.setStrokeWidth(1);
+        canvas->drawLine(pts[1].fX - miterLength / 2, pts[1].fY + 50,
+                         pts[1].fX + miterLength / 2, pts[1].fY + 50, paint);
+    }
+##
+
+#Method SkScalar getStrokeMiter() const
+
+    The limit at which a sharp corner is drawn beveled.
+
+    #Return  zero and greater Miter_Limit ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("default miter limit == %g\n", paint.getStrokeMiter());
+
+        #StdOut
+        default miter limit == 4
+        ##
+    ##
+
+    #SeeAlso Miter_Limit setStrokeMiter Join
+
+##
+
+#Method void setStrokeMiter(SkScalar miter)
+
+    The limit at which a sharp corner is drawn beveled.
+    Valid values are zero and greater.
+    Has no effect if miter is less than zero.
+
+    #Param miter  zero and greater Miter_Limit
+    ##
+
+    #Example
+        SkPaint paint;
+        paint.setStrokeMiter(8);
+        paint.setStrokeMiter(-1);
+        SkDebugf("default miter limit == %g\n", paint.getStrokeMiter());
+
+        #StdOut
+        default miter limit == 8
+        ##
+    ##
+
+    #SeeAlso Miter_Limit getStrokeMiter Join
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Stroke_Cap
+
+#Enum Cap
+
+#Code
+    enum Cap {
+        kButt_Cap,
+        kRound_Cap,
+        kSquare_Cap,
+
+        kLast_Cap = kSquare_Cap,
+        kDefault_Cap = kButt_Cap
+    };
+    static constexpr int kCapCount = kLast_Cap + 1;
+##
+
+Stroke_Cap draws at the beginning and end of an open Path_Contour.
+
+    #Const kButt_Cap 0
+        Does not extend the stroke past the beginning or the end.
+    ##
+    #Const kRound_Cap 1
+        Adds a circle with a diameter equal to Stroke_Width at the beginning
+        and end.
+    ##
+    #Const kSquare_Cap 2
+        Adds a square with sides equal to Stroke_Width at the beginning
+        and end. The square sides are parallel to the initial and final direction
+        of the stroke.
+    ##
+    #Const kLast_Cap 2
+        Equivalent to the largest value for Stroke_Cap.
+    ##
+    #Const kDefault_Cap 0
+        Equivalent to kButt_Cap.
+        Stroke_Cap is set to kButt_Cap by default.
+    ##
+
+    #Const kCapCount 3
+        The number of different Stroke_Cap values defined.
+        May be used to verify that Stroke_Cap is a legal value.
+    ##
+#Enum ##
+
+Stroke describes the area covered by a pen of Stroke_Width as it 
+follows the Path_Contour, moving parallel to the contours's direction.
+
+If the Path_Contour is not terminated by SkPath::kClose_Verb, the contour has a
+visible beginning and end.
+
+Path_Contour may start and end at the same point; defining Zero_Length_Contour.
+
+kButt_Cap and Zero_Length_Contour is not drawn.
+kRound_Cap and Zero_Length_Contour draws a circle of diameter Stroke_Width 
+at the contour point.
+kSquare_Cap and Zero_Length_Contour draws an upright square with a side of
+Stroke_Width at the contour point.
+
+Stroke_Cap is kButt_Cap by default.
+
+#Example
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(20);
+    SkPath path;
+    path.moveTo(30, 30);
+    path.lineTo(30, 30);
+    path.moveTo(70, 30);
+    path.lineTo(90, 40);
+    for (SkPaint::Cap c : { SkPaint::kButt_Cap, SkPaint::kRound_Cap, SkPaint::kSquare_Cap } ) {
+        paint.setStrokeCap(c);
+        canvas->drawPath(path, paint);
+        canvas->translate(0, 70);
+    }
+##
+
+#Method Cap getStrokeCap() const
+
+    The geometry drawn at the beginning and end of strokes.
+
+    #Return  one of: kButt_Cap, kRound_Cap, kSquare_Cap ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("kButt_Cap %c= default stroke cap\n",
+                SkPaint::kButt_Cap == paint.getStrokeCap() ? '=' : '!');
+
+        #StdOut
+            kButt_Cap == default stroke cap
+        ##
+    ##
+
+    #SeeAlso Stroke_Cap setStrokeCap
+##
+
+#Method void setStrokeCap(Cap cap)
+
+    The geometry drawn at the beginning and end of strokes.
+
+    #Param cap  one of: kButt_Cap, kRound_Cap, kSquare_Cap;
+                has no effect if cap is not valid
+    ## 
+
+    #Example
+        SkPaint paint;
+        paint.setStrokeCap(SkPaint::kRound_Cap);
+        paint.setStrokeCap((SkPaint::Cap) SkPaint::kCapCount);
+        SkDebugf("kRound_Cap %c= paint.getStrokeCap()\n",
+                SkPaint::kRound_Cap == paint.getStrokeCap() ? '=' : '!');
+    
+        #StdOut
+            kRound_Cap == paint.getStrokeCap()
+        ##
+    ##
+
+    #SeeAlso Stroke_Cap getStrokeCap
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Stroke_Join
+
+Stroke_Join draws at the sharp corners of an open or closed Path_Contour.
+
+Stroke describes the area covered by a pen of Stroke_Width as it 
+follows the Path_Contour, moving parallel to the contours's direction.
+
+If the contour direction changes abruptly, because the tangent direction leading
+to the end of a curve within the contour does not match the tangent direction of
+the following curve, the pair of curves meet at Stroke_Join.
+
+#Example
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(20);
+    SkPath path;
+    path.moveTo(30, 30);
+    path.lineTo(40, 50);
+    path.conicTo(70, 30, 100, 30, .707f);
+    for (SkPaint::Join j : { SkPaint::kMiter_Join, SkPaint::kRound_Join, SkPaint::kBevel_Join } ) {
+        paint.setStrokeJoin(j);
+        canvas->drawPath(path, paint);
+        canvas->translate(0, 70);
+    }
+##
+
+#Enum Join
+#Code
+    enum Join {
+        kMiter_Join,
+        kRound_Join,
+        kBevel_Join,
+
+        kLast_Join = kBevel_Join,
+        kDefault_Join = kMiter_Join
+    };
+    static constexpr int kJoinCount = kLast_Join + 1;
+##
+
+Join specifies how corners are drawn when a shape is stroked. The paint's Join setting
+affects the four corners of a stroked rectangle, and the connected segments in a
+stroked path.
+
+Choose miter join to draw sharp corners. Choose round join to draw a circle with a
+radius equal to the stroke width on top of the corner. Choose bevel join to minimally
+connect the thick strokes.
+
+The fill path constructed to describe the stroked path respects the join setting but may 
+not contain the actual join. For instance, a fill path constructed with round joins does
+not necessarily include circles at each connected segment.
+
+#Const kMiter_Join 0
+    Extends the outside corner to the extent allowed by Miter_Limit.
+    If the extension exceeds Miter_Limit, kBevel_Join is used instead.
+##
+
+#Const kRound_Join 1
+    Adds a circle with a diameter of Stroke_Width at the sharp corner.
+##
+
+#Const kBevel_Join 2
+    Connects the outside edges of the sharp corner.
+##
+
+#Const kLast_Join 2
+    Equivalent to the largest value for Stroke_Join.
+##
+
+#Const kDefault_Join 1
+    Equivalent to kMiter_Join.
+    Stroke_Join is set to kMiter_Join by default. 
+##
+
+#Const kJoinCount 3
+    The number of different Stroke_Join values defined.
+    May be used to verify that Stroke_Join is a legal value.
+##
+
+#Example
+#Width 462
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.moveTo(10, 50);
+    path.quadTo(35, 110, 60, 210);
+    path.quadTo(105, 110, 130, 10);
+    SkPaint paint;  // set to default kMiter_Join
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(20);
+    canvas->drawPath(path, paint);
+    canvas->translate(150, 0);
+    paint.setStrokeJoin(SkPaint::kBevel_Join);
+    canvas->drawPath(path, paint);
+    canvas->translate(150, 0);
+    paint.setStrokeJoin(SkPaint::kRound_Join);
+    canvas->drawPath(path, paint);
+}
+##
+
+#SeeAlso setStrokeJoin getStrokeJoin setStrokeMiter getStrokeMiter
+
+#Enum ##
+
+#Method Join getStrokeJoin() const
+
+    The geometry drawn at the corners of strokes. 
+
+    #Return  one of: kMiter_Join, kRound_Join, kBevel_Join ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("kMiter_Join %c= default stroke join\n",
+                SkPaint::kMiter_Join == paint.getStrokeJoin() ? '=' : '!');
+
+        #StdOut
+            kMiter_Join == default stroke join
+        ##
+    ##
+
+    #SeeAlso Stroke_Join setStrokeJoin
+##
+
+#Method void setStrokeJoin(Join join)
+
+    The geometry drawn at the corners of strokes. 
+
+    #Param join  one of: kMiter_Join, kRound_Join, kBevel_Join;
+                 otherwise, setStrokeJoin has no effect 
+    ##
+
+    #Example
+        SkPaint paint;
+        paint.setStrokeJoin(SkPaint::kMiter_Join);
+        paint.setStrokeJoin((SkPaint::Join) SkPaint::kJoinCount);
+        SkDebugf("kMiter_Join %c= paint.getStrokeJoin()\n",
+                SkPaint::kMiter_Join == paint.getStrokeJoin() ? '=' : '!');
+
+        #StdOut
+            kMiter_Join == paint.getStrokeJoin()
+        ##
+    ##
+
+    #SeeAlso Stroke_Join getStrokeJoin
+##
+
+#SeeAlso Miter_Limit
+
+#Topic Stroke_Join ##
+# ------------------------------------------------------------------------------
+#Topic Fill_Path
+
+Fill_Path creates a Path by applying the Path_Effect, followed by the Style_Stroke.
+
+If Paint contains Path_Effect, Path_Effect operates on the source Path; the result
+replaces the destination Path. Otherwise, the source Path is replaces the
+destination Path.
+
+Fill Path can request the Path_Effect to restrict to a culling rectangle, but
+the Path_Effect is not required to do so.
+
+If Style is kStroke_Style or kStrokeAndFill_Style, 
+and Stroke_Width is greater than zero, the Stroke_Width, Stroke_Cap, Stroke_Join,
+and Miter_Limit operate on the destination Path, replacing it.
+
+Fill Path can specify the precision used by Stroke_Width to approximate the stroke geometry. 
+
+If the Style is kStroke_Style and the Stroke_Width is zero, getFillPath
+returns false since Hairline has no filled equivalent.
+
+#Method bool getFillPath(const SkPath& src, SkPath* dst, const SkRect* cullRect,
+                     SkScalar resScale = 1) const
+
+    The filled equivalent of the stroked path.
+
+    #Param src       Path read to create a filled version ##
+    #Param dst       resulting Path; may be the same as src, but may not be nullptr ##
+    #Param cullRect  optional limit passed to Path_Effect ##
+    #Param resScale  if > 1, increase precision, else if (0 < res < 1) reduce precision
+                     to favor speed and size
+    ##
+    #Return         true if the path represents Style_Fill, or false if it represents Hairline ##
+
+    #Example
+    #Height 192
+    #Description
+    A very small quad stroke is turned into a filled path with increasing levels of precision.
+    At the lowest precision, the quad stroke is approximated by a rectangle. 
+    At the highest precision, the filled path has high fidelity compared to the original stroke.
+    ##
+        void draw(SkCanvas* canvas) {
+            SkPaint strokePaint;
+            strokePaint.setAntiAlias(true);
+            strokePaint.setStyle(SkPaint::kStroke_Style);
+            strokePaint.setStrokeWidth(.1f);
+            SkPath strokePath;
+            strokePath.moveTo(.08f, .08f);
+            strokePath.quadTo(.09f, .08f, .17f, .17f);
+            SkPath fillPath;
+            SkPaint outlinePaint(strokePaint);
+            outlinePaint.setStrokeWidth(2);
+            SkMatrix scale = SkMatrix::MakeScale(300, 300);
+            for (SkScalar precision : { 0.01f, .1f, 1.f, 10.f, 100.f } ) {
+                strokePaint.getFillPath(strokePath, &fillPath, nullptr, precision);
+                fillPath.transform(scale);
+                canvas->drawPath(fillPath, outlinePaint);
+                canvas->translate(60, 0);
+                if (1.f == precision) canvas->translate(-180, 100);
+            }
+            strokePath.transform(scale);
+            strokePaint.setStrokeWidth(30);
+            canvas->drawPath(strokePath, strokePaint);
+        }
+    ##
+
+##
+
+#Method bool getFillPath(const SkPath& src, SkPath* dst) const
+
+    The filled equivalent of the stroked path.
+
+    Replaces dst with the src path modified by Path_Effect and Style_Stroke.
+    Path_Effect, if any, is not culled. Stroke_Width is created with default precision.
+
+    #Param src  Path read to create a filled version ##
+    #Param dst  resulting Path dst may be the same as src, but may not be nullptr ##
+    #Return     true if the path represents Style_Fill, or false if it represents Hairline ##
+
+    #Example
+    #Height 128
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setStyle(SkPaint::kStroke_Style);
+            paint.setStrokeWidth(10);
+            SkPath strokePath;
+            strokePath.moveTo(20, 20);
+            strokePath.lineTo(100, 100);
+            canvas->drawPath(strokePath, paint);
+            SkPath fillPath;
+            paint.getFillPath(strokePath, &fillPath);
+            paint.setStrokeWidth(2);
+            canvas->translate(40, 0);
+            canvas->drawPath(fillPath, paint);
+        }
+    ##
+
+##
+
+#SeeAlso Style_Stroke Stroke_Width Path_Effect
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Shader_Methods
+
+Shader defines the colors used when drawing a shape.
+Shader may be an image, a gradient, or a computed fill.
+If Paint has no Shader, then Color fills the shape. 
+
+Shader is modulated by Color_Alpha component of Color.
+If Shader object defines only Color_Alpha, then Color modulated by Color_Alpha describes
+the fill.
+
+The drawn transparency can be modified without altering Shader, by changing Color_Alpha.
+
+#Example
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   SkPoint center = { 50, 50 };
+   SkScalar radius = 50;
+   const SkColor colors[] = { 0xFFFFFFFF, 0xFF000000 };
+   paint.setShader(SkGradientShader::MakeRadial(center, radius, colors,
+        nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
+   for (SkScalar a : { 0.3f, 0.6f, 1.0f } ) {
+       paint.setAlpha((int) (a * 255));
+       canvas->drawCircle(center.fX, center.fY, radius, paint);
+       canvas->translate(70, 70);
+   }
+}
+##
+
+If Shader generates only Color_Alpha then all components of Color modulate the output.
+
+#Example
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   SkBitmap bitmap;
+   bitmap.setInfo(SkImageInfo::MakeA8(5, 1), 5);  // bitmap only contains alpha
+   uint8_t pixels[5] = { 0x22, 0x55, 0x88, 0xBB, 0xFF };
+   bitmap.setPixels(pixels);
+   paint.setShader(SkShader::MakeBitmapShader(bitmap, 
+            SkShader::kMirror_TileMode, SkShader::kMirror_TileMode));
+   for (SkColor c : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
+       paint.setColor(c);  // all components in color affect shader
+       canvas->drawCircle(50, 50, 50, paint);
+       canvas->translate(70, 70);
+   }
+}
+##
+
+#Method SkShader* getShader() const
+
+    Optional colors used when filling a path, such as a gradient.
+
+    Does not alter Shader Reference_Count.
+    
+    #Return  Shader if previously set, nullptr otherwise ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+           SkPaint paint;
+           SkDebugf("nullptr %c= shader\n", paint.getShader() ? '!' : '=');
+           paint.setShader(SkShader::MakeEmptyShader());
+           SkDebugf("nullptr %c= shader\n", paint.getShader() ? '!' : '=');
+        }
+
+        #StdOut
+            nullptr == shader
+            nullptr != shader
+        ##
+    ##
+
+##
+
+#Method sk_sp<SkShader> refShader() const
+
+    Optional colors used when filling a path, such as a gradient.
+
+    Increases Shader Reference_Count by one.
+
+    #Return  Shader if previously set, nullptr otherwise ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+           SkPaint paint1, paint2;
+           paint1.setShader(SkShader::MakeEmptyShader());
+           SkDebugf("shader unique: %s\n", paint1.getShader()->unique() ? "true" : "false");
+           paint2.setShader(paint1.refShader());
+           SkDebugf("shader unique: %s\n", paint1.getShader()->unique() ? "true" : "false");
+        }
+
+        #StdOut
+            shader unique: true
+            shader unique: false
+        ##
+    ##
+
+##
+
+#Method void setShader(sk_sp<SkShader> shader)
+
+    Optional colors used when filling a path, such as a gradient.
+
+    Sets Shader to shader, decrementing Reference_Count of the previous Shader.
+    Does not alter shader Reference_Count.
+
+    #Param shader  how geometry is filled with color; if nullptr, Color is used instead ##
+
+    #Example
+    #Height 64
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setColor(SK_ColorBLUE);
+            paint.setShader(SkShader::MakeColorShader(SK_ColorRED));
+            canvas->drawRect(SkRect::MakeWH(40, 40), paint);
+            paint.setShader(nullptr);
+            canvas->translate(50, 0);
+            canvas->drawRect(SkRect::MakeWH(40, 40), paint);
+        }
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Color_Filter_Methods
+
+Color_Filter alters the color used when drawing a shape.
+Color_Filter may apply Blend_Mode, transform the color through a matrix, or composite multiple filters.
+If Paint has no Color_Filter, the color is unaltered.
+
+The drawn transparency can be modified without altering Color_Filter, by changing Color_Alpha.
+
+#Example
+#Height 128
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColorFilter(SkColorMatrixFilter::MakeLightingFilter(0xFFFFFF, 0xFF0000));
+    for (SkColor c : { SK_ColorBLACK, SK_ColorGREEN } ) {
+        paint.setColor(c);
+        canvas->drawRect(SkRect::MakeXYWH(10, 10, 50, 50), paint);
+        paint.setAlpha(0x80);
+        canvas->drawRect(SkRect::MakeXYWH(60, 60, 50, 50), paint);
+        canvas->translate(100, 0);
+    }
+}
+##
+
+#Method SkColorFilter* getColorFilter() const
+
+    Returns Color_Filter if set, or nullptr.
+    Does not alter Color_Filter Reference_Count.
+    
+    #Return  Color_Filter if previously set, nullptr otherwise ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+           SkPaint paint;
+           SkDebugf("nullptr %c= color filter\n", paint.getColorFilter() ? '!' : '=');
+           paint.setColorFilter(SkColorFilter::MakeModeFilter(SK_ColorLTGRAY, SkBlendMode::kSrcIn));
+           SkDebugf("nullptr %c= color filter\n", paint.getColorFilter() ? '!' : '=');
+        }
+
+        #StdOut
+            nullptr == color filter
+            nullptr != color filter
+        ##
+    ##
+##
+
+#Method sk_sp<SkColorFilter> refColorFilter() const
+
+    Returns Color_Filter if set, or nullptr.
+    Increases Color_Filter Reference_Count by one.
+
+    #Return  Color_Filter if set, or nullptr ##
+
+    #Example
+    void draw(SkCanvas* canvas) {
+        SkPaint paint1, paint2;
+        paint1.setColorFilter(SkColorFilter::MakeModeFilter(0xFFFF0000, SkBlendMode::kSrcATop));
+        SkDebugf("color filter unique: %s\n", paint1.getColorFilter()->unique() ? "true" : "false");
+        paint2.setColorFilter(paint1.refColorFilter());
+        SkDebugf("color filter unique: %s\n", paint1.getColorFilter()->unique() ? "true" : "false");
+    }
+
+        #StdOut
+            color filter unique: true
+            color filter unique: false
+        ##
+    ##
+##
+
+#Method void setColorFilter(sk_sp<SkColorFilter> colorFilter)
+
+    Sets Color_Filter to filter, decrementing Reference_Count of the previous Color_Filter. 
+    Pass nullptr to clear Color_Filter.
+    Does not alter filter Reference_Count.
+
+    #Param colorFilter  Color_Filter to apply to subsequent draw ##
+
+    #Example
+    #Height 64
+        void draw(SkCanvas* canvas) {
+           SkPaint paint;
+           paint.setColorFilter(SkColorFilter::MakeModeFilter(SK_ColorLTGRAY, SkBlendMode::kSrcIn));
+           canvas->drawRect(SkRect::MakeWH(50, 50), paint);
+           paint.setColorFilter(nullptr);
+           canvas->translate(70, 0);
+           canvas->drawRect(SkRect::MakeWH(50, 50), paint);
+        }
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Blend_Mode_Methods
+
+Blend_Mode describes how Color combines with the destination color.
+The default setting, SkBlendMode::kSrcOver, draws the source color
+over the destination color.
+
+#Example
+void draw(SkCanvas* canvas) {
+    SkPaint normal, blender;
+    normal.setColor(0xFF58a889);
+    blender.setColor(0xFF8958a8);
+    canvas->clear(0);
+    for (SkBlendMode m : { SkBlendMode::kSrcOver, SkBlendMode::kSrcIn, SkBlendMode::kSrcOut } ) {
+        normal.setBlendMode(SkBlendMode::kSrcOver);
+        canvas->drawOval(SkRect::MakeXYWH(30, 30, 30, 80), normal);
+        blender.setBlendMode(m);
+        canvas->drawOval(SkRect::MakeXYWH(10, 50, 80, 30), blender);
+        canvas->translate(70, 70);
+    }
+}
+##
+
+#SeeAlso Blend_Mode
+
+#Method SkBlendMode getBlendMode() const
+
+    Returns Blend_Mode.
+    By default, getBlendMode returns SkBlendMode::kSrcOver.
+
+    #Return  mode used to combine source color with destination color ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+           SkPaint paint;
+           SkDebugf("kSrcOver %c= getBlendMode\n", 
+                    SkBlendMode::kSrcOver == paint.getBlendMode() ? '=' : '!');
+           paint.setBlendMode(SkBlendMode::kSrc);
+           SkDebugf("kSrcOver %c= getBlendMode\n", 
+                    SkBlendMode::kSrcOver == paint.getBlendMode() ? '=' : '!');
+        }
+
+        #StdOut
+            kSrcOver == getBlendMode
+            kSrcOver != getBlendMode
+        ##
+    ##
+
+##
+
+#Method bool isSrcOver() const
+
+    Returns true if Blend_Mode is SkBlendMode::kSrcOver, the default.
+
+    #Return  true if Blend_Mode is SkBlendMode::kSrcOver  ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+           SkPaint paint;
+           SkDebugf("isSrcOver %c= true\n", paint.isSrcOver() ? '=' : '!');
+           paint.setBlendMode(SkBlendMode::kSrc);
+           SkDebugf("isSrcOver %c= true\n", paint.isSrcOver() ? '=' : '!');
+        }
+
+        #StdOut
+            isSrcOver == true
+            isSrcOver != true
+        ##
+    ##
+
+##
+
+#Method void setBlendMode(SkBlendMode mode)
+
+    Sets Blend_Mode to mode. 
+    Does not check for valid input.
+
+    #Param mode  SkBlendMode used to combine source color and destination ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+           SkPaint paint;
+           SkDebugf("isSrcOver %c= true\n", paint.isSrcOver() ? '=' : '!');
+           paint.setBlendMode(SkBlendMode::kSrc);
+           SkDebugf("isSrcOver %c= true\n", paint.isSrcOver() ? '=' : '!');
+        }
+
+        #StdOut
+            isSrcOver == true
+            isSrcOver != true
+        ##
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Path_Effect_Methods
+
+Path_Effect modifies the path geometry before drawing it.
+Path_Effect may implement dashing, custom fill effects and custom stroke effects.
+If Paint has no Path_Effect, the path geometry is unaltered when filled or stroked.
+
+#Example
+#Height 160
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setStyle(SkPaint::kStroke_Style);
+            paint.setStrokeWidth(16);
+            SkScalar intervals[] = {30, 10};
+            paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 1));
+            canvas->drawRoundRect({20, 20, 120, 120}, 20, 20, paint);
+        }
+##
+
+#SeeAlso Path_Effect
+
+#Method SkPathEffect* getPathEffect() const
+
+    Returns Path_Effect if set, or nullptr.
+    Does not alter Path_Effect Reference_Count.
+    
+    #Return  Path_Effect if previously set, nullptr otherwise ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+           SkPaint paint;
+           SkDebugf("nullptr %c= path effect\n", paint.getPathEffect() ? '!' : '=');
+           paint.setPathEffect(SkCornerPathEffect::Make(10));
+           SkDebugf("nullptr %c= path effect\n", paint.getPathEffect() ? '!' : '=');
+        }
+
+        #StdOut
+            nullptr == path effect
+            nullptr != path effect
+        ##
+    ##
+
+##
+
+
+#Method sk_sp<SkPathEffect> refPathEffect() const
+
+    Returns Path_Effect if set, or nullptr.
+    Increases Path_Effect Reference_Count by one.
+
+    #Return  Path_Effect if previously set, nullptr otherwise ##
+
+    #Example
+    void draw(SkCanvas* canvas) {
+        SkPaint paint1, paint2;
+        paint1.setPathEffect(SkArcToPathEffect::Make(10));
+        SkDebugf("path effect unique: %s\n", paint1.getPathEffect()->unique() ? "true" : "false");
+        paint2.setPathEffect(paint1.refPathEffect());
+        SkDebugf("path effect unique: %s\n", paint1.getPathEffect()->unique() ? "true" : "false");
+    }
+
+        #StdOut
+            path effect unique: true
+            path effect unique: false
+        ##
+    ##
+
+##
+
+
+#Method void setPathEffect(sk_sp<SkPathEffect> pathEffect)
+
+    Sets Path_Effect to pathEffect, 
+    decrementing Reference_Count of the previous Path_Effect. 
+    Pass nullptr to leave the path geometry unaltered.
+    Does not alter pathEffect Reference_Count.
+
+    #Param pathEffect  replace Path with a modification when drawn ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setPathEffect(SkDiscretePathEffect::Make(3, 5));
+            canvas->drawRect(SkRect::MakeXYWH(40, 40, 175, 175), paint);
+        }
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Mask_Filter_Methods
+
+Mask_Filter uses Color_Alpha of the shape drawn to create Mask_Alpha.
+Mask_Filter operates at a lower level than Rasterizer; Mask_Filter takes a Mask,
+and returns a Mask.
+Mask_Filter may change the geometry and transparency of the shape, such as creating a blur effect.
+Set Mask_Filter to nullptr to prevent Mask_Filter from modifying the draw.
+
+#Example
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setMaskFilter(SkBlurMaskFilter::Make(kSolid_SkBlurStyle, 3));
+        canvas->drawRect(SkRect::MakeXYWH(40, 40, 175, 175), paint);
+    }
+##
+
+#Method SkMaskFilter* getMaskFilter() const
+
+    Returns Mask_Filter if set, or nullptr.
+    Does not alter Mask_Filter Reference_Count.
+    
+    #Return  Mask_Filter if previously set, nullptr otherwise ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+           SkPaint paint;
+           SkDebugf("nullptr %c= mask filter\n", paint.getMaskFilter() ? '!' : '=');
+           paint.setMaskFilter(SkBlurMaskFilter::Make(kOuter_SkBlurStyle, 3));
+           SkDebugf("nullptr %c= mask filter\n", paint.getMaskFilter() ? '!' : '=');
+        }
+
+        #StdOut
+            nullptr == mask filter
+            nullptr != mask filter
+        ##
+    ##
+
+##
+
+#Method sk_sp<SkMaskFilter> refMaskFilter() const
+
+    Returns Mask_Filter if set, or nullptr.
+    Increases Mask_Filter Reference_Count by one.
+    
+    #Return  Mask_Filter if previously set, nullptr otherwise ##
+
+    #Example
+    void draw(SkCanvas* canvas) {
+        SkPaint paint1, paint2;
+        paint1.setMaskFilter(SkBlurMaskFilter::Make(kNormal_SkBlurStyle, 1));
+        SkDebugf("mask filter unique: %s\n", paint1.getMaskFilter()->unique() ? "true" : "false");
+        paint2.setMaskFilter(paint1.refMaskFilter());
+        SkDebugf("mask filter unique: %s\n", paint1.getMaskFilter()->unique() ? "true" : "false");
+    }
+
+        #StdOut
+            mask filter unique: true
+            mask filter unique: false
+        ##
+    ##
+
+##
+
+#Method void setMaskFilter(sk_sp<SkMaskFilter> maskFilter)
+
+    Sets Mask_Filter to maskFilter,
+    decrementing Reference_Count of the previous Mask_Filter. 
+    Pass nullptr to clear Mask_Filter and leave Mask_Filter effect on Mask_Alpha unaltered.
+    Does not affect Rasterizer.
+    Does not alter maskFilter Reference_Count.
+
+    #Param maskFilter   modifies clipping mask generated from drawn geometry ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setStyle(SkPaint::kStroke_Style);
+            paint.setStrokeWidth(10);
+            paint.setMaskFilter(SkBlurMaskFilter::Make(kNormal_SkBlurStyle, 10));
+            canvas->drawRect(SkRect::MakeXYWH(40, 40, 175, 175), paint);
+        }
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Typeface_Methods
+
+Typeface identifies the font used when drawing and measuring text.
+Typeface may be specified by name, from a file, or from a data stream.
+The default Typeface defers to the platform-specific default font
+implementation.
+
+#Example
+#Height 100
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setTypeface(SkTypeface::MakeDefault(SkTypeface::kBold));
+        paint.setAntiAlias(true);
+        paint.setTextSize(36);
+        canvas->drawString("A Big Hello!", 10, 40, paint);
+        paint.setTypeface(nullptr);
+        paint.setFakeBoldText(true);
+        canvas->drawString("A Big Hello!", 10, 80, paint);
+    }
+##
+
+#Method SkTypeface* getTypeface() const
+
+    Returns Typeface if set, or nullptr.
+    Does not alter Typeface Reference_Count.
+    
+    #Return  Typeface if previously set, nullptr otherwise ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+           SkPaint paint;
+           SkDebugf("nullptr %c= typeface\n", paint.getTypeface() ? '!' : '=');
+           paint.setTypeface(SkTypeface::MakeFromName("Times New Roman", SkFontStyle()));
+           SkDebugf("nullptr %c= typeface\n", paint.getTypeface() ? '!' : '=');
+        }
+
+        #StdOut
+            nullptr == typeface
+            nullptr != typeface
+        ##
+    ##
+
+##
+
+#Method sk_sp<SkTypeface> refTypeface() const
+
+    Increases Typeface Reference_Count by one.
+
+    #Return  Typeface if previously set, nullptr otherwise ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+           SkPaint paint1, paint2;
+           paint1.setTypeface(SkTypeface::MakeFromName("Times New Roman", 
+                    SkFontStyle(SkFontStyle::kNormal_Weight, SkFontStyle::kNormal_Width,
+                    SkFontStyle::kItalic_Slant)));
+           SkDebugf("typeface1 %c= typeface2\n",
+                    paint1.getTypeface() == paint2.getTypeface() ? '=' : '!');
+           paint2.setTypeface(paint1.refTypeface());
+           SkDebugf("typeface1 %c= typeface2\n",
+                    paint1.getTypeface() == paint2.getTypeface() ? '=' : '!');
+        }
+
+        #StdOut
+            typeface1 != typeface2
+            typeface1 == typeface2
+        ##
+    ##
+
+##
+
+#Method void setTypeface(sk_sp<SkTypeface> typeface)
+
+    Sets Typeface to typeface,
+    decrementing Reference_Count of the previous Typeface. 
+    Pass nullptr to clear Typeface and use the default typeface.
+    Does not alter typeface Reference_Count.
+
+    #Param typeface  font and style used to draw text ##
+
+    #Example
+    #Height 64
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setTypeface(SkTypeface::MakeFromName("Courier New", SkFontStyle()));
+            canvas->drawString("Courier New", 10, 30, paint);
+            paint.setTypeface(nullptr);
+            canvas->drawString("default", 10, 50, paint);
+        }
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Rasterizer_Methods
+
+Rasterizer controls how shapes are converted to Mask_Alpha. 
+Rasterizer operates at a higher level than Mask_Filter; Rasterizer takes a Path,
+and returns a Mask.
+Rasterizer may change the geometry and transparency of the shape, such as
+creating a shadow effect. Rasterizer forms the base of Rasterizer_Layer, which
+creates effects like embossing and outlining.
+Rasterizer applies to Rect, Region, Round_Rect, Arc, Circle, Oval,
+Path, and Text.
+
+#Example
+#Height 64
+    void draw(SkCanvas* canvas) {
+        SkLayerRasterizer::Builder layerBuilder;
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setStyle(SkPaint::kStroke_Style);
+        paint.setStrokeWidth(1);
+        layerBuilder.addLayer(paint);
+        paint.setAlpha(0x10);
+        paint.setStyle(SkPaint::kFill_Style);
+        paint.setBlendMode(SkBlendMode::kSrc);
+        layerBuilder.addLayer(paint);
+        paint.reset();
+        paint.setAntiAlias(true);
+        paint.setTextSize(50);
+        paint.setRasterizer(layerBuilder.detach());
+        canvas->drawString("outline", 10, 50, paint);
+    }
+##
+
+#Method SkRasterizer* getRasterizer() const
+
+    Returns Rasterizer if set, or nullptr.
+    Does not alter Rasterizer Reference_Count.
+    
+    #Return  Rasterizer if previously set, nullptr otherwise ##
+
+    #Example
+    #Function
+        class DummyRasterizer : public SkRasterizer {
+        public:
+            SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(DummyRasterizer)
+        };
+
+        sk_sp<SkFlattenable> DummyRasterizer::CreateProc(SkReadBuffer&) {
+            return sk_make_sp<DummyRasterizer>();
+        }
+
+    #Function ##
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            DummyRasterizer dummy;
+            SkDebugf("nullptr %c= rasterizer\n", paint.getRasterizer() ? '!' : '=');
+            paint.setRasterizer(sk_make_sp<DummyRasterizer>());
+            SkDebugf("nullptr %c= rasterizer\n", paint.getRasterizer() ? '!' : '=');
+        }
+
+        #StdOut
+            nullptr == rasterizer
+            nullptr != rasterizer
+        ##
+    ##
+
+##
+
+#Method sk_sp<SkRasterizer> refRasterizer() const
+
+    Returns Rasterizer if set, or nullptr.
+    Increases Rasterizer Reference_Count by one.
+
+    #Return  Rasterizer if previously set, nullptr otherwise ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+           SkLayerRasterizer::Builder layerBuilder;
+           SkPaint paint1, paint2;
+           layerBuilder.addLayer(paint2);
+           paint1.setRasterizer(layerBuilder.detach());
+           SkDebugf("rasterizer unique: %s\n", paint1.getRasterizer()->unique() ? "true" : "false");
+           paint2.setRasterizer(paint1.refRasterizer());
+           SkDebugf("rasterizer unique: %s\n", paint1.getRasterizer()->unique() ? "true" : "false");
+        }
+
+        #StdOut
+            rasterizer unique: true
+            rasterizer unique: false
+        ##
+    ##
+
+##
+
+#Method void setRasterizer(sk_sp<SkRasterizer> rasterizer)
+
+    Sets Rasterizer to rasterizer,
+    decrementing Reference_Count of the previous Rasterizer. 
+    Pass nullptr to clear Rasterizer and leave Rasterizer effect on Mask_Alpha unaltered.
+    Does not affect Mask_Filter.
+    Does not alter rasterizer Reference_Count.
+
+    #Param rasterizer  how geometry is converted to Mask_Alpha ##
+
+    #Example
+    #Height 64
+        SkLayerRasterizer::Builder layerBuilder;
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setStyle(SkPaint::kStroke_Style);
+        paint.setStrokeWidth(2);
+        layerBuilder.addLayer(paint);
+        paint.reset();
+        paint.setAntiAlias(true);
+        paint.setTextSize(50);
+        paint.setMaskFilter(SkBlurMaskFilter::Make(kNormal_SkBlurStyle, 3));
+        paint.setRasterizer(layerBuilder.detach());
+        canvas->drawString("blurry out", 0, 50, paint);
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Image_Filter_Methods
+
+Image_Filter operates on the pixel representation of the shape, as modified by Paint
+with Blend_Mode set to SkBlendMode::kSrcOver. Image_Filter creates a new bitmap,
+which is drawn to the device using the set Blend_Mode.
+Image_Filter is higher level than Mask_Filter; for instance, an Image_Filter
+can operate on all channels of Color, while Mask_Filter generates Color_Alpha only.
+Image_Filter operates independently of and can be used in combination with
+Mask_Filter and Rasterizer.
+
+#Example
+    #ToDo explain why the two draws are so different ##
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setStyle(SkPaint::kStroke_Style);
+        paint.setStrokeWidth(2);
+        SkRegion region;
+        region.op( 10, 10, 50, 50, SkRegion::kUnion_Op);
+        region.op( 10, 50, 90, 90, SkRegion::kUnion_Op);
+        paint.setImageFilter(SkImageFilter::MakeBlur(5.0f, 5.0f, nullptr));
+        canvas->drawRegion(region, paint);
+        paint.setImageFilter(nullptr);
+        paint.setMaskFilter(SkBlurMaskFilter::Make(kNormal_SkBlurStyle, 5));
+        canvas->translate(100, 100);
+        canvas->drawRegion(region, paint);
+    }
+##
+
+#Method SkImageFilter* getImageFilter() const
+
+    Returns Image_Filter if set, or nullptr.
+    Does not alter Image_Filter Reference_Count.
+    
+    #Return  Image_Filter if previously set, nullptr otherwise ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+           SkPaint paint;
+           SkDebugf("nullptr %c= image filter\n", paint.getImageFilter() ? '!' : '=');
+           paint.setImageFilter(SkImageFilter::MakeBlur(kOuter_SkBlurStyle, 3, nullptr, nullptr));
+           SkDebugf("nullptr %c= image filter\n", paint.getImageFilter() ? '!' : '=');
+        }
+
+        #StdOut
+            nullptr == image filter
+            nullptr != image filter
+        ##
+    ##
+
+##
+
+#Method sk_sp<SkImageFilter> refImageFilter() const
+
+    Returns Image_Filter if set, or nullptr.
+    Increases Image_Filter Reference_Count by one.
+
+    #Return  Image_Filter if previously set, nullptr otherwise ##
+
+    #Example
+    void draw(SkCanvas* canvas) {
+        SkPaint paint1, paint2;
+        paint1.setImageFilter(SkOffsetImageFilter::Make(25, 25, nullptr));
+        SkDebugf("image filter unique: %s\n", paint1.getImageFilter()->unique() ? "true" : "false");
+        paint2.setImageFilter(paint1.refImageFilter());
+        SkDebugf("image filter unique: %s\n", paint1.getImageFilter()->unique() ? "true" : "false");
+    }
+
+        #StdOut
+            image filter unique: true
+            image filter unique: false
+        ##
+    ##
+
+##
+
+#Method void setImageFilter(sk_sp<SkImageFilter> imageFilter)
+
+    Sets Image_Filter to imageFilter,
+    decrementing Reference_Count of the previous Image_Filter. 
+    Pass nullptr to clear Image_Filter, and remove Image_Filter effect
+    on drawing.
+    Does not affect Rasterizer or Mask_Filter.
+    Does not alter imageFilter Reference_Count.
+
+    #Param imageFilter  how Image is sampled when transformed ##
+
+    #Example
+    #Height 160
+    void draw(SkCanvas* canvas) {
+        SkBitmap bitmap;
+        bitmap.allocN32Pixels(100, 100);
+        SkCanvas offscreen(bitmap);
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setColor(SK_ColorWHITE);
+        paint.setTextSize(96);
+        offscreen.clear(0);
+        offscreen.drawString("e", 20, 70, paint);
+        paint.setImageFilter(
+               SkLightingImageFilter::MakePointLitDiffuse(SkPoint3::Make(80, 100, 10),
+               SK_ColorWHITE, 1, 2, nullptr, nullptr));
+        canvas->drawBitmap(bitmap, 0, 0, &paint);
+    }
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Draw_Looper_Methods
+
+Draw_Looper sets a modifier that communicates state from one Draw_Layer
+to another to construct the draw.
+Draw_Looper draws one or more times, modifying the canvas and paint each time.
+Draw_Looper may be used to draw multiple colors or create a colored shadow.
+Set Draw_Looper to nullptr to prevent Draw_Looper from modifying the draw. 
+
+#Example
+#Height 128
+    void draw(SkCanvas* canvas) {
+        SkLayerDrawLooper::LayerInfo info;
+        info.fPaintBits = (SkLayerDrawLooper::BitFlags) SkLayerDrawLooper::kColorFilter_Bit;
+        info.fColorMode = SkBlendMode::kSrc;
+        SkLayerDrawLooper::Builder looperBuilder;
+        SkPaint* loopPaint = looperBuilder.addLayer(info);
+        loopPaint->setColor(SK_ColorRED);
+        info.fOffset.set(20, 20);
+        loopPaint = looperBuilder.addLayer(info);
+        loopPaint->setColor(SK_ColorBLUE);
+        SkPaint paint;
+        paint.setDrawLooper(looperBuilder.detach());
+        canvas->drawCircle(50, 50, 50, paint);
+    }
+
+##
+
+#Method SkDrawLooper* getDrawLooper() const
+
+    Returns Draw_Looper if set, or nullptr.
+    Does not alter Draw_Looper Reference_Count.
+    
+    #Return  Draw_Looper if previously set, nullptr otherwise ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+           SkPaint paint;
+           SkDebugf("nullptr %c= draw looper\n", paint.getDrawLooper() ? '!' : '=');
+           SkLayerDrawLooper::Builder looperBuilder;
+           paint.setDrawLooper(looperBuilder.detach());
+           SkDebugf("nullptr %c= draw looper\n", paint.getDrawLooper() ? '!' : '=');
+        }
+
+        #StdOut
+            nullptr == draw looper
+            nullptr != draw looper
+        ##
+    ##
+
+##
+
+#Method sk_sp<SkDrawLooper> refDrawLooper() const
+
+    Returns Draw_Looper if set, or nullptr.
+    Increases Draw_Looper Reference_Count by one.
+
+    #Return  Draw_Looper if previously set, nullptr otherwise ##
+
+    #Example
+    void draw(SkCanvas* canvas) {
+        SkPaint paint1, paint2;
+        SkLayerDrawLooper::Builder looperBuilder;
+        paint1.setDrawLooper(looperBuilder.detach());
+        SkDebugf("draw looper unique: %s\n", paint1.getDrawLooper()->unique() ? "true" : "false");
+        paint2.setDrawLooper(paint1.refDrawLooper());
+        SkDebugf("draw looper unique: %s\n", paint1.getDrawLooper()->unique() ? "true" : "false");
+    }
+
+        #StdOut
+            draw looper unique: true
+            draw looper unique: false
+        ##
+    ##
+
+##
+
+#Method SkDrawLooper* getLooper() const
+
+Deprecated.
+
+#Deprecated
+(see bug.skia.org/6259)
+#Deprecated ##
+
+#Return  Draw_Looper if previously set, nullptr otherwise ##
+##
+
+#Method void setDrawLooper(sk_sp<SkDrawLooper> drawLooper)
+
+    Sets Draw_Looper to drawLooper,
+    decrementing Reference_Count of the previous drawLooper. 
+    Pass nullptr to clear Draw_Looper and leave Draw_Looper effect on drawing unaltered.
+    setDrawLooper does not alter drawLooper Reference_Count.
+
+    #Param drawLooper  Iterates through drawing one or more time, altering Paint ##
+
+    #Example
+    #Height 128
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setDrawLooper(SkBlurDrawLooper::Make(0x7FFF0000, 4, -5, -10));
+            paint.setStyle(SkPaint::kStroke_Style);
+            paint.setStrokeWidth(10);
+            paint.setAntiAlias(true);
+            paint.setColor(0x7f0000ff);
+            canvas->drawCircle(70, 70, 50, paint);
+        }
+    ##
+
+##
+
+#Method void setLooper(sk_sp<SkDrawLooper> drawLooper)
+
+Deprecated.
+
+#Deprecated
+(see bug.skia.org/6259)
+#Deprecated ##
+
+#Param drawLooper  sets Draw_Looper to drawLooper ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Text_Align
+
+#Enum Align
+#Code
+    enum Align {
+        kLeft_Align,
+        kCenter_Align,
+        kRight_Align,
+    };
+##
+
+Align adjusts the text relative to the text position.
+Align affects glyphs drawn with: SkCanvas::drawText, SkCanvas::drawPosText,
+SkCanvas::drawPosTextH, SkCanvas::drawTextOnPath, 
+SkCanvas::drawTextOnPathHV, SkCanvas::drawTextRSXform, SkCanvas::drawTextBlob,
+and SkCanvas::drawString; 
+as well as calls that place text glyphs like getTextWidths and getTextPath.
+
+The text position is set by the font for both horizontal and vertical text.
+Typically, for horizontal text, the position is to the left side of the glyph on the
+base line; and for vertical text, the position is the horizontal center of the glyph
+at the caps height.
+
+Align adjusts the glyph position to center it or move it to abut the position 
+using the metrics returned by the font.
+
+Align defaults to kLeft_Align.
+
+#Const kLeft_Align 0
+    Leaves the glyph at the position computed by the font offset by the text position.
+##
+
+#Const kCenter_Align 1
+    Moves the glyph half its width if Flags has kVerticalText_Flag clear, and
+    half its height if Flags has kVerticalText_Flag set.
+##
+
+#Const kRight_Align 2
+    Moves the glyph by its width if Flags has kVerticalText_Flag clear,
+    and by its height if Flags has kVerticalText_Flag set.
+##
+
+#Enum ##
+
+#Enum
+
+#Code
+    enum {
+        kAlignCount = 3
+    };
+##
+
+#Const kAlignCount 3
+    The number of different Text_Align values defined.
+##
+
+#Enum ##
+
+#Example
+    #Height 160
+    #Description
+    Each position separately moves the glyph in drawPosText.
+    ##
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setTextSize(40);
+        SkPoint position[] = {{100, 50}, {150, 40}};
+        for (SkPaint::Align a : { SkPaint::kLeft_Align,
+                                  SkPaint::kCenter_Align,
+                                  SkPaint::kRight_Align}) {
+            paint.setTextAlign(a);
+            canvas->drawPosText("Aa", 2, position, paint);
+            canvas->translate(0, 50);
+        }
+    }
+##
+
+#Example
+    #Height 160
+    #Description
+    Vertical_Text treats kLeft_Align as top align, and kRight_Align as bottom align.
+    ##
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setTextSize(40);
+        paint.setVerticalText(true);
+        for (SkPaint::Align a : { SkPaint::kLeft_Align,
+                                  SkPaint::kCenter_Align,
+                                  SkPaint::kRight_Align }) {
+            paint.setTextAlign(a);
+            canvas->drawString("Aa", 50, 80, paint);
+            canvas->translate(50, 0);
+        }
+    }
+##
+
+#Method Align getTextAlign() const
+
+    Returns Text_Align.
+    Returns kLeft_Align if Text_Align has not been set.
+
+    #Return  text placement relative to position ##
+
+    #Example
+    SkPaint paint;
+    SkDebugf("kLeft_Align %c= default\n", SkPaint::kLeft_Align == paint.getTextAlign() ? '=' : '!');
+
+        #StdOut
+        kLeft_Align == default
+        ##
+    ##
+##
+
+#Method void    setTextAlign(Align align)
+
+    Sets Text_Align to align.
+    Has no effect if align is an invalid value.
+
+    #Param align  text placement relative to position ##
+
+    #Example
+    #Height 160
+    #Description
+    Text is left-aligned by default, and then set to center. Setting the
+    alignment out of range has no effect.
+    ##
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setTextSize(40);
+            canvas->drawString("Aa", 100, 50, paint);
+            paint.setTextAlign(SkPaint::kCenter_Align);
+            canvas->drawString("Aa", 100, 100, paint);
+            paint.setTextAlign((SkPaint::Align) SkPaint::kAlignCount);
+            canvas->drawString("Aa", 100, 150, paint);
+        }
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Text_Size
+
+Text_Size adjusts the overall text size in points.
+Text_Size can be set to any positive value or zero.
+Text_Size defaults to 12.
+Set SkPaintDefaults_TextSize at compile time to change the default setting.
+
+#Example
+#Height 135
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        canvas->drawString("12 point", 10, 20, paint);
+        paint.setTextSize(24);
+        canvas->drawString("24 point", 10, 60, paint);
+        paint.setTextSize(48);
+        canvas->drawString("48 point", 10, 120, paint);
+    }
+##
+
+#Method SkScalar getTextSize() const
+
+    Returns Text_Size in points.
+
+    #Return  typographic height of text ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("12 %c= default text size\n", 12 == paint.getTextSize() ? '=' : '!');
+    ##
+
+##
+
+#Method void setTextSize(SkScalar textSize)
+
+    Sets Text_Size in points.
+    Has no effect if textSize is not greater than or equal to zero.
+     
+    #Param textSize  typographic height of text ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("12 %c= text size\n", 12 == paint.getTextSize() ? '=' : '!');
+        paint.setTextSize(-20);
+        SkDebugf("12 %c= text size\n", 12 == paint.getTextSize() ? '=' : '!');
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Text_Scale_X
+
+Text_Scale_X adjusts the text horizontal scale.
+Text scaling approximates condensed and expanded type faces when the actual face
+is not available.
+Text_Scale_X can be set to any value.
+Text_Scale_X defaults to 1.
+
+#Example
+#Height 128
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setTextSize(24);
+        paint.setTextScaleX(.8f);
+        canvas->drawString("narrow", 10, 20, paint);
+        paint.setTextScaleX(1);
+        canvas->drawString("normal", 10, 60, paint);
+        paint.setTextScaleX(1.2f);
+        canvas->drawString("wide", 10, 100, paint);
+    }
+##
+
+#Method SkScalar getTextScaleX() const
+
+    Returns Text_Scale_X.
+    Default value is 1.
+
+    #Return  text horizontal scale ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("1 %c= default text scale x\n", 1 == paint.getTextScaleX() ? '=' : '!');
+    ##
+
+##
+
+
+#Method void setTextScaleX(SkScalar scaleX)
+
+    Sets Text_Scale_X.
+    Default value is 1.
+     
+    #Param scaleX  text horizontal scale ##
+
+    #Example
+        SkPaint paint;
+        paint.setTextScaleX(0.f / 0.f);
+        SkDebugf("text scale %s-a-number\n", SkScalarIsNaN(paint.getTextScaleX()) ? "not" : "is");
+    ##
+
+##
+
+#Topic ##
+
+#Topic Text_Skew_X
+
+
+Text_Skew_X adjusts the text horizontal slant.
+Text skewing approximates italic and oblique type faces when the actual face
+is not available.
+Text_Skew_X can be set to any value.
+Text_Skew_X defaults to 0.
+
+#Example
+#Height 128
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setTextSize(24);
+        paint.setTextSkewX(-.25f);
+        canvas->drawString("right-leaning", 10, 100, paint);
+        paint.setTextSkewX(0);
+        canvas->drawString("normal", 10, 60, paint);
+        paint.setTextSkewX(.25f);
+        canvas->drawString("left-leaning", 10, 20, paint);
+    }
+##
+
+#Method SkScalar getTextSkewX() const
+
+    Returns Text_Skew_X.
+    Default value is zero.
+
+    #Return  additional shear in x-axis relative to y-axis ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("0 %c= default text skew x\n", 0 == paint.getTextSkewX() ? '=' : '!');
+    ##
+
+##
+
+#Method void setTextSkewX(SkScalar skewX)
+
+    Sets Text_Skew_X.
+    Default value is zero.
+
+    #Param skewX  additional shear in x-axis relative to y-axis ##
+
+    #Example
+        SkPaint paint;
+        paint.setTextScaleX(1.f / 0.f);
+        SkDebugf("text scale %s-finite\n", SkScalarIsFinite(paint.getTextScaleX()) ? "is" : "not");
+    ##
+
+##
+
+#Topic ##
+
+# ------------------------------------------------------------------------------
+#Topic Text_Encoding
+
+#Enum TextEncoding
+
+#Code
+    enum TextEncoding {
+        kUTF8_TextEncoding,
+        kUTF16_TextEncoding,
+        kUTF32_TextEncoding,
+        kGlyphID_TextEncoding
+    };
+##
+
+TextEncoding determines whether text specifies character codes and their encoded size,
+or glyph indices. Character codes use the encoding specified by the 
+#A Unicode standard # http://unicode.org/standard/standard.html ##.
+Character codes encoded size are specified by UTF-8, UTF-16, or UTF-32.
+All character encoding are able to represent all of Unicode, differing only
+in the total storage required.
+
+#A UTF-8 (RFC 3629) # https://tools.ietf.org/html/rfc3629 ## is made up of 8-bit bytes, 
+and is a superset of ASCII.
+#A UTF-16 (RFC 2781) # https://tools.ietf.org/html/rfc2781 ## is made up of 16-bit words, 
+and is a superset of Unicode ranges 0x0000 to 0xD7FF and 0xE000 to 0xFFFF.
+#A UTF-32 # http://www.unicode.org/versions/Unicode5.0.0/ch03.pdf ## is
+made up of 32-bit words, and is a superset of Unicode.
+
+Font_Manager uses font data to convert character code points into glyph indices. 
+A glyph index is a 16-bit word.
+
+TextEncoding is set to kUTF8_TextEncoding by default.
+
+#Const kUTF8_TextEncoding 0
+Uses bytes to represent UTF-8 or ASCII.
+##
+#Const kUTF16_TextEncoding 1
+Uses two byte words to represent most of Unicode.
+##
+#Const kUTF32_TextEncoding 2
+Uses four byte words to represent all of Unicode.
+##
+#Const kGlyphID_TextEncoding 3
+Uses two byte words to represent glyph indices.
+##
+
+#Enum ##
+
+#Example
+#Height 128
+#Description
+First line has UTF-8 encoding.
+Second line has UTF-16 encoding.
+Third line has UTF-32 encoding.
+Fourth line has 16 bit glyph indices.
+##
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    const char hello8[] = "Hello" "\xE2" "\x98" "\xBA";
+    const uint16_t hello16[] = { 'H', 'e', 'l', 'l', 'o', 0x263A };
+    const uint32_t hello32[] = { 'H', 'e', 'l', 'l', 'o', 0x263A };
+    paint.setTextSize(24);
+    canvas->drawText(hello8, sizeof(hello8) - 1, 10, 30, paint);
+    paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
+    canvas->drawText(hello16, sizeof(hello16), 10, 60, paint);
+    paint.setTextEncoding(SkPaint::kUTF32_TextEncoding);
+    canvas->drawText(hello32, sizeof(hello32), 10, 90, paint);
+    uint16_t glyphs[SK_ARRAY_COUNT(hello32)];
+    paint.textToGlyphs(hello32, sizeof(hello32), glyphs);
+    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+    canvas->drawText(glyphs, sizeof(glyphs), 10, 120, paint);
+}
+##
+
+#Method TextEncoding getTextEncoding() const
+
+    Returns Text_Encoding.
+    Text_Encoding determines how character code points are mapped to font glyph indices.
+
+    #Return  one of: kUTF8_TextEncoding, kUTF16_TextEncoding, kUTF32_TextEncoding, or 
+             kGlyphID_TextEncoding 
+    ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("kUTF8_TextEncoding %c= text encoding\n", 
+                SkPaint::kUTF8_TextEncoding == paint.getTextEncoding() ? '=' : '!');
+        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+        SkDebugf("kGlyphID_TextEncoding %c= text encoding\n", 
+                SkPaint::kGlyphID_TextEncoding == paint.getTextEncoding() ? '=' : '!');
+
+        #StdOut
+            kUTF8_TextEncoding == text encoding
+            kGlyphID_TextEncoding == text encoding
+        ##
+    ##
+
+##
+
+
+#Method void setTextEncoding(TextEncoding encoding)
+
+    Sets Text_Encoding to encoding. 
+    Text_Encoding determines how character code points are mapped to font glyph indices.
+    Invalid values for encoding are ignored.
+
+    #Param encoding  one of: kUTF8_TextEncoding, kUTF16_TextEncoding, kUTF32_TextEncoding, or 
+                     kGlyphID_TextEncoding ##
+
+    #Example
+        SkPaint paint;
+        paint.setTextEncoding((SkPaint::TextEncoding) 4);
+        SkDebugf("4 %c= text encoding\n", 4 == paint.getTextEncoding() ? '=' : '!');
+
+        #StdOut
+            4 != text encoding
+        ##
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Font_Metrics
+
+Font_Metrics describe dimensions common to the glyphs in Typeface.
+The dimensions are computed by Font_Manager from font data and do not take 
+Paint settings other than Text_Size into account.
+
+Font dimensions specify the anchor to the left of the glyph at baseline as the origin.
+X-axis values to the left of the glyph are negative, and to the right of the left glyph edge
+are positive.
+Y-axis values above the baseline are negative, and below the baseline are positive.
+ 
+#Example
+#Width 512
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(120);
+    SkPaint::FontMetrics fm;
+    SkScalar lineHeight = paint.getFontMetrics(&fm);
+    SkPoint pt = { 70, 180 };
+    canvas->drawString("M", pt.fX, pt.fY, paint);
+    canvas->drawLine(pt.fX, pt.fY, pt.fX, pt.fY + fm.fTop, paint);
+    SkScalar ascent = pt.fY + fm.fAscent;
+    canvas->drawLine(pt.fX - 25, ascent, pt.fX - 25, ascent + lineHeight, paint);
+    canvas->drawLine(pt.fX - 50, pt.fY, pt.fX - 50, pt.fY + fm.fDescent, paint);
+    canvas->drawLine(pt.fX + 100, pt.fY, pt.fX + 100, pt.fY + fm.fAscent, paint);
+    canvas->drawLine(pt.fX + 125, pt.fY, pt.fX + 125, pt.fY - fm.fXHeight, paint);
+    canvas->drawLine(pt.fX + 150, pt.fY, pt.fX + 150, pt.fY - fm.fCapHeight, paint);
+    canvas->drawLine(pt.fX + 5, pt.fY, pt.fX + 5, pt.fY + fm.fBottom, paint);
+    SkScalar xmin = pt.fX + fm.fXMin;
+    canvas->drawLine(xmin, pt.fY + 60, xmin + fm.fMaxCharWidth, pt.fY + 60, paint);
+    canvas->drawLine(xmin, pt.fY - 145, pt.fX, pt.fY - 145, paint);
+    canvas->drawLine(pt.fX + fm.fXMax, pt.fY - 160, pt.fX, pt.fY - 160, paint);
+    SkScalar upos = pt.fY + fm.fUnderlinePosition;
+    canvas->drawLine(pt.fX + 25, upos, pt.fX + 130, upos, paint);
+    SkScalar urad = fm.fUnderlineThickness / 2;
+    canvas->drawLine(pt.fX + 130, upos - urad, pt.fX + 160, upos - urad, paint);
+    canvas->drawLine(pt.fX + 130, upos + urad, pt.fX + 160, upos + urad, paint);
+    paint.setTextSize(12);
+    canvas->drawString("x-min",          pt.fX - 50, pt.fY - 148, paint);
+    canvas->drawString("x-max",          pt.fX + 140, pt.fY - 150, paint);
+    canvas->drawString("max char width", pt.fX + 120, pt.fY + 57, paint);
+    canvas->drawString("underline position", pt.fX + 30, pt.fY + 22, paint);
+    canvas->drawString("underline thickness", pt.fX + 162, pt.fY + 13, paint);
+    canvas->rotate(-90);
+    canvas->drawString("descent",     -pt.fY - 30, pt.fX - 54,  paint);
+    canvas->drawString("line height", -pt.fY,      pt.fX - 29,  paint);
+    canvas->drawString("top",         -pt.fY + 30, pt.fX - 4,   paint);
+    canvas->drawString("ascent",      -pt.fY,      pt.fX + 110, paint);
+    canvas->drawString("x-height",    -pt.fY,      pt.fX + 135, paint);
+    canvas->drawString("cap-height",  -pt.fY,      pt.fX + 160, paint);
+    canvas->drawString("bottom",      -pt.fY - 50, pt.fX + 15,  paint);
+}
+##
+
+#Struct FontMetrics
+
+#Code
+    struct FontMetrics {
+        enum FontMetricsFlags {
+            kUnderlineThicknessIsValid_Flag = 1 << 0,
+            kUnderlinePositionIsValid_Flag = 1 << 1,
+            kStrikeoutThicknessIsValid_Flag = 1 << 2,
+            kStrikeoutPositionIsValid_Flag = 1 << 3,
+        };
+
+        uint32_t    fFlags;
+        SkScalar    fTop;
+        SkScalar    fAscent;
+        SkScalar    fDescent;
+        SkScalar    fBottom;
+        SkScalar    fLeading;
+        SkScalar    fAvgCharWidth;
+        SkScalar    fMaxCharWidth;
+        SkScalar    fXMin;
+        SkScalar    fXMax;
+        SkScalar    fXHeight;
+        SkScalar    fCapHeight;
+        SkScalar    fUnderlineThickness;
+        SkScalar    fUnderlinePosition;
+        SkScalar    fStrikeoutThickness;
+        SkScalar    fStrikeoutPosition;
+
+        bool hasUnderlineThickness(SkScalar* thickness) const;
+        bool hasUnderlinePosition(SkScalar* position) const;
+        bool hasStrikeoutThickness(SkScalar* thickness) const;
+        bool hasStrikeoutPosition(SkScalar* position) const;
+    };
+##
+
+    FontMetrics is filled out by getFontMetrics. FontMetrics contents reflect the values
+    computed by Font_Manager using Typeface. Values are set to zero if they are
+    not availble.
+
+    fUnderlineThickness and fUnderlinePosition have a bit set in fFlags if their values
+    are valid, since their value may be zero.
+    
+    fStrikeoutThickness and fStrikeoutPosition have a bit set in fFlags if their values
+    are valid, since their value may be zero.
+
+    #Enum FontMetricsFlags
+    #Code
+        enum FontMetricsFlags {
+            kUnderlineThicknessIsValid_Flag = 1 << 0,
+            kUnderlinePositionIsValid_Flag = 1 << 1,
+            kStrikeoutThicknessIsValid_Flag = 1 << 2,
+            kStrikeoutPositionIsValid_Flag = 1 << 3,
+        };
+    ##
+
+        FontMetricsFlags are set in fFlags when underline and strikeout metrics are valid;
+        the underline or strikeout metric may be valid and zero.
+        Fonts with embedded bitmaps may not have valid underline or strikeout metrics.
+
+        #Const kUnderlineThicknessIsValid_Flag 0x0001
+            Set if fUnderlineThickness is valid.
+        ##
+        #Const kUnderlinePositionIsValid_Flag  0x0002
+            Set if fUnderlinePosition is valid.
+        ##
+        #Const kStrikeoutThicknessIsValid_Flag 0x0004
+            Set if fStrikeoutThickness is valid.
+        ##
+        #Const kStrikeoutPositionIsValid_Flag  0x0008
+            Set if fStrikeoutPosition is valid.
+        ##
+
+    #Enum ##
+
+    #Member uint32_t    fFlags
+        fFlags is set when underline metrics are valid.
+    ##
+    
+    #Member SkScalar    fTop
+        Largest height for any glyph.
+        A measure from the baseline, and is less than or equal to zero.
+    ##
+    
+    #Member SkScalar    fAscent
+        Recommended distance above the baseline to reserve for a line of text.
+        A measure from the baseline, and is less than or equal to zero.
+    ##
+    
+    #Member SkScalar    fDescent
+        Recommended distance below the baseline to reserve for a line of text.
+        A measure from the baseline, and is greater than or equal to zero.
+    ##
+    
+    #Member SkScalar    fBottom
+        Greatest extent below the baseline for any glyph. 
+        A measure from the baseline, and is greater than or equal to zero.
+    ##
+    
+    #Member SkScalar    fLeading
+        Recommended distance to add between lines of text.
+        Greater than or equal to zero.
+    ##
+    
+    #Member SkScalar    fAvgCharWidth
+        Average character width, if it is available.
+        Zero if no average width is stored in the font.
+    ##
+    
+    #Member SkScalar    fMaxCharWidth
+        Maximum character width.
+    ##
+    
+    #Member SkScalar    fXMin
+        Minimum bounding box x value for all glyphs. 
+        Typically less than zero.
+    ##
+    
+    #Member SkScalar    fXMax
+        Maximum bounding box x value for all glyphs.
+        Typically greater than zero.
+    ##
+    
+    #Member SkScalar    fXHeight
+        Height of a lower-case 'x'.
+        May be zero if no lower-case height is stored in the font.
+    ##
+    
+    #Member SkScalar    fCapHeight
+        Height of an upper-case letter.
+        May be zero if no upper-case height is stored in the font.
+    ##
+    
+    #Member SkScalar    fUnderlineThickness
+        Underline thickness. If the metric
+        is valid, the kUnderlineThicknessIsValid_Flag is set in fFlags.
+        If kUnderlineThicknessIsValid_Flag is clear, fUnderlineThickness is zero.
+    ##
+    
+    #Member SkScalar    fUnderlinePosition
+       Underline position relative to the baseline.
+       It may be negative, to draw the underline above the baseline, zero
+       to draw the underline on the baseline, or positive to draw the underline
+       below the baseline. 
+
+       If the metric is valid, the kUnderlinePositionIsValid_Flag is set in fFlags.
+       If kUnderlinePositionIsValid_Flag is clear, fUnderlinePosition is zero.
+    ##
+
+    #Member SkScalar    fStrikeoutThickness
+        Strikeout thickness. If the metric
+        is valid, the kStrikeoutThicknessIsValid_Flag is set in fFlags.
+        If kStrikeoutThicknessIsValid_Flag is clear, fStrikeoutThickness is zero.
+    ##
+
+    #Member SkScalar    fStrikeoutPosition
+       Strikeout position relative to the baseline.
+       It may be negative, to draw the strikeout above the baseline, zero
+       to draw the strikeout on the baseline, or positive to draw the strikeout
+       below the baseline. 
+
+       If the metric is valid, the kStrikeoutPositionIsValid_Flag is set in fFlags.
+       If kStrikeoutPositionIsValid_Flag is clear, fStrikeoutPosition is zero.
+    ##
+
+    #Method bool hasUnderlineThickness(SkScalar* thickness) const
+
+        If Font_Metrics has a valid underline thickness, return true, and set 
+        thickness to that value. If it doesn't, return false, and ignore
+        thickness.
+
+        #Param thickness  storage for underline width ##
+
+        #Return  true if font specifies underline width ##
+
+        #NoExample
+        ##
+    ##
+
+    #Method bool hasUnderlinePosition(SkScalar* position) const
+
+        If Font_Metrics has a valid underline position, return true, and set 
+        position to that value. If it doesn't, return false, and ignore
+        position.
+
+        #Param position  storage for underline position ##
+
+        #Return  true if font specifies underline position ##
+
+        #NoExample
+        ##
+    ##
+
+    #Method bool hasStrikeoutThickness(SkScalar* thickness) const
+
+        If Font_Metrics has a valid strikeout thickness, return true, and set 
+        thickness to that value. If it doesn't, return false, and ignore
+        thickness.
+
+        #Param thickness  storage for strikeout width ##
+
+        #Return  true if font specifies strikeout width ##
+
+        #NoExample
+        ##
+    ##
+
+    #Method bool hasStrikeoutPosition(SkScalar* position) const
+
+        If Font_Metrics has a valid strikeout position, return true, and set 
+        position to that value. If it doesn't, return false, and ignore
+        position.
+
+        #Param position  storage for strikeout position ##
+
+        #Return  true if font specifies strikeout position ##
+
+        #NoExample
+        ##
+    ##
+
+#Struct ##
+
+#Method SkScalar getFontMetrics(FontMetrics* metrics, SkScalar scale = 0) const
+
+    Returns Font_Metrics associated with Typeface.
+    The return value is the recommended spacing between lines: the sum of metrics
+    descent, ascent, and leading.
+    If metrics is not nullptr, Font_Metrics is copied to metrics.
+    Results are scaled by Text_Size but does not take into account
+    dimensions required by Text_Scale_X, Text_Skew_X, Fake_Bold,
+    Style_Stroke, and Path_Effect.
+    Results can be additionally scaled by scale; a scale of zero
+    is ignored.
+
+    #Param metrics  storage for Font_Metrics from Typeface; may be nullptr ##
+    #Param scale    additional multiplier for returned values ##
+
+    #Return         recommended spacing between lines ##
+
+    #Example
+    #Height 128
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setTextSize(32);
+            SkScalar lineHeight = paint.getFontMetrics(nullptr);
+            canvas->drawString("line 1", 10, 40, paint);
+            canvas->drawString("line 2", 10, 40 + lineHeight, paint);
+            paint.setStyle(SkPaint::kStroke_Style);
+            paint.setStrokeWidth(10);
+            lineHeight = paint.getFontMetrics(nullptr, 1.10f);  // account for stroke height
+            canvas->drawString("line 3", 120, 40, paint);
+            canvas->drawString("line 4", 120, 40 + lineHeight, paint);
+        }
+    ##
+
+    #SeeAlso Text_Size Typeface Typeface_Methods
+
+##
+
+
+#Method SkScalar getFontSpacing() const
+
+    Returns the recommended spacing between lines: the sum of metrics
+    descent, ascent, and leading.
+    Result is scaled by Text_Size but does not take into account
+    dimensions required by stroking and Path_Effect.
+    getFontSpacing returns the same result as getFontMetrics.
+
+    #Return         recommended spacing between lines ##
+
+    #Example
+        SkPaint paint;
+        for (SkScalar textSize : { 12, 18, 24, 32 } ) {
+            paint.setTextSize(textSize);
+            SkDebugf("textSize: %g fontSpacing: %g\n", textSize, paint.getFontSpacing());
+        }
+
+        #StdOut
+            textSize: 12 fontSpacing: 13.9688
+            textSize: 18 fontSpacing: 20.9531
+            textSize: 24 fontSpacing: 27.9375
+            textSize: 32 fontSpacing: 37.25
+        ##
+    ##
+
+##
+
+
+#Method SkRect getFontBounds() const
+
+Returns the union of bounds of all glyphs.
+Returned dimensions are computed by Font_Manager from font data, 
+ignoring Hinting. getFontBounds includes Text_Size, Text_Scale_X,
+and Text_Skew_X, but not Fake_Bold or Path_Effect.
+
+If Text_Size is large, Text_Scale_X is one, and Text_Skew_X is zero,
+getFontBounds returns the same bounds as Font_Metrics { FontMetrics::fXMin, 
+FontMetrics::fTop, FontMetrics::fXMax, FontMetrics::fBottom }.
+
+#Return  union of bounds of all glyphs ##
+
+#Example
+    SkPaint paint;
+    SkPaint::FontMetrics fm;
+    paint.getFontMetrics(&fm);
+    SkRect fb = paint.getFontBounds();
+    SkDebugf("metrics bounds = { %g, %g, %g, %g }\n", fm.fXMin, fm.fTop, fm.fXMax, fm.fBottom );
+    SkDebugf("font bounds    = { %g, %g, %g, %g }\n", fb.fLeft, fb.fTop, fb.fRight, fm.fBottom );
+
+    #StdOut
+        metrics bounds = { -12.2461, -14.7891, 21.5215, 5.55469 }
+        font bounds    = { -12.2461, -14.7891, 21.5215, 5.55469 }
+    ##
+##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+
+#Method int textToGlyphs(const void* text, size_t byteLength,
+                     SkGlyphID glyphs[]) const
+
+Converts text into glyph indices.
+Returns the number of glyph indices represented by text.
+Text_Encoding specifies how text represents characters or glyphs.
+glyphs may be nullptr, to compute the glyph count.
+
+Does not check text for valid character encoding or valid
+glyph indices.
+
+If byteLength equals zero, textToGlyphs returns zero.
+If byteLength includes a partial character, the partial character is ignored.
+
+If Text_Encoding is kUTF8_TextEncoding and
+text contains an invalid UTF-8 sequence, zero is returned.
+
+#Param text        character stroage encoded with Text_Encoding ##
+#Param byteLength  length of character storage in bytes ##
+#Param glyphs      storage for glyph indices; may be nullptr ##
+
+#Return            number of glyphs represented by text of length byteLength ##
+
+    #Example
+    #Height 64
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            const uint8_t utf8[] = { 0x24, 0xC2, 0xA2, 0xE2, 0x82, 0xAC, 0xC2, 0xA5, 0xC2, 0xA3 };
+            std::vector<SkGlyphID> glyphs;
+            int count = paint.textToGlyphs(utf8, sizeof(utf8), nullptr);
+            glyphs.resize(count);
+            (void) paint.textToGlyphs(utf8, sizeof(utf8), &glyphs.front());
+            paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+            paint.setTextSize(32);
+            canvas->drawText(&glyphs.front(), glyphs.size() * sizeof(SkGlyphID), 10, 40, paint);
+        }
+    ##
+
+##
+
+#Method int countText(const void* text, size_t byteLength) const
+
+    Returns the number of glyphs in text.
+    Uses Text_Encoding to count the glyphs.
+    Returns the same result as textToGlyphs.
+
+#Param text        character stroage encoded with Text_Encoding ##
+#Param byteLength  length of character storage in bytes ##
+
+#Return            number of glyphs represented by text of length byteLength ##
+
+    #Example
+        SkPaint paint;
+        const uint8_t utf8[] = { 0x24, 0xC2, 0xA2, 0xE2, 0x82, 0xAC, 0xC2, 0xA5, 0xC2, 0xA3 };
+        SkDebugf("count = %d\n", paint.countText(utf8, sizeof(utf8)));
+
+        #StdOut
+            count = 5
+        ##
+    ##
+##
+
+# ------------------------------------------------------------------------------
+
+#Method bool containsText(const void* text, size_t byteLength) const
+
+    Returns true if all text corresponds to a non-zero glyph index. 
+    Returns false if any characters in text are not supported in
+    Typeface.
+
+    If Text_Encoding is kGlyphID_TextEncoding, containsText
+    returns true if all glyph indices in text are non-zero; containsText
+    does not check to see if text contains valid glyph indices for Typeface.
+
+    Returns true if bytelength is zero.
+
+    #Param text  array of characters or glyphs ##
+    #Param byteLength  number of bytes in text array ##
+
+    #Return  true if all text corresponds to a non-zero glyph index ##
+
+    #Example
+    #Description
+    containsText succeeds for degree symbol, but cannot find a glyph index
+    corresponding to the Unicode surrogate code point.
+    ##
+        SkPaint paint;
+        const uint16_t goodChar = 0x00B0;  // degree symbol
+        const uint16_t badChar = 0xD800;   // Unicode surrogate
+        paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
+        SkDebugf("0x%04x %c= has char\n", goodChar, 
+                paint.containsText(&goodChar, 2) ? '=' : '!');
+        SkDebugf("0x%04x %c= has char\n", badChar,
+                paint.containsText(&badChar, 2) ? '=' : '!');
+
+        #StdOut
+            0x00b0 == has char
+            0xd800 != has char
+        ##
+    ##
+
+    #Example
+    #Description
+    containsText returns true that glyph index is greater than zero, not
+    that it corresponds to an entry in Typeface.
+    ##
+        SkPaint paint;
+        const uint16_t goodGlyph = 511;
+        const uint16_t zeroGlyph = 0;
+        const uint16_t badGlyph = 65535; // larger than glyph count in font
+        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+        SkDebugf("0x%04x %c= has glyph\n", goodGlyph, 
+                paint.containsText(&goodGlyph, 2) ? '=' : '!');
+        SkDebugf("0x%04x %c= has glyph\n", zeroGlyph,
+                paint.containsText(&zeroGlyph, 2) ? '=' : '!');
+        SkDebugf("0x%04x %c= has glyph\n", badGlyph,
+                paint.containsText(&badGlyph, 2) ? '=' : '!');
+
+        #StdOut
+            0x01ff == has glyph
+            0x0000 != has glyph
+            0xffff == has glyph
+        ##
+    ##
+
+#SeeAlso setTextEncoding Typeface
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void glyphsToUnichars(const SkGlyphID glyphs[],
+                              int count, SkUnichar text[]) const
+
+    Converts glyphs into text if possible. 
+    Glyph values without direct Unicode equivalents are mapped to zero. 
+    Uses the Typeface, but is unaffected
+    by Text_Encoding; the text values returned are equivalent to kUTF32_TextEncoding.
+
+    Only supported on platforms that use FreeType as the Font_Engine.
+
+    #Param glyphs  array of indices into font ##
+    #Param count   length of glyph array ##
+    #Param text    storage for character codes, one per glyph ##
+
+    #Example
+    #Height 64
+    #Description
+    Convert UTF-8 text to glyphs; then convert glyphs to Unichar code points.
+    ##
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        const char hello[] = "Hello!";
+        const int count = sizeof(hello) - 1;
+        SkGlyphID glyphs[count];
+        if (count != paint.textToGlyphs(hello, count, glyphs)) {
+            return;
+        }
+        SkUnichar unichars[count];
+        paint.glyphsToUnichars(glyphs, count, unichars);
+        paint.setTextEncoding(SkPaint::kUTF32_TextEncoding);
+        canvas->drawText(unichars, sizeof(unichars), 10, 30, paint);
+    }
+    ##
+
+##
+
+# ------------------------------------------------------------------------------
+#Topic Measure_Text
+
+#Method SkScalar measureText(const void* text, size_t length, SkRect* bounds) const
+
+    Returns the advance width of text if kVerticalText_Flag is clear,
+    and the height of text if kVerticalText_Flag is set.
+    The advance is the normal distance to move before drawing additional text.
+    Uses Text_Encoding to decode text, Typeface to get the font metrics,
+    and Text_Size, Text_Scale_X, Text_Skew_X, Stroke_Width, and
+    Path_Effect to scale the metrics and bounds.
+    Returns the bounding box of text if bounds is not nullptr.
+    The bounding box is computed as if the text was drawn at the origin.
+     
+    #Param text         character codes or glyph indices to be measured ##
+    #Param length       number of bytes of text to measure ##
+    #Param bounds       returns bounding box relative to (0, 0) if not nullptr ##
+
+    #Return             advance width or height ##
+
+    #Example
+    #Height 64
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setAntiAlias(true);
+            paint.setTextSize(50);
+            const char str[] = "ay^jZ";
+            const int count = sizeof(str) - 1;
+            canvas->drawText(str, count, 25, 50, paint);
+            SkRect bounds;
+            paint.measureText(str, count, &bounds);
+            canvas->translate(25, 50);
+            paint.setStyle(SkPaint::kStroke_Style);
+            canvas->drawRect(bounds, paint);
+        }
+    ##
+
+##
+
+#Method SkScalar measureText(const void* text, size_t length) const
+
+    Returns the advance width of text if kVerticalText_Flag is clear,
+    and the height of text if kVerticalText_Flag is set.
+    The advance is the normal distance to move before drawing additional text.
+    Uses Text_Encoding to decode text, Typeface to get the font metrics,
+    and Text_Size to scale the metrics.
+    Does not scale the advance or bounds by Fake_Bold or Path_Effect.
+
+    #Param text         character codes or glyph indices to be measured ##
+    #Param length       number of bytes of text to measure ##
+
+    #Return             advance width or height ##
+
+    #Example
+        SkPaint paint;
+        SkDebugf("default width = %g\n", paint.measureText("!", 1));
+        paint.setTextSize(paint.getTextSize() * 2);
+        SkDebugf("double width = %g\n", paint.measureText("!", 1));
+
+        #StdOut
+            default width = 5
+            double width = 10
+        ##
+    ##
+
+##
+
+#Method size_t breakText(const void* text, size_t length, SkScalar maxWidth,
+                      SkScalar* measuredWidth = NULL) const
+
+    Returns the bytes of text that fit within maxWidth.
+    If kVerticalText_Flag is clear, the text fragment fits if its advance width is less than or
+    equal to maxWidth.
+    If kVerticalText_Flag is set, the text fragment fits if its advance height is less than or
+    equal to maxWidth.
+    Measures only while the advance is less than or equal to maxWidth.
+    Returns the advance or the text fragment in measuredWidth if it not nullptr.
+    Uses Text_Encoding to decode text, Typeface to get the font metrics,
+    and Text_Size to scale the metrics.
+    Does not scale the advance or bounds by Fake_Bold or Path_Effect.
+
+    #Param text          character codes or glyph indices to be measured ##
+    #Param length        number of bytes of text to measure ##
+    #Param maxWidth      advance limit; text is measured while advance is less than maxWidth ##
+    #Param measuredWidth returns the width of the text less than or equal to maxWidth ##
+    #Return              bytes of text that fit, always less than or equal to length  ##
+     
+    #Example
+    #Description
+    Line under "Breakfast" shows desired width, shorter than available characters.
+    Line under "Bre" shows measured width after breaking text.
+    ##
+    #Height 128
+    #Width 280
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setAntiAlias(true);
+            paint.setTextSize(50);
+            const char str[] = "Breakfast";
+            const int count = sizeof(str) - 1;
+            canvas->drawText(str, count, 25, 50, paint);
+            SkScalar measuredWidth;
+            int partialBytes = paint.breakText(str, count, 100, &measuredWidth);
+            canvas->drawText(str, partialBytes, 25, 100, paint);
+            canvas->drawLine(25, 60, 25 + 100, 60, paint);
+            canvas->drawLine(25, 110, 25 + measuredWidth, 110, paint);
+        }
+    ##
+
+##
+
+#Method int getTextWidths(const void* text, size_t byteLength, SkScalar widths[],
+                      SkRect bounds[] = NULL) const
+
+    Retrieves the advance and bounds for each glyph in text, and returns
+    the glyph count in text.
+    Both widths and bounds may be nullptr.
+    If widths is not nullptr, widths must be an array of glyph count entries.
+    if bounds is not nullptr, bounds must be an array of glyph count entries. 
+    If kVerticalText_Flag is clear, widths returns the horizontal advance.
+    If kVerticalText_Flag is set, widths returns the vertical advance.
+    Uses Text_Encoding to decode text, Typeface to get the font metrics,
+    and Text_Size to scale the widths and bounds.
+    Does not scale the advance by Fake_Bold or Path_Effect.
+    Does include Fake_Bold and Path_Effect in the bounds.
+   
+    #Param text          character codes or glyph indices to be measured ##
+    #Param byteLength    number of bytes of text to measure ##
+    #Param widths        returns text advances for each glyph; may be nullptr ##
+    #Param bounds        returns bounds for each glyph relative to (0, 0); may be nullptr ##
+    
+    #Return              glyph count in text ##
+
+    #Example
+    #Height 160
+    #Description
+    Bounds of glyphs increase for stroked text, but text advance remains the same.
+    The underlines show the text advance, spaced to keep them distinct.
+    ##
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setAntiAlias(true);
+            paint.setTextSize(50);
+            const char str[] = "abc";
+            const int bytes = sizeof(str) - 1;
+            int count = paint.getTextWidths(str, bytes, nullptr);
+            std::vector<SkScalar> widths;
+            std::vector<SkRect> bounds;
+            widths.resize(count);
+            bounds.resize(count);
+            for (int loop = 0; loop < 2; ++loop) {
+                (void) paint.getTextWidths(str, count, &widths.front(), &bounds.front());
+                SkPoint loc = { 25, 50 };
+                canvas->drawText(str, bytes, loc.fX, loc.fY, paint);
+                paint.setStyle(SkPaint::kStroke_Style);
+                paint.setStrokeWidth(0);
+                SkScalar advanceY = loc.fY + 10;
+                for (int index = 0; index < count; ++index) {
+                    bounds[index].offset(loc.fX, loc.fY);
+                    canvas->drawRect(bounds[index], paint);
+                    canvas->drawLine(loc.fX, advanceY, loc.fX + widths[index], advanceY, paint);
+                    loc.fX += widths[index];
+                    advanceY += 5;
+                }
+                canvas->translate(0, 80);
+                paint.setStrokeWidth(3);
+            }
+        }
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Text_Path
+
+Text_Path describes the geometry of glyphs used to draw text.
+
+#Method void getTextPath(const void* text, size_t length, SkScalar x, SkScalar y,
+                     SkPath* path) const
+
+Returns the geometry as Path equivalent to the drawn text.
+Uses Text_Encoding to decode text, Typeface to get the glyph paths,
+and Text_Size, Fake_Bold, and Path_Effect to scale and modify the glyph paths.
+All of the glyph paths are stored in path.
+getTextPath uses x, y, and Text_Align to position path.
+
+    #Param text          character codes or glyph indices ##
+    #Param length        number of bytes of text ##
+    #Param x             x-coordinate of the origin of the text ##
+    #Param y             y-coordinate of the origin of the text ##
+    #Param path          geometry of the glyphs ##
+
+    #Example
+    #Description
+    Text is added to Path, offset, and subtracted from Path, then added at
+    the offset location. The result is rendered with one draw call.
+    ##
+    #Height 128
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setTextSize(80);
+            SkPath path, path2;
+            paint.getTextPath("ABC", 3, 20, 80, &path);
+            path.offset(20, 20, &path2);
+            Op(path, path2, SkPathOp::kDifference_SkPathOp, &path);
+            path.addPath(path2);
+            paint.setStyle(SkPaint::kStroke_Style);
+            canvas->drawPath(path, paint);
+        }
+    ##
+
+##
+
+#Method void getPosTextPath(const void* text, size_t length,
+                        const SkPoint pos[], SkPath* path) const
+
+Returns the geometry as Path equivalent to the drawn text.
+Uses Text_Encoding to decode text, Typeface to get the glyph paths,
+and Text_Size, Fake_Bold, and Path_Effect to scale and modify the glyph paths.
+All of the glyph paths are stored in path.
+Uses pos array and Text_Align to position path.
+pos contains a position for each glyph.
+
+    #Param text          character codes or glyph indices ##
+    #Param length        number of bytes of text ##
+    #Param pos           positions of each glyph ##
+    #Param path          geometry of the glyphs ##
+
+    #Example
+    #Height 85
+    #Description
+    Simplifies three glyphs to eliminate overlaps, and strokes the result.
+    ##
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setTextSize(80);
+            SkPath path, path2;
+            SkPoint pos[] = {{20, 60}, {30, 70}, {40, 80}};
+            paint.getPosTextPath("ABC", 3, pos, &path);
+            Simplify(path, &path);
+            paint.setStyle(SkPaint::kStroke_Style);
+            canvas->drawPath(path, paint);
+        }
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+#Topic Text_Intercepts
+
+Text_Intercepts describe the intersection of drawn text glyphs with a pair
+of lines parallel to the text advance. Text_Intercepts permits creating a
+underline that skips descenders.
+
+#Method int getTextIntercepts(const void* text, size_t length, SkScalar x, SkScalar y,
+                          const SkScalar bounds[2], SkScalar* intervals) const
+
+    Returns the number of intervals that intersect bounds.
+    bounds describes a pair of lines parallel to the text advance.
+    The return count is zero or a multiple of two, and is at most twice the number of glyphs in
+    the string. 
+    Uses Text_Encoding to decode text, Typeface to get the glyph paths,
+    and Text_Size, Fake_Bold, and Path_Effect to scale and modify the glyph paths.
+    Uses x, y, and Text_Align to position intervals.
+    
+    Pass nullptr for intervals to determine the size of the interval array.
+    
+    intervals are cached to improve performance for multiple calls.
+
+    #Param text          character codes or glyph indices ##
+    #Param length        number of bytes of text ##
+    #Param x             x-coordinate of the origin of the text ##
+    #Param y             y-coordinate of the origin of the text ##
+    #Param bounds        lower and upper line parallel to the advance ##
+    #Param intervals     returned intersections; may be nullptr ##
+
+    #Return              number of intersections; may be zero ##
+
+#Example
+#Height 128
+#Description
+Underline uses intercepts to draw on either side of the glyph descender.
+##
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(120);
+    SkPoint textOrigin = { 20, 100 };
+    SkScalar bounds[] = { 100, 108 };
+    int count = paint.getTextIntercepts("y", 1, textOrigin.fX, textOrigin.fY, bounds, nullptr);
+    std::vector<SkScalar> intervals;
+    intervals.resize(count);
+    (void) paint.getTextIntercepts("y", 1, textOrigin.fX, textOrigin.fY, bounds,
+            &intervals.front());
+    canvas->drawString("y", textOrigin.fX, textOrigin.fY, paint);
+    paint.setColor(SK_ColorRED);
+    SkScalar x = textOrigin.fX;
+    for (int i = 0; i < count; i += 2) {
+        canvas->drawRect({x, bounds[0], intervals[i], bounds[1]}, paint);
+        x = intervals[i + 1];
+    }
+    canvas->drawRect({intervals[count - 1], bounds[0],
+        textOrigin.fX + paint.measureText("y", 1), bounds[1]}, paint);
+}
+##
+
+##
+
+#Method int getPosTextIntercepts(const void* text, size_t length, const SkPoint pos[],
+                             const SkScalar bounds[2], SkScalar* intervals) const
+
+    Returns the number of intervals that intersect bounds.
+    bounds describes a pair of lines parallel to the text advance.
+    The return count is zero or a multiple of two, and is at most twice the number of glyphs in
+    the string. 
+    Uses Text_Encoding to decode text, Typeface to get the glyph paths,
+    and Text_Size, Fake_Bold, and Path_Effect to scale and modify the glyph paths.
+    Uses pos array and Text_Align to position intervals.
+    
+    Pass nullptr for intervals to determine the size of the interval array.
+    
+    intervals are cached to improve performance for multiple calls.
+
+    #Param text          character codes or glyph indices ##
+    #Param length        number of bytes of text ##
+    #Param pos           positions of each glyph ##
+    #Param bounds        lower and upper line parallel to the advance ##
+    #Param intervals     returned intersections; may be nullptr ##
+
+    #Return              The number of intersections; may be zero ##
+
+    #Example
+    #Description
+    Text intercepts draw on either side of, but not inside, glyphs in a run.
+    ##
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setTextSize(120);
+            paint.setVerticalText(true);
+            SkPoint textPos[] = {{ 60, 40 }, { 60, 140 }};
+            SkScalar bounds[] = { 56, 64 };
+            const char str[] = "A-";
+            int len = sizeof(str) - 1;
+            int count = paint.getPosTextIntercepts(str, len, textPos, bounds, nullptr);
+            std::vector<SkScalar> intervals;
+            intervals.resize(count);
+            (void) paint.getPosTextIntercepts(str, len, textPos, bounds, &intervals.front());
+            canvas->drawPosText(str, len, textPos, paint);
+            paint.setColor(SK_ColorRED);
+            SkScalar y = textPos[0].fY;
+            for (int i = 0; i < count; i+= 2) {
+                canvas->drawRect({bounds[0], y, bounds[1], intervals[i]}, paint);
+                y = intervals[i + 1];
+            }
+            canvas->drawRect({bounds[0], intervals[count - 1], bounds[1], 240}, paint);
+        }
+    ##
+
+##
+
+#Method int getPosTextHIntercepts(const void* text, size_t length, const SkScalar xpos[],
+                                  SkScalar constY, const SkScalar bounds[2],
+                                  SkScalar* intervals) const
+
+    Returns the number of intervals that intersect bounds.
+    bounds describes a pair of lines parallel to the text advance.
+    The return count is zero or a multiple of two, and is at most twice the number of glyphs in
+    the string. 
+    Uses Text_Encoding to decode text, Typeface to get the glyph paths,
+    and Text_Size, Fake_Bold, and Path_Effect to scale and modify the glyph paths.
+    Uses xpos array, constY, and Text_Align to position intervals.
+    
+    Pass nullptr for intervals to determine the size of the interval array.
+    
+    intervals are cached to improve performance for multiple calls.
+
+    #Param text          character codes or glyph indices ##
+    #Param length        number of bytes of text ##
+    #Param xpos          positions of each glyph in x ##
+    #Param constY        position of each glyph in y ##
+    #Param bounds        lower and upper line parallel to the advance ##
+    #Param intervals     returned intersections; may be nullptr ##
+
+    #Return              number of intersections; may be zero ##
+
+    #Example
+    #Height 128
+    #Description
+    Text intercepts do not take stroke thickness into consideration.
+    ##
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setTextSize(120);
+            paint.setStyle(SkPaint::kStroke_Style);
+            paint.setStrokeWidth(4);
+            SkScalar textPosH[] = { 20, 80, 140 };
+            SkScalar y = 100;
+            SkScalar bounds[] = { 56, 78 };
+            const char str[] = "\\-/";
+            int len = sizeof(str) - 1;
+            int count = paint.getPosTextHIntercepts(str, len, textPosH, y, bounds, nullptr);
+            std::vector<SkScalar> intervals;
+            intervals.resize(count);
+            (void) paint.getPosTextHIntercepts(str, len, textPosH, y, bounds, &intervals.front());
+            canvas->drawPosTextH(str, len, textPosH, y, paint);
+            paint.setColor(0xFFFF7777);
+            paint.setStyle(SkPaint::kFill_Style);
+            SkScalar x = textPosH[0];
+            for (int i = 0; i < count; i+= 2) {
+                canvas->drawRect({x, bounds[0], intervals[i], bounds[1]}, paint);
+                x = intervals[i + 1];
+            }
+            canvas->drawRect({intervals[count - 1], bounds[0], 180, bounds[1]}, paint);
+        }
+    ##
+
+##
+
+
+#Method int getTextBlobIntercepts(const SkTextBlob* blob, const SkScalar bounds[2],
+                              SkScalar* intervals) const
+
+    Returns the number of intervals that intersect bounds.
+    bounds describes a pair of lines parallel to the text advance.
+    The return count is zero or a multiple of two, and is at most twice the number of glyphs in
+    the string. 
+    Uses Text_Encoding to decode text, Typeface to get the glyph paths,
+    and Text_Size, Fake_Bold, and Path_Effect to scale and modify the glyph paths.
+    Uses pos array and Text_Align to position intervals.
+    
+    Pass nullptr for intervals to determine the size of the interval array.
+    
+    intervals are cached to improve performance for multiple calls.
+
+    #Param blob          glyphs, positions, and text paint attributes ##
+    #Param bounds        lower and upper line parallel to the advance ##
+    #Param intervals     returned intersections; may be nullptr ##
+
+    #Return              number of intersections; may be zero ##
+
+    #Example
+    #Height 143
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setTextSize(120);
+            SkPoint textPos = { 20, 110 };
+            int len = 3;
+            SkTextBlobBuilder textBlobBuilder;
+            const SkTextBlobBuilder::RunBuffer& run = 
+                    textBlobBuilder.allocRun(paint, len, textPos.fX, textPos.fY);
+            run.glyphs[0] = 10;
+            run.glyphs[1] = 20;
+            run.glyphs[2] = 30;       
+            sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
+            canvas->drawTextBlob(blob.get(), textPos.fX, textPos.fY, paint);
+            SkScalar bounds[] = { 116, 134 };
+            int count = paint.getTextBlobIntercepts(blob.get(), bounds, nullptr);
+            std::vector<SkScalar> intervals;
+            intervals.resize(count);
+            (void) paint.getTextBlobIntercepts(blob.get(), bounds, &intervals.front());
+            canvas->drawTextBlob(blob.get(), 0, 0, paint);
+            paint.setColor(0xFFFF7777);
+            SkScalar x = textPos.fX;
+            for (int i = 0; i < count; i+= 2) {
+                canvas->drawRect({x, bounds[0], intervals[i], bounds[1]}, paint);
+                x = intervals[i + 1];
+            }
+            canvas->drawRect({intervals[count - 1], bounds[0], 180, bounds[1]}, paint);
+        }
+    ##
+
+##
+
+#Topic ##
+# ------------------------------------------------------------------------------
+
+#Method bool nothingToDraw() const
+
+    Returns true if Paint prevents all drawing.
+    If nothingToDraw returns false, the Paint may or may not allow drawing.
+
+    Returns true if Blend_Mode and Color_Alpha are enabled,
+    and computed Color_Alpha is zero.
+
+    #Return  true if Paint prevents all drawing ##
+
+    #Example
+        void draw(SkCanvas* canvas) {
+            auto debugster = [](const char* prefix, const SkPaint& p) -> void {
+                SkDebugf("%s nothing to draw: %s\n", prefix, 
+                         p.nothingToDraw() ? "true" : "false");
+            };
+            SkPaint paint;
+            debugster("initial", paint);
+            paint.setBlendMode(SkBlendMode::kDst);
+            debugster("blend dst", paint);
+            paint.setBlendMode(SkBlendMode::kSrcOver);
+            debugster("blend src over", paint);
+            paint.setAlpha(0);
+            debugster("alpha 0", paint);
+        }
+
+        #StdOut
+            initial nothing to draw: false
+            blend dst nothing to draw: true
+            blend src over nothing to draw: false
+            alpha 0 nothing to draw: true
+        #StdOut  ##
+    ##
+
+##
+
+# ------------------------------------------------------------------------------
+#Topic Fast_Bounds
+    #Private  
+    To be made private. 
+    ##
+
+Fast_Bounds methods conservatively outset a drawing bounds by additional area
+Paint may draw to.
+
+#Method bool canComputeFastBounds() const
+    #Private  
+    (to be made private)
+    ##
+
+    Returns true if Paint does not include elements requiring extensive computation
+    to compute Device bounds of drawn geometry. For instance, Paint with Path_Effect
+    always returns false.
+
+    #Return  true if Paint allows for fast computation of bounds ##
+##
+
+#Method const SkRect& computeFastBounds(const SkRect& orig, SkRect* storage) const
+    #Private  
+    (to be made private)
+    ##
+
+    Only call this if canComputeFastBounds returned true. This takes a
+    raw rectangle (the raw bounds of a shape), and adjusts it for stylistic
+    effects in the paint (e.g. stroking). If needed, it uses the storage
+    rect parameter. It returns the adjusted bounds that can then be used
+    for SkCanvas::quickReject tests.
+
+    The returned rect will either be orig or storage, thus the caller
+    should not rely on storage being set to the result, but should always
+    use the retured value. It is legal for orig and storage to be the same
+    rect.
+
+    #Private  
+    e.g.
+    if (paint.canComputeFastBounds()) {
+    SkRect r, storage;
+    path.computeBounds(&r, SkPath::kFast_BoundsType);
+    const SkRect& fastR = paint.computeFastBounds(r, &storage);
+    if (canvas->quickReject(fastR, ...)) {
+    // don't draw the path
+    }
+    }
+    ##
+
+    #Param orig     geometry modified by Paint when drawn ##
+    #Param storage  computed bounds of geometry; may not be nullptr  ##
+
+    #Return  fast computed bounds ##
+##
+
+#Method const SkRect& computeFastStrokeBounds(const SkRect& orig,
+                                              SkRect* storage) const
+    #Private  
+    (to be made private)
+    ##
+
+    #Param orig     geometry modified by Paint when drawn ##
+    #Param storage  computed bounds of geometry  ##
+
+    #Return  fast computed bounds ##
+##
+
+#Method const SkRect& doComputeFastBounds(const SkRect& orig, SkRect* storage,
+                                          Style style) const
+    #Private  
+    (to be made private)
+    ##
+
+    Take the style explicitly, so the caller can force us to be stroked
+    without having to make a copy of the paint just to change that field.
+
+    #Param orig     geometry modified by Paint when drawn ##
+    #Param storage  computed bounds of geometry  ##
+    #Param style    overrides Style ##
+
+    #Return  fast computed bounds ##
+##
+
+#Topic Fast_Bounds ##
+
+# ------------------------------------------------------------------------------
+#Method void toString(SkString* str) const;
+
+#DefinedBy SK_TO_STRING_NONVIRT() ##
+
+#Private
+macro expands to: void toString(SkString* str) const;
+##
+
+Converts Paint to machine parsable form in developer mode.
+
+#Param str  storage for string containing parsable Paint ##
+
+#Example
+    SkPaint paint;
+    SkString str;
+    paint.toString(&str);
+    const char textSize[] = "TextSize:";
+    const int trailerSize = strlen("</dd><dt>");
+    int textSizeLoc = str.find(textSize) + strlen(textSize) + trailerSize;
+    const char* sizeStart = &str.c_str()[textSizeLoc];
+    int textSizeEnd = SkStrFind(sizeStart, "</dd>");
+    SkDebugf("text size = %.*s\n", textSizeEnd, sizeStart);
+
+    #StdOut
+    text size = 12
+    ##
+
+##
+
+#ToDo incomplete ##
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Class SkPaint ##
+
+#Topic Paint ##
diff --git a/docs/SkPath.bmh b/docs/SkPath.bmh
new file mode 100644
index 0000000..8c41451
--- /dev/null
+++ b/docs/SkPath.bmh
@@ -0,0 +1,5801 @@
+#Topic Path

+#Alias Paths

+

+Path contains Lines and Curves which can be stroked or filled. Contour is 

+composed of a series of connected Lines and Curves. Path may contain zero, 

+one, or more Contours.

+Each Line and Curve are described by Verb, Points, and optional Weight.

+

+Each pair of connected Lines and Curves share common Point; for instance, Path

+containing two connected Lines are described the Verb sequence: 

+SkPath::kMove_Verb, SkPath::kLine_Verb, SkPath::kLine_Verb; and a Point sequence

+with three entries, sharing

+the middle entry as the end of the first Line and the start of the second Line.

+

+Path components Arc, Rect, Round_Rect, Circle, and Oval are composed of

+Lines and Curves with as many Verbs and Points required

+for an exact description. Once added to Path, these components may lose their

+identity; although Path can be inspected to determine if it decribes a single

+Rect, Oval, Round_Rect, and so on.

+

+#Example

+#Height 192

+#Description

+Path contains three Contours: Line, Circle, and Quad. Line is stroked but

+not filled. Circle is stroked and filled; Circle stroke forms a loop. Quad

+is stroked and filled, but since it is not closed, Quad does not stroke a loop.

+##

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    SkPath path;

+    path.moveTo(124, 108);

+    path.lineTo(172, 24);

+    path.addCircle(50, 50, 30);

+    path.moveTo(36, 148);

+    path.quadTo(66, 188, 120, 136);

+    canvas->drawPath(path, paint);

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setColor(SK_ColorBLUE);

+    paint.setStrokeWidth(3);

+    canvas->drawPath(path, paint);

+}

+##

+

+Path contains a Fill_Type which determines whether overlapping Contours

+form fills or holes. Fill_Type also determines whether area inside or outside

+Lines and Curves is filled.

+

+#Example

+#Height 192

+#Description

+Path is drawn filled, then stroked, then stroked and filled.

+##

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    SkPath path;

+    path.moveTo(36, 48);

+    path.quadTo(66, 88, 120, 36);

+    canvas->drawPath(path, paint);

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setColor(SK_ColorBLUE);

+    paint.setStrokeWidth(8);

+    canvas->translate(0, 50);

+    canvas->drawPath(path, paint);

+    paint.setStyle(SkPaint::kStrokeAndFill_Style);

+    paint.setColor(SK_ColorRED);

+    canvas->translate(0, 50);

+    canvas->drawPath(path, paint);

+}

+##

+

+Path contents are never shared. Copying Path by value effectively creates

+a new Path independent of the original. Internally, the copy does not duplicate

+its contents until it is edited, to reduce memory use and improve performance.

+

+#Subtopic Subtopics
+#ToDo not all methods are in topics ##
+#ToDo subtopics are not in topics ##
+#Table
+#Legend
+# topics                 # description                                 ##
+#Legend ##
+#Table ##
+# Contour                # A loop of lines and curves.                 ##
+# Convexity              # Whether Path contains simple loop.          ##
+# Last_Point             # Final Point in Contour.                     ##
+# Point_Array            # All Points in Path.                         ##
+# Verb                   # How Points and Contours are defined.        ##
+# Verb_Array             # All Verbs in Path.                          ##
+# Verb                   # How Points and Contours are defined.        ##
+# Weight                 # Strength of control Point in Conic.         ##
+#Subtopic ##
+

+

+#Subtopic Contour
+#Alias Contours
+Contour contains one or more Verbs, and as many Points as
+are required to satisfy Verb_Array. First Verb in Path is always
+SkPath::kMove_Verb; each SkPath::kMove_Verb that follows starts a new Contour.
+
+#Example
+#Description 

+Each SkPath::moveTo starts a new Contour, and content after SkPath::close()

+also starts a new Contour. Since SkPath::conicTo wasn't preceded by 

+SkPath::moveTo, the first Point of the third Contour starts at the last Point

+of the second Contour.

+##

+#Height 192

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    canvas->drawString("1st contour", 150, 100, paint);

+    canvas->drawString("2nd contour", 130, 160, paint);

+    canvas->drawString("3rd contour", 40, 30, paint);

+    paint.setStyle(SkPaint::kStroke_Style);

+    SkPath path;

+    path.moveTo(124, 108);

+    path.lineTo(172, 24);

+    path.moveTo(36, 148);

+    path.quadTo(66, 188, 120, 136);

+    path.close();

+    path.conicTo(70, 20, 110, 40, 0.6f);

+    canvas->drawPath(path, paint);

+##
+
+If final Verb in Contour is SkPath::kClose_Verb, Line connects Last_Point in
+Contour with first Point. A closed Contour, stroked, draws 
+Paint_Stroke_Join at Last_Point and first Point. Without SkPath::kClose_Verb
+as final Verb, Last_Point and first Point are not connected; Contour
+remains open. An open Contour, stroked, draws Paint_Stroke_Cap at 
+Last_Point and first Point.
+
+#Example
+#Height 160

+#Description

+Path is drawn stroked, with an open Contour and a closed Contour.

+##

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setStrokeWidth(8);

+    SkPath path;

+    path.moveTo(36, 48);

+    path.quadTo(66, 88, 120, 36);

+    canvas->drawPath(path, paint);

+    path.close();

+    canvas->translate(0, 50);

+    canvas->drawPath(path, paint);

+}

+##
+
+#Subtopic Zero_Length
+#Alias Zero_Length_Contour
+Contour length is distance traveled from first Point to Last_Point,
+plus, if Contour is closed, distance from Last_Point to first Point.
+Even if Contour length is zero, stroked Lines are drawn if Paint_Stroke_Cap
+makes them visible.
+
+#Example
+#Height 64

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setStrokeWidth(8);

+    paint.setStrokeCap(SkPaint::kRound_Cap);

+    SkPath path;

+    path.moveTo(36, 48);

+    path.lineTo(36, 48);

+    canvas->drawPath(path, paint);

+    path.reset();

+    paint.setStrokeCap(SkPaint::kSquare_Cap);

+    path.moveTo(56, 48);

+    path.close();

+    canvas->drawPath(path, paint);
+##
+
+#Subtopic Zero_Length ##
+
+#Subtopic Contour ##
+ 

+# ------------------------------------------------------------------------------

+
+#Class SkPath

+

+#Topic Overview

+

+#Subtopic Constants
+#ToDo incomplete ##
+#Table
+#Legend
+# constants                      # description                                 ##
+#Legend ##
+# AddPathMode                    # Sets addPath options.                       ##
+# ArcSize                        # Sets arcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc, Direction sweep, SkScalar x, SkScalar y) options. ##
+# Convexity                      # Returns if Path is convex or concave.       ##
+# Direction                      # Sets Contour clockwise or counterclockwise. ##
+# FillType                       # Sets winding rule and inverse fill.         ##
+# SegmentMask                    
+# Verb                           # Controls how Path Points are interpreted.   ##
+#Table ##
+#Subtopic ##
+

+#Subtopic Classes_and_Structs
+#Table
+#Legend
+# class or struct                # description                                  ##
+#Legend ##
+# Iter                           # Iterates through lines and curves, skipping degenerates.  ##
+# RawIter                        # Iterates through lines and curves, including degenerates. ##
+#Table ##
+#Subtopic ##
+

+#Subtopic Constructors
+#Table
+#Legend
+#                                # description                                 ##
+#Legend ##
+# SkPath()                       # Constructs with default values.             ##
+# SkPath(const SkPath& path)     # Makes a shallow copy.                       ##
+# ~SkPath()                      # Decreases Reference_Count of owned objects. ##
+#Table ##
+#Subtopic ##
+
+#Subtopic Operators
+#Table
+#Legend
+# operator                                     # description                     ##
+#Legend ##
+# operator=(const SkPath& path)                # Makes a shallow copy.           ##
+# operator==(const SkPath& a, const SkPath& b) # Compares paths for equality.    ##
+# operator!=(const SkPath& a, const SkPath& b) # Compares paths for inequality.  ##
+#Table ##
+#Subtopic ##
+

+#Subtopic Member_Functions

+#Table

+#Legend

+# function                    # description                                                   ##

+#Legend ##

+# ConvertConicToQuads         # Approximates Conic with Quad array. ##

+# ConvertToNonInverseFillType # Returns Fill_Type representing inside geometry. ##

+# IsCubicDegenerate           # Returns if Cubic is very small. ##

+# IsInverseFillType           # Returns if Fill_Type represents outside geometry. ##

+# IsLineDegenerate            # Returns if Line is very small. ##

+# IsQuadDegenerate            # Returns if Quad is very small. ##

+# addArc                      # Adds one Contour containing Arc. ##

+# addCircle                   # Adds one Contour containing Circle. ##

+# addOval                     # Adds one Contour containing Oval. ##

+# addPath                     # Adds contents of Path. ##

+# addPoly                     # Adds one Contour containing connected lines. ##

+# addRRect                    # Adds one Contour containing Round_Rect. ##

+# addRect                     # Adds one Contour containing Rect. ##

+# addRoundRect                # Adds one Contour containing Round_Rect with common corner radii. ##

+# arcTo                       # Appends Arc. ##

+# close()                     # Makes last Contour a loop. ##

+# computeTightBounds          # Returns extent of geometry. ##

+# conicTo                     # Appends Conic. ##

+# conservativelyContainsRect  # Returns true if Rect may be inside. ##

+# contains()                  # Returns if Point is in fill area. ##

+# countPoints                 # Returns Point_Array length. ##

+# countVerbs                  # Returns Verb_Array length. ##

+# cubicTo                     # Appends Cubic. ##

+# dump()                      # Sends text representation using floats to stdout. ##

+# dumpHex                     # Sends text representation using hexadecimal to stdout. ##

+# experimentalValidateRef     # Experimental; debugging only. ##

+# getBounds                   # Returns maximum and minimum of Point_Array. ##

+# getConvexity                # Returns geometry convexity, computing if necessary. ##

+# getConvexityOrUnknown       # Returns geometry convexity if known. ##

+# getFillType                 # Returns Fill_Type: winding, even-odd, inverse. ##

+# getGenerationID             # Returns unique ID. ##

+# getLastPt                   # Returns Last_Point. ##

+# getPoint                    # Returns entry from Point_Array. ##

+# getPoints                   # Returns Point_Array. ##

+# getSegmentMasks             # Returns types in Verb_Array. ##

+# getVerbs                    # Returns Verb_Array. ##

+# incReserve                  # Hint to reserve space for additional data. ##

+# interpolate()               # Interpolates between Path pair. ##

+# isConvex                    # Returns if geometry is convex. ##

+# isEmpty                     # Returns if verb count is zero. ##

+# isFinite                    # Returns if all Point values are finite. ##

+# isInterpolatable            # Returns if pair contains equal counts of Verb_Array and Weights. ##

+# isInverseFillType           # Returns if Fill_Type fills outside geometry. ##

+# isLastContourClosed         # Returns if final Contour forms a loop. ##

+# isLine                      # Returns if describes Line. ##

+# isNestedFillRects           # Returns if describes Rect pair, one inside the other. ##

+# isOval                      # Returns if describes Oval. ##

+# isRRect                     # Returns if describes Round_Rect. ##

+# isRect                      # Returns if describes Rect. ##

+# isVolatile                  # Returns if Device should not cache. ##

+# lineTo                      # Appends Line. ##

+# moveTo                      # Starts Contour. ##

+# offset()                    # Translates Point_Array. ##

+# quadTo                      # Appends Quad. ##

+# rArcTo                      # Appends Arc relative to Last_Point. ##

+# rConicTo                    # Appends Conic relative to Last_Point. ##

+# rCubicTo                    # Appends Cubic relative to Last_Point. ##

+# rLineTo                     # Appends Line relative to Last_Point. ##

+# rMoveTo                     # Starts Contour relative to Last_Point. ##

+# rQuadTo                     # Appends Quad relative to Last_Point. ##

+# readFromMemory              # Initialize from buffer. ##

+# reset()                     # Removes Verb_Array, Point_Array, and Weights; frees memory. ##

+# reverseAddPath              # Adds contents of Path back to front. ##

+# rewind()                    # Removes Verb_Array, Point_Array, and Weights; leaves memory allocated. ##

+# setConvexity                # Sets if geometry is convex to avoid future computation. ##

+# setFillType                 # Sets Fill_Type: winding, even-odd, inverse. ##

+# setIsConvex                 # Deprecated. ##

+# setIsVolatile               # Sets if Device should not cache. ##

+# setLastPt                   # Replaces Last_Point. ##

+# swap()                      # Exchanges Path pair. ##

+# toggleInverseFillType       # Toggles Fill_Type between inside and outside geometry. ##

+# transform()                 # Applies Matrix to Point_Array and Weights. ##

+# unique()                    # Returns if data has single owner. ##

+# updateBoundsCache           # Refresh result of getBounds. ##

+# writeToMemory               # Copy data to buffer. ##

+#Table ##

+#Subtopic Path_Member_Functions ##

+#Topic Overview ##

+

+#Subtopic Verb
+#Alias Verbs
+
+#Enum Verb

+

+#Code

+    enum Verb {

+        kMove_Verb 

+        kLine_Verb 

+        kQuad_Verb 

+        kConic_Verb 

+        kCubic_Verb 

+        kClose_Verb 

+        kDone_Verb 

+    };

+##

+

+Verb instructs Path how to interpret one or more Point and optional Weight;
+manage Contour, and terminate Path.
+
+#Const kMove_Verb 0

+    Starts new Contour at next Point.

+##

+#Const kLine_Verb 1

+    Adds Line from Last_Point to next Point.

+    Line is a straight segment from Point to Point.

+##

+#Const kQuad_Verb 2

+    Adds Quad from Last_Point, using control Point, and end Point. 

+    Quad is a parabolic section within tangents from Last_Point to control Point,

+    and control Point to end Point.

+##

+#Const kConic_Verb 3

+    Adds Conic from Last_Point, using control Point, end Point, and Weight.

+    Conic is a elliptical, parabolic, or hyperbolic section within tangents 

+    from Last_Point to control Point, and control Point to end Point, constrained

+    by Weight. Weight less than one is elliptical; equal to one is parabolic

+    (and identical to Quad); greater than one hyperbolic.

+##

+#Const kCubic_Verb 4

+    Adds Cubic from Last_Point, using two control Points, and end Point. 

+    Cubic is a third-order Bezier section within tangents from Last_Point to

+    first control Point, and from second control Point to end Point.

+##

+#Const kClose_Verb 5

+    Closes Contour, connecting Last_Point to kMove_Verb Point.

+##

+#Const kDone_Verb 6

+    Terminates Path. Not in Verb_Array, but returned by Path iterator.

+##
+
+Each Verb has zero or more Points stored in Path.
+Path iterator returns complete curve descriptions, duplicating shared Points
+for consecutive entries.
+
+#Table
+#Legend
+# Verb        # Allocated Points # Iterated Points # Weights ##
+##
+# kMove_Verb  # 1                # 1               # 0       ##
+# kLine_Verb  # 1                # 2               # 0       ##
+# kQuad_Verb  # 2                # 3               # 0       ##
+# kConic_Verb # 2                # 3               # 1       ##
+# kCubic_Verb # 3                # 4               # 0       ##
+# kClose_Verb # 0                # 1               # 0       ##
+# kDone_Verb  # --               # 0               # 0       ##
+##
+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPath path;

+    path.lineTo(20, 20);

+    path.quadTo(-10, -10, 30, 30);

+    path.close();

+    path.cubicTo(1, 2, 3, 4, 5, 6);

+    path.conicTo(0, 0, 0, 0, 2);

+    uint8_t verbs[7];

+    int count = path.getVerbs(verbs, (int) SK_ARRAY_COUNT(verbs));

+    const char* verbStr[] = { "Move", "Line", "Quad", "Conic", "Cubic", "Close" };

+    SkDebugf("verb count: %d\nverbs: ", count);

+    for (int i = 0; i < count; ++i) {

+        SkDebugf("k%s_Verb ", verbStr[verbs[i]]);

+    }

+    SkDebugf("\n");

+}

+#StdOut

+verb count: 7

+verbs: kMove_Verb kLine_Verb kQuad_Verb kClose_Verb kMove_Verb kCubic_Verb kConic_Verb 

+##

+##

+

+#Enum Verb ##

+#Subtopic Verb ##

+

+# ------------------------------------------------------------------------------

+#Subtopic Direction

+#Alias Directions

+

+#Enum Direction

+

+#Code

+    enum Direction {

+        kCW_Direction 

+        kCCW_Direction 

+    };

+##

+

+Direction describes whether Contour is clockwise or counterclockwise.

+When Path contains multiple overlapping Contours, Direction together with

+Fill_Type determines whether overlaps are filled or form holes.

+

+Direction also determines how Contour is measured. For instance, dashing

+measures along Path to determine where to start and stop stroke; Direction

+will change dashed results as it steps clockwise or counterclockwise.

+

+Closed Contours like Rect, Round_Rect, Circle, and Oval added with 

+kCW_Direction travel clockwise; the same added with kCCW_Direction

+travel counterclockwise.

+

+#Const kCW_Direction

+    Contour travels in a clockwise direction. 

+##

+#Const kCCW_Direction

+    Contour travels in a counterclockwise direction. 

+##

+

+

+#Example

+#Height 100

+void draw(SkCanvas* canvas) {

+    const SkPoint arrow[] = { {40, -5}, {45, 0}, {40, 5} };

+    const SkRect rect = {10, 10, 90, 90};

+    SkPaint rectPaint;

+    rectPaint.setAntiAlias(true);

+    SkPaint textPaint(rectPaint);

+    textPaint.setTextAlign(SkPaint::kCenter_Align);

+    rectPaint.setStyle(SkPaint::kStroke_Style);

+    SkPaint arrowPaint(rectPaint);

+    SkPath arrowPath;

+    arrowPath.addPoly(arrow, SK_ARRAY_COUNT(arrow), true);

+    arrowPaint.setPathEffect(SkPath1DPathEffect::Make(arrowPath, 320, 0,

+                             SkPath1DPathEffect::kRotate_Style));

+    for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {

+        canvas->drawRect(rect, rectPaint);

+        for (unsigned start : { 0, 1, 2, 3 } ) {

+           SkPath path;

+           path.addRect(rect, direction, start);

+           canvas->drawPath(path, arrowPaint);

+       }

+       canvas->drawString(SkPath::kCW_Direction == direction ? "CW" : "CCW",  rect.centerX(),

+            rect.centerY(), textPaint);

+       canvas->translate(120, 0);

+    }

+}

+##

+

+#SeeAlso arcTo rArcTo isRect isNestedFillRects addRect addOval 

+

+#Enum Direction ##

+#Subtopic Direction ##

+

+# ------------------------------------------------------------------------------

+

+#Method SkPath()

+

+By default, Path has no Verbs, no Points, and no Weights.

+Fill_Type is set to kWinding_FillType.

+

+#Return  empty Path. ##

+

+#Example

+    SkPath path;

+    SkDebugf("path is " "%s" "empty", path.isEmpty() ? "" : "not ");

+#StdOut

+path is empty

+##

+##

+

+#SeeAlso reset rewind

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method SkPath(const SkPath& path)

+

+Copy constructor makes two paths identical by value. Internally, path and

+the returned result share pointer values. The underlying Verb_Array, Point_Array

+and Weights are copied when modified.

+

+Creating a Path copy is very efficient and never allocates memory.

+Paths are always copied by value from the interface; the underlying shared

+pointers are not exposed.

+

+#Param path  Path to copy by value. ##

+

+#Return  Copy of Path. ##

+

+#Example

+#Description

+    Modifying one path does not effect another, even if they started as copies

+    of each other.

+##

+    SkPath path;

+    path.lineTo(20, 20);

+    SkPath path2(path);

+    path2.close();

+    SkDebugf("path verbs: %d\n", path.countVerbs());

+    SkDebugf("path2 verbs: %d\n", path2.countVerbs());

+    path.reset();

+    SkDebugf("after reset\n" "path verbs: %d\n", path.countVerbs());

+    SkDebugf("path2 verbs: %d\n", path2.countVerbs());

+#StdOut

+path verbs: 2

+path2 verbs: 3

+after reset

+path verbs: 0

+path2 verbs: 3

+##

+##

+

+#SeeAlso operator=(const SkPath& path)

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method ~SkPath()

+

+Releases ownership of any shared data and deletes data if Path is sole owner.

+

+#Example

+#Description

+delete calls Path destructor, but copy of original in path2 is unaffected.

+##

+void draw(SkCanvas* canvas) {

+    SkPath* path = new SkPath();

+    path->lineTo(20, 20);

+    SkPath path2(*path);

+    delete path;

+    SkDebugf("path2 is " "%s" "empty", path2.isEmpty() ? "" : "not ");

+}

+##

+

+#SeeAlso SkPath() SkPath(const SkPath& path) operator=(const SkPath& path)

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method SkPath& operator=(const SkPath& path)

+

+Path assignment makes two paths identical by value. Internally, assignment

+shares pointer values. The underlying Verb_Array, Point_Array and Weights

+are copied when modified.

+

+Copying Paths by assignment is very efficient and never allocates memory.

+Paths are always copied by value from the interface; the underlying shared

+pointers are not exposed.

+

+#Param path  Verb_Array, Point_Array, Weights, amd Fill_Type to copy. ##

+

+#Return  Path copied by value. ##

+

+#Example

+SkPath path1;

+path1.addRect({10, 20, 30, 40});

+SkPath path2 = path1;

+const SkRect& b1 = path1.getBounds();

+SkDebugf("path1 bounds = %g, %g, %g, %g\n", b1.fLeft, b1.fTop, b1.fRight, b1.fBottom);

+const SkRect& b2 = path2.getBounds();

+SkDebugf("path2 bounds = %g, %g, %g, %g\n", b2.fLeft, b2.fTop, b2.fRight, b2.fBottom);

+#StdOut

+path1 bounds = 10, 20, 30, 40

+path2 bounds = 10, 20, 30, 40

+#StdOut ##

+##

+

+#SeeAlso swap() SkPath(const SkPath& path)

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method friend SK_API bool operator==(const SkPath& a, const SkPath& b)

+

+Compares a and b; returns true if Fill_Type, Verb_Array, Point_Array, and Weights

+are equivalent.

+

+#Param a  Path to compare. ##

+#Param b  Path to compare. ##

+

+#Return  true if Path pair are equivalent. ##

+

+#Example

+#Description

+Rewind removes Verb_Array but leaves storage; since storage is not compared,

+Path pair are equivalent.

+##

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& a, const SkPath& b) -> void {

+                SkDebugf("%s one %c= two\n", prefix, a == b ? '=' : '!');

+    };

+    SkPath one;

+    SkPath two;

+    debugster("empty", one, two);

+    one.moveTo(0, 0);

+    debugster("moveTo", one, two);

+    one.rewind();

+    debugster("rewind", one, two);

+    one.moveTo(0, 0);

+    one.reset();

+    debugster("reset", one, two);

+}

+#StdOut

+empty one == two

+moveTo one != two

+rewind one == two

+reset one == two

+##

+##

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method friend bool operator!=(const SkPath& a, const SkPath& b) 

+

+Compares a and b; returns true if Fill_Type, Verb_Array, Point_Array, and Weights

+are not equivalent.

+

+#Param a  Path to compare. ##

+#Param b  Path to compare. ##

+

+#Return  true if Path pair are not equivalent. ##

+

+#Example

+#Description

+Path pair are equal though their convexity is not equal.

+##

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& a, const SkPath& b) -> void {

+                SkDebugf("%s one %c= two\n", prefix, a != b ? '!' : '=');

+    };

+    SkPath one;

+    SkPath two;

+    debugster("empty", one, two);

+    one.addRect({10, 20, 30, 40});

+    two.addRect({10, 20, 30, 40});

+    debugster("addRect", one, two);

+    one.setConvexity(SkPath::kConcave_Convexity);

+    debugster("setConvexity", one, two);

+    SkDebugf("convexity %c=\n", one.getConvexity() == two.getConvexity() ? '=' : '!');

+}

+#StdOut

+empty one == two

+addRect one == two

+setConvexity one == two

+convexity !=

+##

+##

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool isInterpolatable(const SkPath& compare) const

+

+Return true if Paths contain equal Verbs and equal Weights.

+If Paths contain one or more Conics, the Weights must match.

+

+conicTo may add different Verbs depending on Conic_Weight, so it is not

+trival to interpolate a pair of Paths containing Conics with different

+Conic_Weight values. 

+

+#Param compare  Path to compare. ##

+

+#Return  true if Paths Verb_Array and Weights are equivalent. ##

+

+#Example

+    SkPath path, path2;

+    path.moveTo(20, 20);

+    path.lineTo(40, 40);

+    path.lineTo(20, 20);

+    path.lineTo(40, 40);

+    path.close();

+    path2.addRect({20, 20, 40, 40});

+    SkDebugf("paths are " "%s" "interpolatable", path.isInterpolatable(path2) ? "" : "not ");

+#StdOut

+paths are interpolatable

+##

+##

+

+#SeeAlso isInterpolatable

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const

+

+Interpolate between Paths with equal sized Point_Arrays.

+Copy Verb_Array and Weights to out,

+and set out Point_Array to a weighted average of this Point_Array and ending 

+Point_Array, using the formula:

+#Formula

+(this->points * weight) + ending->points * (1 - weight)

+##

+

+interpolate() returns false and leaves out unchanged if Point_Array is not

+the same size as ending Point_Array. Call isInterpolatable to check Path 

+compatibility prior to calling interpolate().

+

+#Param ending  Point_Array averaged with this Point_Array. ##

+#Param weight  Most useful when between zero (ending Point_Array) and 

+               one (this Point_Array); will work with values outside of this 

+               range. 

+##

+#Param out  ##

+

+#Return  true if Paths contain same number of Points. ##

+

+#Example

+#Height 60

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    SkPath path, path2;

+    path.moveTo(20, 20);

+    path.lineTo(40, 40);

+    path.lineTo(20, 40);

+    path.lineTo(40, 20);

+    path.close();

+    path2.addRect({20, 20, 40, 40});

+    for (SkScalar i = 0; i <= 1; i += 1.f / 6) {

+      SkPath interp;

+      path.interpolate(path2, i, &interp);

+      canvas->drawPath(interp, paint);

+      canvas->translate(30, 0);

+    }

+}

+##

+

+#SeeAlso isInterpolatable

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool unique() const 

+

+#Private

+To be deprecated; only valid for Android framework.

+##

+

+#Return  true if Path has one owner. ##

+

+##

+

+# ------------------------------------------------------------------------------

+#Subtopic Fill_Type
+
+#Enum FillType

+

+#Code

+    enum FillType {

+        kWinding_FillType 

+        kEvenOdd_FillType 

+        kInverseWinding_FillType 

+        kInverseEvenOdd_FillType 

+    };

+##

+
+Fill_Type selects the rule used to fill Path. Path set to kWinding_FillType 
+fills if the sum of Contour edges is not zero, where clockwise edges add one, and
+counterclockwise edges subtract one. Path set to kEvenOdd_FillType fills if the
+number of Contour edges is odd. Each Fill_Type has an inverse variant that 
+reverses the rule:
+kInverseWinding_FillType fills where the sum of Contour edges is zero;
+kInverseEvenOdd_FillType fills where the number of Contour edges is even.
+
+#Example
+#Height 100
+#Description
+The top row has two clockwise rectangles. The second row has one clockwise and
+one counterclockwise rectangle. The even-odd variants draw the same. The
+winding variants draw the top rectangle overlap, which has a winding of 2, the
+same as the outer parts of the top rectangles, which have a winding of 1.
+##
+void draw(SkCanvas* canvas) {

+   SkPath path;

+   path.addRect({10, 10, 30, 30}, SkPath::kCW_Direction);

+   path.addRect({20, 20, 40, 40}, SkPath::kCW_Direction);

+   path.addRect({10, 60, 30, 80}, SkPath::kCW_Direction);

+   path.addRect({20, 70, 40, 90}, SkPath::kCCW_Direction);

+   SkPaint strokePaint;

+   strokePaint.setStyle(SkPaint::kStroke_Style);

+   SkRect clipRect = {0, 0, 51, 100};

+   canvas->drawPath(path, strokePaint);

+   SkPaint fillPaint;

+   for (auto fillType : { SkPath::kWinding_FillType, SkPath::kEvenOdd_FillType, 

+                      SkPath::kInverseWinding_FillType, SkPath::kInverseEvenOdd_FillType } ) {

+        canvas->translate(51, 0);

+        canvas->save();

+        canvas->clipRect(clipRect);

+        path.setFillType(fillType);

+        canvas->drawPath(path, fillPaint);

+        canvas->restore();

+    }

+}
+##
+

+#Const kWinding_FillType

+Specifies fill as area is enclosed by a non-zero sum of Contour Directions.

+##

+#Const kEvenOdd_FillType

+Specifies fill as area enclosed by an odd number of Contours.

+##

+#Const kInverseWinding_FillType

+Specifies fill as area is enclosed by a zero sum of Contour Directions.

+##

+#Const kInverseEvenOdd_FillType

+Specifies fill as area enclosed by an even number of Contours.

+##

+

+#Example

+#Height 230

+void draw(SkCanvas* canvas) {

+   SkPath path;

+   path.addRect({20, 10, 80, 70}, SkPath::kCW_Direction);

+   path.addRect({40, 30, 100, 90}, SkPath::kCW_Direction);

+   SkPaint strokePaint;

+   strokePaint.setStyle(SkPaint::kStroke_Style);

+   SkRect clipRect = {0, 0, 128, 128};

+   canvas->drawPath(path, strokePaint);

+   canvas->drawLine({0, 50}, {120, 50}, strokePaint);

+   SkPaint textPaint;

+   textPaint.setAntiAlias(true);

+   textPaint.setTextAlign(SkPaint::kCenter_Align);

+   SkScalar textHPos[] = { 10, 30, 60, 90, 110 };

+   canvas->drawPosTextH("01210", 5, textHPos, 48, textPaint);

+   textPaint.setTextSize(18);

+   canvas->translate(0, 128);

+   canvas->scale(.5f, .5f);

+   canvas->drawString("inverse", 384, 150, textPaint);

+   SkPaint fillPaint;

+   for (auto fillType : { SkPath::kWinding_FillType, SkPath::kEvenOdd_FillType, 

+                      SkPath::kInverseWinding_FillType, SkPath::kInverseEvenOdd_FillType } ) {

+        canvas->save();

+        canvas->clipRect(clipRect);

+        path.setFillType(fillType);

+        canvas->drawPath(path, fillPaint);

+        canvas->restore();

+        canvas->drawString(fillType & 1 ? "even-odd" : "winding", 64, 170, textPaint);

+        canvas->translate(128, 0);

+    }

+}

+##

+

+#SeeAlso SkPaint::Style Direction getFillType setFillType

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method FillType getFillType() const 

+

+Returns FillType, the rule used to fill Path. FillType of a new Path is

+kWinding_FillType.

+

+#Return  one of: kWinding_FillType, kEvenOdd_FillType,  kInverseWinding_FillType, 

+kInverseEvenOdd_FillType. 

+##

+

+#Example

+    SkPath path;

+    SkDebugf("default path fill type is %s\n",

+            path.getFillType() == SkPath::kWinding_FillType ? "kWinding_FillType" :

+            path.getFillType() == SkPath::kEvenOdd_FillType ? "kEvenOdd_FillType" : 

+            path.getFillType() == SkPath::kInverseWinding_FillType ? "kInverseWinding_FillType" :

+                                                                     "kInverseEvenOdd_FillType");

+#StdOut

+default path fill type is kWinding_FillType

+##

+##

+

+#SeeAlso FillType setFillType isInverseFillType

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void setFillType(FillType ft) 

+

+Sets FillType, the rule used to fill Path. While setFillType does not check

+that ft is legal, values outside of FillType are not supported.

+

+#Param ft  one of: kWinding_FillType, kEvenOdd_FillType,  kInverseWinding_FillType, 

+kInverseEvenOdd_FillType. 

+##

+

+#Example

+#Description

+If empty Path is set to inverse FillType, it fills all pixels.

+##

+#Height 64

+     SkPath path;

+     path.setFillType(SkPath::kInverseWinding_FillType);

+     SkPaint paint;

+     paint.setColor(SK_ColorBLUE);

+     canvas->drawPath(path, paint);

+##

+

+#SeeAlso FillType getFillType toggleInverseFillType

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool isInverseFillType() const 

+

+Returns if FillType describes area outside Path geometry. The inverse fill area

+extends indefinitely.

+

+#Return  true if FillType is kInverseWinding_FillType or kInverseEvenOdd_FillType. ##

+

+#Example

+    SkPath path;

+    SkDebugf("default path fill type is inverse: %s\n",

+            path.isInverseFillType() ? "true" : "false");

+#StdOut

+default path fill type is inverse: false

+##

+##

+

+#SeeAlso FillType getFillType setFillType toggleInverseFillType

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void toggleInverseFillType() 

+

+Replace FillType with its inverse. The inverse of FillType describes the area

+unmodified by the original FillType.

+

+#Table 

+#Legend

+# FillType                 # toggled FillType         ## 

+##

+# kWinding_FillType        # kInverseWinding_FillType ##

+# kEvenOdd_FillType        # kInverseEvenOdd_FillType ##

+# kInverseWinding_FillType # kWinding_FillType        ##

+# kInverseEvenOdd_FillType # kEvenOdd_FillType        ##

+##

+

+#Example

+#Description

+Path drawn normally and through its inverse touches every pixel once.

+##

+#Height 100

+SkPath path;

+SkPaint paint;

+paint.setColor(SK_ColorRED);

+paint.setTextSize(80);
+paint.getTextPath("ABC", 3, 20, 80, &path);
+canvas->drawPath(path, paint);

+path.toggleInverseFillType();

+paint.setColor(SK_ColorGREEN);

+canvas->drawPath(path, paint);

+##

+

+#SeeAlso FillType getFillType setFillType isInverseFillType

+

+##

+

+#Subtopic Fill_Type ##
+

+# ------------------------------------------------------------------------------

+
+#Subtopic Convexity
+

+#Enum Convexity

+

+#Code

+    enum Convexity {

+        kUnknown_Convexity, 

+        kConvex_Convexity,

+        kConcave_Convexity 

+    };

+##

+

+Path is convex if it contains one Contour and Contour loops no more than 
+360 degrees, and Contour angles all have same Direction. Convex Path 
+may have better performance and require fewer resources on GPU_Surface.
+
+Path is concave when either at least one Direction change is clockwise and

+another is counterclockwise, or the sum of the changes in Direction is not 360

+degrees.

+

+Initially Path Convexity is kUnknown_Convexity. Path Convexity is computed 

+if needed by destination Surface.

+

+#Const kUnknown_Convexity

+    Indicates Convexity has not been determined.

+##

+#Const kConvex_Convexity

+    Path has one Contour made of a simple geometry without indentations.

+##

+#Const kConcave_Convexity

+    Path has more than one Contour, or a geometry with indentations.

+##

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    SkPoint quad[] = {{70, 70}, {20, 20}, {120, 20}, {120, 120}}; 

+    const char* labels[] = { "unknown", "convex", "concave" };

+    for (SkScalar x : { 40, 100 } ) {

+        SkPath path;

+        quad[0].fX = x;

+        path.addPoly(quad, SK_ARRAY_COUNT(quad), true);

+        canvas->drawPath(path, paint);

+        canvas->drawString(labels[(int) path.getConvexity()], 30, 100, paint);

+        canvas->translate(100, 100);

+    }

+}

+##

+

+#SeeAlso Contour Direction getConvexity getConvexityOrUnknown setConvexity isConvex

+

+#Enum Convexity ##

+

+#Method Convexity getConvexity() const 

+

+Computes Convexity if required, and returns stored value. 

+Convexity is computed if stored value is kUnknown_Convexity,

+or if Path has been altered since Convexity was computed or set.

+

+#Return  Computed or stored Convexity. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& path) -> void {

+        SkDebugf("%s path convexity is %s\n", prefix, 

+                SkPath::kUnknown_Convexity == path.getConvexity() ? "unknown" :

+                SkPath::kConvex_Convexity == path.getConvexity() ? "convex" : "concave"); };

+    SkPath path;

+    debugster("initial", path);

+    path.lineTo(50, 0);

+    debugster("first line", path);

+    path.lineTo(50, 50);

+    debugster("second line", path);

+    path.lineTo(100, 50);

+    debugster("third line", path);

+}

+##

+

+#SeeAlso Convexity Contour Direction getConvexityOrUnknown setConvexity isConvex

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method Convexity getConvexityOrUnknown() const 

+

+Returns last computed Convexity, or kUnknown_Convexity if 

+Path has been altered since Convexity was computed or set.

+

+#Return  Stored Convexity. ##

+

+#Example

+#Description

+Convexity is unknown unless getConvexity is called without a subsequent call

+that alters the path.

+##

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& path) -> void {

+        SkDebugf("%s path convexity is %s\n", prefix, 

+            SkPath::kUnknown_Convexity == path.getConvexityOrUnknown() ? "unknown" :

+            SkPath::kConvex_Convexity == path.getConvexityOrUnknown() ? "convex" : "concave"); };

+    SkPath path;

+    debugster("initial", path);

+    path.lineTo(50, 0);

+    debugster("first line", path);

+    path.getConvexity();

+    path.lineTo(50, 50);

+    debugster("second line", path);

+    path.lineTo(100, 50);

+    path.getConvexity();

+    debugster("third line", path);

+}

+##

+

+#SeeAlso Convexity Contour Direction getConvexity setConvexity isConvex

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void setConvexity(Convexity convexity)

+

+Stores convexity so that it is later returned by getConvexity or getConvexityOrUnknown.

+convexity may differ from getConvexity, although setting an incorrect value may

+cause incorrect or inefficient drawing.

+

+If convexity is kUnknown_Convexity: getConvexity will

+compute Convexity, and getConvexityOrUnknown will return kUnknown_Convexity.

+

+If convexity is kConvex_Convexity or kConcave_Convexity, getConvexity

+and getConvexityOrUnknown will return convexity until the path is

+altered.

+

+#Param convexity  One of kUnknown_Convexity, kConvex_Convexity, or kConcave_Convexity. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& path) -> void {

+        SkDebugf("%s path convexity is %s\n", prefix, 

+                SkPath::kUnknown_Convexity == path.getConvexity() ? "unknown" :

+                SkPath::kConvex_Convexity == path.getConvexity() ? "convex" : "concave"); };

+        SkPoint quad[] = {{70, 70}, {20, 20}, {120, 20}, {120, 120}}; 

+        SkPath path;

+        path.addPoly(quad, SK_ARRAY_COUNT(quad), true);

+        debugster("initial", path);

+        path.setConvexity(SkPath::kConcave_Convexity);

+        debugster("after forcing concave", path);

+        path.setConvexity(SkPath::kUnknown_Convexity);

+        debugster("after forcing unknown", path);

+}

+##

+

+#SeeAlso Convexity Contour Direction getConvexity getConvexityOrUnknown isConvex

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool isConvex() const 

+

+Computes Convexity if required, and returns true if value is kConvex_Convexity.

+If setConvexity was called with kConvex_Convexity or kConcave_Convexity, and

+the path has not been altered, Convexity is not recomputed.

+

+#Return  true if Convexity stored or computed is kConvex_Convexity. ##

+

+#Example

+#Description

+Concave shape is erroneously considered convex after a forced call to 

+setConvexity.

+##

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    SkPoint quad[] = {{70, 70}, {20, 20}, {120, 20}, {120, 120}}; 

+    for (SkScalar x : { 40, 100 } ) {

+        SkPath path;

+        quad[0].fX = x;

+        path.addPoly(quad, SK_ARRAY_COUNT(quad), true);

+        path.setConvexity(SkPath::kConvex_Convexity);

+        canvas->drawPath(path, paint);

+        canvas->drawString(path.isConvex() ? "convex" : "not convex", 30, 100, paint);

+        canvas->translate(100, 100);

+    }

+}

+##

+

+#SeeAlso Convexity Contour Direction getConvexity getConvexityOrUnknown setConvexity

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void setIsConvex(bool isConvex) 

+

+#Deprecated

+Use setConvexity.

+##

+

+##

+

+#Subtopic Convexity ##

+

+# ------------------------------------------------------------------------------

+

+#Method bool isOval(SkRect* rect, Direction* dir = nullptr,

+                unsigned* start = nullptr) const 

+

+Path is Oval if constructed by addCircle, addOval; and in some cases,

+addRoundRect, addRRect.  Path constructed with conicTo or rConicTo will not

+return true though Path draws Oval.

+

+isOval triggers performance optimizations on some GPU_Surface implementations.

+

+#Param rect  storage for bounding Rect of Oval. Oval is Circle if rect width

+equals rect height. Unwritten if Path is not Oval. May be nullptr.

+##

+#Param dir  storage for Direction; kCW_Direction if clockwise, kCCW_Direction if

+counterclockwise. Unwritten if Path is not Oval. May be nullptr.

+##

+#Param start  storage for start of Oval: 0 for top,

+1 for right, 2 for bottom, 3 for left. Unwritten if Path is not Oval. May be nullptr. 

+##

+

+#Return  true if Path was constructed by method that reduces to Oval. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    SkPath path;

+    path.addOval({20, 20, 220, 220}, SkPath::kCW_Direction, 1);

+    SkRect bounds;

+    SkPath::Direction direction;

+    unsigned start;

+    path.isOval(&bounds, &direction, &start);

+    paint.setColor(0xFF9FBFFF);

+    canvas->drawRect(bounds, paint);

+    paint.setColor(0x3f000000);

+    canvas->drawPath(path, paint);

+    paint.setColor(SK_ColorBLACK);

+    canvas->rotate(start * 90, bounds.centerX(), bounds.centerY());

+    char startText = '0' + start;

+    paint.setTextSize(20);

+    canvas->drawText(&startText, 1, bounds.centerX(), bounds.fTop + 20, paint);

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setStrokeWidth(4);

+    path.reset();

+    path.addArc(bounds, -90, SkPath::kCW_Direction == direction ? 90 : -90);

+    path.rLineTo(20, -20); 

+    canvas->drawPath(path, paint);

+}

+##

+

+#SeeAlso Oval addCircle addOval

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool isRRect(SkRRect* rrect, Direction* dir = nullptr,

+                 unsigned* start = nullptr) const 

+

+Path is Round_Rect if constructed by addRoundRect, addRRect; and if construction

+is not empty, not Rect, and not Oval. Path constructed with other other calls

+will not return true though Path draws Round_Rect.

+

+isRRect triggers performance optimizations on some GPU_Surface implementations.

+

+#Param rrect  storage for bounding Rect of Round_Rect. 

+Unwritten if Path is not Round_Rect. May be nullptr. 

+##

+#Param dir  storage for Direction; kCW_Direction if clockwise, kCCW_Direction if

+counterclockwise. Unwritten if Path is not Round_Rect. May be nullptr.

+##

+#Param start  storage for start of Round_Rect: 0 for top,

+1 for right, 2 for bottom, 3 for left. Unwritten if Path is not Round_Rect. May be nullptr. 

+##

+

+#Return  true for Round_Rect Path constructed by addRoundRect or addRRect. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    SkPath path;

+    path.addRRect(SkRRect::MakeRectXY({20, 20, 220, 220}, 30, 50), SkPath::kCCW_Direction, 3);

+    SkRRect rrect;

+    SkPath::Direction direction;

+    unsigned start;

+    path.isRRect(&rrect, &direction, &start);

+    const SkRect& bounds = rrect.rect();

+    paint.setColor(0xFF9FBFFF);

+    canvas->drawRect(bounds, paint);

+    paint.setColor(0x3f000000);

+    canvas->drawPath(path, paint);

+    paint.setColor(SK_ColorBLACK);

+    canvas->rotate(start * 90, bounds.centerX(), bounds.centerY());

+    char startText = '0' + start;

+    paint.setTextSize(20);

+    canvas->drawText(&startText, 1, bounds.centerX(), bounds.fTop + 20, paint);

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setStrokeWidth(4);

+    path.reset();

+    path.addArc(bounds, -90, SkPath::kCW_Direction == direction ? 90 : -90);

+    path.rLineTo(20, -20); 

+    canvas->drawPath(path, paint);

+}

+##

+

+#SeeAlso Round_Rect addRoundRect addRRect 

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void reset()

+

+Sets Path to its intial state.

+Removes Verb_Array, Point_Array, and Weights, and sets FillType to kWinding_FillType.

+Internal storage associated with Path is released.

+

+#Example

+   SkPath path1, path2;

+   path1.setFillType(SkPath::kInverseWinding_FillType);

+   path1.addRect({10, 20, 30, 40});

+   SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');

+   path1.reset();

+   SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');

+##

+

+#SeeAlso rewind()

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void rewind()

+

+Sets Path to its intial state, preserving internal storage.

+Removes Verb_Array, Point_Array, and Weights, and sets FillType to kWinding_FillType.

+Internal storage associated with Path is retained.

+

+Use rewind() instead of reset() if Path storage will be reused and performance

+is critical. 

+

+#Example

+#Description

+Although path1 retains its internal storage, it is indistinguishable from

+a newly initialized path.

+##

+   SkPath path1, path2;

+   path1.setFillType(SkPath::kInverseWinding_FillType);

+   path1.addRect({10, 20, 30, 40});

+   SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');

+   path1.rewind();

+   SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');

+##

+

+#SeeAlso reset()

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool isEmpty() const 

+

+Empty Path may have FillType but has no SkPoint, Verb, or Conic_Weight.

+SkPath() constructs empty Path; reset() and (rewind) make Path empty. 

+

+#Return  true if the path contains no Verb array.  ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& path) -> void {

+        SkDebugf("%s path is %s" "empty\n", prefix, path.isEmpty() ? "" : "not ");

+    };

+    SkPath path;

+    debugster("initial", path);

+    path.moveTo(0, 0);

+    debugster("after moveTo", path);

+    path.rewind();

+    debugster("after rewind", path);

+    path.lineTo(0, 0);

+    debugster("after lineTo", path);

+    path.reset();

+    debugster("after reset", path);

+}

+#StdOut

+initial path is empty

+after moveTo path is not empty

+after rewind path is empty

+after lineTo path is not empty

+after reset path is empty

+##

+##

+

+#SeeAlso SkPath() reset() rewind()

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool isLastContourClosed() const

+

+Contour is closed if Path Verb array was last modified by close(). When stroked,

+closed Contour draws Paint_Stroke_Join instead of Paint_Stroke_Cap at first and last Point. 

+

+#Return  true if the last Contour ends with a kClose_Verb. ##

+

+#Example

+#Description

+close() has no effect if Path is empty; isLastContourClosed() returns

+false until Path has geometry followed by close().

+##

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& path) -> void {

+        SkDebugf("%s last contour is %s" "closed\n", prefix,

+                 path.isLastContourClosed() ? "" : "not ");

+    };

+    SkPath path;

+    debugster("initial", path);

+    path.close();

+    debugster("after close", path);

+    path.lineTo(0, 0);

+    debugster("after lineTo", path);

+    path.close();

+    debugster("after close", path);

+}

+#StdOut

+initial last contour is not closed

+after close last contour is not closed

+after lineTo last contour is not closed

+after close last contour is closed

+##

+##

+

+#SeeAlso close()

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool isFinite() const 

+

+Finite Point array values are between negative SK_ScalarMax and

+positive SK_ScalarMax. Any Point array value of

+SK_ScalarInfinity, SK_ScalarNegativeInfinity, or SK_ScalarNaN

+cause isFinite to return false.

+

+#Return  true if all Point values are finite. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& path) -> void {

+        SkDebugf("%s path is %s" "finite\n", prefix, path.isFinite() ? "" : "not ");

+    };

+    SkPath path;

+    debugster("initial", path);

+    path.lineTo(SK_ScalarMax, SK_ScalarMax);

+    debugster("after line", path);

+    SkMatrix matrix;

+    matrix.setScale(2, 2);

+    path.transform(matrix);

+    debugster("after scale", path);

+}

+#StdOut

+initial path is finite

+after line path is finite

+after scale path is not finite

+##

+##

+

+#SeeAlso SkScalar

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool isVolatile() const 

+

+Returns true if the path is volatile; it will not be altered or discarded

+by the caller after it is drawn. Paths by default have volatile set false, allowing 

+Surface to attach a cache of data which speeds repeated drawing. If true, Surface

+may not speed repeated drawing.

+

+#Return  true if caller will alter Path after drawing. ##

+

+#Example

+    SkPath path;

+    SkDebugf("volatile by default is %s\n", path.isVolatile() ? "true" : "false");

+#StdOut

+volatile by default is false

+##

+##

+

+#SeeAlso setIsVolatile

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void setIsVolatile(bool isVolatile) 

+

+Specify whether Path is volatile; whether it will be altered or discarded

+by the caller after it is drawn. Paths by default have volatile set false, allowing 

+Device to attach a cache of data which speeds repeated drawing.

+

+Mark temporary paths, discarded or modified after use, as volatile

+to inform Device that the path need not be cached.

+

+Mark animating Path volatile to improve performance.

+Mark unchanging Path non-volative to improve repeated rendering.

+

+Raster_Surface Path draws are affected by volatile for some shadows.

+GPU_Surface Path draws are affected by volatile for some shadows and concave geometries.

+

+#Param isVolatile  true if caller will alter Path after drawing. ##

+

+#Example

+#Height 50

+#Width 50

+    SkPaint paint;

+    paint.setStyle(SkPaint::kStroke_Style);

+    SkPath path;

+    path.setIsVolatile(true);

+    path.lineTo(40, 40);

+    canvas->drawPath(path, paint);

+    path.rewind();

+    path.moveTo(0, 40);

+    path.lineTo(40, 0);

+    canvas->drawPath(path, paint);

+##

+

+#ToDo tie example to bench to show how volatile affects speed or dm to show resource usage ##

+

+#SeeAlso isVolatile

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method static bool IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) 

+

+Test if Line between Point pair is degenerate.

+Line with no length or that moves a very short distance is degenerate; it is

+treated as a point. 

+

+#Param p1     Line start point. ##

+#Param p2     Line end point. ##

+#Param exact  If true, returns true only if p1 equals p2. If false, returns true

+              if p1 equals or nearly equals p2. 

+##

+

+#Return  true if Line is degenerate; its length is effectively zero. ##

+

+#Example

+#Description

+As single precision floats, 100 and 100.000001f have the same bit representation,

+and are exactly equal. 100 and 100.0001f have different bit representations, and

+are not exactly equal, but are nearly equal.

+##

+void draw(SkCanvas* canvas) {

+    SkPoint points[] = { {100, 100}, {100.000001f, 100.000001f}, {100.0001f, 100.0001f} };

+    for (size_t i = 0; i < SK_ARRAY_COUNT(points) - 1; ++i) {

+        for (bool exact : { false, true } ) {

+            SkDebugf("line from (%1.8g,%1.8g) to (%1.8g,%1.8g) is %s" "degenerate, %s\n",

+                    points[i].fX, points[i].fY, points[i + 1].fX, points[i + 1].fY,

+                    SkPath::IsLineDegenerate(points[i], points[i + 1], exact)

+                    ? "" : "not ", exact ? "exactly" : "nearly");

+        }

+    }

+}

+#StdOut

+line from (100,100) to (100,100) is degenerate, nearly

+line from (100,100) to (100,100) is degenerate, exactly

+line from (100,100) to (100.0001,100.0001) is degenerate, nearly

+line from (100,100) to (100.0001,100.0001) is not degenerate, exactly

+#StdOut ##

+##

+

+#SeeAlso IsQuadDegenerate IsCubicDegenerate SkPoint::equalsWithinTolerance 

+##

+

+# ------------------------------------------------------------------------------

+

+#Method static bool IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,

+                                 const SkPoint& p3, bool exact) 

+

+Test if Quad is degenerate.

+Quad with no length or that moves a very short distance is degenerate; it is

+treated as a point. 

+

+#Param p1  Quad start point. ##

+#Param p2  Quad control point. ##

+#Param p3  Quad end point. ##

+#Param exact  If true, returns true only if p1, p2, and p3 are equal. 

+              If false, returns true if p1, p2, and p3 are equal or nearly equal. 

+##

+

+#Return  true if Quad is degenerate; its length is effectively zero. ##

+

+#Example

+#Description

+As single precision floats: 100, 100.00001f, and 100.00002f have different bit representations

+but nearly the same value. Translating all three by 1000 gives them the same bit representation;

+the fractional portion of the number can't be represented by the float and is lost.

+##

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const SkPath& path, bool exact) -> void {

+        SkDebugf("quad (%1.8g,%1.8g), (%1.8g,%1.8g), (%1.8g,%1.8g) is %s" "degenerate, %s\n", 

+            path.getPoint(0).fX, path.getPoint(0).fY, path.getPoint(1).fX,

+            path.getPoint(1).fY, path.getPoint(2).fX, path.getPoint(2).fY,

+            SkPath::IsQuadDegenerate(path.getPoint(0), path.getPoint(1), path.getPoint(2), exact) ?

+            "" : "not ", exact ? "exactly" : "nearly");

+    };

+    SkPath path, offset;

+    path.moveTo({100, 100});

+    path.quadTo({100.00001f, 100.00001f}, {100.00002f, 100.00002f});

+    offset.addPath(path, 1000, 1000);

+    for (bool exact : { false, true } ) {

+        debugster(path, exact);

+        debugster(offset, exact);

+    }

+}

+#StdOut

+quad (100,100), (100.00001,100.00001), (100.00002,100.00002) is degenerate, nearly

+quad (1100,1100), (1100,1100), (1100,1100) is degenerate, nearly

+quad (100,100), (100.00001,100.00001), (100.00002,100.00002) is not degenerate, exactly

+quad (1100,1100), (1100,1100), (1100,1100) is degenerate, exactly

+#StdOut ##

+##

+

+#SeeAlso IsLineDegenerate IsCubicDegenerate SkPoint::equalsWithinTolerance 

+##

+

+# ------------------------------------------------------------------------------

+

+#Method static bool IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,

+                                  const SkPoint& p3, const SkPoint& p4, bool exact) 

+

+Test if Cubic is degenerate.

+Cubic with no length or that moves a very short distance is degenerate; it is

+treated as a point. 

+

+#Param p1  Cubic start point. ##

+#Param p2  Cubic control point 1. ##

+#Param p3  Cubic control point 2. ##

+#Param p4  Cubic end point. ##

+#Param exact  If true, returns true only if p1, p2, p3, and p4 are equal. 

+              If false, returns true if p1, p2, p3, and p4 are equal or nearly equal.

+##

+

+#Return  true if Cubic is degenerate; its length is effectively zero. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPoint points[] = {{1, 0}, {0, 0}, {0, 0}, {0, 0}};

+    SkScalar step = 1;

+    SkScalar prior, length, degenerate;

+    do {

+        prior = points[0].fX;

+        step /= 2;

+        if (SkPath::IsCubicDegenerate(points[0], points[1], points[2], points[3], false)) {

+            degenerate = prior;

+            points[0].fX += step;

+        } else {

+            length = prior;

+            points[0].fX -= step;

+        }

+    } while (prior != points[0].fX);

+    SkDebugf("%1.8g is degenerate\n", degenerate);

+    SkDebugf("%1.8g is length\n", length);

+}

+#StdOut

+0.00024414062 is degenerate

+0.00024414065 is length

+#StdOut ##

+##

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool isLine(SkPoint line[2]) const

+

+Returns true if Path contains only one Line;

+Path_Verb array has two entries: kMove_Verb, kLine_Verb. 

+If Path contains one Line and line is not nullptr, line is set to 

+Line start point and Line end point.

+Returns false if Path is not one Line; line is unaltered.

+

+#Param line  storage for Line. May be nullptr. ##

+

+#Return  true if Path contains exactly one Line. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& path) -> void {

+        SkPoint line[2];

+        if (path.isLine(line)) {

+            SkDebugf("%s is line (%1.8g,%1.8g) (%1.8g,%1.8g)\n", prefix,

+                 line[0].fX, line[0].fY, line[1].fX, line[1].fY);

+        } else {

+            SkDebugf("%s is not line\n", prefix);

+        }

+    };

+    SkPath path;

+    debugster("empty", path);

+    path.lineTo(0, 0);

+    debugster("zero line", path);

+    path.rewind();

+    path.moveTo(10, 10);

+    path.lineTo(20, 20);

+    debugster("line", path);

+    path.moveTo(20, 20);

+    debugster("second move", path);

+}

+#StdOut

+empty is not line

+zero line is line (0,0) (0,0)

+line is line (10,10) (20,20)

+second move is not line

+##

+##

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Subtopic Point_Array
+#Alias Point_Arrays
+
+Point_Array contains Points satisfying the allocated Points for
+each Verb in Verb_Array. For instance, Path containing one Contour with Line
+and Quad is described by Verb_Array: move to, line to, quad to; and
+one Point for move, one Point for Line, two Points for Quad; totaling four Points.
+
+Point_Array may be read directly from Path with getPoints, or inspected with
+getPoint, with Iter, or with RawIter.
+

+#Method int getPoints(SkPoint points[], int max) const

+

+Returns number of points in Path. Up to max points are copied.

+points may be nullptr; then, max must be zero.

+If max is greater than number of points, excess points storage is unaltered. 

+

+#Param points  storage for Path Point array. May be nullptr. ##

+#Param max  Number of points alloted in points storage; must be greater than or equal to zero. ##

+

+#Return  Path Point array length. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& path, SkPoint* points, int max) -> void {

+         int count = path.getPoints(points, max);

+         SkDebugf("%s point count: %d  ", prefix, count);

+         for (int i = 0; i < SkTMin(count, max) && points; ++i) {

+             SkDebugf("(%1.8g,%1.8g) ", points[i].fX, points[i].fY);

+         }

+         SkDebugf("\n");

+    };

+    SkPath path;

+    path.lineTo(20, 20);

+    path.lineTo(-10, -10);

+    SkPoint points[3];

+    debugster("no points",  path, nullptr, 0);

+    debugster("zero max",  path, points, 0);

+    debugster("too small",  path, points, 2);

+    debugster("just right",  path, points, path.countPoints());

+}

+#StdOut

+no points point count: 3

+zero max point count: 3

+too small point count: 3  (0,0) (20,20)

+just right point count: 3  (0,0) (20,20) (-10,-10)

+##

+##

+

+#SeeAlso countPoints getPoint

+##

+

+#Method int countPoints() const

+

+Returns the number of points in Path.

+Point count is initially zero. 

+

+#Return  Path Point array length. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& path) -> void {

+         SkDebugf("%s point count: %d\n", prefix, path.countPoints());

+    };

+    SkPath path;

+    debugster("empty", path);

+    path.lineTo(0, 0);

+    debugster("zero line", path);

+    path.rewind();

+    path.moveTo(10, 10);

+    path.lineTo(20, 20);

+    debugster("line", path);

+    path.moveTo(20, 20);

+    debugster("second move", path);

+}

+#StdOut

+empty point count: 0

+zero line point count: 2

+line point count: 2

+second move point count: 3

+##

+##

+

+#SeeAlso getPoints

+##

+

+#Method SkPoint getPoint(int index) const

+

+Returns Point at index in Point_Array. Valid range for index is

+0 to countPoints - 1.

+If the index is out of range, getPoint returns (0, 0). 

+

+#Param index  Point_Array element selector. ##

+

+#Return  Point_Array value or (0, 0). ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& path) -> void {

+         SkDebugf("%s point count: %d\n", prefix, path.countPoints());

+    };

+    SkPath path;

+    path.lineTo(20, 20);

+    path.offset(-10, -10);

+    for (int i= 0; i < path.countPoints(); ++i) {

+         SkDebugf("point %d: (%1.8g,%1.8g)\n", i, path.getPoint(i).fX, path.getPoint(i).fY);

+    }  

+}

+#StdOut

+point 0: (-10,-10)

+point 1: (10,10)

+##

+##

+

+#SeeAlso countPoints getPoints

+##

+

+

+#Subtopic Point_Array ##

+

+# ------------------------------------------------------------------------------

+#Subtopic Verb_Array
+
+Verb_Array always starts with kMove_Verb.
+If kClose_Verb is not the last entry, it is always followed by kMove_Verb;
+the quantity of kMove_Verb equals the Contour count.
+Verb_Array does not include or count kDone_Verb; it is a convenience
+returned when iterating through Verb_Array.
+
+Verb_Array may be read directly from Path with getVerbs, or inspected with Iter, 
+or with RawIter.
+

+#Method int countVerbs() const

+

+Returns the number of Verbs: kMove_Verb, kLine_Verb, kQuad_Verb, kConic_Verb, 

+kCubic_Verb, and kClose_Verb; added to Path.

+

+#Return  Length of Verb_Array. ##

+

+#Example

+SkPath path;

+SkDebugf("empty verb count: %d\n", path.countVerbs());

+path.addRoundRect({10, 20, 30, 40}, 5, 5);

+SkDebugf("round rect verb count: %d\n", path.countVerbs());

+#StdOut

+empty verb count: 0

+round rect verb count: 10

+##

+##

+

+#SeeAlso getVerbs Iter RawIter

+

+##

+

+#Method int getVerbs(uint8_t verbs[], int max) const

+

+Returns the number of verbs in the path. Up to max verbs are copied. The

+verbs are copied as one byte per verb.

+

+#Param verbs  If not null, receives up to max verbs ##

+#Param max  The maximum number of verbs to copy into verbs ##

+

+#Return  the actual number of verbs in the path ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& path, uint8_t* verbs, int max) -> void {

+         int count = path.getVerbs(verbs, max);

+         SkDebugf("%s verb count: %d  ", prefix, count);

+         const char* verbStr[] = { "move", "line", "quad", "conic", "cubic", "close" };

+         for (int i = 0; i < SkTMin(count, max) && verbs; ++i) {

+             SkDebugf("%s ", verbStr[verbs[i]]);

+         }

+         SkDebugf("\n");

+    };

+    SkPath path;

+    path.lineTo(20, 20);

+    path.lineTo(-10, -10);

+    uint8_t verbs[3];

+    debugster("no verbs",  path, nullptr, 0);

+    debugster("zero max",  path, verbs, 0);

+    debugster("too small",  path, verbs, 2);

+    debugster("just right",  path, verbs, path.countVerbs());

+}

+#StdOut

+no verbs verb count: 3  

+zero max verb count: 3  

+too small verb count: 3  move line 

+just right verb count: 3  move line line 

+##

+##

+

+#SeeAlso countVerbs getPoints Iter RawIter

+##

+
+#Subtopic Verb_Array ##
+

+# ------------------------------------------------------------------------------

+

+#Method void swap(SkPath& other)

+

+Exchanges the Verb_Array, Point_Array, Weights, and Fill_Type with other.

+Cached state is also exchanged. swap() internally exchanges pointers, so

+it is lightweight and does not allocate memory.

+

+swap() usage has largely been replaced by operator=(const SkPath& path).

+Paths do not copy their content on assignment util they are written to,

+making assignment as efficient as swap().

+

+#Param other  Path exchanged by value. ##

+

+#Example

+SkPath path1, path2;

+path1.addRect({10, 20, 30, 40});

+path1.swap(path2);

+const SkRect& b1 = path1.getBounds();

+SkDebugf("path1 bounds = %g, %g, %g, %g\n", b1.fLeft, b1.fTop, b1.fRight, b1.fBottom);

+const SkRect& b2 = path2.getBounds();

+SkDebugf("path2 bounds = %g, %g, %g, %g\n", b2.fLeft, b2.fTop, b2.fRight, b2.fBottom);

+#StdOut

+path1 bounds = 0, 0, 0, 0

+path2 bounds = 10, 20, 30, 40

+#StdOut ##

+##

+

+#SeeAlso operator=(const SkPath& path)

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method const SkRect& getBounds() const 

+

+Returns minimum and maximum x and y values of Point_Array. If Path contains

+no points, getBounds returns (0, 0, 0, 0). Returned bounds width and height may

+be larger or smaller than area affected when Path is drawn.

+

+getBounds includes all Points added to Path, including Points associated with

+kMove_Verb that define empty Contours.

+

+#Return  bounds of all Points in Point_Array. ##

+

+#Example

+#Description

+Bounds of upright Circle can be predicted from center and radius.

+Bounds of rotated Circle includes control Points outside of filled area.

+##

+    auto debugster = [](const char* prefix, const SkPath& path) -> void {

+            const SkRect& bounds = path.getBounds();

+            SkDebugf("%s bounds = %g, %g, %g, %g\n", prefix, 

+                     bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);

+    };

+    SkPath path;

+    debugster("empty", path);

+    path.addCircle(50, 45, 25);

+    debugster("circle", path);

+    SkMatrix matrix;

+    matrix.setRotate(45, 50, 45);

+    path.transform(matrix);

+    debugster("rotated circle", path);

+#StdOut

+empty bounds = 0, 0, 0, 0

+circle bounds = 25, 20, 75, 70

+rotated circle bounds = 14.6447, 9.64466, 85.3553, 80.3553

+##

+##

+

+#SeeAlso computeTightBounds updateBoundsCache

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void updateBoundsCache() const 

+

+Update internal bounds so that subsequent calls to getBounds are instantaneous.

+Unaltered copies of Path may also access cached bounds through getBounds.

+

+For now, updateBoundsCache is identical to getBounds, where the

+returned value is ignored.

+

+updateBoundsCache prepares a Path subsequently drawn from multiple threads, 

+to avoid a race condition where each draw separately computes the bounds.

+

+#Example

+    double times[2] = { 0, 0 };

+    for (int i = 0; i < 10000; ++i) {

+      SkPath path;

+      for (int j = 1; j < 100; ++ j) {

+        path.addCircle(50 + j, 45 + j, 25 + j);

+      }

+      if (1 & i) {

+        path.updateBoundsCache();

+      }

+      double start = SkTime::GetNSecs();

+      (void) path.getBounds();

+      times[1 & i] += SkTime::GetNSecs() - start;

+    }

+    SkDebugf("uncached avg: %g ms\n", times[0] * 1e-6);

+    SkDebugf("cached avg: %g ms\n", times[1] * 1e-6);

+#StdOut

+#Volatile

+uncached avg: 0.18048 ms

+cached avg: 0.182784 ms

+##

+##

+

+#SeeAlso getBounds

+#ToDo the results don't make sense, need to profile to figure this out ##

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method SkRect computeTightBounds() const

+

+Returns minimum and maximum x and y values of the lines and curves in Path.

+If Path contains no points, computeTightBounds returns (0, 0, 0, 0). 

+Returned bounds width and height may be larger or smaller than area affected 

+when Path is drawn.

+

+computeTightBounds behaves identically to getBounds when Path contains

+only lines. If Path contains curves, compute computeTightBounds includes

+the maximum extent of the Quad, Conic, or Cubic; is slower,

+and does not cache the result.

+

+Like getBounds, computeTightBounds includes Points associated with

+kMove_Verb that define empty Contours.

+

+#Return  ##

+

+#Example

+    auto debugster = [](const char* prefix, const SkPath& path) -> void {

+            const SkRect& bounds = path.computeTightBounds();

+            SkDebugf("%s bounds = %g, %g, %g, %g\n", prefix, 

+                     bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);

+    };

+    SkPath path;

+    debugster("empty", path);

+    path.addCircle(50, 45, 25);

+    debugster("circle", path);

+    SkMatrix matrix;

+    matrix.setRotate(45, 50, 45);

+    path.transform(matrix);

+    debugster("rotated circle", path);

+#StdOut

+empty bounds = 0, 0, 0, 0

+circle bounds = 25, 20, 75, 70

+rotated circle bounds = 25, 20, 75, 70

+##

+##

+

+#SeeAlso getBounds

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool conservativelyContainsRect(const SkRect& rect) const

+

+Returns true if rect is contained by Path. 

+May return false when rect is contained by Path.

+

+For now, only returns true if Path has one Contour and is convex.

+rect may share points and edges with Path and be contained.

+If rect is empty, that is, it has zero width or height; conservativelyContainsRect

+returns true if the Point or Line described by rect is contained by Path.

+

+#Param rect  Rect, Line, or Point checked for containment. ##

+

+#Return  true if rect is contained. ##

+

+#Example

+#Height 140

+#Description

+Rect is drawn in blue if it is contained by red Path.

+##

+void draw(SkCanvas* canvas) {

+    SkPath path;

+    path.addRoundRect({10, 20, 54, 120}, 10, 20);

+    SkRect tests[] = {

+      { 10, 40, 54, 80 },

+      { 25, 20, 39, 120 },

+      { 15, 25, 49, 115 },

+      { 13, 27, 51, 113 },

+    };

+    for (unsigned i = 0; i < SK_ARRAY_COUNT(tests); ++i) {

+      SkPaint paint;

+      paint.setColor(SK_ColorRED);

+      canvas->drawPath(path, paint);

+      bool rectInPath = path.conservativelyContainsRect(tests[i]);

+      paint.setColor(rectInPath ? SK_ColorBLUE : SK_ColorBLACK);

+      canvas->drawRect(tests[i], paint);

+      canvas->translate(64, 0);

+    }

+}

+##

+

+#SeeAlso contains Op Rect Convexity

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void incReserve(unsigned extraPtCount)

+

+grows Path Verb_Array and Point_Array to contain extraPtCount additional Points.

+incReserve may improve performance and use less memory by

+reducing the number and size of allocations when creating Path.

+

+#Param extraPtCount  number of additional Points to preallocate. ##

+

+#Example

+#Height 192

+void draw(SkCanvas* canvas) {

+    auto addPoly = [](int sides, SkScalar size, SkPath* path) -> void {

+        path->moveTo(size, 0);

+        for (int i = 1; i < sides; i++) {

+            SkScalar c, s = SkScalarSinCos(SK_ScalarPI * 2 * i / sides, &c);

+            path->lineTo(c * size, s * size);

+        }

+        path->close();

+    };

+    SkPath path;

+    path.incReserve(3 + 4 + 5 + 6 + 7 + 8 + 9);

+    for (int sides = 3; sides < 10; ++sides) {

+       addPoly(sides, sides, &path);

+    }

+    SkMatrix matrix;

+    matrix.setScale(10, 10, -10, -10);

+    path.transform(matrix);

+    SkPaint paint;

+    paint.setStyle(SkPaint::kStroke_Style);

+    canvas->drawPath(path, paint);

+}

+##

+

+#SeeAlso Point_Array

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void moveTo(SkScalar x, SkScalar y)

+

+Adds beginning of Contour at Point (x, y).

+

+#Param x  x-coordinate of Contour start. ##

+#Param y  y-coordinate of Contour start. ##

+

+#Example

+    #Width 140

+    #Height 100

+    void draw(SkCanvas* canvas) {

+        SkRect rect = { 20, 20, 120, 80 };

+        SkPath path;

+        path.addRect(rect);

+        path.moveTo(rect.fLeft, rect.fTop);

+        path.lineTo(rect.fRight, rect.fBottom);

+        path.moveTo(rect.fLeft, rect.fBottom);

+        path.lineTo(rect.fRight, rect.fTop);

+        SkPaint paint;

+        paint.setStyle(SkPaint::kStroke_Style);

+        canvas->drawPath(path, paint);

+    }

+##

+

+#SeeAlso Contour lineTo rMoveTo quadTo conicTo cubicTo close()

+

+##

+

+#Method void moveTo(const SkPoint& p) 

+

+Adds beginning of Contour at Point p.

+

+#Param p  Contour start. ##

+

+#Example

+    #Width 128

+    #Height 128

+void draw(SkCanvas* canvas) {

+    SkPoint data[][3] = {{{30,40},{60,60},{90,30}}, {{30,120},{60,100},{90,120}}, 

+                         {{60,100},{60,40},{70,30}}, {{60,40},{50,20},{70,30}}};

+    SkPath path;

+    for (unsigned i = 0; i < SK_ARRAY_COUNT(data); ++i) {

+        path.moveTo(data[i][0]);

+        path.lineTo(data[i][1]);

+        path.lineTo(data[i][2]);

+    }

+    SkPaint paint;

+    paint.setStyle(SkPaint::kStroke_Style);

+    canvas->drawPath(path, paint);

+}

+##

+

+#SeeAlso Contour lineTo rMoveTo quadTo conicTo cubicTo close()

+

+##

+

+#Method void rMoveTo(SkScalar dx, SkScalar dy)

+

+Adds beginning of Contour relative to Last_Point.

+If Path is empty, starts Contour at (dx, dy).

+Otherwise, start Contour at Last_Point offset by (dx, dy). 

+rMoveTo stands for relative move to.

+

+#Param dx  offset from Last_Point x to Contour start x. ##

+#Param dy  offset from Last_Point y to Contour start y. ##

+

+#Example

+    #Height 100

+    SkPath path;

+    path.addRect({20, 20, 80, 80}, SkPath::kCW_Direction, 2);

+    path.rMoveTo(25, 2);

+    SkVector arrow[] = {{0, -4}, {-20, 0}, {0, -3}, {-5, 5}, {5, 5}, {0, -3}, {20, 0}};

+    for (unsigned i = 0; i < SK_ARRAY_COUNT(arrow); ++i) {

+        path.rLineTo(arrow[i].fX, arrow[i].fY);

+    }

+    SkPaint paint;

+    canvas->drawPath(path, paint);

+    SkPoint lastPt;

+    path.getLastPt(&lastPt);

+    canvas->drawString("start", lastPt.fX, lastPt.fY, paint);

+##

+

+#SeeAlso Contour lineTo moveTo quadTo conicTo cubicTo close()

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void lineTo(SkScalar x, SkScalar y)

+

+Adds Line from Last_Point to (x, y). If Path is empty, or last Verb is

+kClose_Verb, Last_Point is set to (0, 0) before adding Line.

+

+lineTo appends kMove_Verb to Verb_Array and (0, 0) to Point_Array, if needed.

+lineTo then appends kLine_Verb to Verb_Array and (x, y) to Point_Array.

+ 

+#Param x  end of added Line in x. ##

+#Param y  end of added Line in y. ##

+

+#Example

+#Height 100

+###$

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setTextSize(72);

+    canvas->drawString("#", 120, 80, paint);

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setStrokeWidth(5);

+    SkPath path;

+    SkPoint hash[] = {{58, 28}, {43, 80}, {37, 45}, {85, 45}};

+    SkVector offsets[] = {{0, 0}, {17, 0}, {0, 0}, {-5, 17}};

+    unsigned o = 0;

+    for (unsigned i = 0; i < SK_ARRAY_COUNT(hash); i += 2) {

+        for (unsigned j = 0; j < 2; o++, j++) {

+            path.moveTo(hash[i].fX + offsets[o].fX, hash[i].fY + offsets[o].fY);

+            path.lineTo(hash[i + 1].fX + offsets[o].fX, hash[i + 1].fY + offsets[o].fY);

+        }

+    }

+    canvas->drawPath(path, paint);

+}

+$$$#

+##

+

+#SeeAlso Contour moveTo rLineTo addRect

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void lineTo(const SkPoint& p) 

+

+Adds Line from Last_Point to Point p. If Path is empty, or last Verb is

+kClose_Verb, Last_Point is set to (0, 0) before adding Line.

+

+lineTo first appends kMove_Verb to Verb_Array and (0, 0) to Point_Array, if needed.

+lineTo then appends kLine_Verb to Verb_Array and Point p to Point_Array.

+

+#Param p  end Point of added Line. ##

+

+#Example

+#Height 100

+    SkPath path;

+    SkVector oxo[] = {{25, 25}, {35, 35}, {25, 35}, {35, 25},

+                      {40, 20}, {40, 80}, {60, 20}, {60, 80},

+                      {20, 40}, {80, 40}, {20, 60}, {80, 60}};

+    for (unsigned i = 0; i < SK_ARRAY_COUNT(oxo); i += 2) {

+        path.moveTo(oxo[i]);

+        path.lineTo(oxo[i + 1]);

+    }

+    SkPaint paint;

+    paint.setStyle(SkPaint::kStroke_Style);

+    canvas->drawPath(path, paint);

+##

+

+#SeeAlso Contour moveTo rLineTo addRect

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void rLineTo(SkScalar dx, SkScalar dy)

+

+Adds Line from Last_Point to Vector (dx, dy). If Path is empty, or last Verb is

+kClose_Verb, Last_Point is set to (0, 0) before adding Line.

+

+rLineTo first appends kMove_Verb to Verb_Array and (0, 0) to Point_Array, if needed.

+rLineTo then appends kLine_Verb to Verb_Array and Line end to Point_Array.

+Line end is Last_Point plus Vector (dx, dy).

+rLineTo stands for relative line to.

+

+#Param dx  offset from Last_Point x to Line end x. ##

+#Param dy  offset from Last_Point y to Line end y. ##

+

+#Example

+#Height 128

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    SkPath path;

+    path.moveTo(10, 98);

+    SkScalar x = 0, y = 0;

+    for (int i = 10; i < 100; i += 5) {

+        x += i * ((i & 2) - 1);

+        y += i * (((i + 1) & 2) - 1);

+        path.rLineTo(x, y);

+        

+    }

+    canvas->drawPath(path, paint);

+}

+##

+

+#SeeAlso Contour moveTo lineTo addRect

+

+##

+

+# ------------------------------------------------------------------------------

+#Topic Quad
+
+Quad describes a quadratic Bezier, a second-order curve identical to a section
+of a parabola. Quad begins at a start Point, curves towards a control Point,
+and then curves to an end Point.
+
+#Example
+#Height 110
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    SkPoint quadPts[] = {{20, 90}, {120, 10}, {220, 90}};

+    canvas->drawLine(quadPts[0], quadPts[1], paint);

+    canvas->drawLine(quadPts[1], quadPts[2], paint);

+    SkPath path;

+    path.moveTo(quadPts[0]);

+    path.quadTo(quadPts[1], quadPts[2]);

+    paint.setStrokeWidth(3);

+    canvas->drawPath(path, paint);

+}

+##
+
+Quad is a special case of Conic where Conic_Weight is set to one.
+
+Quad is always contained by the triangle connecting its three Points. Quad
+begins tangent to the line between start Point and control Point, and ends
+tangent to the line between control Point and end Point.
+
+#Example
+#Height 160
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    SkPoint quadPts[] = {{20, 150}, {120, 10}, {220, 150}};

+    SkColor colors[] = { 0xff88ff00, 0xff0088bb, 0xff6600cc, 0xffbb3377 };

+    for (unsigned i = 0; i < SK_ARRAY_COUNT(colors); ++i) {

+        paint.setColor(0x7fffffff & colors[i]);

+        paint.setStrokeWidth(1);

+        canvas->drawLine(quadPts[0], quadPts[1], paint);

+        canvas->drawLine(quadPts[1], quadPts[2], paint);

+        SkPath path;

+        path.moveTo(quadPts[0]);

+        path.quadTo(quadPts[1], quadPts[2]);

+        paint.setStrokeWidth(3);

+        paint.setColor(colors[i]);

+        canvas->drawPath(path, paint);

+        quadPts[1].fY += 30;

+   }

+}
+##
+

+#Method void quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2)

+

+    Adds Quad from Last_Point towards (x1, y1), to (x2, y2). 

+    If Path is empty, or last Verb is kClose_Verb, Last_Point is set to (0, 0)

+    before adding Quad.

+

+    quadTo appends kMove_Verb to Verb_Array and (0, 0) to Point_Array, if needed.

+    quadTo then appends kQuad_Verb to Verb_Array; and (x1, y1), (x2, y2)

+    to Point_Array.

+

+    #Param x1  control Point of Quad in x. ##

+    #Param y1  control Point of Quad in y. ##

+    #Param x2  end Point of Quad in x. ##

+    #Param y2  end Point of Quad in y. ##

+

+    #Example

+    void draw(SkCanvas* canvas) {

+        SkPaint paint;

+        paint.setAntiAlias(true);

+        paint.setStyle(SkPaint::kStroke_Style);

+        SkPath path;

+        path.moveTo(0, -10);

+        for (int i = 0; i < 128; i += 16) {

+            path.quadTo( 10 + i, -10 - i,  10 + i,       0);

+            path.quadTo( 14 + i,  14 + i,       0,  14 + i);

+            path.quadTo(-18 - i,  18 + i, -18 - i,       0);

+            path.quadTo(-22 - i, -22 - i,       0, -22 - i);

+        }

+        path.offset(128, 128);

+        canvas->drawPath(path, paint);
+    }

+    ##

+

+    #SeeAlso Contour moveTo conicTo rQuadTo

+

+##

+

+#Method void quadTo(const SkPoint& p1, const SkPoint& p2) 

+

+    Adds Quad from Last_Point towards Point p1, to Point p2. 

+    If Path is empty, or last Verb is kClose_Verb, Last_Point is set to (0, 0)

+    before adding Quad.

+

+    quadTo appends kMove_Verb to Verb_Array and (0, 0) to Point_Array, if needed.

+    quadTo then appends kQuad_Verb to Verb_Array; and Points p1, p2

+    to Point_Array.

+

+    #Param p1  control Point of added Quad. ##

+    #Param p2  end Point of added Quad. ##

+

+    #Example

+    void draw(SkCanvas* canvas) {

+        SkPaint paint;

+        paint.setStyle(SkPaint::kStroke_Style);

+        paint.setAntiAlias(true);

+        SkPath path;

+        SkPoint pts[] = {{128, 10}, {10, 214}, {236, 214}};

+        path.moveTo(pts[1]);

+        for (int i = 0; i < 3; ++i) {

+            path.quadTo(pts[i % 3],  pts[(i + 2) % 3]);

+        }

+        canvas->drawPath(path, paint);

+    }

+    ##

+

+    #SeeAlso Contour moveTo conicTo rQuadTo

+

+##

+

+#Method void rQuadTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2)

+

+    Adds Quad from Last_Point towards Vector (dx1, dy1), to Vector (dx2, dy2).

+    If Path is empty, or last Verb

+    is kClose_Verb, Last_Point is set to (0, 0) before adding Quad.

+

+    rQuadTo first appends kMove_Verb to Verb_Array and (0, 0) to Point_Array,

+    if needed. rQuadTo then appends kQuad_Verb to Verb_Array; and appends Quad

+    control and Quad end to Point_Array.

+    Quad control is Last_Point plus Vector (dx1, dy1).

+    Quad end is Last_Point plus Vector (dx2, dy2).

+    rQuadTo stands for relative quad to.

+

+    #Param dx1  offset from Last_Point x to Quad control x. ##

+    #Param dy1  offset from Last_Point x to Quad control y. ##

+    #Param dx2  offset from Last_Point x to Quad end x. ##

+    #Param dy2  offset from Last_Point x to Quad end y. ##

+

+    #Example

+    void draw(SkCanvas* canvas) {

+        SkPaint paint;

+        paint.setAntiAlias(true);

+        SkPath path;

+        path.moveTo(128, 20);

+        path.rQuadTo(-6, 10, -7, 10);

+        for (int i = 1; i < 32; i += 4) {

+           path.rQuadTo(10 + i, 10 + i, 10 + i * 4, 10);

+           path.rQuadTo(-10 - i, 10 + i, -10 - (i + 2) * 4, 10);

+        }

+        path.quadTo(92, 220, 128, 215);

+        canvas->drawPath(path, paint);

+    }

+    ##

+

+    #SeeAlso Contour moveTo conicTo quadTo

+

+##

+

+#Topic Quad ##
+

+# ------------------------------------------------------------------------------

+

+#Topic Conic
+#Alias Conics
+
+Conic describes a conical section: a piece of an ellipse, or a piece of a
+parabola, or a piece of a hyperbola. Conic begins at a start Point, 
+curves towards a control Point, and then curves to an end Point. The influence
+of the control Point is determined by Conic_Weight.
+
+Each Conic in Path adds two Points and one Weight. Weights in Path may be
+inspected with Iter, or with RawIter.
+
+#Subtopic Weight
+#Alias Weights
+
+Weight determines both the strength of the control Point and the type of Conic.
+If Weight is exactly one, then Conic is identical to Quad; it is always a
+parabolic segment. 
+
+
+
+#Example
+#Description
+When Conic weight is one, Quad is added to path; the two are identical. 
+##
+void draw(SkCanvas* canvas) {

+    const char* verbNames[] = { "move", "line", "quad", "conic", "cubic", "close", "done" };

+    const int pointCount[]  = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };

+    SkPath path;

+    path.conicTo(20, 30, 50, 60, 1);

+    SkPath::Iter iter(path, false);

+    SkPath::Verb verb;

+    do {

+       SkPoint points[4];

+       verb = iter.next(points);

+       SkDebugf("%s ", verbNames[(int) verb]);

+       for (int i = 0; i < pointCount[(int) verb]; ++i) {

+            SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);

+       }

+       if (SkPath::kConic_Verb == verb) {

+           SkDebugf("weight = %g", iter.conicWeight());

+       }

+       SkDebugf("\n");

+    } while (SkPath::kDone_Verb != verb);

+}
+#StdOut
+move {0, 0}, 

+quad {0, 0}, {20, 30}, {50, 60}, 

+done 
+##
+##
+
+If weight is less than one, Conic is an elliptical segment.
+
+#Example 
+#Description
+A 90 degree circular arc has the weight 
+#Formula
+1 / sqrt(2)
+##
+ .
+##
+void draw(SkCanvas* canvas) {

+    const char* verbNames[] = { "move", "line", "quad", "conic", "cubic", "close", "done" };

+    const int pointCount[]  = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };

+    SkPath path;

+    path.arcTo(20, 0, 20, 20, 20);

+    SkPath::Iter iter(path, false);

+    SkPath::Verb verb;

+    do {

+       SkPoint points[4];

+       verb = iter.next(points);

+       SkDebugf("%s ", verbNames[(int) verb]);

+       for (int i = 0; i < pointCount[(int) verb]; ++i) {

+            SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);

+       }

+       if (SkPath::kConic_Verb == verb) {

+           SkDebugf("weight = %g", iter.conicWeight());

+       }

+       SkDebugf("\n");

+    } while (SkPath::kDone_Verb != verb);

+}
+#StdOut
+move {0, 0}, 

+conic {0, 0}, {20, 0}, {20, 20}, weight = 0.707107

+done 
+##
+##
+
+If weight is greater than one, Conic is a hyperbolic segment. As w gets large,
+a hyperbolic segment can be approximated by straight lines connecting the
+control Point with the end Points.
+
+#Example
+void draw(SkCanvas* canvas) {

+    const char* verbNames[] = { "move", "line", "quad", "conic", "cubic", "close", "done" };

+    const int pointCount[]  = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };

+    SkPath path;

+    path.conicTo(20, 0, 20, 20, SK_ScalarInfinity);

+    SkPath::Iter iter(path, false);

+    SkPath::Verb verb;

+    do {

+       SkPoint points[4];

+       verb = iter.next(points);

+       SkDebugf("%s ", verbNames[(int) verb]);

+       for (int i = 0; i < pointCount[(int) verb]; ++i) {

+            SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);

+       }

+       if (SkPath::kConic_Verb == verb) {

+           SkDebugf("weight = %g", iter.conicWeight());

+       }

+       SkDebugf("\n");

+    } while (SkPath::kDone_Verb != verb);

+}
+#StdOut
+move {0, 0}, 

+line {0, 0}, {20, 0}, 

+line {20, 0}, {20, 20}, 

+done 
+##
+##
+
+#Subtopic Weight ##
+

+#Method void conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,

+                 SkScalar w)

+

+    Adds Conic from Last_Point towards (x1, y1), to (x2, y2), weighted by w. 

+    If Path is empty, or last Verb is kClose_Verb, Last_Point is set to (0, 0)

+    before adding Conic.

+

+    conicTo appends kMove_Verb to Verb_Array and (0, 0) to Point_Array, if needed.

+

+    If w is finite and not one, conicTo then appends kConic_Verb to Verb_Array;

+    and (x1, y1), (x2, y2) to Point_Array; and w to Weights.

+

+    If w is one, conicTo appends kQuad_Verb to Verb_Array, and

+    (x1, y1), (x2, y2) to Point_Array.

+

+    If w is not finite, conicTo appends kLine_Verb twice to Verb_Array, and

+    (x1, y1), (x2, y2) to Point_Array.

+

+    #Param x1  control Point of Conic in x. ##

+    #Param y1  control Point of Conic in y. ##

+    #Param x2  end Point of Conic in x. ##

+    #Param y2  end Point of Conic in y. ##

+    #Param w   weight of added Conic. ##

+

+    #Example

+    #Height 160
+    #Description

+    As weight increases, curve is pulled towards control point. 

+    The bottom two curves are elliptical; the next is parabolic; the

+    top curve is hyperbolic.

+    ##

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    SkPoint conicPts[] = {{20, 150}, {120, 10}, {220, 150}};

+    canvas->drawLine(conicPts[0], conicPts[1], paint);

+    canvas->drawLine(conicPts[1], conicPts[2], paint);

+    SkColor colors[] = { 0xff88ff00, 0xff0088bb, 0xff6600cc, 0xffbb3377 };

+    paint.setStrokeWidth(3);

+    SkScalar weight = 0.5f;

+    for (unsigned i = 0; i < SK_ARRAY_COUNT(colors); ++i) {

+        SkPath path;

+        path.moveTo(conicPts[0]);

+        path.conicTo(conicPts[1], conicPts[2], weight);

+        paint.setColor(colors[i]);

+        canvas->drawPath(path, paint);

+        weight += 0.25f;

+   }

+}

+    ##

+

+    #SeeAlso rConicTo arcTo addArc quadTo

+

+##

+

+#Method void conicTo(const SkPoint& p1, const SkPoint& p2, SkScalar w) 

+

+    Adds Conic from Last_Point towards Point p1, to Point p2, weighted by w. 

+    If Path is empty, or last Verb is kClose_Verb, Last_Point is set to (0, 0)

+    before adding Conic.

+

+    conicTo appends kMove_Verb to Verb_Array and (0, 0) to Point_Array, if needed.

+

+    If w is finite and not one, conicTo then appends kConic_Verb to Verb_Array;

+    and Points p1, p2 to Point_Array; and w to Weights.

+

+    If w is one, conicTo appends kQuad_Verb to Verb_Array, and Points p1, p2

+    to Point_Array.

+

+    If w is not finite, conicTo appends kLine_Verb twice to Verb_Array, and

+    Points p1, p2 to Point_Array.

+

+    #Param p1  control Point of added Conic. ##

+    #Param p2  end Point of added Conic. ##

+    #Param w   weight of added Conic. ##

+

+    #Example

+    #Height 128
+    #Description

+    Conics and arcs use identical representations. As the arc sweep increases

+    the conic weight also increases, but remains smaller than one.

+    ##

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    SkRect oval = {0, 20, 120, 140};

+    SkPath path;

+    for (int i = 0; i < 4; ++i) {

+        path.moveTo(oval.centerX(), oval.fTop);

+        path.arcTo(oval, -90, 90 - 20 * i, false);

+        oval.inset(15, 15);

+    }

+    path.offset(100, 0);

+    SkScalar conicWeights[] = { 0.707107f, 0.819152f, 0.906308f, 0.965926f };

+    SkPoint conicPts[][3] = { { {40, 20}, {100, 20}, {100, 80} },

+                              { {40, 35}, {71.509f, 35}, {82.286f, 64.6091f} },

+                              { {40, 50}, {53.9892f, 50}, {62.981f, 60.7164f} },

+                              { {40, 65}, {44.0192f, 65}, {47.5f, 67.0096f} } };

+    for (int i = 0; i < 4; ++i) {

+         path.moveTo(conicPts[i][0]);

+         path.conicTo(conicPts[i][1], conicPts[i][2], conicWeights[i]);

+    }

+    canvas->drawPath(path, paint);

+}

+    ##

+

+    #SeeAlso rConicTo arcTo addArc quadTo

+

+##

+

+#Method void rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,

+                  SkScalar w)

+

+    Adds Conic from Last_Point towards Vector (dx1, dy1), to Vector (dx2, dy2),

+    weighted by w. If Path is empty, or last Verb

+    is kClose_Verb, Last_Point is set to (0, 0) before adding Conic.

+

+    rConicTo first appends kMove_Verb to Verb_Array and (0, 0) to Point_Array,

+    if needed. 

+    

+    If w is finite and not one, rConicTo then appends kConic_Verb to Verb_Array,

+    and w is recorded as Conic_Weight; otherwise, if w is one, rConicTo appends

+    kQuad_Verb to Verb_Array; or if w is not finite, rConicTo appends kLine_Verb

+    twice to Verb_Array.

+

+    In all cases rConicTo then appends Points control and end to Point_Array.

+    control is Last_Point plus Vector (dx1, dy1).

+    end is Last_Point plus Vector (dx2, dy2).

+

+    rConicTo stands for relative conic to.

+

+    #Param dx1  offset from Last_Point x to Conic control x. ##

+    #Param dy1  offset from Last_Point x to Conic control y. ##

+    #Param dx2  offset from Last_Point x to Conic end x.  ##

+    #Param dy2  offset from Last_Point x to Conic end y.  ##

+    #Param w    weight of added Conic. ##

+

+    #Example

+    #Height 140

+    void draw(SkCanvas* canvas) {

+        SkPaint paint;

+        paint.setAntiAlias(true);

+        paint.setStyle(SkPaint::kStroke_Style);

+        SkPath path;

+        path.moveTo(20, 80);

+        path.rConicTo( 60,   0,  60,  60, 0.707107f);

+        path.rConicTo(  0, -60,  60, -60, 0.707107f);

+        path.rConicTo(-60,   0, -60, -60, 0.707107f);

+        path.rConicTo(  0,  60, -60,  60, 0.707107f);

+        canvas->drawPath(path, paint);

+    }

+    ##

+

+    #SeeAlso conicTo arcTo addArc quadTo

+

+##

+

+#Topic Conic ##

+

+# ------------------------------------------------------------------------------

+#Topic Cubic
+#Alias Cubics
+
+Cubic describes a cubic Bezier, a third-order curve. 
+Cubic begins at a start Point, curving towards the first control Point;
+and curves from the end Point towards the second control Point.
+
+#Example
+#Height 160
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    SkPoint cubicPts[] = {{20, 150}, {90, 10}, {160, 150}, {230, 10}};

+    SkColor colors[] = { 0xff88ff00, 0xff0088bb, 0xff6600cc, 0xffbb3377 };

+    for (unsigned i = 0; i < SK_ARRAY_COUNT(colors); ++i) {

+        paint.setColor(0x7fffffff & colors[i]);

+        paint.setStrokeWidth(1);

+        for (unsigned j = 0; j < 3; ++j) {

+            canvas->drawLine(cubicPts[j], cubicPts[j + 1], paint);

+        }

+        SkPath path;

+        path.moveTo(cubicPts[0]);

+        path.cubicTo(cubicPts[1], cubicPts[2], cubicPts[3]);

+        paint.setStrokeWidth(3);

+        paint.setColor(colors[i]);

+        canvas->drawPath(path, paint);

+        cubicPts[1].fY += 30;

+        cubicPts[2].fX += 30;

+   }

+}
+##
+

+#Method void cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,

+                 SkScalar x3, SkScalar y3)

+

+Adds Cubic from Last_Point towards (x1, y1), then towards (x2, y2), ending at

+(x3, y3). If Path is empty, or last Verb is kClose_Verb, Last_Point is set to

+(0, 0) before adding Cubic.

+

+cubicTo appends kMove_Verb to Verb_Array and (0, 0) to Point_Array, if needed.

+cubicTo then appends kCubic_Verb to Verb_Array; and (x1, y1), (x2, y2), (x3, y3)

+to Point_Array.

+

+#Param x1  first control Point of Cubic in x. ##

+#Param y1  first control Point of Cubic in y. ##

+#Param x2  second control Point of Cubic in x. ##

+#Param y2  second control Point of Cubic in y. ##

+#Param x3  end Point of Cubic in x. ##

+#Param y3  end Point of Cubic in y. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    SkPath path;

+    path.moveTo(0, -10);

+    for (int i = 0; i < 128; i += 16) {

+        SkScalar c = i * 0.5f;

+        path.cubicTo( 10 + c, -10 - i,  10 + i, -10 - c,  10 + i,       0);

+        path.cubicTo( 14 + i,  14 + c,  14 + c,  14 + i,       0,  14 + i);

+        path.cubicTo(-18 - c,  18 + i, -18 - i,  18 + c, -18 - i,       0);

+        path.cubicTo(-22 - i, -22 - c, -22 - c, -22 - i,       0, -22 - i);

+    }

+    path.offset(128, 128);

+    canvas->drawPath(path, paint);

+}

+##

+

+#SeeAlso Contour moveTo rCubicTo quadTo

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void cubicTo(const SkPoint& p1, const SkPoint& p2, const SkPoint& p3) 

+

+Adds Cubic from Last_Point towards Point p1, then towards Point p2, ending at

+Point p3. If Path is empty, or last Verb is kClose_Verb, Last_Point is set to

+(0, 0) before adding Cubic.

+

+cubicTo appends kMove_Verb to Verb_Array and (0, 0) to Point_Array, if needed.

+cubicTo then appends kCubic_Verb to Verb_Array; and Points p1, p2, p3

+to Point_Array.

+

+#Param p1  first control Point of Cubic. ##

+#Param p2  second control Point of Cubic. ##

+#Param p3  end Point of Cubic. ##

+

+#Example

+#Height 84

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    SkPoint pts[] = { {20, 20}, {300, 80}, {-140, 90}, {220, 10} };

+    SkPath path;

+    path.moveTo(pts[0]);

+    path.cubicTo(pts[1], pts[2], pts[3]);

+    canvas->drawPath(path, paint);

+##

+

+#SeeAlso Contour moveTo rCubicTo quadTo

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,

+                  SkScalar x3, SkScalar y3)

+

+    Adds Cubic from Last_Point towards Vector (dx1, dy1), then towards

+    Vector (dx2, dy2), to Vector (dx3, dy3).

+    If Path is empty, or last Verb

+    is kClose_Verb, Last_Point is set to (0, 0) before adding Cubic.

+

+    rCubicTo first appends kMove_Verb to Verb_Array and (0, 0) to Point_Array,

+    if needed. rCubicTo then appends kCubic_Verb to Verb_Array; and appends Cubic

+    control and Cubic end to Point_Array.

+    Cubic control is Last_Point plus Vector (dx1, dy1).

+    Cubic end is Last_Point plus Vector (dx2, dy2).

+    rCubicTo stands for relative cubic to.

+

+    #Param x1  offset from Last_Point x to first Cubic control x. ##

+    #Param y1  offset from Last_Point x to first Cubic control y. ##

+    #Param x2  offset from Last_Point x to second Cubic control x. ##

+    #Param y2  offset from Last_Point x to second Cubic control y. ##

+    #Param x3  offset from Last_Point x to Cubic end x. ##

+    #Param y3  offset from Last_Point x to Cubic end y. ##

+

+#Example

+    void draw(SkCanvas* canvas) {

+        SkPaint paint;

+        paint.setAntiAlias(true);

+        paint.setStyle(SkPaint::kStroke_Style);

+        SkPath path;

+        path.moveTo(24, 108);

+        for (int i = 0; i < 16; i++) {

+           SkScalar sx, sy;

+           sx = SkScalarSinCos(i * SK_ScalarPI / 8, &sy);

+           path.rCubicTo(40 * sx, 4 * sy, 4 * sx, 40 * sy, 40 * sx, 40 * sy);

+        }

+        canvas->drawPath(path, paint);

+    }

+##

+

+#SeeAlso Contour moveTo cubicTo quadTo

+

+##

+

+#Topic Cubic ##

+

+# ------------------------------------------------------------------------------

+

+#Topic Arc

+

+Arc can be constructed in a number of ways. Arc may be described by part of Oval and angles,

+by start point and end point, and by radius and tangent lines. Each construction has advantages,

+and some constructions correspond to Arc drawing in graphics standards.

+

+All Arc draws are implemented by one or more Conic draws. When Conic_Weight is less than one,

+Conic describes an Arc of some Oval or Circle.

+

+arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo)

+describes Arc as a piece of Oval, beginning at start angle, sweeping clockwise or counterclockwise,

+which may continue Contour or start a new one. This construction is similar to PostScript and 

+HTML_Canvas arcs. Variation addArc always starts new Contour. Canvas::drawArc draws without

+requiring Path.

+

+arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius)

+describes Arc as tangent to the line (x0, y0), (x1, y1) and tangent to the line (x1, y1), (x2, y2)

+where (x0, y0) is the last Point added to Path. This construction is similar to PostScript and

+HTML_Canvas arcs.

+

+arcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc, Direction sweep,

+      SkScalar x, SkScalar y) 

+describes Arc as part of Oval with radii (rx, ry), beginning at

+last Point added to Path and ending at (x, y). More than one Arc satisfies this criteria,

+so additional values choose a single solution. This construction is similar to SVG arcs.

+

+conicTo describes Arc of less than 180 degrees as a pair of tangent lines and Conic_Weight.

+conicTo can represent any Arc with a sweep less than 180 degrees at any rotation. All arcTo

+constructions are converted to Conic data when added to Path. 

+

+#ToDo  allow example to hide source and not be exposed as fiddle since markdown / html can't

+       do the kind of table shown in the illustration.

+       example is spaced correctly on fiddle but spacing is too wide on pc

+##

+

+#Example

+#Height 300

+#Width 600

+#Description

+#List

+# <sup>1</sup> arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo) ##

+# <sup>2</sup> parameter sets force MoveTo  ##

+# <sup>3</sup> start angle must be multiple of 90 degrees. ##

+# <sup>4</sup> arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) ##

+# <sup>5</sup> arcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc,

+               Direction sweep, SkScalar x, SkScalar y) ##

+#List ##

+#Description ##

+#Function

+struct data {

+   const char* name;

+   char super;

+   int yn[10];

+};

+

+const data dataSet[] = {

+{ "arcTo sweep",    '1', {1,  3, 1, 0, 0, 0, 0, 1, 0, 0 }},

+{ "drawArc",         0,  {1, -1, 1, 1, 1, 1, 1, 0, 0, 0 }},

+{ "addArc",          0,  {1,  1, 1, 4, 0, 1, 1, 1, 0, 0 }},

+{ "arcTo tangents", '4', {0,  0, 0, 0, 0, 0, 0, 1, 1, 0 }},

+{ "arcTo radii",    '5', {1,  0, 1, 0, 0, 0, 0, 1, 1, 0 }},

+{ "conicTo",         0,  {1,  1, 0, 0, 0, 0, 0, 1, 1, 1 }}

+};

+

+#define __degree_symbol__ "\xC2" "\xB0"

+

+const char* headers[] = {

+    "Oval part",

+    "force moveTo",

+    "can draw 180" __degree_symbol__,

+    "can draw 360" __degree_symbol__,

+    "can draw greater than 360" __degree_symbol__,

+    "ignored if radius is zero",

+    "ignored if sweep is zero",

+    "requires Path",

+    "describes rotation",

+    "describes perspective",

+};

+

+const char* yna[] = {

+     "n/a",

+     "no",

+     "yes"

+};

+

+##

+void draw(SkCanvas* canvas) {

+    SkPaint lp;

+    lp.setAntiAlias(true);

+    SkPaint tp(lp);

+    SkPaint sp(tp);

+    SkPaint bp(tp);

+    bp.setFakeBoldText(true);

+    sp.setTextSize(10);

+    lp.setColor(SK_ColorGRAY);

+    canvas->translate(0, 32);

+    const int tl = 115;

+    for (unsigned col = 0; col <= SK_ARRAY_COUNT(headers); ++col) {

+       canvas->drawLine(tl + col * 35, 100, tl + col * 35, 250, lp);

+       if (0 == col) {

+          continue;

+       }

+       canvas->drawLine(tl + col * 35, 100, tl + 100 + col * 35, 0, lp);

+       SkPath path;

+       path.moveTo(tl - 3 + col * 35, 103);

+       path.lineTo(tl + 124 + col * 35, -24);

+       canvas->drawTextOnPathHV(headers[col -1], strlen(headers[col -1]), path, 0, -9, bp);

+    }

+    for (unsigned row = 0; row <= SK_ARRAY_COUNT(dataSet); ++row) {

+        if (0 == row) {

+            canvas->drawLine(tl, 100, tl + 350, 100, lp);

+        } else {

+            canvas->drawLine(5, 100 + row * 25, tl + 350, 100 + row * 25, lp);

+        }

+        if (row == SK_ARRAY_COUNT(dataSet)) {

+            break;

+        }

+        canvas->drawString(dataSet[row].name, 5, 117 + row * 25, bp);

+        if (dataSet[row].super) {

+            SkScalar width = bp.measureText(dataSet[row].name, strlen(dataSet[row].name));

+            canvas->drawText(&dataSet[row].super, 1, 8 + width, 112 + row * 25, sp);

+        }

+        for (unsigned col = 0; col < SK_ARRAY_COUNT(headers); ++col) {

+            int val = dataSet[row].yn[col];

+            canvas->drawString(yna[SkTMin(2, val + 1)], tl + 5 + col * 35, 117 + row * 25, tp);

+            if (val > 1) {

+                char supe = '0' + val - 1;

+                canvas->drawText(&supe, 1, tl + 25 + col * 35, 112 + row * 25, sp);

+            }

+        }

+    }

+}

+#Example ##

+

+#Example

+#Height 128

+#Description

+#ToDo make this a list or table ##

+1 describes an arc from an oval, a starting angle, and a sweep angle.

+2 is similar to 1, but does not require building a path to draw.

+3 is similar to 1, but always begins new Contour.

+4 describes an arc from a pair of tangent lines and a radius.

+5 describes an arc from Oval center, arc start Point and arc end Point.

+6 describes an arc from a pair of tangent lines and a Conic_Weight.

+##

+void draw(SkCanvas* canvas) {

+    SkRect oval = {8, 8, 56, 56};

+    SkPaint ovalPaint;

+    ovalPaint.setAntiAlias(true);

+    SkPaint textPaint(ovalPaint);

+    ovalPaint.setStyle(SkPaint::kStroke_Style);

+    SkPaint arcPaint(ovalPaint);

+    arcPaint.setStrokeWidth(5);

+    arcPaint.setColor(SK_ColorBLUE);

+    canvas->translate(-64, 0);

+    for (char arcStyle = '1'; arcStyle <= '6'; ++arcStyle) {

+        '4' == arcStyle ? canvas->translate(-96, 55) : canvas->translate(64, 0);

+        canvas->drawText(&arcStyle, 1, 30, 36, textPaint);

+        canvas->drawOval(oval, ovalPaint);

+        SkPath path;

+        path.moveTo({56, 32});

+        switch (arcStyle) {

+            case '1':

+                path.arcTo(oval, 0, 90, false);

+                break;

+            case '2':

+                canvas->drawArc(oval, 0, 90, false, arcPaint);

+                continue;

+            case '3':

+                path.addArc(oval, 0, 90);

+                break;

+            case '4':

+                path.arcTo({56, 56}, {32, 56}, 24);

+                break;

+            case '5':

+                path.arcTo({24, 24}, 0, SkPath::kSmall_ArcSize, SkPath::kCW_Direction, {32, 56});

+                break;

+            case '6':

+                path.conicTo({56, 56}, {32, 56}, SK_ScalarRoot2Over2);

+                break;

+         }

+         canvas->drawPath(path, arcPaint);

+     }

+}

+#Example ##

+

+

+#Method void arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo)

+

+Append Arc to Path. Arc added is part of ellipse

+bounded by oval, from startAngle through sweepAngle. Both startAngle and

+sweepAngle are measured in degrees, where zero degrees is aligned with the

+positive x-axis, and positive sweeps extends Arc clockwise.

+

+arcTo adds Line connecting Path last Point to initial Arc Point if forceMoveTo

+is false and Path is not empty. Otherwise, added Contour begins with first point

+of Arc. Angles greater than -360 and less than 360 are treated modulo 360.

+

+#Param oval        bounds of ellipse containing Arc. ##

+#Param startAngle  starting angle of Arc in degrees. ##

+#Param sweepAngle  sweep, in degrees. Positive is clockwise; treated modulo 360. ##

+#Param forceMoveTo  true to start a new contour with Arc. ##

+

+#Example

+#Height 200

+#Description

+arcTo continues a previous contour when forceMoveTo is false and when Path

+is not empty.

+##

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    SkPath path;

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setStrokeWidth(4);

+    path.moveTo(0, 0);

+    path.arcTo({20, 20, 120, 120}, -90, 90, false);

+    canvas->drawPath(path, paint);

+    path.rewind();

+    path.arcTo({120, 20, 220, 120}, -90, 90, false);

+    canvas->drawPath(path, paint);

+    path.rewind();

+    path.moveTo(0, 0);

+    path.arcTo({20, 120, 120, 220}, -90, 90, true);

+    canvas->drawPath(path, paint);

+}

+##

+

+#SeeAlso addArc SkCanvas::drawArc conicTo

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius)

+

+Append Arc to Path, after appending Line if needed. Arc is implemented by Conic

+weighted to describe part of Circle. Arc is contained by tangent from

+last Path point (x0, y0) to (x1, y1), and tangent from (x1, y1) to (x2, y2). Arc

+is part of Circle sized to radius, positioned so it touches both tangent lines. 

+

+#ToDo  allow example to hide source and not be exposed as fiddle ##

+

+#Example

+#Height 226

+void draw(SkCanvas* canvas) {

+    SkPaint tangentPaint;

+    tangentPaint.setAntiAlias(true);

+    SkPaint textPaint(tangentPaint);

+    tangentPaint.setStyle(SkPaint::kStroke_Style);

+    tangentPaint.setColor(SK_ColorGRAY);

+    SkPaint arcPaint(tangentPaint);

+    arcPaint.setStrokeWidth(5);

+    arcPaint.setColor(SK_ColorBLUE);

+    SkPath path;

+    SkPoint pts[] = { {56, 20}, {200, 20}, {90, 190} };

+    SkScalar radius = 50;

+    path.moveTo(pts[0]);

+    path.arcTo(pts[1], pts[2], radius);

+    canvas->drawLine(pts[0], pts[1], tangentPaint);

+    canvas->drawLine(pts[1], pts[2], tangentPaint);

+    SkPoint lastPt;

+    (void) path.getLastPt(&lastPt);

+    SkVector radial = pts[2] - pts[1];

+    radial.setLength(radius);

+    SkPoint center = { lastPt.fX - radial.fY, lastPt.fY + radial.fX };

+    canvas->drawCircle(center, radius, tangentPaint);

+    canvas->drawLine(lastPt, center, tangentPaint);

+    radial = pts[1] - pts[0];

+    radial.setLength(radius);

+    SkPoint arcStart = { center.fX + radial.fY, center.fY - radial.fX };

+    canvas->drawLine(center, arcStart, tangentPaint);

+    canvas->drawPath(path, arcPaint);

+    textPaint.setTextAlign(SkPaint::kRight_Align);

+    canvas->drawString("(x0, y0)", pts[0].fX - 5, pts[0].fY, textPaint);

+    textPaint.setTextAlign(SkPaint::kLeft_Align);

+    canvas->drawString("(x1, y1)", pts[1].fX + 5, pts[1].fY, textPaint);

+    textPaint.setTextAlign(SkPaint::kCenter_Align);

+    canvas->drawString("(x2, y2)", pts[2].fX, pts[2].fY + 15, textPaint);

+    textPaint.setTextAlign(SkPaint::kRight_Align);

+    canvas->drawString("radius", center.fX + 15, center.fY + 25, textPaint);

+    canvas->drawString("radius", center.fX - 3, center.fY - 16, textPaint);

+}

+##

+

+If last Path Point does not start Arc, arcTo appends connecting Line to Path.

+The length of Vector from (x1, y1) to (x2, y2) does not affect Arc.

+

+#Example

+#Height 128

+void draw(SkCanvas* canvas) {

+    SkPaint tangentPaint;

+    tangentPaint.setAntiAlias(true);

+    SkPaint textPaint(tangentPaint);

+    tangentPaint.setStyle(SkPaint::kStroke_Style);

+    tangentPaint.setColor(SK_ColorGRAY);

+    SkPaint arcPaint(tangentPaint);

+    arcPaint.setStrokeWidth(5);

+    arcPaint.setColor(SK_ColorBLUE);

+    SkPath path;

+    SkPoint pts[] = { {156, 20}, {200, 20}, {170, 50} };

+    SkScalar radius = 50;

+    path.moveTo(pts[0]);

+    path.arcTo(pts[1], pts[2], radius);

+    canvas->drawLine(pts[0], pts[1], tangentPaint);

+    canvas->drawLine(pts[1], pts[2], tangentPaint);

+    SkPoint lastPt;

+    (void) path.getLastPt(&lastPt);

+    SkVector radial = pts[2] - pts[1];

+    radial.setLength(radius);

+    SkPoint center = { lastPt.fX - radial.fY, lastPt.fY + radial.fX };

+    canvas->drawLine(lastPt, center, tangentPaint);

+    radial = pts[1] - pts[0];

+    radial.setLength(radius);

+    SkPoint arcStart = { center.fX + radial.fY, center.fY - radial.fX };

+    canvas->drawLine(center, arcStart, tangentPaint);

+    canvas->drawPath(path, arcPaint);

+    textPaint.setTextAlign(SkPaint::kCenter_Align);

+    canvas->drawString("(x0, y0)", pts[0].fX, pts[0].fY - 7, textPaint);

+    textPaint.setTextAlign(SkPaint::kLeft_Align);

+    canvas->drawString("(x1, y1)", pts[1].fX + 5, pts[1].fY, textPaint);

+    textPaint.setTextAlign(SkPaint::kCenter_Align);

+    canvas->drawString("(x2, y2)", pts[2].fX, pts[2].fY + 15, textPaint);

+    textPaint.setTextAlign(SkPaint::kRight_Align);

+    canvas->drawString("radius", center.fX + 15, center.fY + 25, textPaint);

+    canvas->drawString("radius", center.fX - 5, center.fY - 20, textPaint);

+}

+##

+

+Arc sweep is always less than 180 degrees. If radius is zero, or if

+tangents are nearly parallel, arcTo appends Line from last Path Point to (x1, y1).

+

+arcTo appends at most one Line and one Conic.

+arcTo implements the functionality of PostScript_arct and HTML_Canvas_arcTo.

+

+#Param x1      x common to pair of tangents. ##

+#Param y1      y common to pair of tangents. ##

+#Param x2      x end of second tangent. ##

+#Param y2      y end of second tangent. ##

+#Param radius  distance from Arc to Circle center. ##

+

+#Example

+#Description

+arcTo is represented by Line and circular Conic in Path.

+##

+void draw(SkCanvas* canvas) {

+    SkPath path;

+    path.moveTo({156, 20});

+    path.arcTo(200, 20, 170, 50, 50);

+    SkPath::Iter iter(path, false);

+    SkPoint p[4];

+    SkPath::Verb verb;

+    while (SkPath::kDone_Verb != (verb = iter.next(p))) {

+        switch (verb) {

+            case SkPath::kMove_Verb:

+                SkDebugf("move to (%g,%g)\n", p[0].fX, p[0].fY);

+                break;

+            case SkPath::kLine_Verb:

+                SkDebugf("line (%g,%g),(%g,%g)\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY);

+                break;

+            case SkPath::kConic_Verb:

+                SkDebugf("conic (%g,%g),(%g,%g),(%g,%g) weight %g\n",

+                         p[0].fX, p[0].fY, p[1].fX, p[1].fY, p[2].fX, p[2].fY, iter.conicWeight());

+                break;

+            default:

+                SkDebugf("unexpected verb\n");

+        }

+    }

+}

+#StdOut

+move to (156,20)

+line (156,20),(79.2893,20)

+conic (79.2893,20),(200,20),(114.645,105.355) weight 0.382683

+##

+##

+

+#SeeAlso conicTo 

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void arcTo(const SkPoint p1, const SkPoint p2, SkScalar radius) 

+

+Append Arc to Path, after appending Line if needed. Arc is implemented by Conic

+weighted to describe part of Circle. Arc is contained by tangent from

+last Path point to p1, and tangent from p1 to p2. Arc

+is part of Circle sized to radius, positioned so it touches both tangent lines. 

+

+If last Path Point does not start Arc, arcTo appends connecting Line to Path.

+The length of Vector from p1 to p2 does not affect Arc.

+

+Arc sweep is always less than 180 degrees. If radius is zero, or if

+tangents are nearly parallel, arcTo appends Line from last Path Point to p1.

+

+arcTo appends at most one Line and one Conic.

+arcTo implements the functionality of PostScript_arct and HTML_Canvas_arcTo.

+

+#Param p1      Point common to pair of tangents. ##

+#Param p2      end of second tangent. ##

+#Param radius  distance from Arc to Circle center. ##

+

+#Example

+#Description

+Because tangent lines are parallel, arcTo appends line from last Path Point to

+p1, but does not append a circular Conic.

+##

+void draw(SkCanvas* canvas) {

+    SkPath path;

+    path.moveTo({156, 20});

+    path.arcTo({200, 20}, {170, 20}, 50);

+    SkPath::Iter iter(path, false);

+    SkPoint p[4];

+    SkPath::Verb verb;

+    while (SkPath::kDone_Verb != (verb = iter.next(p))) {

+        switch (verb) {

+            case SkPath::kMove_Verb:

+                SkDebugf("move to (%g,%g)\n", p[0].fX, p[0].fY);

+                break;

+            case SkPath::kLine_Verb:

+                SkDebugf("line (%g,%g),(%g,%g)\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY);

+                break;

+            case SkPath::kConic_Verb:

+                SkDebugf("conic (%g,%g),(%g,%g),(%g,%g) weight %g\n",

+                          p[0].fX, p[0].fY, p[1].fX, p[1].fY, p[2].fX, p[2].fY, iter.conicWeight());

+                break;

+            default:

+                SkDebugf("unexpected verb\n");

+        }

+    }

+}

+#StdOut

+move to (156,20)

+line (156,20),(200,20)

+##

+##

+

+#SeeAlso conicTo 

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Enum ArcSize

+

+#Code

+    enum ArcSize {

+        kSmall_ArcSize 

+        kLarge_ArcSize 

+    };

+##

+

+Four Oval parts with radii (rx, ry) start at last Path Point and ends at (x, y).

+ArcSize and Direction select one of the four Oval parts.

+

+#Const kSmall_ArcSize 0

+Smaller of Arc pair.

+##

+#Const kLarge_ArcSize 1

+Larger of Arc pair. 

+##

+

+#Example

+#Height 160

+#Description

+Arc begins at top of Oval pair and ends at bottom. Arc can take four routes to get there.

+Two routes are large, and two routes are counterclockwise. The one route both large

+and counterclockwise is blue.

+##

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    for (auto sweep: { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {

+        for (auto arcSize : { SkPath::kSmall_ArcSize, SkPath::kLarge_ArcSize } ) {

+            SkPath path;

+            path.moveTo({120, 50});

+            path.arcTo(70, 40, 30, arcSize, sweep, 156, 100);

+            if (SkPath::kCCW_Direction == sweep && SkPath::kLarge_ArcSize == arcSize) {

+                paint.setColor(SK_ColorBLUE);

+                paint.setStrokeWidth(3);

+            }

+            canvas->drawPath(path, paint);

+         }

+    }

+}

+##

+

+#SeeAlso arcTo Direction

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void arcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc,

+               Direction sweep, SkScalar x, SkScalar y)

+

+Append Arc to Path. Arc is implemented by one or more Conic weighted to describe part of Oval

+with radii (rx, ry) rotated by xAxisRotate degrees. Arc curves from last Path Point to (x, y),

+choosing one of four possible routes: clockwise or counterclockwise, and smaller or larger.

+

+Arc sweep is always less than 360 degrees. arcTo appends Line to (x, y) if either radii are zero,

+or if last Path Point equals (x, y). arcTo scales radii (rx, ry) to fit last Path Point and

+(x, y) if both are greater than zero but too small.

+

+arcTo appends up to four Conic curves.

+arcTo implements the functionatlity of SVG_Arc, although SVG sweep-flag value is

+opposite the integer value of sweep; SVG sweep-flag uses 1 for clockwise, while kCW_Direction 

+cast to int is zero.

+

+#Param rx           radius in x before x-axis rotation. ##

+#Param ry           radius in y before x-axis rotation. ##

+#Param xAxisRotate  x-axis rotation in degrees; positve values are clockwise. ##

+#Param largeArc     chooses smaller or larger Arc. ##

+#Param sweep        chooses clockwise or counterclockwise Arc. ##

+#Param x            end of Arc. ##

+#Param y            end of Arc. ##

+

+#Example

+#Height 160

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    for (auto sweep: { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {

+        for (auto arcSize : { SkPath::kSmall_ArcSize, SkPath::kLarge_ArcSize } ) {

+            SkPath path;

+            path.moveTo({120, 50});

+            path.arcTo(70, 40, 30, arcSize, sweep, 120.1, 50);

+            if (SkPath::kCCW_Direction == sweep && SkPath::kLarge_ArcSize == arcSize) {

+                paint.setColor(SK_ColorBLUE);

+                paint.setStrokeWidth(3);

+            }

+            canvas->drawPath(path, paint);

+         }

+    }

+}

+##

+

+#SeeAlso rArcTo ArcSize Direction

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void arcTo(const SkPoint r, SkScalar xAxisRotate, ArcSize largeArc, Direction sweep,

+               const SkPoint xy) 

+

+Append Arc to Path. Arc is implemented by one or more Conic weighted to describe part of Oval

+with radii (r.fX, r.fY) rotated by xAxisRotate degrees. Arc curves from last Path Point to

+(xy.fX, xy.fY), choosing one of four possible routes: clockwise or counterclockwise,

+and smaller or larger.

+

+Arc sweep is always less than 360 degrees. arcTo appends Line to xy if either radii are zero,

+or if last Path Point equals (x, y). arcTo scales radii r to fit last Path Point and

+xy if both are greater than zero but too small.

+

+arcTo appends up to four Conic curves.

+arcTo implements the functionatlity of SVG_Arc, although SVG sweep-flag value is

+opposite the integer value of sweep; SVG sweep-flag uses 1 for clockwise, while kCW_Direction 

+cast to int is zero.

+

+#Param r            radii in x and y before x-axis rotation. ##

+#Param xAxisRotate  x-axis rotation in degrees; positve values are clockwise. ##

+#Param largeArc     chooses smaller or larger Arc. ##

+#Param sweep        chooses clockwise or counterclockwise Arc. ##

+#Param xy           end of Arc. ##

+

+#Example

+#Height 108

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    SkPath path;

+    const SkPoint starts[] = {{20, 20}, {120, 20}, {70, 60}};

+    for (auto start : starts) {

+        path.moveTo(start.fX, start.fY);

+        path.rArcTo(20, 20, 0, SkPath::kSmall_ArcSize, SkPath::kCCW_Direction, 60, 0);

+    }

+    canvas->drawPath(path, paint);

+}

+##

+

+#SeeAlso rArcTo ArcSize Direction

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc,

+                Direction sweep, SkScalar dx, SkScalar dy)

+

+Append Arc to Path, relative to last Path Point. Arc is implemented by one or 

+more Conic, weighted to describe part of Oval with radii (r.fX, r.fY) rotated by

+xAxisRotate degrees. Arc curves from last Path Point (x0, y0) to

+(x0 + dx, y0 + dy), choosing one of four possible routes: clockwise or

+counterclockwise, and smaller or larger. If Path is empty, the start Arc Point

+is (0, 0).

+

+Arc sweep is always less than 360 degrees. arcTo appends Line to xy if either

+radii are zero, or if last Path Point equals (x, y). arcTo scales radii r to fit

+last Path Point and xy if both are greater than zero but too small.

+

+arcTo appends up to four Conic curves.

+arcTo implements the functionatlity of SVG_Arc, although SVG sweep-flag value is

+opposite the integer value of sweep; SVG sweep-flag uses 1 for clockwise, while

+kCW_Direction cast to int is zero.

+

+#Param rx           radius in x before x-axis rotation. ##

+#Param ry           radius in y before x-axis rotation. ##

+#Param xAxisRotate  x-axis rotation in degrees; positve values are clockwise. ##

+#Param largeArc     chooses smaller or larger Arc. ##

+#Param sweep        chooses clockwise or counterclockwise Arc. ##

+#Param dx           x offset end of Arc from last Path Point. ##

+#Param dy           y offset end of Arc from last Path Point. ##

+

+#Example

+#Height 108

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    SkPath path;

+    const SkPoint starts[] = {{20, 20}, {120, 20}, {70, 60}};

+    for (auto start : starts) {

+        path.moveTo(start.fX, start.fY);

+        path.rArcTo(20, 20, 0, SkPath::kSmall_ArcSize, SkPath::kCCW_Direction, 60, 0);

+    }

+    canvas->drawPath(path, paint);

+}

+##

+

+#SeeAlso arcTo ArcSize Direction

+

+##

+

+#Topic Arc ##

+

+# ------------------------------------------------------------------------------

+

+#Method void close()

+

+Append kClose_Verb to Path. A closed Contour connects the first and last Point

+with Line, forming a continous loop. Open and closed Contour draw the same

+with SkPaint::kFill_Style. With SkPaint::kStroke_Style, open Contour draws

+Paint_Stroke_Cap at Contour start and end; closed Contour draws 

+Paint_Stroke_Join at Contour start and end.

+

+close() has no effect if Path is empty or last Path Verb is kClose_Verb.

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setStrokeWidth(15);

+    paint.setStrokeCap(SkPaint::kRound_Cap);

+    SkPath path;

+    const SkPoint points[] = {{20, 20}, {70, 20}, {40, 90}};

+    path.addPoly(points, SK_ARRAY_COUNT(points), false);

+    for (int loop = 0; loop < 2; ++loop) {

+        for (auto style : {SkPaint::kStroke_Style, SkPaint::kFill_Style,

+                SkPaint::kStrokeAndFill_Style} ) {

+            paint.setStyle(style);

+            canvas->drawPath(path, paint);

+            canvas->translate(85, 0);

+        }

+        path.close();

+        canvas->translate(-255, 128);

+    }

+}

+##

+

+#SeeAlso 

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method static bool IsInverseFillType(FillType fill) 

+

+Returns true if fill is inverted and Path with fill represents area outside

+of its geometric bounds.

+

+#Table

+#Legend

+# FillType                 # is inverse ##

+##

+# kWinding_FillType        # false      ##

+# kEvenOdd_FillType        # false      ##

+# kInverseWinding_FillType # true       ##

+# kInverseEvenOdd_FillType # true       ##

+##

+

+#Param fill  one of: kWinding_FillType, kEvenOdd_FillType,

+             kInverseWinding_FillType, kInverseEvenOdd_FillType.

+##

+

+#Return  true if Path fills outside its bounds. ##

+

+#Example

+#Function

+#define nameValue(fill) { SkPath::fill, #fill }

+

+##

+void draw(SkCanvas* canvas) {

+    struct {

+        SkPath::FillType fill;

+        const char* name;

+    } fills[] = {

+        nameValue(kWinding_FillType),

+        nameValue(kEvenOdd_FillType),

+        nameValue(kInverseWinding_FillType),

+        nameValue(kInverseEvenOdd_FillType),

+    };

+    for (auto fill: fills ) {

+        SkDebugf("IsInverseFillType(%s) == %s\n", fill.name, SkPath::IsInverseFillType(fill.fill) ?

+                 "true" : "false");

+    }

+}

+#StdOut

+IsInverseFillType(kWinding_FillType) == false

+IsInverseFillType(kEvenOdd_FillType) == false

+IsInverseFillType(kInverseWinding_FillType) == true

+IsInverseFillType(kInverseEvenOdd_FillType) == true

+##

+##

+

+#SeeAlso FillType getFillType setFillType ConvertToNonInverseFillType

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method static FillType ConvertToNonInverseFillType(FillType fill) 

+

+Returns equivalent Fill_Type representing Path fill inside its bounds.

+.

+

+#Table

+#Legend

+# FillType                 # inside FillType   ##

+##

+# kWinding_FillType        # kWinding_FillType ##

+# kEvenOdd_FillType        # kEvenOdd_FillType ##

+# kInverseWinding_FillType # kWinding_FillType ##

+# kInverseEvenOdd_FillType # kEvenOdd_FillType ##

+##

+

+#Param fill  one of: kWinding_FillType, kEvenOdd_FillType,

+             kInverseWinding_FillType, kInverseEvenOdd_FillType.

+##

+

+#Return  fill, or kWinding_FillType or kEvenOdd_FillType if fill is inverted. ##

+

+#Example

+#Function

+#define nameValue(fill) { SkPath::fill, #fill }

+

+##

+void draw(SkCanvas* canvas) {

+    struct {

+        SkPath::FillType fill;

+        const char* name;

+    } fills[] = {

+        nameValue(kWinding_FillType),

+        nameValue(kEvenOdd_FillType),

+        nameValue(kInverseWinding_FillType),

+        nameValue(kInverseEvenOdd_FillType),

+    };

+    for (unsigned i = 0; i < SK_ARRAY_COUNT(fills); ++i) {

+        if (fills[i].fill != (SkPath::FillType) i) {

+            SkDebugf("fills array order does not match FillType enum order");

+            break;

+        } 

+        SkDebugf("ConvertToNonInverseFillType(%s) == %s\n", fills[i].name,

+                fills[(int) SkPath::ConvertToNonInverseFillType(fills[i].fill)].name);

+    }

+}

+#StdOut

+ConvertToNonInverseFillType(kWinding_FillType) == kWinding_FillType

+ConvertToNonInverseFillType(kEvenOdd_FillType) == kEvenOdd_FillType

+ConvertToNonInverseFillType(kInverseWinding_FillType) == kWinding_FillType

+ConvertToNonInverseFillType(kInverseEvenOdd_FillType) == kEvenOdd_FillType

+##

+##

+

+#SeeAlso FillType getFillType setFillType IsInverseFillType

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method static int ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,

+                                   SkScalar w, SkPoint pts[], int pow2)

+

+Approximates Conic with Quad array. Conic is constructed from start Point p0,

+control Point p1, end Point p2, and weight w. 

+Quad array is stored in pts; this storage is supplied by caller.

+Maximum Quad count is 2 to the pow2.

+Every third point in array shares last Point of previous Quad and first Point of 

+next Quad. Maximum pts storage size is given by: 

+#Formula

+(1 + 2 * (1 << pow2)) * sizeof(SkPoint)

+##

+ConvertConicToQuads returns Quad count used the approximation, which may be smaller

+than the number requested.

+ 

+Conic_Weight determines the amount of influence Conic control point has on the curve.

+w less than one represents an elliptical section. w greater than one represents

+a hyperbolic section. w equal to one represents a parabolic section.

+

+Two Quad curves are sufficient to approximate an elliptical Conic with a sweep

+of up to 90 degrees; in this case, set pow2 to one.

+

+#Param p0    Conic start Point. ##

+#Param p1    Conic control Point. ##

+#Param p2    Conic end Point. ##

+#Param w     Conic weight. ##

+#Param pts   storage for Quad array. ##

+#Param pow2  Quad count, as power of two, normally 0 to 5 (1 to 32 Quad curves). ##

+

+#Return  Number of Quad curves written to pts. ##

+

+#Example

+#Description

+A pair of Quad curves are drawn in red on top of the elliptical Conic curve in black.

+The middle curve is nearly circular. The top-right curve is parabolic, which can

+be drawn exactly with a single Quad.

+##

+void draw(SkCanvas* canvas) {

+      SkPaint conicPaint;

+      conicPaint.setAntiAlias(true);

+      conicPaint.setStyle(SkPaint::kStroke_Style);

+      SkPaint quadPaint(conicPaint);

+      quadPaint.setColor(SK_ColorRED);

+      SkPoint conic[] = { {20, 170}, {80, 170}, {80, 230} };

+      for (auto weight : { .25f, .5f, .707f, .85f, 1.f } ) {

+          SkPoint quads[5];

+          SkPath::ConvertConicToQuads(conic[0], conic[1], conic[2], weight, quads, 1);

+          SkPath path;

+          path.moveTo(conic[0]);

+          path.conicTo(conic[1], conic[2], weight);

+          canvas->drawPath(path, conicPaint);

+          path.rewind();

+          path.moveTo(quads[0]);

+          path.quadTo(quads[1], quads[2]);

+          path.quadTo(quads[3], quads[4]);

+          canvas->drawPath(path, quadPaint);

+          canvas->translate(50, -50);

+      }

+}

+##

+

+#SeeAlso Conic Quad

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool isRect(SkRect* rect, bool* isClosed = NULL, Direction* direction = NULL) const

+

+Returns true if Path is eqivalent to Rect when filled.

+If isRect returns false: rect, isClosed, and direction are unchanged.

+If isRect returns true: rect, isClosed, and direction are written to if not nullptr.

+

+rect may be smaller than the Path bounds. Path bounds may include kMove_Verb points

+that do not alter the area drawn by the returned rect.

+

+#Param rect       storage for bounds of Rect; may be nullptr. ##

+#Param isClosed   storage set to true if Path is closed; may be nullptr ##

+#Param direction  storage set to Rect direction; may be nullptr. ##

+

+#Return  true if Path contains Rect. ##

+

+#Example

+#Description

+After addRect, isRect returns true. Following moveTo permits isRect to return true, but

+following lineTo does not. addPoly returns true even though rect is not closed, and one

+side of rect is made up of consecutive line segments.

+##

+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& path) -> void {

+        SkRect rect;

+        SkPath::Direction direction;

+        bool isClosed;

+        path.isRect(&rect, &isClosed, &direction) ? 

+                SkDebugf("%s is rect (%g, %g, %g, %g); is %s" "closed; direction %s\n", prefix,

+                         rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, isClosed ? "" : "not ",

+                         SkPath::kCW_Direction == direction ? "CW" : "CCW") :

+                SkDebugf("%s is not rect\n", prefix);

+    };

+    SkPath path;

+    debugster("empty", path);

+    path.addRect({10, 20, 30, 40});

+    debugster("addRect", path);

+    path.moveTo(60, 70);

+    debugster("moveTo", path);

+    path.lineTo(60, 70);

+    debugster("lineTo", path);

+    path.reset();

+    const SkPoint pts[] = { {0, 0}, {0, 80}, {80, 80}, {80, 0}, {40, 0}, {20, 0} };

+    path.addPoly(pts, SK_ARRAY_COUNT(pts), false);

+    debugster("addPoly", path);

+}

+#StdOut

+empty is not rect

+addRect is rect (10, 20, 30, 40); is closed; direction CW

+moveTo is rect (10, 20, 30, 40); is closed; direction CW

+lineTo is not rect

+addPoly is rect (0, 0, 80, 80); is not closed; direction CCW

+##

+##

+

+#SeeAlso computeTightBounds conservativelyContainsRect getBounds isConvex isLastContourClosed isNestedFillRects

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool isNestedFillRects(SkRect rect[2], Direction dirs[2] = NULL) const

+

+Returns true if Path is equivalent to nested Rect pair when filled.

+If isNestedFillRects returns false, rect and dirs are unchanged.

+If isNestedFillRects returns true, rect and dirs are written to if not nullptr:

+setting rect[0] to outer Rect, and rect[1] to inner Rect;

+setting dirs[0] to Direction of outer Rect, and dirs[1] to Direction of inner

+Rect.

+

+#Param rect  storage for Rect pair; may be nullptr. ##

+#Param dirs  storage for Direction pair; may be nullptr. ##

+

+#Return  true if Path contains nested Rect pair. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setStrokeWidth(5);

+    SkPath path;

+    path.addRect({10, 20, 30, 40});

+    paint.getFillPath(path, &path);

+    SkRect rects[2];

+    SkPath::Direction directions[2];

+    if (path.isNestedFillRects(rects, directions)) {

+        for (int i = 0; i < 2; ++i) {

+            SkDebugf("%s (%g, %g, %g, %g); direction %s\n", i ? "inner" : "outer",

+                     rects[i].fLeft, rects[i].fTop, rects[i].fRight, rects[i].fBottom,

+                     SkPath::kCW_Direction == directions[i] ? "CW" : "CCW");

+        }

+    } else {

+        SkDebugf("is not nested rectangles\n");

+    }

+}

+#StdOut

+outer (7.5, 17.5, 32.5, 42.5); direction CW

+inner (12.5, 22.5, 27.5, 37.5); direction CCW

+##

+##

+

+#SeeAlso computeTightBounds conservativelyContainsRect getBounds isConvex isLastContourClosed isRect

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addRect(const SkRect& rect, Direction dir = kCW_Direction)

+

+Add Rect to Path, appending kMove_Verb, three kLine_Verb, and kClose_Verb,

+starting with top-left corner of Rect; followed by top-right, bottom-right,

+and bottom-left if dir is kCW_Direction; or followed by bottom-left,

+bottom-right, and top-right if dir is kCCW_Direction.

+

+#Param rect  Rect to add as a closed contour. ##

+#Param dir   Direction to wind added contour. ##

+

+#Example

+#Description

+The left Rect dashes starting at the top-left corner, to the right.

+The right Rect dashes starting at the top-left corner, towards the bottom.

+##

+#Height 128

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setStrokeWidth(15);

+    paint.setStrokeCap(SkPaint::kSquare_Cap);

+    float intervals[] = { 5, 21.75f };

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));

+    SkPath path;

+    path.addRect({20, 20, 100, 100}, SkPath::kCW_Direction);

+    canvas->drawPath(path, paint);

+    path.rewind();

+    path.addRect({140, 20, 220, 100}, SkPath::kCCW_Direction);

+    canvas->drawPath(path, paint);

+}

+##

+

+#SeeAlso SkCanvas::drawRect Direction

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addRect(const SkRect& rect, Direction dir, unsigned start)

+

+Add Rect to Path, appending kMove_Verb, three kLine_Verb, and kClose_Verb.

+If dir is kCW_Direction, Rect corners are added clockwise; if dir is

+kCCW_Direction, Rect corners are added counterclockwise.

+start determines the first corner added.

+

+#Table

+#Legend

+# start # first corner ##

+#Legend ##

+# 0     # top-left ##

+# 1     # top-right ##

+# 2     # bottom-right ##

+# 3     # bottom-left ##

+#Table ##

+

+#Param rect   Rect to add as a closed contour. ##

+#Param dir    Direction to wind added contour. ##

+#Param start  Initial corner of Rect to add. ##

+

+#Example

+#Height 128

+#Description

+The arrow is just after the initial corner and points towards the next

+corner appended to Path.

+##

+void draw(SkCanvas* canvas) {

+    const SkPoint arrow[] = { {5, -5}, {15, -5}, {20, 0}, {15, 5}, {5, 5}, {10, 0} };

+    const SkRect rect = {10, 10, 54, 54};

+    SkPaint rectPaint;

+    rectPaint.setAntiAlias(true);

+    rectPaint.setStyle(SkPaint::kStroke_Style);

+    SkPaint arrowPaint(rectPaint);

+    SkPath arrowPath;

+    arrowPath.addPoly(arrow, SK_ARRAY_COUNT(arrow), true);

+    arrowPaint.setPathEffect(SkPath1DPathEffect::Make(arrowPath, 176, 0,

+                             SkPath1DPathEffect::kRotate_Style));

+    for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {

+        for (unsigned start : { 0, 1, 2, 3 } ) {

+           SkPath path;

+           path.addRect(rect, direction, start);

+           canvas->drawPath(path, rectPaint);

+           canvas->drawPath(path, arrowPaint);

+           canvas->translate(64, 0);

+       }

+       canvas->translate(-256, 64);

+    }

+}

+##

+

+#SeeAlso SkCanvas::drawRect Direction

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addRect(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom,

+                 Direction dir = kCW_Direction)

+

+Add Rect (left, top, right, bottom) to Path,

+appending kMove_Verb, three kLine_Verb, and kClose_Verb,

+starting with top-left corner of Rect; followed by top-right, bottom-right,

+and bottom-left if dir is kCW_Direction; or followed by bottom-left,

+bottom-right, and top-right if dir is kCCW_Direction.

+

+#Param left    smaller x of Rect. ##

+#Param top     smaller y of Rect. ##

+#Param right   larger x of Rect. ##

+#Param bottom  larger y of Rect. ##

+#Param dir     Direction to wind added contour. ##

+

+#Example

+#Description

+The left Rect dashes start at the top-left corner, and continue to the right.

+The right Rect dashes start at the top-left corner, and continue down.

+##

+#Height 128

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setStrokeWidth(15);

+    paint.setStrokeCap(SkPaint::kSquare_Cap);

+    float intervals[] = { 5, 21.75f };

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));

+    for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {

+        SkPath path;

+        path.addRect(20, 20, 100, 100, direction);

+        canvas->drawPath(path, paint);

+        canvas->translate(128, 0);

+    }

+}

+##

+

+#SeeAlso SkCanvas::drawRect Direction

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addOval(const SkRect& oval, Direction dir = kCW_Direction)

+

+Add Oval to path, appending kMove_Verb, four kConic_Verb, and kClose_Verb.

+Oval is upright ellipse bounded by Rect oval with radii equal to half oval width

+and half oval height. Oval begins at (oval.fRight, oval.centerY()) and continues

+clockwise if dir is kCW_Direction, counterclockwise if dir is kCCW_Direction.

+

+This form is identical to addOval(oval, dir, 1).

+

+#Param oval  bounds of ellipse added. ##

+#Param dir   Direction to wind ellipse. ##

+

+#Example

+#Height 120

+    SkPaint paint;

+    SkPath oval;

+    oval.addOval({20, 20, 160, 80});

+    canvas->drawPath(oval, paint);

+##

+

+#SeeAlso SkCanvas::drawOval Direction Oval

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addOval(const SkRect& oval, Direction dir, unsigned start)

+

+Add Oval to Path, appending kMove_Verb, four kConic_Verb, and kClose_Verb.

+Oval is upright ellipse bounded by Rect oval with radii equal to half oval width

+and half oval height. Oval begins at start and continues

+clockwise if dir is kCW_Direction, counterclockwise if dir is kCCW_Direction.

+

+#Table

+#Legend

+# start # Point                        ##

+#Legend ##

+# 0     # oval.centerX(), oval.fTop    ##

+# 1     # oval.fRight, oval.centerY()  ##

+# 2     # oval.centerX(), oval.fBottom ##

+# 3     # oval.fLeft, oval.centerY()   ##

+#Table ##

+

+#Param oval   bounds of ellipse added. ##

+#Param dir    Direction to wind ellipse. ##

+#Param start  index of initial point of ellipse. ##

+

+#Example

+#Height 160

+void draw(SkCanvas* canvas) {

+    const SkPoint arrow[] = { {0, -5}, {10, 0}, {0, 5} };

+    const SkRect rect = {10, 10, 54, 54};

+    SkPaint ovalPaint;

+    ovalPaint.setAntiAlias(true);

+    SkPaint textPaint(ovalPaint);

+    textPaint.setTextAlign(SkPaint::kCenter_Align);

+    ovalPaint.setStyle(SkPaint::kStroke_Style);

+    SkPaint arrowPaint(ovalPaint);

+    SkPath arrowPath;

+    arrowPath.addPoly(arrow, SK_ARRAY_COUNT(arrow), true);

+    arrowPaint.setPathEffect(SkPath1DPathEffect::Make(arrowPath, 176, 0,

+                             SkPath1DPathEffect::kRotate_Style));

+    for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {

+        for (unsigned start : { 0, 1, 2, 3 } ) {

+           SkPath path;

+           path.addOval(rect, direction, start);

+           canvas->drawPath(path, ovalPaint);

+           canvas->drawPath(path, arrowPaint);

+           canvas->drawText(&"0123"[start], 1, rect.centerX(), rect.centerY() + 5, textPaint);

+           canvas->translate(64, 0);

+       }

+       canvas->translate(-256, 72);

+       canvas->drawString(SkPath::kCW_Direction == direction ? "clockwise" : "counterclockwise",

+                          128, 0, textPaint);

+    }

+}

+##

+

+#SeeAlso SkCanvas::drawOval Direction Oval

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addCircle(SkScalar x, SkScalar y, SkScalar radius,

+                   Direction dir = kCW_Direction)

+

+Add Circle centered at (x, y) of size radius to Path, appending kMove_Verb,

+four kConic_Verb, and kClose_Verb. Circle begins at (x + radius, y) and

+continues clockwise if dir is kCW_Direction, counterclockwise if dir is

+kCCW_Direction.

+

+addCircle has no effect if radius is zero or negative.

+

+#Param x       center of Circle. ##

+#Param y       center of Circle.  ##

+#Param radius  distance from center to edge. ##

+#Param dir     Direction to wind Circle. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setStyle(SkPaint::kStroke_Style);

+    paint.setStrokeWidth(10);

+    for (int size = 10; size < 300; size += 20) {

+        SkPath path;

+        path.addCircle(128, 128, size, SkPath::kCW_Direction);

+        canvas->drawPath(path, paint);

+    }

+}

+##

+

+#SeeAlso SkCanvas::drawCircle Direction Circle

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle)

+

+Append Arc to Path, as the start of new Contour. Arc added is part of ellipse

+bounded by oval, from startAngle through sweepAngle. Both startAngle and

+sweepAngle are measured in degrees, where zero degrees is aligned with the

+positive x-axis, and positive sweeps extends Arc clockwise.

+

+If sweepAngle <= -360, or sweepAngle >= 360; and startAngle modulo 90 is nearly 

+zero, append Oval instead of Arc. Otherwise, sweepAngle values are treated 

+modulo 360, and Arc may or may not draw depending on numeric rounding.

+

+#Param oval        bounds of ellipse containing Arc. ##

+#Param startAngle  starting angle of Arc in degrees. ##

+#Param sweepAngle  sweep, in degrees. Positive is clockwise; treated modulo 360. ##

+

+#Example

+#Description

+The middle row of the left and right columns draw differently from the entries

+above and below because sweepAngle is outside of the range of +/-360, 

+and startAngle modulo 90 is not zero.

+##

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    for (auto start : { 0, 90, 135, 180, 270 } ) {

+        for (auto sweep : { -450.f, -180.f, -90.f, 90.f, 180.f, 360.1f } ) {

+            SkPath path;

+            path.addArc({10, 10, 35, 45}, start, sweep);

+            canvas->drawPath(path, paint);

+            canvas->translate(252 / 6, 0);

+        }

+        canvas->translate(-252, 255 / 5);

+    }

+}

+##

+

+#SeeAlso Arc arcTo SkCanvas::drawArc

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,

+                      Direction dir = kCW_Direction)

+

+Append Round_Rect to Path, creating a new closed Contour. Round_Rect has bounds

+equal to rect; each corner is 90 degrees of an ellipse with radii (rx, ry). If

+dir is kCW_Direction, Round_Rect starts at top-left of the lower-left corner and

+winds clockwise. If dir is kCCW_Direction, Round_Rect starts at the bottom-left

+of the upper-left corner and winds counterclockwise.

+

+If either rx or ry is too large, rx and ry are scaled uniformly until the

+corners fit. If rx or ry is less than or equal to zero, addRoundRect appends

+Rect rect to Path.

+

+After appending, Path may be empty, or may contain: Rect, Oval, or RoundRect.

+

+#Param rect  bounds of Round_Rect. ##

+#Param rx    x-radius of rounded corners on the Round_Rect ##

+#Param ry    y-radius of rounded corners on the Round_Rect ##

+#Param dir   Direction to wind Round_Rect. ##

+

+#Example

+#Description

+If either radius is zero, path contains Rect and is drawn red.

+If sides are only radii, path contains Oval and is drawn blue.

+All remaining path draws are convex, and are drawn in gray; no

+paths constructed from addRoundRect are concave, so none are

+drawn in green.

+##

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    for (auto xradius : { 0, 7, 13, 20 } ) {

+        for (auto yradius : { 0, 9, 18, 40 } ) {

+            SkPath path;

+            path.addRoundRect({10, 10, 36, 46}, xradius, yradius);

+            paint.setColor(path.isRect(nullptr) ? SK_ColorRED : path.isOval(nullptr) ?

+                           SK_ColorBLUE : path.isConvex() ? SK_ColorGRAY : SK_ColorGREEN);

+            canvas->drawPath(path, paint);

+            canvas->translate(64, 0);

+        }

+        canvas->translate(-256, 64);

+    }

+}

+##

+

+#SeeAlso addRRect SkCanvas::drawRoundRect

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addRoundRect(const SkRect& rect, const SkScalar radii[],

+                      Direction dir = kCW_Direction)

+

+Append Round_Rect to Path, creating a new closed Contour. Round_Rect has bounds

+equal to rect; each corner is 90 degrees of an ellipse with radii from the

+array.

+

+#Table

+#Legend

+# radii index # location                        ##

+#Legend ##

+# 0           # x-radius of top-left corner     ##

+# 1           # y-radius of top-left corner     ##

+# 2           # x-radius of top-right corner    ##

+# 3           # y-radius of top-right corner    ##

+# 4           # x-radius of bottom-right corner ##

+# 5           # y-radius of bottom-right corner ##

+# 6           # x-radius of bottom-left corner  ##

+# 7           # y-radius of bottom-left corner  ##

+#Table ##

+

+If dir is kCW_Direction, Round_Rect starts at top-left of the lower-left corner 

+and winds clockwise. If dir is kCCW_Direction, Round_Rect starts at the 

+bottom-left of the upper-left corner and winds counterclockwise.

+

+If both radii on any side of rect exceed its length, all radii are scaled 

+uniformly until the corners fit. If either radius of a corner is less than or

+equal to zero, both are treated as zero.

+

+After appending, Path may be empty, or may contain: Rect, Oval, or RoundRect.

+

+#Param rect   bounds of Round_Rect. ##

+#Param radii  array of 8 SkScalar values, a radius pair for each corner. ##

+#Param dir    Direction to wind Round_Rect. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    SkScalar radii[] = { 80, 100, 0, 0, 40, 60, 0, 0 };

+    SkPath path;

+    SkMatrix rotate90;

+    rotate90.setRotate(90, 128, 128);

+    for (int i = 0; i < 4; ++i) {

+        path.addRoundRect({10, 10, 110, 110}, radii);

+        path.transform(rotate90);

+    }

+    canvas->drawPath(path, paint);

+}

+##

+

+#SeeAlso addRRect SkCanvas::drawRoundRect

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addRRect(const SkRRect& rrect, Direction dir = kCW_Direction)

+

+Add rrect to Path, creating a new closed Contour. If

+dir is kCW_Direction, rrect starts at top-left of the lower-left corner and

+winds clockwise. If dir is kCCW_Direction, rrect starts at the bottom-left

+of the upper-left corner and winds counterclockwise.

+

+After appending, Path may be empty, or may contain: Rect, Oval, or RRect.

+

+#Param rrect  bounds and radii of rounded rectangle. ##

+#Param dir   Direction to wind Round_Rect. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    SkRRect rrect;

+    SkVector radii[] = {{50, 50}, {0, 0}, {0, 0}, {50, 50}};

+    rrect.setRectRadii({10, 10, 110, 110}, radii);

+    SkPath path;

+    SkMatrix rotate90;

+    rotate90.setRotate(90, 128, 128);

+    for (int i = 0; i < 4; ++i) {

+        path.addRRect(rrect);

+        path.transform(rotate90);

+    }

+    canvas->drawPath(path, paint);

+}

+##

+

+#SeeAlso addRoundRect SkCanvas::drawRRect

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addRRect(const SkRRect& rrect, Direction dir, unsigned start)

+

+Add rrect to Path, creating a new closed Contour. If dir is kCW_Direction, rrect

+winds clockwise; if dir is kCCW_Direction, rrect winds counterclockwise.

+start determines the first point of rrect to add.

+

+#Table

+#Legend

+# start       # location                    ##

+#Legend ##

+# 0           # right of top-left corner    ##

+# 1           # left of top-right corner    ##

+# 2           # bottom of top-right corner  ##

+# 3           # top of bottom-right corner  ##

+# 4           # left of bottom-right corner ##

+# 5           # right of bottom-left corner ##

+# 6           # top of bottom-left corner   ##

+# 7           # bottom of top-left corner   ##

+#Table ##

+

+After appending, Path may be empty, or may contain: Rect, Oval, or RRect.

+

+#Param rrect  bounds and radii of rounded rectangle. ##

+#Param dir    Direction to wind RRect. ##

+#Param start  Index of initial point of RRect. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    SkRRect rrect;

+    rrect.setRectXY({40, 40, 215, 215}, 50, 50);

+    SkPath path;

+    path.addRRect(rrect);

+    canvas->drawPath(path, paint);

+    for (int start = 0; start < 8; ++start) {

+        SkPath textPath;

+        textPath.addRRect(rrect, SkPath::kCW_Direction, start);

+        canvas->drawTextOnPathHV(&"01234567"[start], 1, textPath, 0, -5, paint);

+    }

+}

+##

+

+#SeeAlso addRoundRect SkCanvas::drawRRect 

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addPoly(const SkPoint pts[], int count, bool close)

+

+Add Contour created from Line Array. Given count pts, addPoly adds

+count - 1 Line segments. Contour added starts at pt[0], then adds a line

+for every additional Point in pts array. If close is true, addPoly

+appends kClose_Verb to Path, connecting pts[count - 1] and pts[0].

+

+If count is zero, append kMove_Verb to path.

+addPoly has no effect if count is less than one.

+  

+#Param pts    Array of Line sharing end and start Point. ##

+#Param count  Length of Point array. ##

+#Param close  true to add Line connecting Contour end and start. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setStrokeWidth(15);

+    paint.setStrokeCap(SkPaint::kRound_Cap);

+    const SkPoint points[] = {{20, 20}, {70, 20}, {40, 90}};

+    for (bool close : { false, true } ) {

+        SkPath path;

+        path.addPoly(points, SK_ARRAY_COUNT(points), close);

+        for (auto style : {SkPaint::kStroke_Style, SkPaint::kFill_Style,

+                SkPaint::kStrokeAndFill_Style} ) {

+            paint.setStyle(style);

+            canvas->drawPath(path, paint);

+            canvas->translate(85, 0);

+        }

+        canvas->translate(-255, 128);

+    }

+}

+##

+

+#SeeAlso SkCanvas::drawPoints

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Enum AddPathMode

+

+#Code

+    enum AddPathMode {

+        kAppend_AddPathMode 

+        kExtend_AddPathMode 

+    };

+##

+

+AddPathMode chooses how addPath appends. Adding one Path to another can extend

+the last Contour or start a new Contour.

+

+#Const kAppend_AddPathMode

+    Path Verbs, Points, and Weights are appended to destination unaltered.

+    Since Path Verb_Array begins with kMove_Verb if src is not empty, this

+    starts a new Contour.

+##

+#Const kExtend_AddPathMode

+    If destination is closed or empty, start a new Contour. If destination

+    is not empty, add Line from Last_Point to added Path first Point. Skip added

+    Path initial kMove_Verb, then append remining Verbs, Points, and Weights.

+##

+

+#Example

+#Description

+test is built from path, open on the top row, and closed on the bottom row.

+The left column uses kAppend_AddPathMode; the right uses kExtend_AddPathMode.

+The top right composition is made up of one contour; the other three have two.

+##

+#Height 180

+    SkPath path, path2;
+    path.moveTo(20, 20);
+    path.lineTo(20, 40);
+    path.lineTo(40, 20);
+    path2.moveTo(60, 60);
+    path2.lineTo(80, 60);
+    path2.lineTo(80, 40);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (int i = 0; i < 2; i++) {
+        for (auto addPathMode : { SkPath::kAppend_AddPathMode, SkPath::kExtend_AddPathMode } ) {
+            SkPath test(path);
+            test.addPath(path2, addPathMode);
+            canvas->drawPath(test, paint);
+            canvas->translate(100, 0);
+        }
+        canvas->translate(-200, 100);
+        path.close();
+    }
+##

+

+#SeeAlso addPath reverseAddPath

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addPath(const SkPath& src, SkScalar dx, SkScalar dy,

+                 AddPathMode mode = kAppend_AddPathMode)

+

+Append src to Path, offset by (dx, dy). 

+

+If mode is kAppend_AddPathMode, src Verb_Array, Point_Array, and Weights are

+added unaltered. If mode is kExtend_AddPathMode, add Line before appending

+Verbs, Points, and Weights. 

+

+#Param src  Path Verbs, Points, and Weights to add. ##

+#Param dx   offset added to src Point_Array x coordinates. ##

+#Param dy   offset added to src Point_Array y coordinates. ##

+#Param mode kAppend_AddPathMode or kExtend_AddPathMode. ##

+

+#Example

+#Height 180

+    SkPaint paint;
+    paint.setTextSize(128);
+    paint.setFakeBoldText(true);
+    SkPath dest, text;
+    paint.getTextPath("O", 1, 50, 120, &text);
+    for (int i = 0; i < 3; i++) {
+        dest.addPath(text, i * 20, i * 20);
+    }
+    Simplify(dest, &dest);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(3);
+    canvas->drawPath(dest, paint);
+##

+

+#SeeAlso AddPathMode offset() reverseAddPath

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addPath(const SkPath& src, AddPathMode mode = kAppend_AddPathMode) 

+

+Append src to Path.

+

+If mode is kAppend_AddPathMode, src Verb_Array, Point_Array, and Weights are

+added unaltered. If mode is kExtend_AddPathMode, add Line before appending

+Verbs, Points, and Weights. 

+

+#Param src  Path Verbs, Points, and Weights to add. ##

+#Param mode kAppend_AddPathMode or kExtend_AddPathMode. ##

+

+#Example

+#Height 80

+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPath dest, path;
+    path.addOval({-80, 20, 0, 60}, SkPath::kCW_Direction, 1);
+    for (int i = 0; i < 2; i++) {
+        dest.addPath(path, SkPath::kExtend_AddPathMode);
+        dest.offset(100, 0);
+    }
+    canvas->drawPath(dest, paint);

+##

+

+#SeeAlso AddPathMode reverseAddPath

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void addPath(const SkPath& src, const SkMatrix& matrix, AddPathMode mode = kAppend_AddPathMode)

+

+Append src to Path, transformed by matrix. Transformed curves may have different

+Verbs, Points, and Weights.

+

+If mode is kAppend_AddPathMode, src Verb_Array, Point_Array, and Weights are

+added unaltered. If mode is kExtend_AddPathMode, add Line before appending

+Verbs, Points, and Weights. 

+

+#Param src     Path Verbs, Points, and Weights to add. ##

+#Param matrix  Transform applied to src. ##

+#Param mode    kAppend_AddPathMode or kExtend_AddPathMode. ##

+

+#Example

+#Height 160

+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPath dest, path;
+    path.addOval({20, 20, 200, 120}, SkPath::kCW_Direction, 1);
+    for (int i = 0; i < 6; i++) {
+        SkMatrix matrix;
+        matrix.reset();
+        matrix.setPerspX(i / 400.f);
+        dest.addPath(path, matrix);
+    }
+    canvas->drawPath(dest, paint);
+##

+

+#SeeAlso AddPathMode transform() offset() reverseAddPath

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void reverseAddPath(const SkPath& src)

+

+Append src to Path, from back to front. 

+Reversed src always appends a new Contour to Path.

+

+#Param src     Path Verbs, Points, and Weights to add. ##

+

+#Example

+#Height 200

+    SkPath path;
+    path.moveTo(20, 20);
+    path.lineTo(20, 40);
+    path.lineTo(40, 20);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (int i = 0; i < 2; i++) {
+        SkPath path2;
+        path2.moveTo(60, 60);
+        path2.lineTo(80, 60);
+        path2.lineTo(80, 40);
+        for (int j = 0; j < 2; j++) {
+            SkPath test(path);
+            test.reverseAddPath(path2);
+            canvas->drawPath(test, paint);
+            canvas->translate(100, 0);
+            path2.close();
+        }
+        canvas->translate(-200, 100);
+        path.close();
+    }
+##

+

+#SeeAlso AddPathMode transform() offset() addPath

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void offset(SkScalar dx, SkScalar dy, SkPath* dst) const

+

+Offset Point_Array by (dx, dy). Offset Path replaces dst.

+If dst is nullptr, Path is replaced by offset data.

+

+#Param dx   offset added to Point_Array x coordinates. ##

+#Param dy   offset added to Point_Array y coordinates. ##

+#Param dst  overwritten, translated copy of Path; may be nullptr. ##

+

+#Example

+#Height 60

+    SkPath pattern;
+    pattern.moveTo(20, 20);
+    pattern.lineTo(20, 40);
+    pattern.lineTo(40, 20);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (int i = 0; i < 10; i++) {
+        SkPath path;
+        pattern.offset(20 * i, 0, &path);
+        canvas->drawPath(path, paint);
+    }
+##

+

+#SeeAlso addPath transform

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void offset(SkScalar dx, SkScalar dy) 

+

+Offset Point_Array by (dx, dy). Path is replaced by offset data.

+

+#Param dx  offset added to Point_Array x coordinates. ##

+#Param dy  offset added to Point_Array y coordinates. ##

+

+#Example

+#Height 60

+    SkPath path;
+    path.moveTo(20, 20);
+    path.lineTo(20, 40);
+    path.lineTo(40, 20);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (int i = 0; i < 10; i++) {
+        canvas->drawPath(path, paint);
+        path.offset(20, 0);
+    }
+##

+

+#SeeAlso addPath transform SkCanvas::translate()

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void transform(const SkMatrix& matrix, SkPath* dst) const

+

+Transform Verb_Array, Point_Array, and weight by matrix.

+transform may change Verbs and increase their number.

+Transformed Path replaces dst; if dst is nullptr, original data

+is replaced. 

+

+#Param matrix  Matrix to apply to Path. ##

+#Param dst     overwritten, transformed copy of Path; may be nullptr. ##

+

+#Example

+#Height 200
+    SkPath pattern;
+    pattern.moveTo(100, 100);
+    pattern.lineTo(100, 20);
+    pattern.lineTo(20, 100);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (int i = 0; i < 10; i++) {
+        SkPath path;
+        SkMatrix matrix;
+        matrix.setRotate(36 * i, 100, 100);
+        pattern.transform(matrix, &path);
+        canvas->drawPath(path, paint);
+    }

+##

+

+#SeeAlso addPath offset SkCanvas::concat() SkMatrix

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void transform(const SkMatrix& matrix) 

+

+Transform Verb_Array, Point_Array, and weight by matrix.

+transform may change Verbs and increase their number.

+Path is replaced by transformed data.

+

+#Param matrix  Matrix to apply to Path. ##

+

+#Example

+#Height 200
+    SkPath path;
+    path.moveTo(100, 100);
+    path.quadTo(100, 20, 20, 100);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (int i = 0; i < 10; i++) {
+        SkMatrix matrix;
+        matrix.setRotate(36, 100, 100);
+        path.transform(matrix);
+        canvas->drawPath(path, paint);
+    }

+##

+

+#SeeAlso addPath offset SkCanvas::concat() SkMatrix

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Subtopic Last_Point
+
+Path is defined cumulatively, often by adding a segment to the end of last
+Contour. Last_Point of Contour is shared as first Point of added Line or Curve.
+Last_Point can be read and written directly with getLastPt and setLastPt.
+
+#Method bool getLastPt(SkPoint* lastPt) const

+

+    Returns Last_Point on Path in lastPt. Returns false if Point_Array is empty, 

+    storing (0, 0) if lastPt is not nullptr.

+

+    #Param lastPt  storage for final Point in Point_Array; may be nullptr. ##

+

+    #Return  true if Point_Array contains one or more Points. ##

+

+    #Example

+        SkPath path;
+        path.moveTo(100, 100);
+        path.quadTo(100, 20, 20, 100);
+        SkMatrix matrix;
+        matrix.setRotate(36, 100, 100);
+        path.transform(matrix);
+        SkPoint last;
+        path.getLastPt(&last);
+        SkDebugf("last point: %g, %g\n", last.fX, last.fY);

+    #StdOut

+    last point: 35.2786, 52.9772

+    ##    

+    ##

+

+    #SeeAlso setLastPt

+

+##

+

+#Method void setLastPt(SkScalar x, SkScalar y)

+

+    Set Last_Point to (x, y). If Point_Array is empty, append kMove_Verb to

+    Verb_Array and (x, y) to Point_Array.

+

+    #Param x  set x-coordinate of Last_Point. ##

+    #Param y  set y-coordinate of Last_Point. ##

+

+    #Example

+    #Height 128

+        SkPaint paint;
+        paint.setTextSize(128);
+        SkPath path;
+        paint.getTextPath("@", 1, 60, 100, &path);
+        path.setLastPt(20, 120);
+        canvas->drawPath(path, paint);

+    ##

+

+    #SeeAlso getLastPt

+

+##

+

+#Method void setLastPt(const SkPoint& p) 

+

+    Set the last point on the path. If no points have been added, moveTo(p)

+    is automatically called.

+

+    #Param p  set value of Last_Point. ##

+

+    #Example

+    #Height 128

+        SkPaint paint;
+        paint.setTextSize(128);
+        SkPath path, path2;
+        paint.getTextPath("A", 1, 60, 100, &path);
+        paint.getTextPath("Z", 1, 60, 100, &path2);
+        SkPoint pt, pt2;
+        path.getLastPt(&pt);
+        path2.getLastPt(&pt2);
+        path.setLastPt(pt2);
+        path2.setLastPt(pt);
+        canvas->drawPath(path, paint);
+        canvas->drawPath(path2, paint);
+    ##

+

+    #SeeAlso getLastPt

+

+##

+

+#Subtopic Last_Point ##

+

+# ------------------------------------------------------------------------------

+

+#Enum SegmentMask

+

+#Code

+    enum SegmentMask {

+        kLine_SegmentMask = 1 << 0

+        kQuad_SegmentMask = 1 << 1

+        kConic_SegmentMask = 1 << 2

+        kCubic_SegmentMask = 1 << 3

+    };

+##

+

+SegmentMask constants correspond to each drawing Verb type in Path; for

+instance, if Path only contains Lines, only the kLine_SegmentMask bit is set.

+

+#Bug 6785 ##

+#Const kLine_SegmentMask 1

+Set if Verb_Array contains kLine_Verb.

+##

+#Const kQuad_SegmentMask 2

+Set if Verb_Array contains kQuad_Verb. Note that conicTo may add a Quad.

+##

+#Const kConic_SegmentMask 4

+Set if Verb_Array contains kConic_Verb.

+##

+#Const kCubic_SegmentMask 8

+Set if Verb_Array contains kCubic_Verb.

+##

+

+#Example

+#Description

+When conicTo has a weight of one, Quad is added to Path.

+##

+    SkPath path;

+    path.conicTo(10, 10, 20, 30, 1);

+    SkDebugf("Path kConic_SegmentMask is %s\n", path.getSegmentMasks() & 

+          SkPath::kConic_SegmentMask ? "set" : "clear");

+    SkDebugf("Path kQuad_SegmentMask is %s\n", path.getSegmentMasks() & 

+          SkPath::kQuad_SegmentMask ? "set" : "clear");

+#StdOut

+Path kConic_SegmentMask is clear

+Path kQuad_SegmentMask is set

+##

+##

+

+#SeeAlso getSegmentMasks Verb

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method uint32_t getSegmentMasks() const 

+

+Returns a mask, where each set bit corresponds to a SegmentMask constant

+if Path contains one or more Verbs of that type.

+Returns zero if Path contains no Lines, Quads, Conics, or Cubics.

+

+getSegmentMasks() returns a cached result; it is very fast.

+

+#Return  SegmentMask bits or zero. ##

+

+#Example

+SkPath path;

+path.quadTo(20, 30, 40, 50);

+path.close();

+const char* masks[] = { "line", "quad", "conic", "cubic" };

+int index = 0;

+for (auto mask : { SkPath::kLine_SegmentMask, SkPath::kQuad_SegmentMask,

+        SkPath::kConic_SegmentMask, SkPath::kCubic_SegmentMask } ) {

+    if (mask & path.getSegmentMasks()) {

+       SkDebugf("mask %s set\n", masks[index]);

+    }       

+    ++index;

+}

+#StdOut

+mask quad set

+##

+##

+

+#SeeAlso getSegmentMasks Verb

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method bool contains(SkScalar x, SkScalar y) const

+

+Returns true if the point (x, y) is contained by Path, taking into

+account FillType. 

+

+#Table

+#Legend

+# FillType                 # contains() returns true if Point is enclosed by ##

+##

+# kWinding_FillType        # a non-zero sum of Contour Directions. ##

+# kEvenOdd_FillType        # an odd number of Contours.            ##

+# kInverseWinding_FillType # a zero sum of Contour Directions.     ##

+# kInverseEvenOdd_FillType # and even number of Contours.          ##

+## 

+

+#Param x  x-coordinate of containment test. ##

+#Param y  y-coordinate of containment test. ##

+

+#Return  true if Point is in Path. ##

+

+#Example

+SkPath path;

+SkPaint paint;

+paint.setTextSize(256);

+paint.getTextPath("&", 1, 30, 220, &path);

+for (int y = 2; y < 256; y += 9) {

+   for (int x = 2; x < 256; x += 9) {

+       int coverage = 0;

+       for (int iy = -4; iy <= 4; iy += 2) {

+           for (int ix = -4; ix <= 4; ix += 2) {

+               coverage += path.contains(x + ix, y + iy);

+           }

+       }

+       paint.setColor(SkColorSetARGB(0x5f, 0xff * coverage / 25, 0, 0xff * (25 - coverage) / 25));

+       canvas->drawCircle(x, y, 8, paint);

+   }

+}

+##

+

+#SeeAlso conservativelyContainsRect Fill_Type Op

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void dump(SkWStream* stream, bool forceClose, bool dumpAsHex) const

+

+Writes text representation of Path to stream. If stream is nullptr, dump() writes to

+stdout. Set forceClose to true to get

+edges used to fill Path. Set dumpAsHex true to get exact binary representations

+of floating point numbers used in Point_Array and Weights.

+

+#Param stream      writable Stream receiving Path text representation; may be nullptr. ##

+#Param forceClose  true if missing kClose_Verb is output. ##

+#Param dumpAsHex   true if SkScalar values are written as hexidecimal. ##

+

+#Example

+   SkPath path;

+   path.quadTo(20, 30, 40, 50);

+   for (bool forceClose : { false, true } ) {

+       for (bool dumpAsHex : { false, true } ) {

+           path.dump(nullptr, forceClose, dumpAsHex);

+           SkDebugf("\n");

+       }

+   }

+#StdOut

+path.setFillType(SkPath::kWinding_FillType);

+path.moveTo(0, 0);

+path.quadTo(20, 30, 40, 50);

+

+path.setFillType(SkPath::kWinding_FillType);

+path.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, 0

+path.quadTo(SkBits2Float(0x41a00000), SkBits2Float(0x41f00000), SkBits2Float(0x42200000), SkBits2Float(0x42480000));  // 20, 30, 40, 50

+

+path.setFillType(SkPath::kWinding_FillType);

+path.moveTo(0, 0);

+path.quadTo(20, 30, 40, 50);

+path.lineTo(0, 0);

+path.close();

+

+path.setFillType(SkPath::kWinding_FillType);

+path.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, 0

+path.quadTo(SkBits2Float(0x41a00000), SkBits2Float(0x41f00000), SkBits2Float(0x42200000), SkBits2Float(0x42480000));  // 20, 30, 40, 50

+path.lineTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, 0

+path.close();

+##

+##

+

+#SeeAlso SkRect::dump() SkRRect::dump() SkPathMeasure::dump()

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void dump() const

+

+Writes text representation of Path to stdout. The representation may be

+directly compiled as C++ code. Floating point values are written

+with limited precision; it may not be possible to reconstruct original Path

+from output.

+

+#Example

+SkPath path, copy;

+path.lineTo(6.f / 7, 2.f / 3);

+path.dump();

+copy.setFillType(SkPath::kWinding_FillType);

+copy.moveTo(0, 0);

+copy.lineTo(0.857143f, 0.666667f);

+SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");

+#StdOut

+path.setFillType(SkPath::kWinding_FillType);

+path.moveTo(0, 0);

+path.lineTo(0.857143f, 0.666667f);

+path is not equal to copy

+##

+##

+

+#SeeAlso dumpHex SkRect::dump() SkRRect::dump() SkPathMeasure::dump() writeToMemory

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void dumpHex() const

+

+Writes text representation of Path to stdout. The representation may be

+directly compiled as C++ code. Floating point values are written

+in hexadecimal to preserve their exact bit pattern. The output reconstructs the

+original Path.

+

+Use dumpHex when submitting #A bug reports against Skia # http://bug.skia.org ##.

+Slight value changes in Point_Array may cause the bug to disappear.

+

+#Example

+SkPath path, copy;

+path.lineTo(6.f / 7, 2.f / 3);

+path.dumpHex();

+copy.setFillType(SkPath::kWinding_FillType);

+copy.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, 0

+copy.lineTo(SkBits2Float(0x3f5b6db7), SkBits2Float(0x3f2aaaab));  // 0.857143f, 0.666667f

+SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");

+#StdOut

+path.setFillType(SkPath::kWinding_FillType);

+path.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, 0

+path.lineTo(SkBits2Float(0x3f5b6db7), SkBits2Float(0x3f2aaaab));  // 0.857143f, 0.666667f

+path is equal to copy

+##

+##

+

+#SeeAlso dump SkRect::dumpHex() SkRRect::dumpHex() writeToMemory

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method size_t writeToMemory(void* buffer) const

+

+Write Path to buffer, returning the number of bytes written.

+Pass nullptr to obtain the storage size.

+

+writeToMemory writes Fill_Type, Verb_Array, Point_Array, Conic_Weight, and

+additionally writes computed information like Convexity and bounds.

+

+writeToMemory should only be used in concert with readFromMemory.

+The format used for Path in memory is not guaranteed.

+

+#Param buffer  storage for Path; may be nullptr. ##

+

+#Return  size of storage required for Path; always a multiple of 4. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPath path, copy;

+    path.lineTo(6.f / 7, 2.f / 3);

+    size_t size = path.writeToMemory(nullptr);

+    SkTDArray<char> storage;

+    storage.setCount(size);

+    path.writeToMemory(storage.begin());

+    copy.readFromMemory(storage.begin(), size);

+    SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");

+}

+##

+

+#SeeAlso readFromMemory dump dumpHex

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method size_t readFromMemory(const void* buffer, size_t length)

+

+Initializes Path from buffer of size length. Returns zero if the buffer is

+data is inconsistent, or the length is too small. 

+

+readFromMemory reads Fill_Type, Verb_Array, Point_Array, Conic_Weight, and

+additionally reads computed information like Convexity and bounds.

+

+readFromMemory should only be used in concert with writeToMemory.

+The format used for Path in memory is not guaranteed.

+

+#Param buffer  storage for Path. ##

+#Param length  buffer size in bytes; must be multiple of 4. ##

+

+#Return  number of bytes read, or zero on failure. ##

+

+#Example

+void draw(SkCanvas* canvas) {

+    SkPath path, copy;

+    path.lineTo(6.f / 7, 2.f / 3);

+    size_t size = path.writeToMemory(nullptr);

+    SkTDArray<char> storage;

+    storage.setCount(size);

+    path.writeToMemory(storage.begin());

+    size_t wrongSize = size - 4;

+    size_t bytesRead = copy.readFromMemory(storage.begin(), wrongSize);

+    SkDebugf("length = %u; returned by readFromMemory = %u\n", wrongSize, bytesRead);

+    size_t largerSize = size + 4;

+    bytesRead = copy.readFromMemory(storage.begin(), largerSize);

+    SkDebugf("length = %u; returned by readFromMemory = %u\n", largerSize, bytesRead);

+}

+#StdOut

+length = 60; returned by readFromMemory = 0

+length = 68; returned by readFromMemory = 64

+##

+##

+

+#SeeAlso writeToMemory

+

+##

+

+# ------------------------------------------------------------------------------

+#Topic Generation_ID

+#Alias Generation_IDs

+

+Generation_ID provides a quick way to check if Verb_Array, Point_Array, or

+Conic_Weight has changed. Generation_ID is not a hash; identical Paths will

+not necessarily have matching Generation_IDs.

+

+Empty Paths have a Generation_ID of one.

+

+#Method uint32_t getGenerationID() const

+

+Returns a non-zero, globally unique value. A different value is returned 

+if Verb_Array, Point_Array, or Conic_Weight changes.

+

+Setting Fill_Type does not change Generation_ID.

+

+Each time the path is modified, a different Generation_ID will be returned.

+

+#Bug 1762

+Fill_Type does affect Generation_ID on Android framework.

+##

+

+#Return  non-zero, globally unique value. ##

+

+#Example

+SkPath path;

+SkDebugf("empty genID = %u\n", path.getGenerationID());

+path.lineTo(1, 2);

+SkDebugf("1st lineTo genID = %u\n", path.getGenerationID());

+path.rewind();

+SkDebugf("empty genID = %u\n", path.getGenerationID());

+path.lineTo(1, 2);

+SkDebugf("2nd lineTo genID = %u\n", path.getGenerationID());

+#StdOut

+empty genID = 1

+1st lineTo genID = 2

+empty genID = 1

+2nd lineTo genID = 3

+##

+##

+

+#SeeAlso operator==(const SkPath& a, const SkPath& b)

+

+##

+

+#Topic ##

+

+# ------------------------------------------------------------------------------

+

+#Method void validate() const

+

+Debugging check to see if Path data is consistent.

+Not currently maintained.

+

+#NoExample

+##

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Method void experimentalValidateRef() const

+

+#Private

+Debugging check to see if Path data is consistent.

+Not ready for public use.

+##

+

+##

+

+# ------------------------------------------------------------------------------

+

+#Class Iter
+
+Iterates through Verb_Array, and associated Point_Array and Conic_Weight.
+Provides options to treat open Contours as closed, and to ignore
+degenerate data.
+
+#Example
+#Height 128
+#Description
+Ignoring the actual Verbs and replacing them with quads rounds the
+path of the glyph.
+##
+void draw(SkCanvas* canvas) {

+    SkPaint paint;

+    paint.setAntiAlias(true);

+    paint.setTextSize(256);

+    SkPath asterisk, path;

+    paint.getTextPath("*", 1, 50, 192, &asterisk);

+    SkPath::Iter iter(asterisk, true); 

+    SkPoint start[4], pts[4];

+    iter.next(start);  // skip moveTo

+    iter.next(start);  // first quadTo

+    path.moveTo((start[0] + start[1]) * 0.5f);

+    while (SkPath::kClose_Verb != iter.next(pts)) {

+        path.quadTo(pts[0], (pts[0] + pts[1]) * 0.5f);

+    }

+    path.quadTo(start[0], (start[0] + start[1]) * 0.5f);

+    canvas->drawPath(path, paint);

+}
+##
+
+#SeeAlso RawIter
+
+#Method Iter()
+
+Initializes Iter with an empty Path. next() on Iter returns kDone_Verb.
+Call setPath to initialize Iter at a later time.
+
+#Return  Iter of empty Path. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    SkPath::Iter iter;

+    SkPoint points[4];

+    SkDebugf("iter is " "%s" "done\n", SkPath::kDone_Verb == iter.next(points) ? "" : "not ");

+    SkPath path;

+    iter.setPath(path, false);

+    SkDebugf("iter is " "%s" "done\n", SkPath::kDone_Verb == iter.next(points) ? "" : "not ");

+}
+#StdOut

+iter is done

+iter is done

+##

+##
+
+#SeeAlso setPath
+
+##
+
+#Method Iter(const SkPath& path, bool forceClose)
+
+Sets Iter to return elements of Verb_Array, Point_Array, and Conic_Weight in path.
+If forceClose is true, Iter will add kLine_Verb and kClose_Verb after each
+open Contour. path is not altered.
+
+#Param path  Path to iterate. ##
+#Param forceClose true if open Contours generate kClose_Verb. ##
+
+#Return Iter of path. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, SkPath::Iter& iter) -> void {

+        SkDebugf("%s:\n", prefix);

+        const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };

+        const int pointCount[] = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };

+        SkPath::Verb verb;

+        do {

+           SkPoint points[4];

+           verb = iter.next(points);

+           SkDebugf("k%s_Verb ", verbStr[(int) verb]);

+           for (int i = 0; i < pointCount[(int) verb]; ++i) {

+                SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);

+           }

+           if (SkPath::kConic_Verb == verb) {

+               SkDebugf("weight = %g", iter.conicWeight());

+           }

+           SkDebugf("\n");

+        } while (SkPath::kDone_Verb != verb);

+        SkDebugf("\n");

+    };

+

+    SkPath path;

+    path.quadTo(10, 20, 30, 40);

+    SkPath::Iter openIter(path, false);

+    debugster("open", openIter);

+    SkPath::Iter closedIter(path, true);

+    debugster("closed", closedIter);

+}
+#StdOut
+open:
+kMove_Verb {0, 0}, 

+kQuad_Verb {0, 0}, {10, 20}, {30, 40}, 

+kDone_Verb 

+

+closed:

+kMove_Verb {0, 0}, 

+kQuad_Verb {0, 0}, {10, 20}, {30, 40}, 

+kLine_Verb {30, 40}, {0, 0}, 

+kClose_Verb {0, 0}, 

+kDone_Verb 
+##
+##
+
+#SeeAlso setPath
+
+##
+
+#Method void setPath(const SkPath& path, bool forceClose)
+
+Sets Iter to return elements of Verb_Array, Point_Array, and Conic_Weight in path.
+If forceClose is true, Iter will add kLine_Verb and kClose_Verb after each
+open Contour. path is not altered.
+
+#Param path  Path to iterate. ##
+#Param forceClose true if open Contours generate kClose_Verb. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, SkPath::Iter& iter) -> void {

+        SkDebugf("%s:\n", prefix);

+        const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };

+        const int pointCount[] = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };

+        SkPath::Verb verb;

+        do {

+           SkPoint points[4];

+           verb = iter.next(points);

+           SkDebugf("k%s_Verb ", verbStr[(int) verb]);

+           for (int i = 0; i < pointCount[(int) verb]; ++i) {

+                SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);

+           }

+           if (SkPath::kConic_Verb == verb) {

+               SkDebugf("weight = %g", iter.conicWeight());

+           }

+           SkDebugf("\n");

+        } while (SkPath::kDone_Verb != verb);

+        SkDebugf("\n");

+    };

+

+    SkPath path;

+    path.quadTo(10, 20, 30, 40);

+    SkPath::Iter iter(path, false);

+    debugster("quad open", iter);

+    SkPath path2;

+    path2.conicTo(1, 2, 3, 4, .5f);

+    iter.setPath(path2, true);

+    debugster("conic closed", iter);

+}
+#StdOut
+quad open:

+kMove_Verb {0, 0}, 

+kQuad_Verb {0, 0}, {10, 20}, {30, 40}, 

+kDone_Verb 

+

+conic closed:

+kMove_Verb {0, 0}, 

+kConic_Verb {0, 0}, {1, 2}, {3, 4}, weight = 0.5

+kLine_Verb {3, 4}, {0, 0}, 

+kClose_Verb {0, 0}, 

+kDone_Verb 
+##
+##
+
+#SeeAlso Iter(const SkPath& path, bool forceClose)
+
+##
+
+#Method Verb next(SkPoint pts[4], bool doConsumeDegenerates = true, bool exact = false) 
+
+        Returns next Verb in Verb_Array, and advances Iter.
+        When Verb_Array is exhausted, returns kDone_Verb.
+Zero to four Points are stored in pts, depending on the returned Verb.
+If doConsumeDegenerates is true, skip consecutive kMove_Verb entries, returning
+only the last in the series; and skip very small Lines, Quads, and Conics; and
+skip kClose_Verb following kMove_Verb.
+if doConsumeDegenerates is true and exact is true, only skip Lines, Quads, and
+Conics with zero lengths.
+
+    #Param  pts Storage for Point data describing returned Verb. ##
+    #Param doConsumeDegenerates If true, skip degenerate Verbs. ##
+    #Param exact If true, skip zero length curves. Has no effect if doConsumeDegenerates
+                 is false. 
+    ##
+
+    #Return next Verb from Verb_Array.  ##
+
+#Example
+#Description 
+skip degenerate skips the first in a kMove_Verb pair, the kMove_Verb
+followed by the kClose_Verb, the zero length Line and the very small Line.
+
+skip degenerate if exact skips the same as skip degenerate, but shows
+the very small Line.
+
+skip none shows all of the Verbs and Points in Path.
+##
+void draw(SkCanvas* canvas) {

+    auto debugster = [](const char* prefix, const SkPath& path, bool degen, bool exact) -> void {

+        SkPath::Iter iter(path, false);

+        SkDebugf("%s:\n", prefix);

+        const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };

+        const int pointCount[] = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };

+        SkPath::Verb verb;

+        do {

+           SkPoint points[4];

+           verb = iter.next(points, degen, exact);

+           SkDebugf("k%s_Verb ", verbStr[(int) verb]);

+           for (int i = 0; i < pointCount[(int) verb]; ++i) {

+                SkDebugf("{%1.8g, %1.8g}, ", points[i].fX, points[i].fY);

+           }

+           SkDebugf("\n");

+        } while (SkPath::kDone_Verb != verb);

+        SkDebugf("\n");

+    };

+

+    SkPath path;

+    path.moveTo(10, 10);

+    path.moveTo(20, 20);

+    path.quadTo(10, 20, 30, 40);

+    path.moveTo(1, 1);

+    path.close();

+    path.moveTo(30, 30);

+    path.lineTo(30, 30);

+    path.moveTo(30, 30);

+    path.lineTo(30.00001f, 30);

+    debugster("skip degenerate", path, true, false);

+    debugster("skip degenerate if exact", path, true, true);

+    debugster("skip none", path, false, false);

+}
+#StdOut
+skip degenerate:

+kMove_Verb {20, 20}, 

+kQuad_Verb {20, 20}, {10, 20}, {30, 40}, 

+kDone_Verb 

+

+skip degenerate if exact:

+kMove_Verb {20, 20}, 

+kQuad_Verb {20, 20}, {10, 20}, {30, 40}, 

+kMove_Verb {30, 30}, 

+kLine_Verb {30, 30}, {30.00001, 30}, 

+kDone_Verb 

+

+skip none:

+kMove_Verb {10, 10}, 

+kMove_Verb {20, 20}, 

+kQuad_Verb {20, 20}, {10, 20}, {30, 40}, 

+kMove_Verb {1, 1}, 

+kClose_Verb {1, 1}, 

+kMove_Verb {30, 30}, 

+kLine_Verb {30, 30}, {30, 30}, 

+kMove_Verb {30, 30}, 

+kLine_Verb {30, 30}, {30.00001, 30}, 

+kDone_Verb 

+##
+##
+
+#SeeAlso Verb IsLineDegenerate IsCubicDegenerate IsQuadDegenerate 
+
+##
+
+#Method SkScalar conicWeight() const
+
+    Returns Conic_Weight if next() returned kConic_Verb.
+
+    If next() has not been called, or next() did not return kConic_Verb,
+    result is undefined.
+
+    #Return  Conic_Weight for Conic Points returned by next(). ##
+
+    #Example
+    void draw(SkCanvas* canvas) {

+       SkPath path;

+       path.conicTo(1, 2, 3, 4, .5f);

+       SkPath::Iter iter(path, false); 

+       SkPoint p[4];

+       SkDebugf("first verb is " "%s" "move\n", SkPath::kMove_Verb == iter.next(p) ? "" : "not ");

+       SkDebugf("next verb is " "%s" "conic\n", SkPath::kConic_Verb == iter.next(p) ? "" : "not ");

+       SkDebugf("conic points: {%g,%g}, {%g,%g}, {%g,%g}\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY,

+                    p[2].fX, p[2].fY);

+       SkDebugf("conic weight: %g\n", iter.conicWeight());

+    }
+    #StdOut
+first verb is move

+next verb is conic

+conic points: {0,0}, {1,2}, {3,4}

+conic weight: 0.5
+    ##
+    ##
+
+    #SeeAlso Conic_Weight
+
+##
+
+#Method bool isCloseLine() const
+
+    Returns true if last kLine_Verb returned by next() was generated
+    by kClose_Verb. When true, the end point returned by next() is
+    also the start point of Contour.
+
+    If next() has not been called, or next() did not return kLine_Verb,
+    result is undefined.
+
+    #Return  true if last kLine_Verb was generated by kClose_Verb. ##
+
+    #Example
+void draw(SkCanvas* canvas) {

+   SkPath path;

+   path.moveTo(6, 7);

+   path.conicTo(1, 2, 3, 4, .5f);

+   path.close();

+   SkPath::Iter iter(path, false); 

+   SkPoint p[4];

+   SkDebugf("1st verb is " "%s" "move\n", SkPath::kMove_Verb == iter.next(p) ? "" : "not ");

+   SkDebugf("moveTo point: {%g,%g}\n", p[0].fX, p[0].fY);

+   SkDebugf("2nd verb is " "%s" "conic\n", SkPath::kConic_Verb == iter.next(p) ? "" : "not ");

+   SkDebugf("3rd verb is " "%s" "line\n", SkPath::kLine_Verb == iter.next(p) ? "" : "not ");

+   SkDebugf("line points: {%g,%g}, {%g,%g}\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY);

+   SkDebugf("line " "%s" "generated by close\n", iter.isCloseLine() ? "" : "not ");

+   SkDebugf("4th verb is " "%s" "close\n", SkPath::kClose_Verb == iter.next(p) ? "" : "not ");

+}
+    #StdOut
+1st verb is move

+moveTo point: {6,7}

+2nd verb is conic

+3rd verb is line

+line points: {3,4}, {6,7}

+line generated by close

+4th verb is close
+    ##
+    ##
+
+    #SeeAlso close()
+##
+
+#Method bool isClosedContour() const
+
+Returns true if subsequent calls to next() return kClose_Verb before returning
+kMove_Verb. if true, Contour Iter is processing may end with kClose_Verb, or
+Iter may have been initialized with force close set to true.
+
+#Return true if Contour is closed. ##
+
+#Example
+void draw(SkCanvas* canvas) {

+   for (bool forceClose : { false, true } ) {

+       SkPath path;

+       path.conicTo(1, 2, 3, 4, .5f);

+       SkPath::Iter iter(path, forceClose); 

+       SkDebugf("without close(), forceClose is %s: isClosedContour returns %s\n",

+           forceClose ? "true " : "false", iter.isClosedContour() ? "true" : "false");

+       path.close();

+       iter.setPath(path, forceClose);

+       SkDebugf("with close(),    forceClose is %s: isClosedContour returns %s\n",

+           forceClose ? "true " : "false", iter.isClosedContour() ? "true" : "false");

+    }

+}
+#StdOut
+without close(), forceClose is false: isClosedContour returns false

+with close(),    forceClose is false: isClosedContour returns true

+without close(), forceClose is true : isClosedContour returns true

+with close(),    forceClose is true : isClosedContour returns true
+##
+##
+
+#SeeAlso Iter(const SkPath& path, bool forceClose)
+
+##
+

+#Class Iter ##

+

+#Class RawIter
+
+Iterates through Verb_Array, and associated Point_Array and Conic_Weight.
+Verb_Array, Point_Array, and Conic_Weight are returned unaltered. 
+
+    #Method RawIter()
+
+        Initializes RawIter with an empty Path. next() on RawIter returns kDone_Verb.
+        Call setPath to initialize Iter at a later time.
+
+        #Return RawIter of empty Path. ##

+

+        #NoExample

+        ##

+    ##

+
+    #Method RawIter(const SkPath& path)
+
+
+        Sets RawIter to return elements of Verb_Array, Point_Array, and Conic_Weight in path.
+
+        #Param path  Path to iterate. ##
+
+        #Return RawIter of path. ##
+

+        #NoExample

+        ##

+    ##

+
+    #Method void setPath(const SkPath& path)
+
+        Sets Iter to return elements of Verb_Array, Point_Array, and Conic_Weight in path.
+
+        #Param path  Path to iterate. ##
+

+        #NoExample

+        ##

+   ##

+
+    #Method Verb next(SkPoint pts[4])
+
+        Returns next Verb in Verb_Array, and advances RawIter.
+        When Verb_Array is exhausted, returns kDone_Verb.
+        Zero to four Points are stored in pts, depending on the returned Verb.
+
+        #Param  pts Storage for Point data describing returned Verb. ##
+
+        #Return next Verb from Verb_Array.  ##
+
+        #Example
+        void draw(SkCanvas* canvas) {

+            SkPath path;

+            path.moveTo(50, 60);

+            path.quadTo(10, 20, 30, 40);

+            path.close();

+            path.lineTo(30, 30);

+            path.conicTo(1, 2, 3, 4, .5f);

+            path.cubicTo(-1, -2, -3, -4, -5, -6);

+            SkPath::RawIter iter(path);

+            const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };

+            const int pointCount[] = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };

+            SkPath::Verb verb;

+            do {

+                SkPoint points[4];

+                verb = iter.next(points);

+                SkDebugf("k%s_Verb ", verbStr[(int) verb]);

+                for (int i = 0; i < pointCount[(int) verb]; ++i) {

+                    SkDebugf("{%1.8g, %1.8g}, ", points[i].fX, points[i].fY);

+                }

+                if (SkPath::kConic_Verb == verb) {

+                    SkDebugf("weight = %g", iter.conicWeight());

+                }

+                SkDebugf("\n");

+            } while (SkPath::kDone_Verb != verb);

+        }
+    #StdOut
+        kMove_Verb {50, 60}, 

+        kQuad_Verb {50, 60}, {10, 20}, {30, 40}, 

+        kClose_Verb {50, 60}, 

+        kMove_Verb {50, 60}, 

+        kLine_Verb {50, 60}, {30, 30}, 

+        kConic_Verb {30, 30}, {1, 2}, {3, 4}, weight = 0.5

+        kCubic_Verb {3, 4}, {-1, -2}, {-3, -4}, {-5, -6}, 

+        kDone_Verb 
+    ##
+    ##
+
+    #SeeAlso peek()
+
+    ##

+
+    #Method Verb peek() const
+
+        Returns next Verb, but does not advance RawIter.
+
+        #Return next Verb from Verb_Array.  ##
+
+        #Example
+            SkPath path;

+            path.quadTo(10, 20, 30, 40);

+            path.conicTo(1, 2, 3, 4, .5f);

+            path.cubicTo(1, 2, 3, 4, .5, 6);

+            SkPath::RawIter iter(path);

+            SkPath::Verb verb, peek = iter.peek();

+            const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };

+            do {

+                SkPoint points[4];

+                verb = iter.next(points);

+                SkDebugf("peek %s %c= verb %s\n", verbStr[peek], peek == verb ? '=' : '!', verbStr[verb]);

+                peek = iter.peek();

+            } while (SkPath::kDone_Verb != verb);
+            SkDebugf("peek %s %c= verb %s\n", verbStr[peek], peek == verb ? '=' : '!', verbStr[verb]);

+            #StdOut

+                #Volatile

+                peek Move == verb Move

+                peek Quad == verb Quad

+                peek Conic == verb Conic

+                peek Cubic == verb Cubic

+                peek Done == verb Done

+                peek Done == verb Done

+            ##

+        ##
+
+        #Bug 6832
+        StdOut isn't really volatile, it just produces the wrong result.
+        A simple fix changes the output of hairlines and needs to be
+        investigated to see if the change is correct or not.
+        https://skia-review.googlesource.com/c/21340/
+        ##
+
+        #SeeAlso next()
+
+    ##

+
+    #Method SkScalar conicWeight() const

+

+        Returns Conic_Weight if next() returned kConic_Verb.
+
+        If next() has not been called, or next() did not return kConic_Verb,
+        result is undefined.
+

+        #Return  Conic_Weight for Conic Points returned by next(). ##
+
+    #Example
+    void draw(SkCanvas* canvas) {

+       SkPath path;

+       path.conicTo(1, 2, 3, 4, .5f);

+       SkPath::RawIter iter(path); 

+       SkPoint p[4];

+       SkDebugf("first verb is " "%s" "move\n", SkPath::kMove_Verb == iter.next(p) ? "" : "not ");

+       SkDebugf("next verb is " "%s" "conic\n", SkPath::kConic_Verb == iter.next(p) ? "" : "not ");

+       SkDebugf("conic points: {%g,%g}, {%g,%g}, {%g,%g}\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY,

+                    p[2].fX, p[2].fY);

+       SkDebugf("conic weight: %g\n", iter.conicWeight());

+    }
+    #StdOut
+        first verb is move

+        next verb is conic

+        conic points: {0,0}, {1,2}, {3,4}

+        conic weight: 0.5
+    ##
+    ##
+
+    #SeeAlso Conic_Weight
+

+    ##

+

+#Class RawIter ##

+

+#Class SkPath ##

+

+#Topic Path ##

diff --git a/docs/markup.bmh b/docs/markup.bmh
new file mode 100644
index 0000000..2127fc7
--- /dev/null
+++ b/docs/markup.bmh
@@ -0,0 +1,88 @@
+#Topic Bookmaker_Markup
+
+# redefine markup character so examples below will not be parsed
+###$  
+
+Text, except for the single markup character, requires no annotation.
+
+# comments are preceded by a hash symbol and whitespace
+# comments may terminated by linefeed or double hash ## <- end of comment
+
+Keywords are preceded by a single hash symbol without whitespace.
+#Keyword
+
+Keywords are terminated by double hash and may be labeled
+##            <- end of #keyword
+
+#Keyword
+#Keyword ##   <- alternate labeled end of #Keyword
+
+Tables use single hash symbols to delimit columns, and double to end row.
+#Table
+#Legend
+# first column in table # next column in table ##
+##            <- end of #Legend
+# a row                 # another row ##
+# another row           # another row ##
+#Table ##     <- or, just ##
+
+$Table
+$Legend
+$ first column in table $ next column in table $$
+$$
+$ a row                 $ another row $$
+$ another row           $ another row $$
+$Table $$
+
+The markup character is initially # at the start of any .bmh file
+###x          <- redefine the markup character as 'x'
+xxx#          <- restore the default markup character
+
+  anchor, ala HTML
+  anchors may start anywhere in the line
+#A text #_reference ##
+
+  class description
+#Class SkClassName
+description
+methods
+##
+
+  if the example is not named, it inherits the name of its container
+#Example
+    #Description
+    ##
+    #Image
+    #Width
+    #Height
+        code...
+    #StdOut
+        expected example output
+    ##
+##
+
+#Enum __required_reference
+description
+#Code
+##
+#Example
+##
+#Enum ##
+
+  method description
+  the _method_reference must be unique within the class
+#Method type name(params..) 
+description
+#Param name  description ##
+#Return return ##
+#Example
+##
+#SeeAlso ##
+##
+
+#ToDo  description ##
+
+$ restore markup character
+$$$#   
+
+##
diff --git a/docs/overview.bmh b/docs/overview.bmh
new file mode 100644
index 0000000..c6b0d1f
--- /dev/null
+++ b/docs/overview.bmh
@@ -0,0 +1,8 @@
+overview
+
+--------------------------------------------------------------------------------
+
+Skia draws 2D primitives, paths and bitmaps, using the styles in the SkPaint, to
+the device contained by the SkCanvas.
+
+--------------------------------------------------------------------------------
diff --git a/docs/undocumented.bmh b/docs/undocumented.bmh
new file mode 100644
index 0000000..8aecc31
--- /dev/null
+++ b/docs/undocumented.bmh
@@ -0,0 +1,528 @@
+# external references that will be documented eventually ...
+#External
+ DirectWrite TrueType Windows Linux Android
+ FreeType FreeType-based Harfbuzz   
+ PostScript PostScript_arct
+ OS_X Core_Graphics Core_Text iOS
+ LCD RGB
+ Premultiplied Unpremultiplied
+ Unicode Unicode5 UTF-8 UTF-16 UTF-32 ASCII Unichar
+ HTML_Canvas HTML_Canvas_arcTo
+ API
+ CPU
+ GPU GPU-backed GPU_Context OpenGL Vulkan
+ NULL
+ RFC
+ Bezier Coons
+ SkUserConfig.h  # not external, but still thinking about how markup refers to this
+ Skia           # ditto
+ SK_USE_FREETYPE_EMBOLDEN # ditto
+ SK_SUPPORT_LEGACY_PAINT_TEXTDECORATION # ditto
+ SK_BUILD_FOR_ANDROID_FRAMEWORK # ditto
+ Developer_Mode # ditto
+ Draw_Layer # ditto
+ Raster_Engine # ditto
+
+# FreeType related
+FT_LOAD_TARGET_LIGHT
+FT_LOAD_TARGET_NORMAL
+FT_LOAD_TARGET_LCD
+FT_LOAD_TARGET_LCD_V
+FT_LOAD_NO_HINTING
+FT_Load_Glyph
+
+#External ##
+
+#Topic Arc
+#Substitute arcs
+#Topic ##
+
+#Topic BBH_Factory
+#Class SkBBHFactory
+##
+##
+
+#Topic Bitmap
+#Class SkBitmap
+    #Subtopic Row_Bytes
+    ##
+#Class ##
+##
+
+#Topic Blend_Mode
+#EnumClass SkBlendMode
+    #Const kSrc 1
+    ##
+    #Const kSrcOver 3
+    ##
+    #Const kPlus 12
+    ##
+#EnumClass ##
+#Topic ##
+
+#Topic Circle
+#Substitute circles
+#Topic ##
+
+#Topic Clip_Op
+#EnumClass SkClipOp
+    #Const kDifference 0
+    ##
+    #Const kIntersect 1
+    ##
+##
+##
+
+#Topic Color
+    #Typedef SkColor
+    #Typedef ##
+
+    # fixme: defines, not methods, need new markup type
+    #Method int SkColorGetA(color)
+    ##
+    #Method int SkColorGetR(color)
+    ##
+    #Method int SkColorGetG(color)
+    ##
+    #Method int SkColorGetB(color)
+    ##
+    #Method int SkColorSetARGB(a, r, g, b)
+    ##
+
+    #Const SK_ColorBLACK 0xFF000000 
+    ##
+    #Const SK_ColorBLUE 0xFF0000FF 
+    ##
+    #Const SK_ColorGREEN 0xFF00FF00 
+    ##
+    #Const SK_ColorRED 0xFFFF0000 
+    ##
+    #Const SK_ColorWHITE 0xFFFFFFFF 
+    ##
+    #Subtopic Alpha
+    #Substitute alpha
+    #Subtopic ##
+    #Subtopic RGB
+    #Substitute RGB
+        #Subtopic Red
+        #Substitute red
+        #Subtopic ##
+        #Subtopic Blue
+        #Substitute blue
+        #Subtopic ##
+        #Subtopic Green
+        #Substitute green
+        #Subtopic ##
+    #Subtopic ##
+    #Subtopic ARGB
+    #Substitute ARGB
+    #Subtopic ##
+
+    #Subtopic RBG
+    #Substitute RBG
+    #Subtopic ##
+
+    #Subtopic RGB-565
+    #Substitute RGB-565
+    #Alias Color_RGB-565 # quit changing - to _ !
+    #Subtopic ##
+#Topic ##
+
+#Topic Color_Filter
+#Class SkColorFilter
+#Class ##
+#Topic ##
+
+#Topic Color_Space
+##
+
+#Topic Curve
+#Alias Curves
+##
+
+#Topic Data
+##
+
+#Topic Device
+#Class SkBaseDevice
+##
+#Topic ##
+
+#Topic Document
+#Class SkDocument
+    #Method SkCanvas* beginPage(SkScalar width, SkScalar height,
+                        const SkRect* content = NULL)
+    ##
+##
+#Subtopic PDF
+##
+##
+
+#Topic Draw_Filter
+#Class SkDrawFilter
+##
+##
+
+#Topic Draw_Looper
+#Class SkDrawLooper
+#Class ##
+#Topic ##
+
+#Topic Drawable
+#Class SkDrawable
+    #Method void draw(SkCanvas*, const SkMatrix* = NULL)
+    ##
+##
+##
+
+#Topic Dump_Canvas
+#Class SkDumpCanvas
+##
+#Topic ##
+
+#Topic Filter_Quality
+#Enum SkFilterQuality
+    #Const kNone_SkFilterQuality 0
+    ##
+    #Const kLow_SkFilterQuality 1
+    ##
+    #Const kMedium_SkFilterQuality 2
+    ##
+    #Const kHigh_SkFilterQuality 3
+    ##
+#Enum ##
+#Topic ##
+
+#Topic Font
+#Subtopic Advance
+#Subtopic ##
+#Subtopic Engine
+##
+#Topic ##
+
+#Topic Font_Manager
+#Topic ##
+
+#Topic Glyph
+##
+
+#Topic Image
+    #Subtopic Alpha_Type
+        #Enum SkAlphaType
+            #Const kPremul_SkAlphaType 2
+            ##
+        ##
+    #Subtopic ##
+    #Subtopic Color_Type 
+        #Enum SkColorType
+            #Const kUnknown_SkColorType 0
+            ##
+            #Const kAlpha_8_SkColorType 1
+            ##
+            #Const kRGB_565_SkColorType 2
+            ##
+            #Const kARGB_4444_SkColorType 3
+            ##
+            #Const kRGBA_8888_SkColorType 4
+            ##
+            #Const kBGRA_8888_SkColorType 5
+            ##
+            #Const kIndex_8_SkColorType 6
+            ##
+            #Const kGray_8_SkColorType 7
+            ##
+            #Const kRGBA_F16_SkColorType 8
+            ##
+            #ToDo   this is a lie; need to not require values for consts ##
+            #Const kN32_SkColorType 4
+            ##
+        #Enum ##
+    #Subtopic ##
+    #Subtopic Info
+        #Struct SkImageInfo
+            #Method SkImageInfo()
+            ##
+        ## 
+    #Subtopic ##
+    #Class SkImage
+        #Method sk_sp<SkShader> makeShader(SkShader::TileMode, SkShader::TileMode,
+                                   const SkMatrix* localMatrix = nullptr) const
+        ##
+    ##
+#Topic ##
+
+#Topic Image_Filter
+#Subtopic Scaling
+#Subtopic ##
+#Class SkImageFilter
+#Class ##
+#Topic ##
+
+#Topic Image_Scaling
+##
+
+#Topic IRect
+#Struct SkIRect
+##
+##
+
+#Topic Line
+#Substitute lines
+#Alias Lines
+#Topic ##
+
+#Topic Mask
+#Topic ##
+
+#Topic Mask_Alpha
+#Topic ##
+
+#Topic Mask_Filter
+#Class SkMaskFilter
+#Class ##
+#Topic ##
+
+#Topic Matrix
+#Struct SkMatrix
+#Struct ##
+#Topic ##
+
+#Topic Nine_Patch
+##
+
+#Topic Number_Types
+    #Typedef SkGlyphID
+    #Typedef ##
+    #Typedef SkScalar
+    #Typedef ##
+    #Const SK_ScalarMax
+    to be written
+    ##
+    #Const SK_ScalarInfinity
+    to be written
+    ##
+    #Const SK_ScalarNegativeInfinity
+    to be written
+    ##
+    #Const SK_ScalarNaN
+    to be written
+    ##
+    #Typedef SkUnichar
+    #Typedef ##
+    #Typedef U8CPU
+    #Typedef ##
+#Topic ##
+
+#Topic Oval
+#Substitute ovals
+#Topic ##
+
+#Topic Paint_Defaults
+#Const SkPaintDefaults_Flags 0
+##
+#Const SkPaintDefaults_Hinting 2
+##
+#Const SkPaintDefaults_TextSize 12
+##
+#Const SkPaintDefaults_MiterLimit 4
+##
+#Topic ##
+
+#Topic Patch
+#Substitute patches
+#Topic ##
+
+#Topic Path_Effect
+    #Class SkPathEffect
+    #Class ##
+#Topic ##
+
+#Topic Path_Measure
+    #Class SkPathMeasure
+            #Method void dump() const
+            ##
+    ##
+##
+
+#Topic PathOps
+    #Method bool SK_API Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result)
+    ##
+#Topic ##
+
+#Topic Picture
+#Subtopic Recorder
+    #Class SkPictureRecorder
+        #Method SkCanvas* beginRecording(const SkRect& bounds,
+                             SkBBHFactory* bbhFactory = NULL,
+                             uint32_t recordFlags = 0)
+        ##
+    ##
+##
+##
+
+#Topic Pixel
+#Subtopic Storage
+##
+##
+
+#Topic Pixmap
+#Class SkPixmap
+##
+##
+
+#Topic Point
+#Alias Points
+    #Struct SkPoint
+        #Method bool equalsWithinTolerance(const SkPoint& p) const
+        ##
+    #Struct ##
+    #Subtopic Array
+    #Substitute SkPoint arrays
+    #Subtopic ##
+#Topic ##
+
+#Topic Raster_Handle_Allocator
+#Class SkRasterHandleAllocator
+    #Struct Rec
+    ##
+    #Method static std::unique_ptr<SkCanvas> MakeCanvas(std::unique_ptr<SkRasterHandleAllocator>, const SkImageInfo&, const Rec* rec = nullptr)
+    ##
+##
+##
+
+#Topic Rasterizer
+#Class SkRasterizer
+#Class ##
+#Subtopic Layer
+#Subtopic ##
+#Topic ##
+
+#Topic Rect
+#Alias Rects
+    #Struct SkRect
+        #Method static constexpr SkRect SK_WARN_UNUSED_RESULT MakeEmpty()
+        ##
+        #Method void dump() const
+        ##
+        #Method void dumpHex() const
+        ##
+    #Struct ##
+#Topic ##
+
+#Topic Reference_Count
+#Substitute SkRefCnt
+#Class sk_sp
+#Class ##
+#Topic ##
+
+#Topic Region
+#Class SkRegion
+##
+#Topic ##
+
+#Topic Round_Rect
+    #Class SkRRect
+        #Method void dump() const
+        ##
+        #Method void dumpHex() const
+        ##
+    ##
+#Topic ##
+
+#Topic RSXform
+#Struct SkRSXform
+##
+##
+
+#Topic Shader
+#Class SkShader
+    #Enum TileMode
+        #Const kClamp_TileMode 0
+        ##
+    ##
+    #Method static sk_sp<SkShader> MakeBitmapShader(const SkBitmap& src, TileMode tmx, TileMode tmy,
+                                            const SkMatrix* localMatrix = nullptr)
+    ##
+#Class ##
+#Subtopic Gradient
+#Subtopic ##
+#Topic ##
+
+#Topic Sprite
+#Substitute sprites
+#Topic ##
+
+#Topic Stream
+#Class SkFlattenable
+#Class ##
+#Topic ##
+
+#Topic String
+#Class SkString
+#Class ##
+#Topic ##
+
+#Topic Surface
+#Class SkSurface
+    #Method static sk_sp<SkSurface> MakeRasterDirect(const SkImageInfo&, void* pixels, size_t rowBytes,
+                                             const SkSurfaceProps* = nullptr)
+    ##
+##
+#Subtopic Properties
+    #Class SkSurfaceProps
+        #Enum InitType
+            #Const kLegacyFontHost_InitType 0
+            ##
+        ##
+    ##
+##
+#Subtopic GPU
+#Alias GPU_Surface
+##
+#Subtopic Raster
+#Alias Raster_Surface
+##
+##
+
+#Topic SVG
+#Subtopic Canvas
+##
+#Subtopic Arc
+##
+##
+
+#Topic Text
+#Topic ##
+
+#Topic Text_Blob
+#Class SkTextBlob
+#Class ##
+#Topic ##
+
+#Topic Typeface
+#Class SkTypeface
+#Class ##
+#Topic ##
+
+#Topic Vector
+#Struct SkVector
+##
+##
+
+#Topic Vertices
+#Substitute vertices
+#Subtopic Colors
+##
+#Subtopic Texs
+##
+#Topic ##
+
+#Topic Read_Buffer
+    #Struct SkReadBuffer
+    #Struct ##
+##
+
+#Topic Write_Buffer
+    #Struct SkWriteBuffer
+    #Struct ##
+#Topic ##
diff --git a/docs/usingBookmaker.bmh b/docs/usingBookmaker.bmh
new file mode 100644
index 0000000..65a8df5
--- /dev/null
+++ b/docs/usingBookmaker.bmh
@@ -0,0 +1,95 @@
+#External

+SkXXX

+bmh_SkXXX

+CL

+C

+Visual_Studio

+##

+

+#Topic Bookmaker

+

+How to use the Bookmaker utility.

+

+Get the fiddle command line interface tool.

+

+#Code

+$ go get go.skia.org/infra/fiddle/go/fiddlecli

+##

+

+Get the Bookmaker CL and build it.

+

+#Code

+$ git cl patch 9919

+$ ninja -C out/dir bookmaker

+##

+

+Generate an starter Bookmaker file from an existing include.

+This writes SkXXX.bmh in the current directory, which is

+out/dir/obj/ from an IDE.

+

+#Code

+$ ./out/dir/bookmaker -t -i include/core/SkXXX.h

+##

+

+Use your favorite editor to fill out SkXXX.bmh.

+

+Generate fiddle.json from all examples, including the ones you just wrote.

+Error checking is syntatic: starting keywords are closed, keywords have the

+correct parents.

+If you run Bookmaker inside Visual_Studio, you can click on errors and it

+will take you to the source line in question.

+

+#Code

+$ ./out/dir/bookmaker -e fiddle.json -b current_directory

+##

+

+Once complete, run fiddlecli to generate the example hashes.

+Errors are contained by the output but aren't reported yet.

+

+#Code

+$ $GOPATH/bin/fiddlecli --input fiddle.json --output fiddleout.json

+##

+

+Generate bmh_SkXXX.md from SkXXX.bmh and fiddleout.json.

+Error checking includes: undefined references, fiddle compiler errors,

+missing or mismatched printf output.

+Again, you can click on any errors inside Visual_Studio.

+

+#Code

+$ ./out/dir/bookmaker -r site/user/api -b current_directory -f fiddleout.json

+##

+

+The original include may have changed since you started creating the markdown.

+Check to see if it is up to date.

+This reports if a method no longer exists or its parameters have changed.

+

+#Code

+$ ./out/dir/bookmaker -x -b current_directory/SkXXX.bmh -i include/core/SkXXX.h

+##

+

+#Topic Bugs

+#List
+overaggressive reference finding in code block

+missing examples

+redundant examples -- got tired so used the same one more than once

+some examples need vertical resizing

+list doesn't work (ironic, huh)

+##

+##

+

+#Topic To_Do

+#List
+check that all methods have one line descriptions in overview

+see also -- anything that can be done automatically? maybe any ref shows up everywhere

+index by example png

+generate pdf or pdf-like out

+generate b/w out instead of color -- have b/w versions of examples?

+formalize voice / syntax for parts of topic and method

+write bmh data back into include

+    have a way to write one block that covers multiple nearly indentical methods?

+    may want to do this for pdf view as well

+write a one-method-per-page online view?

+##

+##

+

+#Topic Bookmaker ##

diff --git a/tools/bookmaker/bookmaker.cpp b/tools/bookmaker/bookmaker.cpp
new file mode 100644
index 0000000..3b67663
--- /dev/null
+++ b/tools/bookmaker/bookmaker.cpp
@@ -0,0 +1,2198 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bookmaker.h"
+
+#include "SkCommandLineFlags.h"
+#include "SkOSFile.h"
+#include "SkOSPath.h"
+
+
+/*  recipe for generating timestamps for existing doxygen comments
+find include/core -type f -name '*.h' -print -exec git blame {} \; > ~/all.blame.txt
+
+space table better for Constants
+should Return be on same line as 'Return Value'?
+remove anonymous header, e.g. Enum SkPaint::::anonymous_2
+Text Encoding anchors in paragraph are echoed instead of being linked to anchor names
+    also should not point to 'undocumented' since they are resolvable links
+#Member lost all formatting
+inconsistent use of capitalization in #Param
+#List needs '# content ##', formatting
+consts like enum members need fully qualfied refs to make a valid link
+enum comments should be disallowed unless after #Enum and before first #Const
+    ... or, should look for enum comments in other places
+
+// in includeWriter.cpp
+lf preceding #A is ignored
+
+Text_Size should become SkPaint's text size if root is not Paint?
+100 column limit done manually -- either error or rewrap
+
+SkPaint.bmh line 22:
+Insert 'the' after 'regardless of' ?
+somewhat intentional. Imagine SkPaint::kXXX is 'Joe'. Then it shouldn't read 'regardless
+of the Joe setting.' To make that work as a proper pronoun, maybe it should read: 
+'regardless of SkPaint's kAntiAlias_Flag setting or 'regardless of SkPaint's anti-alias setting'. 
+It's the way it is so that SkPaint::kAntiAlias_Flag can be a link to the definition. 
+Its awkwardness is compounded because this description is technically outside of 'class SkPaint' 
+so a reference to kAntiAlias_Flag by itself doesn't know that it is defined inside SkPaint,
+but that's a detail I could work around.
+
+SkPaint.bmh line 319, 400, 444
+more complications I haven't figured out. I don't know when or how to pluralize 
+references. This should be "objects' reference counts" probably, but then 
+I lose the link to SkRefCnt.
+
+SkPaint.bmh line 2074
+arcs at front of sentence not capitalized
+
+SkPaint.bmh line 2639
+I'd argue that 'fill path' is OK, in that is it the path that will fill, not the path
+that has already been filled. I see the awkwardness though, and will add it to my bug list.
+
+check for function name in its own description
+
+multiple line #Param / #Return only copies first line?
+
+rework underlinethickness / strikeout thickness
+
+getTextIntercepts lost underline comment
+ */
+
+static string normalized_name(string name) {
+    string normalizedName = name;
+    std::replace(normalizedName.begin(), normalizedName.end(), '-', '_');
+    do {
+        size_t doubleColon = normalizedName.find("::", 0);
+        if (string::npos == doubleColon) {
+            break;
+        }
+        normalizedName = normalizedName.substr(0, doubleColon)
+            + '_' + normalizedName.substr(doubleColon + 2);
+    } while (true);
+    return normalizedName;
+}
+
+static size_t count_indent(const string& text, size_t test, size_t end) {
+    size_t result = test;
+    while (test < end) {
+        if (' ' != text[test]) {
+            break;
+        }
+        ++test;
+    }
+    return test - result;
+}
+
+static void add_code(const string& text, int pos, int end, 
+        size_t outIndent, size_t textIndent, string& example) {
+    do {
+         // fix this to move whole paragraph in, out, but preserve doc indent
+        int nextIndent = count_indent(text, pos, end);
+        size_t len = text.find('\n', pos);
+        if (string::npos == len) {
+            len = end;
+        }
+        if ((size_t) (pos + nextIndent) < len) {
+            size_t indent = outIndent + nextIndent;
+            SkASSERT(indent >= textIndent);
+            indent -= textIndent;
+            for (size_t index = 0; index < indent; ++index) {
+                example += ' ';
+            }
+            pos += nextIndent;
+            while ((size_t) pos < len) {
+                example += '"' == text[pos] ? "\\\"" :
+                    '\\' == text[pos] ? "\\\\" : 
+                    text.substr(pos, 1);
+                ++pos;
+            }
+            example += "\\n";
+        } else {
+            pos += nextIndent;
+        }
+        if ('\n' == text[pos]) {
+            ++pos;
+        }
+    } while (pos < end);
+}
+
+// fixme: this will need to be more complicated to handle all of Skia
+// for now, just handle paint -- maybe fiddle will loosen naming restrictions
+void Definition::setCanonicalFiddle() {
+    fMethodType = Definition::MethodType::kNone;
+    size_t doubleColons = fName.find("::", 0);
+    SkASSERT(string::npos != doubleColons);
+    string result = fName.substr(0, doubleColons) + "_";
+    doubleColons += 2;
+    if (string::npos != fName.find('~', doubleColons)) {
+        fMethodType = Definition::MethodType::kDestructor;
+        result += "destructor";
+    } else {
+        bool isMove = string::npos != fName.find("&&", doubleColons);
+        const char operatorStr[] = "operator";
+        size_t opPos = fName.find(operatorStr, doubleColons);
+        if (string::npos != opPos) {
+            fMethodType = Definition::MethodType::kOperator;
+            opPos += sizeof(operatorStr) - 1;
+            if ('!' == fName[opPos]) {
+                SkASSERT('=' == fName[opPos + 1]);
+                result += "not_equal_operator"; 
+            } else if ('=' == fName[opPos]) {
+                if ('(' == fName[opPos + 1]) {
+                    result += isMove ? "move_" : "copy_"; 
+                    result += "assignment_operator"; 
+                } else {
+                    SkASSERT('=' == fName[opPos + 1]);
+                    result += "equal_operator"; 
+                }
+            } else {
+                SkASSERT(0);  // todo: incomplete
+            }
+        } else if (string::npos != fName.find("()", doubleColons)) {
+            if (isupper(fName[doubleColons])) {
+                fMethodType = Definition::MethodType::kConstructor;
+                result += "empty_constructor"; 
+            } else {
+                result += fName.substr(doubleColons, fName.length() - doubleColons - 2);
+            }
+        } else {
+            size_t comma = fName.find(',', doubleColons);
+            size_t openParen = fName.find('(', doubleColons);
+            if (string::npos == comma && string::npos != openParen) {
+                fMethodType = Definition::MethodType::kConstructor;
+                result += isMove ? "move_" : "copy_"; 
+                result += "constructor"; 
+            } else if (string::npos == openParen) {
+                result += fName.substr(doubleColons);
+            } else {
+                fMethodType = Definition::MethodType::kConstructor;
+                // name them by their param types, e.g. SkCanvas__int_int_const_SkSurfaceProps_star
+                SkASSERT(string::npos != openParen);
+                // TODO: move forward until parens are balanced and terminator =,)
+                TextParser params("", &fName[openParen] + 1, &*fName.end(), 0);
+                bool underline = false;
+                while (!params.eof()) {
+//                    SkDEBUGCODE(const char* end = params.anyOf("(),="));  // unused for now
+//                    SkASSERT(end[0] != '(');  // fixme: put off handling nested parentheseses
+                    if (params.startsWith("const") || params.startsWith("int")
+                            || params.startsWith("Sk")) {
+                        const char* wordStart = params.fChar;
+                        params.skipToNonAlphaNum();
+                        if (underline) {
+                            result += '_';
+                        } else {
+                            underline = true;
+                        }
+                        result += string(wordStart, params.fChar - wordStart);
+                    } else {
+                        params.skipToNonAlphaNum();
+                    }
+                    if (!params.eof() && '*' == params.peek()) {
+                        if (underline) {
+                            result += '_';
+                        } else {
+                            underline = true;
+                        }
+                        result += "star";
+                        params.next();
+                        params.skipSpace();
+                    }
+                    params.skipToAlpha();
+                }
+            }
+        }
+    }
+    fFiddle = normalized_name(result);
+}
+
+bool Definition::exampleToScript(string* result) const {
+    bool hasFiddle = true;
+    const Definition* platform = this->hasChild(MarkType::kPlatform);
+    if (platform) {
+        TextParser platParse(platform);
+        hasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
+    }
+    if (!hasFiddle) {
+        *result = "";
+        return true;
+    }
+    string text = this->extractText(Definition::TrimExtract::kNo);
+    const char drawWrapper[] = "void draw(SkCanvas* canvas) {";
+    const char drawNoCanvas[] = "void draw(SkCanvas* ) {";
+    size_t nonSpace = 0;
+    while (nonSpace < text.length() && ' ' >= text[nonSpace]) {
+        ++nonSpace;
+    }
+    bool hasFunc = !text.compare(nonSpace, sizeof(drawWrapper) - 1, drawWrapper);
+    bool noCanvas = !text.compare(nonSpace, sizeof(drawNoCanvas) - 1, drawNoCanvas);
+    bool hasCanvas = string::npos != text.find("SkCanvas canvas");
+    SkASSERT(!hasFunc || !noCanvas);
+    bool textOut = string::npos != text.find("SkDebugf(")
+            || string::npos != text.find("dump(")
+            || string::npos != text.find("dumpHex(");
+    string heightStr = "256";
+    string widthStr = "256";
+    bool preprocessor = text[0] == '#';
+    string normalizedName(fFiddle);
+    string code;
+    string imageStr = "0";
+    for (auto const& iter : fChildren) {
+        switch (iter->fMarkType) {
+            case MarkType::kError:
+                result->clear();
+                return true;
+            case MarkType::kHeight:
+                heightStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
+                break;
+            case MarkType::kWidth:
+                widthStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
+                break;
+            case MarkType::kDescription:
+                // ignore for now
+                break;
+            case MarkType::kFunction: {
+                // emit this, but don't wrap this in draw()
+                string funcText(iter->fContentStart, iter->fContentEnd - iter->fContentStart - 1);
+                size_t pos = 0;
+                while (pos < funcText.length() && ' ' > funcText[pos]) {
+                    ++pos;
+                }
+                size_t indent = count_indent(funcText, pos, funcText.length());
+                add_code(funcText, pos, funcText.length(), 0, indent, code);
+                code += "\\n";
+                } break;
+            case MarkType::kComment:
+                break;
+            case MarkType::kImage:
+                imageStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
+                break;
+            case MarkType::kToDo:
+                break;
+            case MarkType::kMarkChar:
+            case MarkType::kPlatform:
+                // ignore for now
+                break;
+            case MarkType::kStdOut:
+                textOut = true;
+                break;
+            default:
+                SkASSERT(0);  // more coding to do
+        }
+    }
+    string textOutStr = textOut ? "true" : "false";
+    size_t pos = 0;
+    while (pos < text.length() && ' ' > text[pos]) {
+        ++pos;
+    }
+    size_t end = text.length();
+    size_t outIndent = 0;
+    size_t textIndent = count_indent(text, pos, end);
+    bool wrapCode = !hasFunc && !noCanvas && !preprocessor;
+    if (wrapCode) {
+        code += hasCanvas ? drawNoCanvas : drawWrapper;
+        code += "\\n";
+        outIndent = 4;
+    }
+    add_code(text, pos, end, outIndent, textIndent, code);
+    if (wrapCode) {
+        code += "}";
+    }
+    string example = "\"" + normalizedName + "\": {\n";
+    example += "    \"code\": \"" + code + "\",\n";
+    example += "    \"options\": {\n";
+    example += "        \"width\": " + widthStr + ",\n";
+    example += "        \"height\": " + heightStr + ",\n";
+    example += "        \"source\": " + imageStr + ",\n";
+    example += "        \"srgb\": false,\n";
+    example += "        \"f16\": false,\n";
+    example += "        \"textOnly\": " + textOutStr + ",\n";
+    example += "        \"animated\": false,\n";
+    example += "        \"duration\": 0\n";
+    example += "    },\n";
+    example += "    \"fast\": true\n";
+    example += "}";
+    *result = example;
+    return true;
+}
+
+static void space_pad(string* str) {
+    size_t len = str->length();
+    if (len == 0) {
+        return;
+    }
+    char last = (*str)[len - 1];
+    if ('~' == last || ' ' >= last) {
+        return;
+    }
+    *str += ' ';
+}
+
+//start here;
+// see if it possible to abstract this a little bit so it can
+// additionally be used to find params and return in method prototype that
+// does not have corresponding doxygen comments
+bool Definition::checkMethod() const {
+    SkASSERT(MarkType::kMethod == fMarkType);
+    // if method returns a value, look for a return child
+    // for each parameter, look for a corresponding child
+    const char* end = fContentStart;
+    while (end > fStart && ' ' >= end[-1]) {
+        --end;
+    }
+    TextParser methodParser(fFileName, fStart, end, fLineCount);
+    methodParser.skipWhiteSpace();
+    SkASSERT(methodParser.startsWith("#Method"));
+    methodParser.skipName("#Method");
+    methodParser.skipSpace();
+    string name = this->methodName();
+    if (MethodType::kNone == fMethodType && "()" == name.substr(name.length() - 2)) {
+        name = name.substr(0, name.length() - 2);
+    }
+    bool expectReturn = this->methodHasReturn(name, &methodParser);
+    bool foundReturn = false;
+    bool foundException = false;
+    for (auto& child : fChildren) {
+        foundException |= MarkType::kDeprecated == child->fMarkType
+                || MarkType::kExperimental == child->fMarkType;
+        if (MarkType::kReturn != child->fMarkType) {
+            if (MarkType::kParam == child->fMarkType) {
+                child->fVisited = false;
+            }
+            continue;
+        }
+        if (!expectReturn) {
+            return methodParser.reportError<bool>("no #Return expected");
+        }
+        if (foundReturn) {
+            return methodParser.reportError<bool>("multiple #Return markers");
+        }
+        foundReturn = true;
+    }
+    if (expectReturn && !foundReturn && !foundException) {
+        return methodParser.reportError<bool>("missing #Return marker");
+    }
+    const char* paren = methodParser.strnchr('(', methodParser.fEnd);
+    if (!paren) {
+        return methodParser.reportError<bool>("missing #Method function definition");
+    }
+    const char* nextEnd = paren;
+    do {
+        string paramName;
+        methodParser.fChar = nextEnd + 1;
+        methodParser.skipSpace();
+        if (!this->nextMethodParam(&methodParser, &nextEnd, &paramName)) {
+            continue;
+        }
+        bool foundParam = false;
+        for (auto& child : fChildren) {
+            if (MarkType::kParam != child->fMarkType) {
+                continue;
+            }
+            if (paramName != child->fName) {
+                continue;
+            }
+            if (child->fVisited) {
+                return methodParser.reportError<bool>("multiple #Method param with same name");
+            }
+            child->fVisited = true;
+            if (foundParam) {
+                TextParser paramError(child);
+                return methodParser.reportError<bool>("multiple #Param with same name");
+            }
+            foundParam = true;
+            
+        }
+        if (!foundParam && !foundException) {
+            return methodParser.reportError<bool>("no #Param found");
+        }
+        if (')' == nextEnd[0]) {
+            break;
+        }
+    } while (')' != nextEnd[0]);
+    for (auto& child : fChildren) {
+        if (MarkType::kParam != child->fMarkType) {
+            continue;
+        }
+        if (!child->fVisited) {
+            TextParser paramError(child);
+            return paramError.reportError<bool>("#Param without param in #Method");
+        }
+    }
+    return true;
+}
+
+bool Definition::crossCheck(const char* tokenID, const Definition& includeToken) const {
+    const char* defStart = fStart;
+    SkASSERT('#' == defStart[0]);  // FIXME: needs to be per definition
+    ++defStart;
+    SkASSERT(!strncmp(defStart, tokenID, strlen(tokenID)));
+    defStart += strlen(tokenID);
+    return crossCheckInside(defStart, fContentStart, includeToken);
+}
+
+bool Definition::crossCheck(const Definition& includeToken) const {
+    return crossCheckInside(fContentStart, fContentEnd, includeToken);
+}
+
+bool Definition::crossCheckInside(const char* start, const char* end,
+        const Definition& includeToken) const {
+    TextParser def(fFileName, start, end, fLineCount);
+    TextParser inc("", includeToken.fContentStart, includeToken.fContentEnd, 0);
+    if (inc.startsWith("SK_API")) {
+        inc.skipWord("SK_API");
+    }
+    if (inc.startsWith("friend")) {
+        inc.skipWord("friend");
+    }
+    do {
+        bool defEof;
+        bool incEof;
+        do {
+            defEof = def.eof() || !def.skipWhiteSpace();
+            incEof = inc.eof() || !inc.skipWhiteSpace();
+            if (!incEof && '/' == inc.peek() && (defEof || '/' != def.peek())) {
+                inc.next();
+                if ('*' == inc.peek()) {
+                    inc.skipToEndBracket("*/");
+                    inc.next();
+                } else if ('/' == inc.peek()) {
+                    inc.skipToEndBracket('\n');
+                }
+            } else if (!incEof && '#' == inc.peek() && (defEof || '#' != def.peek())) {
+                inc.next();
+                SkASSERT(inc.startsWith("if"));
+                inc.skipToEndBracket("#");
+                SkASSERT(inc.startsWith("#endif"));
+                inc.skipToEndBracket("\n");
+            } else {
+                break;
+            }
+            inc.next();
+        } while (true);
+        if (defEof || incEof) {
+            return defEof == incEof || (!defEof && ';' == def.peek());
+        }
+        char defCh;
+        do {
+            defCh = def.next();
+            char incCh = inc.next();
+            if (' ' >= defCh && ' ' >= incCh) {
+                break;
+            }
+            if (defCh != incCh) {
+                return false;
+            }
+            if (';' == defCh) {
+                return true;
+            }
+        } while (!def.eof() && !inc.eof());
+    } while (true);
+    return false;
+}
+
+string Definition::formatFunction() const {
+    const char* end = fContentStart;
+    while (end > fStart && ' ' >= end[-1]) {
+        --end;
+    }
+    TextParser methodParser(fFileName, fStart, end, fLineCount);
+    methodParser.skipWhiteSpace();
+    SkASSERT(methodParser.startsWith("#Method"));
+    methodParser.skipName("#Method");
+    methodParser.skipSpace();
+    const char* lastStart = methodParser.fChar;
+    const int limit = 80;  // todo: allow this to be set by caller or in global or something
+    string methodStr;
+    string name = this->methodName();
+    const char* nameInParser = methodParser.strnstr(name.c_str(), methodParser.fEnd);
+    methodParser.skipTo(nameInParser);
+    const char* lastEnd = methodParser.fChar;
+    const char* paren = methodParser.strnchr('(', methodParser.fEnd);
+    size_t indent;
+    if (paren) {
+        indent = (size_t) (paren - lastStart) + 1;
+    } else {
+        indent = (size_t) (lastEnd - lastStart);
+    }
+    int written = 0;
+    do {
+        const char* nextStart = lastEnd;
+        SkASSERT(written < limit);
+        const char* delimiter = methodParser.anyOf(",)");
+        const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
+        if (delimiter) {
+            while (nextStart < nextEnd && ' ' >= nextStart[0]) {
+                ++nextStart;
+            }
+        }
+        while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
+            --nextEnd;
+        }
+        if (delimiter) {
+            nextEnd += 1;
+            delimiter += 1;
+        }
+        if (lastEnd > lastStart) {
+            if (lastStart[0] != ' ') {
+                space_pad(&methodStr);
+            }
+            methodStr += string(lastStart, (size_t) (lastEnd - lastStart));
+            written += (size_t) (lastEnd - lastStart);
+        }
+        if (delimiter) {
+            if (nextEnd - nextStart >= (ptrdiff_t) (limit - written)) {
+                written = indent;
+                methodStr += '\n';
+                methodStr += string(indent, ' ');
+            }
+            methodParser.skipTo(delimiter);
+        }
+        lastStart = nextStart;
+        lastEnd = nextEnd;
+    } while (lastStart < lastEnd);
+    return methodStr;
+}
+
+string Definition::fiddleName() const {
+    string result;
+    size_t start = 0;
+    string parent;
+    const Definition* parentDef = this;
+    while ((parentDef = parentDef->fParent)) {
+        if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
+            parent = parentDef->fFiddle;
+            break;
+        }
+    }
+    if (parent.length() && 0 == fFiddle.compare(0, parent.length(), parent)) {
+        start = parent.length();
+        while (start < fFiddle.length() && '_' == fFiddle[start]) {
+            ++start;
+        }
+    }
+    size_t end = fFiddle.find_first_of('(', start);
+    return fFiddle.substr(start, end - start);
+}
+
+const Definition* Definition::hasChild(MarkType markType) const {
+    for (auto iter : fChildren) {
+        if (markType == iter->fMarkType) {
+            return iter;
+        }
+    }
+    return nullptr;
+}
+
+const Definition* Definition::hasParam(const string& ref) const {
+    SkASSERT(MarkType::kMethod == fMarkType);
+    for (auto iter : fChildren) {
+        if (MarkType::kParam != iter->fMarkType) {
+            continue;
+        }
+        if (iter->fName == ref) {
+            return &*iter;
+        }
+
+    }
+    return nullptr;
+}
+
+bool Definition::methodHasReturn(const string& name, TextParser* methodParser) const {
+    const char* lastStart = methodParser->fChar;
+    const char* nameInParser = methodParser->strnstr(name.c_str(), methodParser->fEnd);
+    methodParser->skipTo(nameInParser);
+    const char* lastEnd = methodParser->fChar;
+    const char* returnEnd = lastEnd;
+    while (returnEnd > lastStart && ' ' == returnEnd[-1]) {
+        --returnEnd;
+    }
+    bool expectReturn = 4 != returnEnd - lastStart || strncmp("void", lastStart, 4);
+    if (MethodType::kNone != fMethodType && !expectReturn) {
+        return methodParser->reportError<bool>("unexpected void");
+    }
+    switch (fMethodType) {
+        case MethodType::kNone:
+        case MethodType::kOperator:
+            // either is fine
+            break;
+        case MethodType::kConstructor:
+            expectReturn = true;
+            break;
+        case MethodType::kDestructor:
+            expectReturn = false;
+            break;
+    }
+    return expectReturn;
+}
+
+string Definition::methodName() const {
+    string result;
+    size_t start = 0;
+    string parent;
+    const Definition* parentDef = this;
+    while ((parentDef = parentDef->fParent)) {
+        if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
+            parent = parentDef->fName;
+            break;
+        }
+    }
+    if (parent.length() && 0 == fName.compare(0, parent.length(), parent)) {
+        start = parent.length();
+        while (start < fName.length() && ':' == fName[start]) {
+            ++start;
+        }
+    }
+    if (fClone) {
+        int lastUnder = fName.rfind('_');
+        return fName.substr(start, (size_t) (lastUnder - start));
+    }
+    size_t end = fName.find_first_of('(', start);
+    if (string::npos == end) {
+        return fName.substr(start);
+    }
+    return fName.substr(start, end - start);
+}
+
+bool Definition::nextMethodParam(TextParser* methodParser, const char** nextEndPtr, 
+        string* paramName) const {
+    *nextEndPtr = methodParser->anyOf(",)");
+    const char* nextEnd = *nextEndPtr;
+    if (!nextEnd) {
+        return methodParser->reportError<bool>("#Method function missing close paren");
+    }
+    const char* paramEnd = nextEnd;
+    const char* assign = methodParser->strnstr(" = ", paramEnd);
+    if (assign) {
+        paramEnd = assign;
+    }
+    const char* closeBracket = methodParser->strnstr("]", paramEnd);
+    if (closeBracket) {
+        const char* openBracket = methodParser->strnstr("[", paramEnd);
+        if (openBracket && openBracket < closeBracket) {
+            while (openBracket < --closeBracket && isdigit(closeBracket[0]))
+                ;
+            if (openBracket == closeBracket) {
+                paramEnd = openBracket;
+            }
+        }
+    }
+    while (paramEnd > methodParser->fChar && ' ' == paramEnd[-1]) {
+        --paramEnd;
+    }
+    const char* paramStart = paramEnd;
+    while (paramStart > methodParser->fChar && isalnum(paramStart[-1])) {
+        --paramStart;
+    }
+    if (paramStart > methodParser->fChar && paramStart >= paramEnd) {
+        return methodParser->reportError<bool>("#Method missing param name");
+    }
+    *paramName = string(paramStart, paramEnd - paramStart);
+    if (!paramName->length()) {
+        if (')' != nextEnd[0]) {
+            return methodParser->reportError<bool>("#Method malformed param");
+        }
+        return false;
+    }
+    return true;
+}
+
+    bool ParserCommon::parseFile(const char* fileOrPath, const char* suffix) {
+    if (!sk_isdir(fileOrPath)) {
+        if (!this->parseFromFile(fileOrPath)) {
+            SkDebugf("failed to parse %s\n", fileOrPath);
+            return false;
+        }
+    } else {
+        SkOSFile::Iter it(fileOrPath, suffix);
+        for (SkString file; it.next(&file); ) {
+            SkString p = SkOSPath::Join(fileOrPath, file.c_str());
+            const char* hunk = p.c_str();
+            if (!SkStrEndsWith(hunk, suffix)) {
+                continue;
+            }
+            if (!this->parseFromFile(hunk)) {
+                SkDebugf("failed to parse %s\n", hunk);
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+bool Definition::paramsMatch(const string& match, const string& name) const {
+    TextParser def(fFileName, fStart, fContentStart, fLineCount);
+    const char* dName = def.strnstr(name.c_str(), fContentStart);
+    if (!dName) {
+        return false;
+    }
+    def.skipTo(dName);
+    TextParser m(fFileName, &match.front(), &match.back() + 1, fLineCount);
+    const char* mName = m.strnstr(name.c_str(), m.fEnd);
+    if (!mName) {
+        return false;
+    }
+    m.skipTo(mName);
+    while (!def.eof() && ')' != def.peek() && !m.eof() && ')' != m.peek()) {
+        const char* ds = def.fChar;
+        const char* ms = m.fChar;
+        const char* de = def.anyOf(") \n");
+        const char* me = m.anyOf(") \n");
+        def.skipTo(de);
+        m.skipTo(me);
+        if (def.fChar - ds != m.fChar - ms) {
+            return false;
+        }
+        if (strncmp(ds, ms, (int) (def.fChar - ds))) {
+            return false;
+        }
+        def.skipWhiteSpace();
+        m.skipWhiteSpace();
+    } 
+    return !def.eof() && ')' == def.peek() && !m.eof() && ')' == m.peek();
+}
+
+void RootDefinition::clearVisited() {
+    fVisited = false;
+    for (auto& leaf : fLeaves) {
+        leaf.second.fVisited = false;
+    }
+    for (auto& branch : fBranches) {
+        branch.second->clearVisited();
+    }
+}
+
+bool RootDefinition::dumpUnVisited() {
+    bool allStructElementsFound = true;
+    for (auto& leaf : fLeaves) {
+        if (!leaf.second.fVisited) {
+            // TODO: parse embedded struct in includeParser phase, then remove this condition
+            size_t firstColon = leaf.first.find("::");
+            size_t lastColon = leaf.first.rfind("::");
+            if (firstColon != lastColon) {  // struct, two sets
+                allStructElementsFound = false;
+                continue;
+            }
+            SkDebugf("defined in bmh but missing in include: %s\n", leaf.first.c_str());
+        }
+    }
+    for (auto& branch : fBranches) {
+        allStructElementsFound &= branch.second->dumpUnVisited();
+    }
+    return allStructElementsFound;
+}
+
+const Definition* RootDefinition::find(const string& ref) const {
+    const auto leafIter = fLeaves.find(ref);
+    if (leafIter != fLeaves.end()) {
+        return &leafIter->second;
+    }
+    const auto branchIter = fBranches.find(ref);
+    if (branchIter != fBranches.end()) {
+        const RootDefinition* rootDef = branchIter->second;
+        return rootDef;
+    }
+    const Definition* result = nullptr;
+    for (const auto& branch : fBranches) {
+        const RootDefinition* rootDef = branch.second;
+        result = rootDef->find(ref);
+        if (result) {
+            break;
+        }
+    }
+    return result;
+}
+
+/* 
+  class contains named struct, enum, enum-member, method, topic, subtopic
+     everything contained by class is uniquely named
+     contained names may be reused by other classes
+  method contains named parameters
+     parameters may be reused in other methods
+ */
+
+bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markType,
+        const vector<string>& typeNameBuilder) {
+    Definition* definition = nullptr;
+    switch (markType) {
+        case MarkType::kComment:
+            if (!this->skipToDefinitionEnd(markType)) {
+                return false;
+            }
+            return true;
+        // these types may be referred to by name
+        case MarkType::kClass:
+        case MarkType::kStruct:
+        case MarkType::kConst:
+        case MarkType::kEnum:
+        case MarkType::kEnumClass:
+        case MarkType::kMember:
+        case MarkType::kMethod:
+        case MarkType::kTypedef: {
+            if (!typeNameBuilder.size()) {
+                return this->reportError<bool>("unnamed markup");
+            }
+            if (typeNameBuilder.size() > 1) {
+                return this->reportError<bool>("expected one name only");
+            }
+            const string& name = typeNameBuilder[0];
+            if (nullptr == fRoot) {
+                fRoot = this->findBmhObject(markType, name);
+                fRoot->fFileName = fFileName;
+                definition = fRoot;
+            } else {
+                if (nullptr == fParent) {
+                    return this->reportError<bool>("expected parent");
+                }
+                if (fParent == fRoot && hasEnd) {
+                    RootDefinition* rootParent = fRoot->rootParent();
+                    if (rootParent) {
+                        fRoot = rootParent;
+                    }
+                    definition = fParent;
+                } else {
+                    if (!hasEnd && fRoot->find(name)) {
+                        return this->reportError<bool>("duplicate symbol");
+                    }
+                    if (MarkType::kStruct == markType || MarkType::kClass == markType) {
+                        // if class or struct, build fRoot hierarchy
+                        // and change isDefined to search all parents of fRoot
+                        SkASSERT(!hasEnd);
+                        RootDefinition* childRoot = new RootDefinition;
+                        (fRoot->fBranches)[name] = childRoot;
+                        childRoot->setRootParent(fRoot);
+                        childRoot->fFileName = fFileName;
+                        fRoot = childRoot;
+                        definition = fRoot;
+                    } else {
+                        definition = &fRoot->fLeaves[name];
+                    }
+                }
+            }
+            if (hasEnd) {
+                Exemplary hasExample = Exemplary::kNo;
+                bool hasExcluder = false;
+                for (auto child : definition->fChildren) {
+                     if (MarkType::kExample == child->fMarkType) {
+                        hasExample = Exemplary::kYes;
+                     }
+                     hasExcluder |= MarkType::kPrivate == child->fMarkType
+                            || MarkType::kDeprecated == child->fMarkType
+                            || MarkType::kExperimental == child->fMarkType
+                            || MarkType::kNoExample == child->fMarkType;
+                }
+                if (fMaps[(int) markType].fExemplary != hasExample
+                        && fMaps[(int) markType].fExemplary != Exemplary::kOptional) {
+                    if (string::npos == fFileName.find("undocumented")
+                            && !hasExcluder) {
+                        hasExample == Exemplary::kNo ? 
+                                this->reportWarning("missing example") : 
+                                this->reportWarning("unexpected example");
+                    }
+
+                }
+                if (MarkType::kMethod == markType) {
+                    if (fCheckMethods && !definition->checkMethod()) {
+                        return false;
+                    }
+                }
+                if (!this->popParentStack(definition)) {
+                    return false;
+                }
+            } else {
+                definition->fStart = defStart;
+                this->skipSpace();
+                definition->fFileName = fFileName;
+                definition->fContentStart = fChar;
+                definition->fLineCount = fLineCount;
+                definition->fClone = fCloned;
+                if (MarkType::kConst == markType) {
+                    // todo: require that fChar points to def on same line as markup
+                    // additionally add definition to class children if it is not already there
+                    if (definition->fParent != fRoot) {
+//                        fRoot->fChildren.push_back(definition);
+                    }
+                }
+                definition->fName = name;
+                if (MarkType::kMethod == markType) {
+                    if (string::npos != name.find(':', 0)) {
+                        definition->setCanonicalFiddle();
+                    } else {
+                        definition->fFiddle = name;
+                    }
+                } else {
+                    definition->fFiddle = normalized_name(name);
+                }
+                definition->fMarkType = markType;
+                this->setAsParent(definition);
+            }
+            } break;
+        case MarkType::kTopic:
+        case MarkType::kSubtopic:
+            SkASSERT(1 == typeNameBuilder.size());
+            if (!hasEnd) {
+                if (!typeNameBuilder.size()) {
+                    return this->reportError<bool>("unnamed topic");
+                }
+                fTopics.emplace_front(markType, defStart, fLineCount, fParent);
+                RootDefinition* rootDefinition = &fTopics.front();
+                definition = rootDefinition;
+                definition->fFileName = fFileName;
+                definition->fContentStart = fChar;
+                definition->fName = typeNameBuilder[0];
+                Definition* parent = fParent;
+                while (parent && MarkType::kTopic != parent->fMarkType 
+                        && MarkType::kSubtopic != parent->fMarkType) {
+                    parent = parent->fParent;
+                }
+                definition->fFiddle = parent ? parent->fFiddle + '_' : "";
+                definition->fFiddle += normalized_name(typeNameBuilder[0]);
+                this->setAsParent(definition);
+            }
+            {
+                const string& fullTopic = hasEnd ? fParent->fFiddle : definition->fFiddle;
+                Definition* defPtr = fTopicMap[fullTopic];
+                if (hasEnd) {
+                    if (!definition) {
+                        definition = defPtr;
+                    } else if (definition != defPtr) {
+                        return this->reportError<bool>("mismatched topic");
+                    }
+                } else {
+                    if (nullptr != defPtr) {
+                        return this->reportError<bool>("already declared topic");
+                    }
+                    fTopicMap[fullTopic] = definition;
+                }
+            }
+            if (hasEnd) {
+                if (!this->popParentStack(definition)) {
+                    return false;
+                }
+            }
+            break;
+        // these types are children of parents, but are not in named maps
+        case MarkType::kDefinedBy: {
+            string prefixed(fRoot->fName);
+            const char* start = fChar;
+            string name(start, this->trimmedBracketEnd(fMC, OneLine::kYes) - start);
+            prefixed += "::" + name;
+            this->skipToEndBracket(fMC);
+            const auto leafIter = fRoot->fLeaves.find(prefixed);
+            if (fRoot->fLeaves.end() != leafIter) {
+                this->reportError<bool>("DefinedBy already defined");
+            }
+            definition = &fRoot->fLeaves[prefixed];
+            definition->fParent = fParent;
+            definition->fStart = defStart;
+            definition->fContentStart = start;
+            definition->fName = name;
+            definition->fFiddle = normalized_name(name);
+            definition->fContentEnd = fChar;
+            this->skipToEndBracket('\n');
+            definition->fTerminator = fChar;
+            definition->fMarkType = markType;
+            definition->fLineCount = fLineCount;
+            fParent->fChildren.push_back(definition);
+            } break;
+        case MarkType::kDescription:
+        case MarkType::kStdOut:
+        // may be one-liner
+        case MarkType::kBug:
+        case MarkType::kNoExample:
+        case MarkType::kParam:
+        case MarkType::kReturn:
+        case MarkType::kToDo:
+            if (hasEnd) {
+                if (markType == fParent->fMarkType) {
+                    definition = fParent;
+                    if (MarkType::kBug == markType || MarkType::kReturn == markType
+                            || MarkType::kToDo == markType) {
+                        this->skipNoName();
+                    }
+                    if (!this->popParentStack(fParent)) { // if not one liner, pop
+                        return false;
+                    }
+                } else {
+                    fMarkup.emplace_front(markType, defStart, fLineCount, fParent);
+                    definition = &fMarkup.front();
+                    definition->fName = typeNameBuilder[0];
+                    definition->fFiddle = normalized_name(typeNameBuilder[0]);
+                    definition->fContentStart = fChar;
+                    definition->fContentEnd = this->trimmedBracketEnd(fMC, OneLine::kYes);
+                    this->skipToEndBracket(fMC);
+                    SkAssertResult(fMC == this->next());
+                    SkAssertResult(fMC == this->next());
+                    definition->fTerminator = fChar;
+                    fParent->fChildren.push_back(definition);
+                }
+                break;
+            }
+        // not one-liners
+        case MarkType::kCode:
+        case MarkType::kDeprecated:
+        case MarkType::kExample:
+        case MarkType::kExperimental:
+        case MarkType::kFormula:
+        case MarkType::kFunction:
+        case MarkType::kLegend:
+        case MarkType::kList:
+        case MarkType::kPrivate:
+        case MarkType::kTable:
+        case MarkType::kTrack:
+            if (hasEnd) {
+                definition = fParent;
+                if (markType != fParent->fMarkType) {
+                    return this->reportError<bool>("end element mismatch");
+                } else if (!this->popParentStack(fParent)) {
+                    return false;
+                }
+                if (MarkType::kExample == markType) {
+                    if (definition->fChildren.size() == 0) {
+                        TextParser emptyCheck(definition);
+                        if (emptyCheck.eof() || !emptyCheck.skipWhiteSpace()) {
+                            return this->reportError<bool>("missing example body");
+                        }
+                    }
+                }
+            } else {
+                fMarkup.emplace_front(markType, defStart, fLineCount, fParent);
+                definition = &fMarkup.front();
+                definition->fContentStart = fChar;
+                definition->fName = typeNameBuilder[0];
+                definition->fFiddle = fParent->fFiddle;
+                char suffix = '\0';
+                bool tryAgain;
+                do {
+                    tryAgain = false;
+                    for (const auto& child : fParent->fChildren) {
+                        if (child->fFiddle == definition->fFiddle) {
+                            if (MarkType::kExample != child->fMarkType) {
+                                continue;
+                            }
+                            if ('\0' == suffix) {
+                                suffix = 'a';
+                            } else if (++suffix > 'z') {
+                                return reportError<bool>("too many examples");
+                            }
+                            definition->fFiddle = fParent->fFiddle + '_';
+                            definition->fFiddle += suffix;
+                            tryAgain = true;
+                            break;
+                        }
+                    }
+                } while (tryAgain);
+                this->setAsParent(definition);
+            }
+            break;
+            // always treated as one-liners (can't detect misuse easily)
+        case MarkType::kAlias:
+        case MarkType::kAnchor: 
+        case MarkType::kDefine:
+        case MarkType::kError:
+        case MarkType::kFile:
+        case MarkType::kHeight:
+        case MarkType::kImage:
+        case MarkType::kPlatform:
+        case MarkType::kSeeAlso:
+        case MarkType::kSubstitute:
+        case MarkType::kTime:
+        case MarkType::kVolatile:
+        case MarkType::kWidth:
+            if (hasEnd) {
+                return this->reportError<bool>("one liners omit end element");
+            }
+            fMarkup.emplace_front(markType, defStart, fLineCount, fParent);
+            definition = &fMarkup.front();
+            definition->fName = typeNameBuilder[0];
+            definition->fFiddle = normalized_name(typeNameBuilder[0]);
+            definition->fContentStart = fChar;
+            definition->fContentEnd = this->trimmedBracketEnd('\n', OneLine::kYes);
+            definition->fTerminator = this->lineEnd() - 1;
+            fParent->fChildren.push_back(definition);
+            if (MarkType::kAnchor == markType) {
+                this->skipToEndBracket(fMC);
+                fMarkup.emplace_front(MarkType::kLink, fChar, fLineCount, definition);
+                SkAssertResult(fMC == this->next());
+                this->skipWhiteSpace();
+                Definition* link = &fMarkup.front();
+                link->fContentStart = fChar;
+                link->fContentEnd = this->trimmedBracketEnd(fMC, OneLine::kYes);
+                this->skipToEndBracket(fMC);
+                SkAssertResult(fMC == this->next());
+                SkAssertResult(fMC == this->next());
+                link->fTerminator = fChar;
+                definition->fContentEnd = link->fContentEnd;
+                definition->fTerminator = fChar;
+                definition->fChildren.emplace_back(link);
+            } else if (MarkType::kAlias == markType) {
+                this->skipWhiteSpace();
+                const char* start = fChar;
+                this->skipToNonAlphaNum();
+                string alias(start, fChar - start);
+                if (fAliasMap.end() != fAliasMap.find(alias)) {
+                    return this->reportError<bool>("duplicate alias");
+                }
+                fAliasMap[alias] = definition;
+            } 
+            break;
+        case MarkType::kExternal:
+            (void) this->collectExternals();  // FIXME: detect errors in external defs?
+            break;
+        default:
+            SkASSERT(0);  // fixme : don't let any types be invisible
+            return true;
+    }
+    if (fParent) {
+        SkASSERT(definition);
+        SkASSERT(definition->fName.length() > 0);
+    }
+    return true;
+}
+
+bool BmhParser::childOf(MarkType markType) const {
+    auto childError = [this](MarkType markType) -> bool {
+        string errStr = "expected ";
+        errStr += fMaps[(int) markType].fName;
+        errStr += " parent";
+        return this->reportError<bool>(errStr.c_str());
+    };
+
+    if (markType == fParent->fMarkType) {
+        return true;
+    }
+    if (this->hasEndToken()) {
+        if (!fParent->fParent) {
+            return this->reportError<bool>("expected grandparent");
+        }
+        if (markType == fParent->fParent->fMarkType) {
+            return true;
+        }
+    }
+    return childError(markType);
+}
+
+string BmhParser::className(MarkType markType) {
+    string builder;
+    const Definition* parent = this->parentSpace();
+    if (parent && (parent != fParent || MarkType::kClass != markType)) {
+        builder += parent->fName;
+    }
+    const char* end = this->lineEnd();
+    const char* mc = this->strnchr(fMC, end);
+    if (mc) {
+        this->skipSpace();
+        const char* wordStart = fChar;
+        this->skipToNonAlphaNum();
+        const char* wordEnd = fChar;
+        if (mc + 1 < fEnd && fMC == mc[1]) {  // if ##
+            if (markType != fParent->fMarkType) {
+                return this->reportError<string>("unbalanced method");
+            }
+            if (builder.length() > 0 && wordEnd > wordStart) {
+                if (builder != fParent->fName) {
+                    builder += "::";
+                    builder += string(wordStart, wordEnd - wordStart);
+                    if (builder != fParent->fName) {
+                        return this->reportError<string>("name mismatch");
+                    }
+                }
+            }
+            this->skipLine();
+            return fParent->fName;
+        }
+        fChar = mc;
+        this->next();
+    }
+    this->skipWhiteSpace();
+    if (MarkType::kEnum == markType && fChar >= end) {
+        fAnonymous = true;
+        builder += "::_anonymous";
+        return uniqueRootName(builder, markType);
+    }
+    builder = this->word(builder, "::");
+    return builder;
+}
+
+bool BmhParser::collectExternals() {
+    do {
+        this->skipWhiteSpace();
+        if (this->eof()) {
+            break;
+        }
+        if (fMC == this->peek()) {
+            this->next();
+            if (this->eof()) {
+                break;
+            }
+            if (fMC == this->peek()) {
+                this->skipLine();
+                break;
+            }
+            if (' ' >= this->peek()) {
+                this->skipLine();
+                continue;
+            }
+            if (this->startsWith(fMaps[(int) MarkType::kExternal].fName)) {
+                this->skipToNonAlphaNum();
+                continue;
+            }
+        }
+        this->skipToAlpha();
+        const char* wordStart = fChar;
+        this->skipToNonAlphaNum();
+        if (fChar - wordStart > 0) {
+            fExternals.emplace_front(MarkType::kExternal, wordStart, fChar, fLineCount, fParent);
+            RootDefinition* definition = &fExternals.front();
+            definition->fFileName = fFileName;
+            definition->fName = string(wordStart ,fChar - wordStart);
+            definition->fFiddle = normalized_name(definition->fName);
+        }
+    } while (!this->eof());
+    return true;
+}
+
+int BmhParser::endHashCount() const {
+    const char* end = fLine + this->lineLength();
+    int count = 0;
+    while (fLine < end && fMC == *--end) {
+        count++;
+    }
+    return count;
+}
+
+// FIXME: some examples may produce different output on different platforms 
+// if the text output can be different, think of how to author that
+
+bool BmhParser::findDefinitions() {
+    bool lineStart = true;
+    fParent = nullptr;
+    while (!this->eof()) {
+        if (this->peek() == fMC) {
+            this->next();
+            if (this->peek() == fMC) {
+                this->next();
+                if (!lineStart && ' ' < this->peek()) {
+                    return this->reportError<bool>("expected definition");
+                }
+                if (this->peek() != fMC) {
+                    vector<string> parentName;
+                    parentName.push_back(fParent->fName);
+                    if (!this->addDefinition(fChar - 1, true, fParent->fMarkType, parentName)) {
+                        return false;
+                    }
+                } else {
+                    SkAssertResult(this->next() == fMC);
+                    fMC = this->next();  // change markup character
+                    if (' ' >= fMC) {
+                        return this->reportError<bool>("illegal markup character");
+                    }
+                    fMarkup.emplace_front(MarkType::kMarkChar, fChar - 1, fLineCount, fParent);
+                    Definition* markChar = &fMarkup.front();
+                    markChar->fContentStart = fChar - 1;
+                    this->skipToEndBracket('\n');
+                    markChar->fContentEnd = fChar;
+                    markChar->fTerminator = fChar;
+                    fParent->fChildren.push_back(markChar);
+                }
+            } else if (this->peek() >= 'A' && this->peek() <= 'Z') {
+                const char* defStart = fChar - 1;
+                MarkType markType = this->getMarkType(MarkLookup::kRequire);
+                bool hasEnd = this->hasEndToken();
+                if (!hasEnd) {
+                    MarkType parentType = fParent ? fParent->fMarkType : MarkType::kRoot;
+                    uint64_t parentMask = fMaps[(int) markType].fParentMask;
+                    if (parentMask && !(parentMask & (1LL << (int) parentType))) {
+                        return this->reportError<bool>("invalid parent");
+                    }
+                }
+                if (!this->skipName(fMaps[(int) markType].fName)) {
+                    return this->reportError<bool>("illegal markup character");
+                }
+                if (!this->skipSpace()) {
+                    return this->reportError<bool>("unexpected end");
+                }
+                bool expectEnd = true;
+                vector<string> typeNameBuilder = this->typeName(markType, &expectEnd);
+                if (fCloned && MarkType::kMethod != markType && MarkType::kExample != markType
+                        && !fAnonymous) {
+                    return this->reportError<bool>("duplicate name");
+                }
+                if (hasEnd && expectEnd) {
+                    SkASSERT(fMC != this->peek());
+                }
+                if (!this->addDefinition(defStart, hasEnd, markType, typeNameBuilder)) {
+                    return false;
+                }
+                continue;
+            } else if (this->peek() == ' ') {
+                if (!fParent || (MarkType::kTable != fParent->fMarkType
+                        && MarkType::kLegend != fParent->fMarkType
+                        && MarkType::kList != fParent->fMarkType)) {
+                    int endHashes = this->endHashCount();
+                    if (endHashes <= 1) {  // one line comment
+                        if (fParent) {
+                            fMarkup.emplace_front(MarkType::kComment, fChar - 1, fLineCount, fParent);
+                            Definition* comment = &fMarkup.front();
+                            comment->fContentStart = fChar - 1;
+                            this->skipToEndBracket('\n');
+                            comment->fContentEnd = fChar;
+                            comment->fTerminator = fChar;
+                            fParent->fChildren.push_back(comment);
+                        } else {
+                            fChar = fLine + this->lineLength() - 1;
+                        }
+                    } else {  // table row
+                        if (2 != endHashes) {
+                            string errorStr = "expect ";
+                            errorStr += fMC;
+                            errorStr += fMC;
+                            return this->reportError<bool>(errorStr.c_str());
+                        }
+                        if (!fParent || MarkType::kTable != fParent->fMarkType) {
+                            return this->reportError<bool>("missing table");
+                        }
+                    }
+                } else {
+                    bool parentIsList = MarkType::kList == fParent->fMarkType;
+                    // fixme? no nested tables for now
+                    const char* colStart = fChar - 1;
+                    fMarkup.emplace_front(MarkType::kRow, colStart, fLineCount, fParent);
+                    Definition* row = &fMarkup.front();
+                    this->skipWhiteSpace();
+                    row->fContentStart = fChar;
+                    this->setAsParent(row);
+                    const char* lineEnd = this->lineEnd();
+                    do {
+                        fMarkup.emplace_front(MarkType::kColumn, colStart, fLineCount, fParent);
+                        Definition* column = &fMarkup.front();
+                        column->fContentStart = fChar;
+                        column->fContentEnd = this->trimmedBracketEnd(fMC, 
+                                 parentIsList ? OneLine::kNo : OneLine::kYes);
+                        this->skipToEndBracket(fMC);
+                        colStart = fChar;
+                        SkAssertResult(fMC == this->next());
+                        if (fMC == this->peek()) {
+                            this->next();
+                        }
+                        column->fTerminator = fChar;
+                        fParent->fChildren.push_back(column);
+                        this->skipSpace();
+                    } while (fChar < lineEnd && '\n' != this->peek());
+                    if (!this->popParentStack(fParent)) {
+                        return false;
+                    }
+                    const Definition* lastCol = row->fChildren.back();
+                    row->fContentEnd = lastCol->fContentEnd;
+                }
+            }
+        }
+        lineStart = this->next() == '\n';
+    }
+    if (fParent) {
+        return this->reportError<bool>("mismatched end");
+    }
+    return true;
+}
+
+MarkType BmhParser::getMarkType(MarkLookup lookup) const {
+    for (int index = 0; index <= Last_MarkType; ++index) {
+        int typeLen = strlen(fMaps[index].fName);
+        if (typeLen == 0) {
+            continue;
+        }
+        if (fChar + typeLen >= fEnd || fChar[typeLen] > ' ') {
+            continue;
+        }
+        int chCompare = strncmp(fChar, fMaps[index].fName, typeLen);
+        if (chCompare < 0) {
+            goto fail;
+        }
+        if (chCompare == 0) {
+            return (MarkType) index;
+        }
+    }
+fail:
+    if (MarkLookup::kRequire == lookup) {
+        return this->reportError<MarkType>("unknown mark type");
+    }
+    return MarkType::kNone;
+}
+
+bool HackParser::hackFiles() {
+    string filename(fFileName);
+    size_t len = filename.length() - 1;
+    while (len > 0 && (isalnum(filename[len]) || '_' == filename[len] || '.' == filename[len])) {
+        --len;
+    }
+    filename = filename.substr(len + 1);
+    // remove trailing period from #Param and #Return
+    FILE* out = fopen(filename.c_str(), "wb");
+    if (!out) {
+        SkDebugf("could not open output file %s\n", filename.c_str());
+        return false;
+    }
+    const char* start = fStart;
+    do {
+        const char* match = this->strnchr('#', fEnd);
+        if (!match) {
+            break;
+        }
+        this->skipTo(match);
+        this->next();
+        if (!this->startsWith("Param") && !this->startsWith("Return")) {
+            continue;
+        }
+        const char* end = this->strnstr("##", fEnd);
+        while (true) {
+            TextParser::Save lastPeriod(this);
+            this->next();
+            if (!this->skipToEndBracket('.', end)) {
+                lastPeriod.restore();
+                break;
+            }
+        }
+        if ('.' == this->peek()) {
+            fprintf(out, "%.*s", (int) (fChar - start), start);
+            this->next();
+            start = fChar;
+        }
+    } while (!this->eof());
+    fprintf(out, "%.*s", (int) (fEnd - start), start);
+    fclose(out);
+    return true;
+}
+
+bool BmhParser::hasEndToken() const {
+    const char* last = fLine + this->lineLength();
+    while (last > fLine && ' ' >= *--last)
+        ;
+    if (--last < fLine) {
+        return false;
+    }
+    return last[0] == fMC && last[1] == fMC;
+}
+
+string BmhParser::memberName() {
+    const char* wordStart;
+    const char* prefixes[] = { "static", "const" };
+    do {
+        this->skipSpace();
+        wordStart = fChar;
+        this->skipToNonAlphaNum();
+    } while (this->anyOf(wordStart, prefixes, SK_ARRAY_COUNT(prefixes)));
+    if ('*' == this->peek()) {
+        this->next();
+    }
+    return this->className(MarkType::kMember);
+}
+
+string BmhParser::methodName() {
+    if (this->hasEndToken()) {
+        if (!fParent || !fParent->fName.length()) {
+            return this->reportError<string>("missing parent method name");
+        }
+        SkASSERT(fMC == this->peek());
+        this->next();
+        SkASSERT(fMC == this->peek());
+        this->next();
+        SkASSERT(fMC != this->peek());
+        return fParent->fName;
+    }
+    string builder;
+    const char* end = this->lineEnd();
+    const char* paren = this->strnchr('(', end);
+    if (!paren) {
+        return this->reportError<string>("missing method name and reference");
+    }
+    const char* nameStart = paren;
+    char ch;
+    bool expectOperator = false;
+    bool isConstructor = false;
+    const char* nameEnd = nullptr;
+    while (nameStart > fChar && ' ' != (ch = *--nameStart)) {
+        if (!isalnum(ch) && '_' != ch) {
+            if (nameEnd) {
+                break;
+            }
+            expectOperator = true;
+            continue;
+        }
+        if (!nameEnd) {
+            nameEnd = nameStart + 1;
+        }
+    }
+    if (!nameEnd) {
+         return this->reportError<string>("unexpected method name char");
+    }
+    if (' ' == nameStart[0]) {
+        ++nameStart;
+    }
+    if (nameEnd <= nameStart) {
+        return this->reportError<string>("missing method name");
+    }
+    if (nameStart >= paren) {
+        return this->reportError<string>("missing method name length");
+    }
+    string name(nameStart, nameEnd - nameStart);
+    bool allLower = true;
+    for (int index = 0; index < (int) (nameEnd - nameStart); ++index) {
+        if (!islower(nameStart[index])) {
+            allLower = false;
+            break;
+        }
+    }
+    if (expectOperator && "operator" != name) {
+         return this->reportError<string>("expected operator");
+    }
+    const Definition* parent = this->parentSpace();
+    if (parent && parent->fName.length() > 0) {
+        if (parent->fName == name) {
+            isConstructor = true;
+        } else if ('~' == name[0]) {
+            if (parent->fName != name.substr(1)) {
+                 return this->reportError<string>("expected destructor");
+            }
+            isConstructor = true;
+        }
+        builder = parent->fName + "::";
+    } 
+    if (isConstructor || expectOperator) {
+        paren = this->strnchr(')', end) + 1;
+    }
+    builder.append(nameStart, paren - nameStart);
+    if (!expectOperator && allLower) {
+        builder.append("()");
+    }
+    int parens = 0;
+    while (fChar < end || parens > 0) {
+        if ('(' == this->peek()) {
+            ++parens;
+        } else if (')' == this->peek()) {
+            --parens;
+        }
+        this->next();
+    }
+    TextParser::Save saveState(this);
+    this->skipWhiteSpace();
+    if (this->startsWith("const")) {
+        this->skipName("const");
+    } else {
+        saveState.restore();
+    }
+//    this->next();
+    return uniqueRootName(builder, MarkType::kMethod);
+}
+
+const Definition* BmhParser::parentSpace() const {
+    Definition* parent = nullptr;
+    Definition* test = fParent;
+    while (test) {
+        if (MarkType::kClass == test->fMarkType ||
+                MarkType::kEnumClass == test->fMarkType ||
+                MarkType::kStruct == test->fMarkType) {
+            parent = test;
+            break;
+        }
+        test = test->fParent;
+    }
+    return parent;
+}
+
+bool BmhParser::popParentStack(Definition* definition) {
+    if (!fParent) {
+        return this->reportError<bool>("missing parent");
+    }
+    if (definition != fParent) {
+        return this->reportError<bool>("definition end is not parent");
+    }
+    if (!definition->fStart) {
+        return this->reportError<bool>("definition missing start");
+    }
+    if (definition->fContentEnd) {
+        return this->reportError<bool>("definition already ended");
+    }
+    definition->fContentEnd = fLine - 1;
+    definition->fTerminator = fChar;
+    fParent = definition->fParent;
+    if (!fParent || (MarkType::kTopic == fParent->fMarkType && !fParent->fParent)) {
+        fRoot = nullptr;
+    }
+    return true;
+}
+
+TextParser::TextParser(const Definition* definition) :
+    TextParser(definition->fFileName, definition->fContentStart, definition->fContentEnd, 
+        definition->fLineCount) {
+}
+
+void TextParser::reportError(const char* errorStr) const {
+    this->reportWarning(errorStr);
+    SkDebugf("");  // convenient place to set a breakpoint
+}
+
+void TextParser::reportWarning(const char* errorStr) const {
+    TextParser err(fFileName, fLine, fEnd, fLineCount);
+    size_t lineLen = this->lineLength();
+    ptrdiff_t spaces = fChar - fLine;
+    while (spaces > 0 && (size_t) spaces > lineLen) {
+        ++err.fLineCount;
+        err.fLine += lineLen;
+        spaces -= lineLen;
+        lineLen = err.lineLength();
+    }
+    SkDebugf("%s(%zd): error: %s\n", fFileName.c_str(), err.fLineCount, errorStr);
+    if (0 == lineLen) {
+        SkDebugf("[blank line]\n");
+    } else {
+        while (lineLen > 0 && '\n' == err.fLine[lineLen - 1]) {
+            --lineLen;
+        }
+        SkDebugf("%.*s\n", (int) lineLen, err.fLine);
+        SkDebugf("%*s^\n", (int) spaces, "");
+    }
+}
+
+bool BmhParser::skipNoName() {
+    if ('\n' == this->peek()) {
+        this->next();
+        return true;
+    }
+    this->skipWhiteSpace();
+    if (fMC != this->peek()) {
+        return this->reportError<bool>("expected end mark");
+    }
+    this->next();
+    if (fMC != this->peek()) {
+        return this->reportError<bool>("expected end mark");
+    }
+    this->next();
+    return true;
+}
+
+bool BmhParser::skipToDefinitionEnd(MarkType markType) {
+    if (this->eof()) {
+        return this->reportError<bool>("missing end");
+    }
+    const char* start = fLine;
+    int startLineCount = fLineCount;
+    int stack = 1;
+    ptrdiff_t lineLen;
+    bool foundEnd = false;
+    do {
+        lineLen = this->lineLength();
+        if (fMC != *fChar++) {
+            continue;
+        }
+        if (fMC == *fChar) {
+            continue;
+        }
+        if (' ' == *fChar) {
+            continue;
+        }
+        MarkType nextType = this->getMarkType(MarkLookup::kAllowUnknown);
+        if (markType != nextType) {
+            continue;
+        }
+        bool hasEnd = this->hasEndToken();
+        if (hasEnd) {
+            if (!--stack) {
+                foundEnd = true;
+                continue;
+            }
+        } else {
+            ++stack;
+        }
+    } while ((void) ++fLineCount, (void) (fLine += lineLen), (void) (fChar = fLine),
+            !this->eof() && !foundEnd);
+    if (foundEnd) {
+        return true;
+    }
+    fLineCount = startLineCount;
+    fLine = start;
+    fChar = start;
+    return this->reportError<bool>("unbalanced stack");
+}
+
+vector<string> BmhParser::topicName() {
+    vector<string> result;
+    this->skipWhiteSpace();
+    const char* lineEnd = fLine + this->lineLength();
+    const char* nameStart = fChar;
+    while (fChar < lineEnd) {
+        char ch = this->next();
+        SkASSERT(',' != ch);
+        if ('\n' == ch) {
+            break;
+        }
+        if (fMC == ch) {
+            break;
+        }
+    }
+    if (fChar - 1 > nameStart) {
+        string builder(nameStart, fChar - nameStart - 1);
+        trim_start_end(builder);
+        result.push_back(builder);
+    }
+    if (fChar < lineEnd && fMC == this->peek()) {
+        this->next();
+    }
+    return result;
+}
+
+// typeName parsing rules depend on mark type
+vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) {
+    fAnonymous = false;
+    fCloned = false;
+    vector<string> result;
+    string builder;
+    if (fParent) {
+        builder = fParent->fName;
+    }
+    switch (markType) {
+        case MarkType::kEnum:
+            // enums may be nameless
+        case MarkType::kConst:
+        case MarkType::kEnumClass:
+        case MarkType::kClass:
+        case MarkType::kStruct:
+        case MarkType::kTypedef:
+            // expect name
+            builder = this->className(markType);
+            break;
+        case MarkType::kExample:
+            // check to see if one already exists -- if so, number this one
+            builder = this->uniqueName(string(), markType);
+            this->skipNoName();
+            break;
+        case MarkType::kCode:
+        case MarkType::kDeprecated:
+        case MarkType::kDescription:
+        case MarkType::kDoxygen:
+        case MarkType::kExperimental:
+        case MarkType::kExternal:
+        case MarkType::kFormula:
+        case MarkType::kFunction:
+        case MarkType::kLegend:
+        case MarkType::kList:
+        case MarkType::kNoExample:
+        case MarkType::kPrivate:
+        case MarkType::kTrack:
+            this->skipNoName();
+            break;
+        case MarkType::kAlias:
+        case MarkType::kAnchor: 
+        case MarkType::kBug:  // fixme: expect number
+        case MarkType::kDefine:
+        case MarkType::kDefinedBy:
+        case MarkType::kError:
+        case MarkType::kFile:
+        case MarkType::kHeight:
+        case MarkType::kImage:
+        case MarkType::kPlatform:
+        case MarkType::kReturn:
+        case MarkType::kSeeAlso:
+        case MarkType::kSubstitute:
+        case MarkType::kTime:
+        case MarkType::kToDo:
+        case MarkType::kVolatile:
+        case MarkType::kWidth:
+            *checkEnd = false;  // no name, may have text body
+            break;
+        case MarkType::kStdOut:
+            this->skipNoName();
+            break;  // unnamed
+        case MarkType::kMember:
+            builder = this->memberName();
+            break;
+        case MarkType::kMethod:
+            builder = this->methodName();
+            break;
+        case MarkType::kParam:
+           // fixme: expect camelCase
+            builder = this->word("", "");
+            this->skipSpace();
+            *checkEnd = false;
+            break;
+        case MarkType::kTable:
+            this->skipNoName();
+            break;  // unnamed
+        case MarkType::kSubtopic:
+        case MarkType::kTopic:
+            // fixme: start with cap, allow space, hyphen, stop on comma
+            // one topic can have multiple type names delineated by comma
+            result = this->topicName();
+            if (result.size() == 0 && this->hasEndToken()) {
+                break;
+            }
+            return result;
+        default:
+            // fixme: don't allow silent failures
+            SkASSERT(0);
+    }
+    result.push_back(builder);
+    return result;
+}
+
+string BmhParser::uniqueName(const string& base, MarkType markType) {
+    string builder(base);
+    if (!builder.length()) {
+        builder = fParent->fName;
+    }
+    if (!fParent) {
+        return builder;
+    }
+    int number = 2;
+    string numBuilder(builder);
+    do {
+        for (const auto& iter : fParent->fChildren) {
+            if (markType == iter->fMarkType) {
+                if (iter->fName == numBuilder) {
+                    if (MarkType::kMethod == markType) {
+                        SkDebugf("");
+                    }
+                    fCloned = true;
+                    numBuilder = builder + '_' + to_string(number);
+                    goto tryNext;
+                }
+            }
+        }
+        break;
+tryNext: ;
+    } while (++number);
+    return numBuilder;
+}
+
+string BmhParser::uniqueRootName(const string& base, MarkType markType) {
+    auto checkName = [markType](const Definition& def, const string& numBuilder) -> bool {
+        return markType == def.fMarkType && def.fName == numBuilder;
+    };
+
+    string builder(base);
+    if (!builder.length()) {
+        builder = fParent->fName;
+    }
+    int number = 2;
+    string numBuilder(builder);
+    Definition* cloned = nullptr;
+    do {
+        if (fRoot) {
+            for (auto& iter : fRoot->fBranches) {
+                if (checkName(*iter.second, numBuilder)) {
+                    cloned = iter.second;
+                    goto tryNext;
+                }
+            }
+            for (auto& iter : fRoot->fLeaves) {
+                if (checkName(iter.second, numBuilder)) {
+                    cloned = &iter.second;
+                    goto tryNext;
+                }
+            }
+        } else if (fParent) {
+            for (auto& iter : fParent->fChildren) {
+                if (checkName(*iter, numBuilder)) {
+                    cloned = &*iter;
+                    goto tryNext;
+                }
+            }
+        }
+        break;
+tryNext: ;
+        if ("()" == builder.substr(builder.length() - 2)) {
+            builder = builder.substr(0, builder.length() - 2);
+        }
+        if (MarkType::kMethod == markType) {
+            cloned->fCloned = true;
+        }
+        fCloned = true;
+        numBuilder = builder + '_' + to_string(number);
+    } while (++number);
+    return numBuilder;
+}
+
+void BmhParser::validate() const {
+    for (int index = 0; index <= (int) Last_MarkType; ++index) {
+        SkASSERT(fMaps[index].fMarkType == (MarkType) index);
+    }
+    const char* last = "";
+    for (int index = 0; index <= (int) Last_MarkType; ++index) {
+        const char* next = fMaps[index].fName;
+        if (!last[0]) {
+            last = next;
+            continue;
+        }
+        if (!next[0]) {
+            continue;
+        }
+        SkASSERT(strcmp(last, next) < 0);
+        last = next;
+    }
+}
+
+string BmhParser::word(const string& prefix, const string& delimiter) {
+    string builder(prefix);
+    this->skipWhiteSpace();
+    const char* lineEnd = fLine + this->lineLength();
+    const char* nameStart = fChar;
+    while (fChar < lineEnd) {
+        char ch = this->next();
+        if (' ' >= ch) {
+            break;
+        }
+        if (',' == ch) {
+            return this->reportError<string>("no comma needed");
+            break;
+        }
+        if (fMC == ch) {
+            return builder;
+        }
+        if (!isalnum(ch) && '_' != ch && ':' != ch && '-' != ch) {
+            return this->reportError<string>("unexpected char");
+        }
+        if (':' == ch) {
+            // expect pair, and expect word to start with Sk
+            if (nameStart[0] != 'S' || nameStart[1] != 'k') {
+                return this->reportError<string>("expected Sk");
+            }
+            if (':' != this->peek()) {
+                return this->reportError<string>("expected ::");
+            }
+            this->next();
+        } else if ('-' == ch) {
+            // expect word not to start with Sk or kX where X is A-Z
+            if (nameStart[0] == 'k' && nameStart[1] >= 'A' && nameStart[1] <= 'Z') {
+                return this->reportError<string>("didn't expected kX");
+            }
+            if (nameStart[0] == 'S' && nameStart[1] == 'k') {
+                return this->reportError<string>("expected Sk");
+            }
+        }
+    }
+    if (prefix.size()) {
+        builder += delimiter;
+    }
+    builder.append(nameStart, fChar - nameStart - 1);
+    return builder;
+}
+
+// pass one: parse text, collect definitions
+// pass two: lookup references
+
+DEFINE_string2(bmh, b, "", "A path to a *.bmh file or a directory.");
+DEFINE_string2(examples, e, "", "File of fiddlecli input, usually fiddle.json (For now, disables -r -f -s)");
+DEFINE_string2(fiddle, f, "fiddleout.json", "File of fiddlecli output.");
+DEFINE_string2(include, i, "", "A path to a *.h file or a directory.");
+DEFINE_bool2(hack, k, false, "Do a find/replace hack to update all *.bmh files. (Requires -b)");
+DEFINE_bool2(populate, p, false, "Populate include from bmh. (Requires -b -i)");
+DEFINE_string2(ref, r, "", "Resolve refs and write bmh_*.md files to path. (Requires -b)");
+DEFINE_bool2(spellcheck, s, false, "Spell-check. (Requires -b)");
+DEFINE_bool2(tokens, t, false, "Output include tokens. (Requires -i)");
+DEFINE_bool2(crosscheck, x, false, "Check bmh against includes. (Requires -b -i)");
+
+static bool dump_examples(FILE* fiddleOut, const Definition& def, bool* continuation) {
+    if (MarkType::kExample == def.fMarkType) {
+        string result;
+        if (!def.exampleToScript(&result)) {
+            return false;
+        }
+        if (result.length() > 0) {
+            if (*continuation) {
+                fprintf(fiddleOut, ",\n");
+            } else {
+                *continuation = true;
+            }
+            fprintf(fiddleOut, "%s", result.c_str());
+        }
+        return true;
+    }
+    for (auto& child : def.fChildren ) {
+        if (!dump_examples(fiddleOut, *child, continuation)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static int count_children(const Definition& def, MarkType markType) {
+    int count = 0;
+    if (markType == def.fMarkType) {
+        ++count;
+    }
+    for (auto& child : def.fChildren ) {
+        count += count_children(*child, markType);
+    }
+    return count;
+}
+
+int main(int argc, char** const argv) {
+    BmhParser bmhParser;
+    bmhParser.validate();
+
+    SkCommandLineFlags::SetUsage(
+        "Common Usage: bookmaker -i path/to/include.h -t\n"
+        "              bookmaker -b path/to/bmh_files -e fiddle.json\n"
+        "              ~/go/bin/fiddlecli --input fiddle.json --output fiddleout.json\n"
+        "              bookmaker -b path/to/bmh_files -f fiddleout.json -r path/to/md_files\n"
+        "              bookmaker -b path/to/bmh_files -i path/to/include.h -x\n"
+        "              bookmaker -b path/to/bmh_files -i path/to/include.h -p\n");
+    bool help = false;
+    for (int i = 1; i < argc; i++) {
+        if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
+            help = true;
+            for (int j = i + 1; j < argc; j++) {
+                if (SkStrStartsWith(argv[j], '-')) {
+                    break;
+                }
+                help = false;
+            }
+            break;
+        }
+    }
+    if (!help) {
+        SkCommandLineFlags::Parse(argc, argv);
+    } else {
+        SkCommandLineFlags::PrintUsage();
+        const char* commands[] = { "", "-h", "bmh", "-h", "examples", "-h", "include", "-h", "fiddle",
+            "-h", "ref", "-h", "tokens",
+            "-h", "crosscheck", "-h", "populate", "-h", "spellcheck" };
+        SkCommandLineFlags::Parse(SK_ARRAY_COUNT(commands), (char**) commands);
+        return 0;
+    }
+    if (FLAGS_bmh.isEmpty() && FLAGS_include.isEmpty()) {
+        SkDebugf("requires -b or -i\n");
+        SkCommandLineFlags::PrintUsage();
+        return 1;
+    }
+    if (FLAGS_bmh.isEmpty() && !FLAGS_examples.isEmpty()) {
+        SkDebugf("-e requires -b\n");
+        SkCommandLineFlags::PrintUsage();
+        return 1;
+    }
+    if (FLAGS_hack) {
+        if (FLAGS_bmh.isEmpty()) {
+            SkDebugf("-k or --hack requires -b\n");
+            SkCommandLineFlags::PrintUsage();
+            return 1;
+        }
+        HackParser hacker;
+        if (!hacker.parseFile(FLAGS_bmh[0], ".bmh")) {
+            SkDebugf("hack failed\n");
+            return -1;
+        }
+        SkDebugf("hack success\n");
+        return 0;
+    }
+    if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_populate) {
+        SkDebugf("-r requires -b -i\n");
+        SkCommandLineFlags::PrintUsage();
+        return 1;
+    }
+    if (FLAGS_bmh.isEmpty() && !FLAGS_ref.isEmpty()) {
+        SkDebugf("-r requires -b\n");
+        SkCommandLineFlags::PrintUsage();
+        return 1;
+    }
+    if (FLAGS_bmh.isEmpty() && FLAGS_spellcheck) {
+        SkDebugf("-s requires -b\n");
+        SkCommandLineFlags::PrintUsage();
+        return 1;
+    }
+    if (FLAGS_include.isEmpty() && FLAGS_tokens) {
+        SkDebugf("-t requires -i\n");
+        SkCommandLineFlags::PrintUsage();
+        return 1;
+    }
+    if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_crosscheck) {
+        SkDebugf("-x requires -b -i\n");
+        SkCommandLineFlags::PrintUsage();
+        return 1;
+    }
+    if (!FLAGS_bmh.isEmpty()) {
+        if (!bmhParser.parseFile(FLAGS_bmh[0], ".bmh")) {
+            return -1;
+        }
+    }
+    bool done = false;
+    if (!FLAGS_include.isEmpty()) {
+        if (FLAGS_tokens || FLAGS_crosscheck) {
+            IncludeParser includeParser;
+            includeParser.validate();
+            if (!includeParser.parseFile(FLAGS_include[0], ".h")) {
+                return -1;
+            }
+            if (FLAGS_tokens) {
+                includeParser.dumpTokens();
+                done = true;
+            } else if (FLAGS_crosscheck) {
+                if (!includeParser.crossCheck(bmhParser)) {
+                    return -1;
+                }
+                done = true;
+            }
+        } else if (FLAGS_populate) {
+            IncludeWriter includeWriter;
+            includeWriter.validate();
+            if (!includeWriter.parseFile(FLAGS_include[0], ".h")) {
+                return -1;
+            }
+            if (!includeWriter.populate(bmhParser)) {
+                return -1;
+            }
+            done = true;
+        }
+    }
+    FiddleParser fparser(&bmhParser);
+    if (!done && !FLAGS_fiddle.isEmpty() && FLAGS_examples.isEmpty()) {
+        if (!fparser.parseFile(FLAGS_fiddle[0], ".txt")) {
+            return -1;
+        }
+    }
+    if (!done && !FLAGS_ref.isEmpty() && FLAGS_examples.isEmpty()) {
+        MdOut mdOut(bmhParser);
+        mdOut.buildReferences(FLAGS_bmh[0], FLAGS_ref[0]);
+    }
+    if (!done && FLAGS_spellcheck && FLAGS_examples.isEmpty()) {
+        bmhParser.spellCheck(FLAGS_bmh[0]);
+        done = true;
+    }
+    int examples = 0;
+    int methods = 0;
+    int topics = 0;
+    FILE* fiddleOut;
+    if (!done && !FLAGS_examples.isEmpty()) {
+        fiddleOut = fopen(FLAGS_examples[0], "wb");
+        if (!fiddleOut) {
+            SkDebugf("could not open output file %s\n", FLAGS_examples[0]);
+            return -1;
+        }
+        fprintf(fiddleOut, "{\n");
+        bool continuation = false;
+        for (const auto& topic : bmhParser.fTopicMap) {
+            if (topic.second->fParent) {
+                continue;
+            }
+            dump_examples(fiddleOut, *topic.second, &continuation);
+        }
+        fprintf(fiddleOut, "\n}\n");
+        fclose(fiddleOut);
+    }
+    for (const auto& topic : bmhParser.fTopicMap) {
+        if (topic.second->fParent) {
+            continue;
+        }
+        examples += count_children(*topic.second, MarkType::kExample);
+        methods += count_children(*topic.second, MarkType::kMethod);
+        topics += count_children(*topic.second, MarkType::kSubtopic);
+        topics += count_children(*topic.second, MarkType::kTopic);
+    }
+    SkDebugf("topics=%d classes=%d methods=%d examples=%d\n", 
+            bmhParser.fTopicMap.size(), bmhParser.fClassMap.size(),
+            methods, examples);
+    return 0;
+}
diff --git a/tools/bookmaker/bookmaker.h b/tools/bookmaker/bookmaker.h
new file mode 100644
index 0000000..8d9d14f
--- /dev/null
+++ b/tools/bookmaker/bookmaker.h
@@ -0,0 +1,1844 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef bookmaker_DEFINED
+#define bookmaker_DEFINED
+
+#define STDOUT_TO_IDE_OUT 01
+
+#include "SkData.h"
+
+#include <algorithm> 
+#include <cmath>
+#include <cctype>
+#include <forward_list>
+#include <list>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+// std::to_string isn't implemented on android
+#include <sstream>
+
+template <typename T>
+std::string to_string(T value)
+{
+    std::ostringstream os ;
+    os << value ;
+    return os.str() ;
+}
+
+using std::forward_list;
+using std::list;
+using std::unordered_map;
+using std::string;
+using std::vector;
+
+enum class KeyWord {
+    kNone,
+    kBool,
+    kChar,
+    kClass,
+    kConst,
+    kConstExpr,
+    kDefine,
+    kDouble,
+    kElif,
+    kElse,
+    kEndif,
+    kEnum,
+    kFloat,
+    kFriend,
+    kIf,
+    kIfdef,
+    kIfndef,
+    kInclude,
+    kInline,
+    kInt,
+    kOperator,
+    kPrivate,
+    kProtected,
+    kPublic,
+    kSigned,
+    kSize_t,
+    kStatic,
+    kStruct,
+    kTemplate,
+    kTypedef,
+    kUint32_t,
+    kUnion,
+    kUnsigned,
+    kVoid,
+};
+
+enum class MarkType {
+    kNone,
+    kAnchor,
+    kAlias,
+    kBug,
+    kClass,
+    kCode,
+    kColumn,
+    kComment,
+    kConst,
+    kDefine,
+    kDefinedBy,
+    kDeprecated,
+    kDescription,
+    kDoxygen,
+    kEnum,
+    kEnumClass,
+    kError,
+    kExample,
+    kExperimental,
+    kExternal,
+    kFile,
+    kFormula,
+    kFunction,
+    kHeight,
+    kImage,
+    kLegend,
+    kLink,
+    kList,
+    kMarkChar,
+    kMember,
+    kMethod,
+    kNoExample,
+    kParam,
+    kPlatform,
+    kPrivate,
+    kReturn,
+    kRoot,
+    kRow,
+    kSeeAlso,
+    kStdOut,
+    kStruct,
+    kSubstitute,
+    kSubtopic,
+    kTable,
+    kTemplate,
+    kText,
+    kTime,
+    kToDo,
+    kTopic,
+    kTrack,
+    kTypedef,
+    kUnion,
+    kVolatile,
+    kWidth,
+};
+
+enum {
+    Last_MarkType = (int) MarkType::kWidth,
+};
+
+enum class Bracket {
+    kNone,
+    kParen,
+    kSquare,
+    kBrace,
+    kAngle,
+    kString,
+    kChar,
+    kSlashStar,
+    kSlashSlash,
+    kPound,
+    kColon,
+};
+
+enum class Punctuation {  // catch-all for misc symbols tracked in C
+    kNone,
+    kAsterisk,  // for pointer-to
+    kSemicolon,  // e.g., to delinate xxx() const ; const int* yyy()
+    kLeftBrace,
+    kColon,     // for foo() : bar(1), baz(2) {}
+};
+
+static inline bool has_nonwhitespace(const string& s) {
+    bool nonwhite = false;
+    for (const char& c : s) {
+        if (' ' < c) {
+            nonwhite = true;
+            break;
+        }
+    }
+    return nonwhite;
+}
+
+static inline void trim_end(string &s) {
+    s.erase(std::find_if(s.rbegin(), s.rend(),
+            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
+}
+
+static inline void trim_end_spaces(string &s) {
+    while (s.length() > 0 && ' ' == s.back()) {
+        s.pop_back();
+    }
+}
+
+static inline void trim_start(string &s) {
+    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
+            std::not1(std::ptr_fun<int, int>(std::isspace))));
+}
+
+static inline void trim_start_end(string& s) {
+    trim_start(s);
+    trim_end(s);
+}
+
+class NonAssignable {
+public:
+    NonAssignable(NonAssignable const&) = delete;
+    NonAssignable& operator=(NonAssignable const&) = delete;
+    NonAssignable() {}
+};
+
+class Definition;
+
+class TextParser : public NonAssignable {
+    TextParser() {}  // only for ParserCommon to call
+    friend class ParserCommon;
+public:
+    enum OneLine {
+        kNo,
+        kYes
+    };
+
+    class Save {
+    public:
+        Save(TextParser* parser) {
+            fParser = parser;
+            fLine = parser->fLine;
+            fChar = parser->fChar;
+            fLineCount = parser->fLineCount;
+        }
+
+        void restore() const {
+            fParser->fLine = fLine;
+            fParser->fChar = fChar;
+            fParser->fLineCount = fLineCount;
+        }
+
+    private:
+        TextParser* fParser;
+        const char* fLine;
+        const char* fChar;
+        int fLineCount;
+    };
+
+    TextParser(const string& fileName, const char* start, const char* end, int lineCount)
+        : fFileName(fileName)
+        , fStart(start)
+        , fLine(start)
+        , fChar(start)
+        , fEnd(end)
+        , fLineCount(lineCount)
+    {
+    }
+
+    TextParser(const Definition* );
+
+    const char* anyOf(const char* str) const {
+        const char* ptr = fChar;
+        while (ptr < fEnd) {
+            if (strchr(str, ptr[0])) {
+                return ptr;
+            }
+            ++ptr;
+        }
+        return nullptr;
+    }
+
+    const char* anyOf(const char* wordStart, const char* wordList[], size_t wordListCount) const {
+        const char** wordPtr = wordList;
+        const char** wordEnd = wordPtr + wordListCount;
+        const size_t matchLen = fChar - wordStart;
+        while (wordPtr < wordEnd) {
+            const char* word = *wordPtr++;
+            if (strlen(word) == matchLen && !strncmp(wordStart, word, matchLen)) {
+                return word;
+            }
+        }
+        return nullptr;
+    }
+
+    char backup(const char* pattern) const {
+        size_t len = strlen(pattern);
+        const char* start = fChar - len;
+        if (start <= fStart) {
+            return '\0';
+        }
+        if (strncmp(start, pattern, len)) {
+            return '\0';
+        }
+        return start[-1];
+    }
+
+    bool contains(const char* match, const char* lineEnd, const char** loc) const {
+        *loc = this->strnstr(match, lineEnd);
+        return *loc;
+    }
+
+    bool eof() const { return fChar >= fEnd; }
+
+    const char* lineEnd() const {
+        const char* ptr = fChar;
+        do {
+            if (ptr >= fEnd) {
+                return ptr;
+            }
+            char test = *ptr++;
+            if (test == '\n' || test == '\0') {
+                break;
+            }
+        } while (true);
+        return ptr;
+    }
+
+    ptrdiff_t lineLength() const {
+        return this->lineEnd() - fLine;
+    }
+
+    bool match(TextParser* );
+
+    char next() { 
+        SkASSERT(fChar < fEnd);
+        char result = *fChar++;
+        if ('\n' == result) {
+            ++fLineCount;
+            fLine = fChar;
+        }
+        return result; 
+    }
+
+    char peek() const { SkASSERT(fChar < fEnd); return *fChar; }
+
+    void restorePlace(const TextParser& save) {
+        fChar = save.fChar;
+        fLine = save.fLine;
+        fLineCount = save.fLineCount;
+    }
+
+    void savePlace(TextParser* save) {
+        save->fChar = fChar;
+        save->fLine = fLine;
+        save->fLineCount = fLineCount;
+    }
+
+    void reportError(const char* errorStr) const;
+    void reportWarning(const char* errorStr) const;
+
+    template <typename T> T reportError(const char* errorStr) const {
+        this->reportError(errorStr);
+        return T();
+    }
+
+    bool sentenceEnd(const char* check) const {
+        while (check > fStart) {
+            --check;
+            if (' ' < check[0] && '.' != check[0]) {
+                return false;
+            }
+            if ('.' == check[0]) {
+                return ' ' >= check[1];
+            }
+            if ('\n' == check[0] && '\n' == check[1]) {
+                return true;
+            }
+        }
+        return true;
+    }
+
+    bool skipToEndBracket(char endBracket, const char* end = nullptr) {
+        if (nullptr == end) {
+            end = fEnd;
+        }
+        while (fChar[0] != endBracket) {
+            if (fChar >= end) {
+                return false;
+            }
+            (void) this->next();
+        }
+        return true;
+    }
+
+    bool skipToEndBracket(const char* endBracket) {
+        size_t len = strlen(endBracket);
+        while (strncmp(fChar, endBracket, len)) {
+            if (fChar >= fEnd) {
+                return false;
+            }
+            (void) this->next();
+        }
+        return true;
+    }
+
+    bool skipLine() {
+        return skipToEndBracket('\n');
+    }
+
+    void skipTo(const char* skip) {
+       while (fChar < skip) {
+           this->next();
+       }
+    }
+
+    void skipToAlpha() {
+        while (fChar < fEnd && !isalpha(fChar[0])) {
+            fChar++;
+        }
+    }
+
+    void skipToAlphaNum() {
+        while (fChar < fEnd && !isalnum(fChar[0])) {
+            fChar++;
+        }
+    }
+
+    bool skipExact(const char* pattern) {
+        if (!this->startsWith(pattern)) {
+            return false;
+        }
+        this->skipName(pattern);
+        return true;
+    }
+
+    // differs from skipToNonAlphaNum in that a.b isn't considered a full name,
+    // since a.b can't be found as a named definition
+    void skipFullName() {
+        while (fChar < fEnd && (isalnum(fChar[0])
+                || '_' == fChar[0] || '-' == fChar[0]
+                || (':' == fChar[0] && fChar +1 < fEnd && ':' == fChar[1]))) {
+            if (':' == fChar[0] && fChar +1 < fEnd && ':' == fChar[1]) {
+                fChar++;
+            }
+            fChar++;
+        }
+    }
+
+    bool skipToLineStart() {
+        if (!this->skipLine()) {
+            return false;
+        }
+        if (!this->eof()) {
+            return this->skipWhiteSpace();
+        }
+        return true;
+    }
+
+    void skipToNonAlphaNum() {
+        while (fChar < fEnd && (isalnum(fChar[0])
+                || '_' == fChar[0] || '-' == fChar[0]
+                || (':' == fChar[0] && fChar +1 < fEnd && ':' == fChar[1])
+                || ('.' == fChar[0] && fChar + 1 < fEnd && isalpha(fChar[1])))) {
+            if (':' == fChar[0] && fChar +1 < fEnd && ':' == fChar[1]) {
+                fChar++;
+            }
+            fChar++;
+        }
+    }
+
+    void skipToSpace() {
+        while (fChar < fEnd && ' ' != fChar[0]) {
+            fChar++;
+        }
+    }
+
+    bool skipName(const char* word) {
+        size_t len = strlen(word);
+        if (len < (size_t) (fEnd - fChar) && !strncmp(word, fChar, len)) {
+            fChar += len;
+        }
+        return this->eof() || ' ' >= fChar[0];
+    }
+
+    bool skipSpace() { 
+        while (' ' == this->peek()) { 
+            (void) this->next();
+            if (fChar >= fEnd) {
+                return false;
+            }
+        } 
+        return true; 
+    }
+
+    bool skipWord(const char* word) {
+        if (!this->skipWhiteSpace()) {
+            return false;
+        }
+        const char* save = fChar;
+        if (!this->skipName(word)) {
+            fChar = save;
+            return false;
+        }
+        if (!this->skipWhiteSpace()) {
+            return false;
+        }
+        return true;
+    }
+
+    bool skipWhiteSpace() { 
+        while (' ' >= this->peek()) { 
+            (void) this->next();
+            if (fChar >= fEnd) {
+                return false;
+            }
+        } 
+        return true; 
+    }
+
+    bool startsWith(const char* str) const {
+        size_t len = strlen(str);
+        ptrdiff_t lineLen = this->lineLength(); 
+        return len <= (size_t) lineLen && 0 == strncmp(str, fChar, len);
+    }
+
+    const char* strnchr(char ch, const char* end) const {
+        const char* ptr = fChar;
+        while (ptr < end) {
+            if (ptr[0] == ch) {
+                return ptr;
+            }
+            ++ptr;
+        }
+        return nullptr;
+    }
+
+    const char* strnstr(const char *match, const char* end) const {
+        size_t matchLen = strlen(match);
+        SkASSERT(matchLen > 0);
+        ptrdiff_t len = end - fChar;
+        SkASSERT(len >= 0);
+        if ((size_t) len < matchLen ) {
+            return nullptr;
+        }
+        size_t count = len - matchLen;
+        for (size_t index = 0; index <= count; index++) {
+            if (0 == strncmp(&fChar[index], match, matchLen)) {
+                return &fChar[index];
+            }
+        }
+        return nullptr;
+    }
+
+    const char* trimmedBracketEnd(const char bracket, OneLine oneLine) const {
+        int max = (int) (OneLine::kYes == oneLine ? this->lineLength() : fEnd - fChar);
+        int index = 0;
+        while (index < max && bracket != fChar[index]) {
+            ++index;
+        }
+        SkASSERT(index < max);
+        while (index > 0 && ' ' >= fChar[index - 1]) {
+            --index;
+        }
+        return fChar + index;
+    }
+
+    const char* trimmedLineEnd() const {
+        const char* result = this->lineEnd();
+        while (result > fChar && ' ' >= result[-1]) {
+            --result;
+        }
+        return result;
+    }
+
+    void trimEnd() {
+        while (fEnd > fStart && ' ' >= fEnd[-1]) {
+            --fEnd;
+        }
+    }
+
+    const char* wordEnd() const {
+        const char* end = fChar;
+        while (isalnum(end[0]) || '_' == end[0] || '-' == end[0]) {
+            ++end;
+        }
+        return end;
+    }
+
+    string fFileName;
+    const char* fStart;
+    const char* fLine;
+    const char* fChar;
+    const char* fEnd;
+    size_t fLineCount;
+};
+
+class EscapeParser : public TextParser {
+public:
+    EscapeParser(const char* start, const char* end) :
+            TextParser("", start, end, 0) {
+        const char* reader = fStart;
+        fStorage = new char[end - start];
+        char* writer = fStorage;
+        while (reader < fEnd) {
+            char ch = *reader++;
+            if (ch != '\\') {
+                *writer++ = ch;
+            } else {
+                char ctrl = *reader++;
+                if (ctrl == 'u') {
+                    unsigned unicode = 0;
+                    for (int i = 0; i < 4; ++i) {
+                        unicode <<= 4;
+                        SkASSERT((reader[0] >= '0' && reader[0] <= '9') ||
+                            (reader[0] >= 'A' && reader[0] <= 'F'));
+                        int nibble = *reader++ - '0';
+                        if (nibble > 9) {
+                            nibble = 'A'- '9' + 1;
+                        }
+                        unicode |= nibble;
+                    }
+                    SkASSERT(unicode < 256);
+                    *writer++ = (unsigned char) unicode;
+                } else {
+                    SkASSERT(ctrl == 'n');
+                    *writer++ = '\n';
+                }
+            }
+        }
+        fStart = fLine = fChar = fStorage;
+        fEnd = writer;
+    }
+
+    virtual ~EscapeParser() {
+        delete fStorage;
+    }
+private:
+    char* fStorage;
+};
+
+class RootDefinition;
+
+class Definition : public NonAssignable {
+public:
+    enum Type {
+        kNone,
+        kWord,
+        kMark,
+        kKeyWord,
+        kBracket,
+        kPunctuation,
+        kFileType,
+    };
+
+    enum class TrimExtract {
+        kNo,
+        kYes
+    };
+
+    enum class MethodType {
+        kNone,
+        kConstructor,
+        kDestructor,
+        kOperator,
+    };
+
+    Definition() {}
+
+    Definition(const char* start, const char* end, int line, Definition* parent) 
+        : fStart(start)
+        , fContentStart(start)
+        , fContentEnd(end)
+        , fParent(parent)
+        , fLineCount(line)
+        , fType(Type::kWord) {
+        if (parent) {
+            SkASSERT(parent->fFileName.length() > 0);
+            fFileName = parent->fFileName;
+        }
+        this->setParentIndex();
+    }
+
+    Definition(MarkType markType, const char* start, int line, Definition* parent) 
+        : Definition(markType, start, nullptr, line, parent) {
+    }
+
+    Definition(MarkType markType, const char* start, const char* end, int line, Definition* parent) 
+        : Definition(start, end, line, parent) {
+        fMarkType = markType;
+        fType = Type::kMark; 
+    }
+
+    Definition(Bracket bracket, const char* start, int lineCount, Definition* parent) 
+        : Definition(start, nullptr, lineCount, parent) {
+        fBracket = bracket;
+        fType = Type::kBracket;
+    }
+
+    Definition(KeyWord keyWord, const char* start, const char* end, int lineCount, 
+            Definition* parent) 
+        : Definition(start, end, lineCount, parent) {
+        fKeyWord = keyWord;
+        fType = Type::kKeyWord;
+    }
+
+    Definition(Punctuation punctuation, const char* start, int lineCount, Definition* parent) 
+        : Definition(start, nullptr, lineCount, parent) {
+        fPunctuation = punctuation;
+        fType = Type::kPunctuation;
+    }
+
+    virtual ~Definition() {}
+
+    virtual RootDefinition* asRoot() { SkASSERT(0); return nullptr; }
+    virtual const RootDefinition* asRoot() const { SkASSERT(0); return nullptr; }
+
+    bool boilerplateIfDef(Definition* parent) {
+        const Definition& label = fTokens.front();
+        if (Type::kWord != label.fType) {
+            return false;
+        }
+        fName = string(label.fContentStart, label.fContentEnd - label.fContentStart);
+        return true;
+   }
+
+    // todo: this is matching #ifndef SkXXX_DEFINED for no particular reason
+    // it doesn't do anything useful with arbitrary input, e.g. #ifdef SK_SUPPORT_LEGACY_CANVAS_HELPERS
+// also doesn't know what to do with SK_REQUIRE_LOCAL_VAR()
+    bool boilerplateDef(Definition* parent) {
+        if (!this->boilerplateIfDef(parent)) {
+            return false;
+        }
+        const char* s = fName.c_str();
+        const char* e = strchr(s, '_');
+        return true; // fixme: if this is trying to do something useful with define, do it here
+        if (!e) {
+            return false;
+        }
+        string prefix(s, e - s);
+        const char* inName = strstr(parent->fName.c_str(), prefix.c_str());
+        if (!inName) {
+            return false;
+        }
+        if ('/' != inName[-1] && '\\' != inName[-1]) {
+            return false;
+        }
+        if (strcmp(inName + prefix.size(), ".h")) {
+            return false;
+        }
+        return true;
+    }
+
+    bool boilerplateEndIf() {
+        return true;
+    }
+
+    bool checkMethod() const;
+
+    void setCanonicalFiddle();
+    bool crossCheck(const char* tokenName, const Definition& includeToken) const;
+    bool crossCheck(const Definition& includeToken) const;
+    bool crossCheckInside(const char* start, const char* end, const Definition& includeToken) const;
+    bool exampleToScript(string* result) const;
+
+    string extractText(TrimExtract trimExtract) const {
+        string result;
+        TextParser parser(fFileName, fContentStart, fContentEnd, fLineCount);
+        int childIndex = 0;
+        char mc = '#';
+        while (parser.fChar < parser.fEnd) {
+            if (TrimExtract::kYes == trimExtract && !parser.skipWhiteSpace()) {
+                break;
+            }
+            if (parser.next() == mc) {
+                if (parser.next() == mc) {
+                    if (parser.next() == mc) {
+                        mc = parser.next();
+                    }
+                } else {
+                    // fixme : more work to do if # style comment is in text
+                    // if in method definition, could be alternate method name
+                    --parser.fChar;
+                    if (' ' < parser.fChar[0]) {
+                        if (islower(parser.fChar[0])) {
+                            result += '\n';
+                            parser.skipLine();
+                        } else {
+                            SkASSERT(isupper(parser.fChar[0]));
+                            parser.skipTo(fChildren[childIndex]->fTerminator);
+                            if (mc == parser.fChar[0] && mc == parser.fChar[1]) {
+                                parser.next();
+                                parser.next();
+                            }
+                            childIndex++;
+                        }
+                    } else {
+                        parser.skipLine();
+                    }
+                    continue;
+                }
+            } else {
+                --parser.fChar;
+            }
+            const char* end = parser.fEnd;
+            const char* mark = parser.strnchr(mc, end);
+            if (mark) {
+                end = mark;
+            }
+            string fragment(parser.fChar, end - parser.fChar);
+            trim_end(fragment);
+            if (TrimExtract::kYes == trimExtract) {
+                trim_start(fragment);
+                if (result.length()) {
+                    result += '\n';
+                    result += '\n';
+                }
+            }
+            if (TrimExtract::kYes == trimExtract || has_nonwhitespace(fragment)) {
+                result += fragment;
+            }
+            parser.skipTo(end);
+        }
+        return result;
+    }
+
+    string fiddleName() const;
+    string formatFunction() const;
+    const Definition* hasChild(MarkType markType) const;
+    const Definition* hasParam(const string& ref) const;
+    bool isClone() const { return fClone; }
+
+    Definition* iRootParent() {
+        Definition* test = fParent;
+        while (test) {
+            if (Type::kKeyWord == test->fType && KeyWord::kClass == test->fKeyWord) {
+                return test;
+            }
+            test = test->fParent;
+        }
+        return nullptr;
+    }
+
+    virtual bool isRoot() const { return false; }
+
+    int length() const {
+        return (int) (fContentEnd - fContentStart);
+    }
+
+    bool methodHasReturn(const string& name, TextParser* methodParser) const;
+    string methodName() const;
+    bool nextMethodParam(TextParser* methodParser, const char** nextEndPtr, 
+                         string* paramName) const;
+    bool paramsMatch(const string& fullRef, const string& name) const;
+
+    string printableName() const {
+        string result(fName);
+        std::replace(result.begin(), result.end(), '_', ' ');
+        return result;
+    }
+
+    virtual RootDefinition* rootParent() { SkASSERT(0); return nullptr; }
+
+    void setParentIndex() {
+        fParentIndex = fParent ? (int) fParent->fTokens.size() : -1;
+    }
+
+    string fText;  // if text is constructed instead of in a file, it's put here
+    const char* fStart = nullptr;  // .. in original text file, or the start of fText
+    const char* fContentStart;  // start past optional markup name
+    string fName;
+    string fFiddle;  // if its a constructor or operator, fiddle name goes here
+    const char* fContentEnd = nullptr;  // the end of the contained text
+    const char* fTerminator = nullptr;  // the end of the markup, normally ##\n or \n
+    Definition* fParent = nullptr;
+    list<Definition> fTokens;
+    vector<Definition*> fChildren;
+    string fHash;  // generated by fiddle
+    string fFileName;
+    size_t fLineCount = 0;
+    int fParentIndex = 0;
+    MarkType fMarkType = MarkType::kNone;
+    KeyWord fKeyWord = KeyWord::kNone;
+    Bracket fBracket = Bracket::kNone;
+    Punctuation fPunctuation = Punctuation::kNone;
+    MethodType fMethodType = MethodType::kNone;
+    Type fType = Type::kNone;
+    bool fClone = false;
+    bool fCloned = false;
+    bool fPrivate = false;
+    bool fShort = false;
+    bool fMemberStart = false;
+    mutable bool fVisited = false;
+};
+
+class RootDefinition : public Definition {
+public:
+    RootDefinition() {
+    }
+    
+    RootDefinition(MarkType markType, const char* start, int line, Definition* parent)
+            : Definition(markType, start, line, parent) {
+    }
+
+    RootDefinition(MarkType markType, const char* start, const char* end, int line, 
+            Definition* parent) : Definition(markType, start, end,  line, parent) {
+    }
+
+    ~RootDefinition() override {
+        for (auto& iter : fBranches) {
+            delete iter.second;
+        }
+    }
+
+    RootDefinition* asRoot() override { return this; }
+    const RootDefinition* asRoot() const override { return this; }
+    void clearVisited();
+    bool dumpUnVisited();
+    const Definition* find(const string& ref) const;
+    bool isRoot() const override { return true; }
+    RootDefinition* rootParent() override { return fRootParent; }
+    void setRootParent(RootDefinition* rootParent) { fRootParent = rootParent; }
+
+    unordered_map<string, RootDefinition*> fBranches;
+    unordered_map<string, Definition> fLeaves;
+private:
+    RootDefinition* fRootParent = nullptr;
+};
+
+struct IClassDefinition : public Definition {
+    unordered_map<string, Definition*> fEnums;
+    unordered_map<string, Definition*> fMembers;
+    unordered_map<string, Definition*> fMethods;
+    unordered_map<string, Definition*> fStructs;
+};
+
+struct Reference {
+    Reference()
+        : fLocation(nullptr)
+        , fDefinition(nullptr) {
+    }
+
+    const char* fLocation;  // .. in original text file
+    const Definition* fDefinition;
+};
+
+struct TypeNames {
+    const char* fName;
+    MarkType fMarkType;
+};
+
+class ParserCommon : public TextParser {
+public:
+
+    ParserCommon() : TextParser()
+        , fParent(nullptr)
+    {
+    }
+
+    virtual ~ParserCommon() {
+    }
+
+    void addDefinition(Definition* def) {
+        fParent->fChildren.push_back(def);
+        fParent = def;
+    }
+
+    void indentToColumn(int column) {
+        SkASSERT(column >= fColumn);
+#if STDOUT_TO_IDE_OUT
+        SkDebugf("%*s", column - fColumn, "");
+#endif
+        fprintf(fOut, "%*s", column - fColumn, "");
+        fColumn = column;
+        fSpaces += column - fColumn;
+    }
+
+    bool leadingPunctuation(const char* str, size_t len) const {
+        if (!fPendingSpace) {
+            return false;
+        }
+        if (len < 2) {
+            return false;
+        }
+        if ('.' != str[0] && ',' != str[0] && ';' != str[0] && ':' != str[0]) {
+            return false;
+        }
+        return ' ' >= str[1];
+    }
+
+    void lf(int count) {
+        fPendingLF = SkTMax(fPendingLF, count);
+        this->nl();
+    }
+
+    void lfAlways(int count) {
+        this->lf(count);
+        this->writePending();
+    }
+
+    void lfcr() {
+        this->lf(1);
+    }
+
+    void nl() {
+        fLinefeeds = 0;
+        fSpaces = 0;
+        fColumn = 0;
+        fPendingSpace = false;
+    }
+
+    bool parseFile(const char* file, const char* suffix);
+    virtual bool parseFromFile(const char* path) = 0;
+    bool parseSetup(const char* path);
+
+    void popObject() {
+        fParent->fContentEnd = fParent->fTerminator = fChar;
+        fParent = fParent->fParent;
+    }
+
+    virtual void reset() = 0;
+
+    void resetCommon() {
+        fLine = fChar = fStart;
+        fLineCount = 0;
+        fParent = nullptr;
+        fIndent = 0;
+        fOut = nullptr;
+        fMaxLF = 2;
+        fPendingLF = 0;
+        fPendingSpace = false;
+        nl();
+   }
+
+    void setAsParent(Definition* definition) {
+        if (fParent) {
+            fParent->fChildren.push_back(definition);
+            definition->fParent = fParent;
+        }
+        fParent = definition;
+    }
+
+    void singleLF() {
+        fMaxLF = 1;
+    }
+
+    bool writeBlockTrim(int size, const char* data) {
+        while (size && ' ' >= data[0]) {
+            ++data;
+            --size;
+        }
+        while (size && ' ' >= data[size - 1]) {
+            --size;
+        }
+        if (size <= 0) {
+            return false;
+        }
+        SkASSERT(size < 8000);
+        if (size > 3 && !strncmp("#end", data, 4)) {
+            fMaxLF = 1;
+        }
+        if (this->leadingPunctuation(data, (size_t) size)) {
+            fPendingSpace = false;
+        }
+        writePending();
+#if STDOUT_TO_IDE_OUT
+        string check(data, size);
+        SkDebugf("%s", check.c_str());
+#endif
+        fprintf(fOut, "%.*s", size, data);
+        int added = 0;
+        while (size > 0 && '\n' != data[--size]) {
+            ++added;
+        }
+        fColumn = size ? added : fColumn + added;
+        fSpaces = 0;
+        fLinefeeds = 0;
+        fMaxLF = added > 2 && !strncmp("#if", &data[size + (size > 0)], 3) ? 1 : 2;
+        return true;
+    }
+
+    void writeBlock(int size, const char* data) {
+        SkAssertResult(writeBlockTrim(size, data));
+    }
+    void writeCommentHeader() {
+        this->lf(2);
+        this->writeString("/**");
+        this->writeSpace();
+    }
+
+    void writeCommentTrailer() {
+        this->writeString("*/");
+        this->lfcr();
+    }
+
+    // write a pending space, so that two consecutive calls
+    // don't double write, and trailing spaces on lines aren't written
+    void writeSpace() {
+        SkASSERT(!fPendingLF);
+        SkASSERT(!fLinefeeds);
+        SkASSERT(fColumn > 0);
+        SkASSERT(!fSpaces);
+        fPendingSpace = true;
+    }
+
+    void writeString(const char* str) {
+        SkASSERT(strlen(str) > 0);
+        SkASSERT(' ' < str[0]);
+        SkASSERT(' ' < str[strlen(str) - 1]);
+        if (this->leadingPunctuation(str, strlen(str))) {
+            fPendingSpace = false;
+        }
+        writePending();
+#if STDOUT_TO_IDE_OUT
+        SkDebugf("%s", str);
+#endif
+        SkASSERT(!strchr(str, '\n'));
+        fprintf(fOut, "%s", str);
+        fColumn += strlen(str);
+        fSpaces = 0;
+        fLinefeeds = 0;
+        fMaxLF = 2;
+    }
+
+    void writePending() {
+        fPendingLF = SkTMin(fPendingLF, fMaxLF);
+        bool wroteLF = false;
+        while (fLinefeeds < fPendingLF) {
+#if STDOUT_TO_IDE_OUT
+            SkDebugf("\n");
+#endif
+            fprintf(fOut, "\n");
+            ++fLinefeeds;
+            wroteLF = true;
+        }
+        fPendingLF = 0;
+        if (wroteLF) {
+            SkASSERT(0 == fColumn);
+            SkASSERT(fIndent >= fSpaces);
+    #if STDOUT_TO_IDE_OUT
+            SkDebugf("%*s", fIndent - fSpaces, "");
+    #endif
+            fprintf(fOut, "%*s", fIndent - fSpaces, "");
+            fColumn = fIndent;
+            fSpaces = fIndent;
+        }
+        if (fPendingSpace) {
+    #if STDOUT_TO_IDE_OUT
+            SkDebugf(" ");
+    #endif
+            fprintf(fOut, " ");
+            ++fColumn;
+            fPendingSpace = false;
+        }
+    }
+
+    unordered_map<string, sk_sp<SkData>> fRawData;
+    unordered_map<string, vector<char>> fLFOnly;
+    Definition* fParent;
+    FILE* fOut;
+    int fLinefeeds;    // number of linefeeds last written, zeroed on non-space
+    int fMaxLF;         // number of linefeeds allowed
+    int fPendingLF;     // number of linefeeds to write (can be suppressed)
+    int fSpaces;        // number of spaces (indent) last written, zeroed on non-space
+    int fColumn;        // current column; number of chars past last linefeed
+    int fIndent;        // desired indention
+    bool fPendingSpace; // a space should preceed the next string or block
+private:
+    typedef TextParser INHERITED;
+};
+
+
+
+class BmhParser : public ParserCommon {
+public:
+    enum class MarkLookup {
+        kRequire,
+        kAllowUnknown,
+    };
+
+    enum class Resolvable {
+        kNo,    // neither resolved nor output
+        kYes,   // resolved, output
+        kOut,   // not resolved, but output
+    };
+
+    enum class Exemplary {
+        kNo,
+        kYes,
+        kOptional,
+    };
+
+#define M(mt) (1LL << (int) MarkType::k##mt)
+#define M_D M(Description)
+#define M_CS M(Class) | M(Struct)
+#define M_ST M(Subtopic) | M(Topic)
+#define M_CSST M_CS | M_ST
+#ifdef M_E
+#undef M_E
+#endif
+#define M_E M(Enum) | M(EnumClass)
+
+#define R_Y Resolvable::kYes
+#define R_N Resolvable::kNo
+#define R_O Resolvable::kOut
+
+#define E_Y Exemplary::kYes
+#define E_N Exemplary::kNo
+#define E_O Exemplary::kOptional
+
+    BmhParser() : ParserCommon()
+        , fMaps { 
+// names without formal definitions (e.g. Column) aren't included
+// fill in other names once they're actually used
+  { "",            nullptr,      MarkType::kNone,        R_Y, E_N, 0 }
+, { "A",           nullptr,      MarkType::kAnchor,      R_Y, E_N, 0 }
+, { "Alias",       nullptr,      MarkType::kAlias,       R_N, E_N, 0 }
+, { "Bug",         nullptr,      MarkType::kBug,         R_N, E_N, 0 }
+, { "Class",       &fClassMap,   MarkType::kClass,       R_Y, E_O, M_CSST | M(Root) }
+, { "Code",        nullptr,      MarkType::kCode,        R_Y, E_N, M_CSST | M_E }      
+, { "",            nullptr,      MarkType::kColumn,      R_Y, E_N, M(Row) }
+, { "",            nullptr,      MarkType::kComment,     R_N, E_N, 0 }
+, { "Const",       &fConstMap,   MarkType::kConst,       R_Y, E_N, M_E | M_ST  }
+, { "Define",      nullptr,      MarkType::kDefine,      R_O, E_N, M_ST }
+, { "DefinedBy",   nullptr,      MarkType::kDefinedBy,   R_N, E_N, M(Method) }
+, { "Deprecated",  nullptr,      MarkType::kDeprecated,  R_Y, E_N, 0 }
+, { "Description", nullptr,      MarkType::kDescription, R_Y, E_N, M(Example) }
+, { "Doxygen",     nullptr,      MarkType::kDoxygen,     R_Y, E_N, 0 }
+, { "Enum",        &fEnumMap,    MarkType::kEnum,        R_Y, E_O, M_CSST | M(Root) }
+, { "EnumClass",   &fClassMap,   MarkType::kEnumClass,   R_Y, E_O, M_CSST | M(Root) }
+, { "Error",       nullptr,      MarkType::kError,       R_N, E_N, M(Example) }
+, { "Example",     nullptr,      MarkType::kExample,     R_O, E_N, M_CSST | M_E | M(Method) }
+, { "Experimental", nullptr,    MarkType::kExperimental, R_Y, E_N, 0 }
+, { "External",    nullptr,      MarkType::kExternal,    R_Y, E_N, M(Root) }
+, { "File",        nullptr,      MarkType::kFile,        R_N, E_N, M(Track) }
+, { "Formula",     nullptr,      MarkType::kFormula,     R_O, E_N, M_ST | M(Method) | M_D }
+, { "Function",    nullptr,      MarkType::kFunction,    R_O, E_N, M(Example) }
+, { "Height",      nullptr,      MarkType::kHeight,      R_N, E_N, M(Example) }
+, { "Image",       nullptr,      MarkType::kImage,       R_N, E_N, M(Example) }
+, { "Legend",      nullptr,      MarkType::kLegend,      R_Y, E_N, M(Table) }
+, { "",            nullptr,      MarkType::kLink,        R_Y, E_N, M(Anchor) }
+, { "List",        nullptr,      MarkType::kList,        R_Y, E_N, M(Method) | M_CSST | M_E | M_D }
+, { "",            nullptr,      MarkType::kMarkChar,    R_N, E_N, 0 }
+, { "Member",      nullptr,      MarkType::kMember,      R_Y, E_N, M(Class) | M(Struct) }
+, { "Method",      &fMethodMap,  MarkType::kMethod,      R_Y, E_Y, M_CSST }
+, { "NoExample",   nullptr,      MarkType::kNoExample,   R_Y, E_N, 0 }
+, { "Param",       nullptr,      MarkType::kParam,       R_Y, E_N, M(Method) }
+, { "Platform",    nullptr,      MarkType::kPlatform,    R_Y, E_N, M(Example) }
+, { "Private",     nullptr,      MarkType::kPrivate,     R_N, E_N, 0 }
+, { "Return",      nullptr,      MarkType::kReturn,      R_Y, E_N, M(Method) }
+, { "",            nullptr,      MarkType::kRoot,        R_Y, E_N, 0 }
+, { "",            nullptr,      MarkType::kRow,         R_Y, E_N, M(Table) | M(List) }
+, { "SeeAlso",     nullptr,      MarkType::kSeeAlso,     R_Y, E_N, M_CSST | M_E | M(Method) }
+, { "StdOut",      nullptr,      MarkType::kStdOut,      R_N, E_N, M(Example) }
+, { "Struct",      &fClassMap,   MarkType::kStruct,      R_Y, E_O, M(Class) | M(Root) | M_ST }
+, { "Substitute",  nullptr,      MarkType::kSubstitute,  R_N, E_N, M_ST }
+, { "Subtopic",    nullptr,      MarkType::kSubtopic,    R_Y, E_Y, M_CSST }
+, { "Table",       nullptr,      MarkType::kTable,       R_Y, E_N, M(Method) | M_CSST | M_E }
+, { "Template",    nullptr,      MarkType::kTemplate,    R_Y, E_N, 0 }
+, { "",            nullptr,      MarkType::kText,        R_Y, E_N, 0 }
+, { "Time",        nullptr,      MarkType::kTime,        R_Y, E_N, M(Track) }
+, { "ToDo",        nullptr,      MarkType::kToDo,        R_N, E_N, 0 }
+, { "Topic",       nullptr,      MarkType::kTopic,       R_Y, E_Y, M_CS | M(Root) | M(Topic) }
+, { "Track",       nullptr,      MarkType::kTrack,       R_Y, E_N, M_E | M_ST }
+, { "Typedef",     &fTypedefMap, MarkType::kTypedef,     R_Y, E_N, M(Subtopic) | M(Topic) }
+, { "",            nullptr,      MarkType::kUnion,       R_Y, E_N, 0 }
+, { "Volatile",    nullptr,      MarkType::kVolatile,    R_N, E_N, M(StdOut) }
+, { "Width",       nullptr,      MarkType::kWidth,       R_N, E_N, M(Example) } }
+        {
+            this->reset();
+        }
+
+#undef R_O
+#undef R_N
+#undef R_Y
+
+#undef M_E
+#undef M_CSST
+#undef M_ST
+#undef M_CS
+#undef M_D
+#undef M
+
+    ~BmhParser() override {}
+
+    bool addDefinition(const char* defStart, bool hasEnd, MarkType markType,
+            const vector<string>& typeNameBuilder);
+    bool childOf(MarkType markType) const;
+    string className(MarkType markType);
+    bool collectExternals();
+    int endHashCount() const;
+
+    RootDefinition* findBmhObject(MarkType markType, const string& typeName) {
+        auto map = fMaps[(int) markType].fBmh;
+        if (!map) {
+            return nullptr;
+        }
+        return &(*map)[typeName];
+    }
+
+    bool findDefinitions();
+    MarkType getMarkType(MarkLookup lookup) const;
+    bool hasEndToken() const;
+    string memberName();
+    string methodName();
+    const Definition* parentSpace() const;
+
+    bool parseFromFile(const char* path) override {
+        if (!INHERITED::parseSetup(path)) {
+            return false;
+        }
+        fCheckMethods = !strstr(path, "undocumented.bmh");
+        return findDefinitions();
+    }
+
+    bool popParentStack(Definition* definition);
+
+    void reset() override {
+        INHERITED::resetCommon();
+        fRoot = nullptr;
+        fMC = '#';
+        fInChar = false;
+        fInCharCommentString = false;
+        fInComment = false;
+        fInEnum = false;
+        fInString = false;
+        fCheckMethods = false;
+    }
+
+    bool skipNoName();
+    bool skipToDefinitionEnd(MarkType markType);
+    void spellCheck(const char* match) const;
+    vector<string> topicName();
+    vector<string> typeName(MarkType markType, bool* expectEnd);
+    string uniqueName(const string& base, MarkType markType);
+    string uniqueRootName(const string& base, MarkType markType);
+    void validate() const;
+    string word(const string& prefix, const string& delimiter);
+
+public:
+    struct DefinitionMap {
+        const char* fName;
+        unordered_map<string, RootDefinition>* fBmh;
+        MarkType fMarkType;
+        Resolvable fResolve;
+        Exemplary fExemplary;  // worthy of an example
+        uint64_t fParentMask;
+    };
+    
+    DefinitionMap fMaps[Last_MarkType + 1];
+    forward_list<RootDefinition> fTopics;
+    forward_list<Definition> fMarkup;
+    forward_list<RootDefinition> fExternals;
+    vector<string> fInputFiles;
+    unordered_map<string, RootDefinition> fClassMap;
+    unordered_map<string, RootDefinition> fConstMap;
+    unordered_map<string, RootDefinition> fEnumMap;
+    unordered_map<string, RootDefinition> fMethodMap;
+    unordered_map<string, RootDefinition> fTypedefMap;
+    unordered_map<string, Definition*> fTopicMap;
+    unordered_map<string, Definition*> fAliasMap;
+    RootDefinition* fRoot;
+    mutable char fMC;  // markup character
+    bool fAnonymous;
+    bool fCloned;
+    bool fInChar;
+    bool fInCharCommentString;
+    bool fInEnum;
+    bool fInComment;
+    bool fInString;
+    bool fCheckMethods;
+
+private:
+    typedef ParserCommon INHERITED;
+};
+
+class IncludeParser : public ParserCommon {
+public:
+    enum class IsStruct {
+        kNo,
+        kYes,
+    };
+
+    IncludeParser() : ParserCommon()
+        , fMaps {
+          { nullptr,        MarkType::kNone }
+        , { nullptr,        MarkType::kAnchor }
+        , { nullptr,        MarkType::kAlias }
+        , { nullptr,        MarkType::kBug }
+        , { nullptr,        MarkType::kClass }
+        , { nullptr,        MarkType::kCode }
+        , { nullptr,        MarkType::kColumn }
+        , { nullptr,        MarkType::kComment }
+        , { nullptr,        MarkType::kConst }
+        , { &fIDefineMap,   MarkType::kDefine }
+        , { nullptr,        MarkType::kDefinedBy }
+        , { nullptr,        MarkType::kDeprecated }
+        , { nullptr,        MarkType::kDescription }
+        , { nullptr,        MarkType::kDoxygen }
+        , { &fIEnumMap,     MarkType::kEnum }
+        , { &fIEnumMap,     MarkType::kEnumClass }
+        , { nullptr,        MarkType::kError }
+        , { nullptr,        MarkType::kExample }
+        , { nullptr,        MarkType::kExperimental }
+        , { nullptr,        MarkType::kExternal }
+        , { nullptr,        MarkType::kFile }
+        , { nullptr,        MarkType::kFormula }
+        , { nullptr,        MarkType::kFunction }
+        , { nullptr,        MarkType::kHeight }
+        , { nullptr,        MarkType::kImage }
+        , { nullptr,        MarkType::kLegend }
+        , { nullptr,        MarkType::kLink }
+        , { nullptr,        MarkType::kList }
+        , { nullptr,        MarkType::kMarkChar }
+        , { nullptr,        MarkType::kMember }
+        , { nullptr,        MarkType::kMethod }
+        , { nullptr,        MarkType::kNoExample }
+        , { nullptr,        MarkType::kParam }
+        , { nullptr,        MarkType::kPlatform }
+        , { nullptr,        MarkType::kPrivate }
+        , { nullptr,        MarkType::kReturn }
+        , { nullptr,        MarkType::kRoot }
+        , { nullptr,        MarkType::kRow }
+        , { nullptr,        MarkType::kSeeAlso }
+        , { nullptr,        MarkType::kStdOut }
+        , { &fIStructMap,   MarkType::kStruct }
+        , { nullptr,        MarkType::kSubstitute }
+        , { nullptr,        MarkType::kSubtopic }
+        , { nullptr,        MarkType::kTable }
+        , { &fITemplateMap, MarkType::kTemplate }
+        , { nullptr,        MarkType::kText }
+        , { nullptr,        MarkType::kTime }
+        , { nullptr,        MarkType::kToDo }
+        , { nullptr,        MarkType::kTopic }
+        , { nullptr,        MarkType::kTrack }
+        , { &fITypedefMap,  MarkType::kTypedef }
+        , { &fIUnionMap,    MarkType::kUnion }
+        , { nullptr,        MarkType::kVolatile }
+        , { nullptr,        MarkType::kWidth } }
+    {
+        this->reset();
+    }
+
+    ~IncludeParser() override {}
+
+    void addKeyword(KeyWord keyWord);
+
+    void addPunctuation(Punctuation punctuation) {
+        fParent->fTokens.emplace_back(punctuation, fChar, fLineCount, fParent);
+    }
+
+    void addWord() {
+        fParent->fTokens.emplace_back(fIncludeWord, fChar, fLineCount, fParent);
+        fIncludeWord = nullptr;
+    }
+
+    void checkForMissingParams(const vector<string>& methodParams,
+                               const vector<string>& foundParams);
+    bool checkForWord();
+    string className() const;
+    bool crossCheck(BmhParser& );
+    IClassDefinition* defineClass(const Definition& includeDef, const string& className);
+    void dumpClassTokens(IClassDefinition& classDef);
+    void dumpComment(Definition* token);
+    void dumpTokens();
+    bool findComments(const Definition& includeDef, Definition* markupDef);
+
+    Definition* findIncludeObject(const Definition& includeDef, MarkType markType,
+            const string& typeName) {
+        typedef Definition* DefinitionPtr;
+        unordered_map<string, Definition>* map = fMaps[(int) markType].fInclude;
+        if (!map) {
+            return reportError<DefinitionPtr>("invalid mark type");
+        }
+        string name = this->uniqueName(*map, typeName);
+        Definition& markupDef = (*map)[name];
+        if (markupDef.fStart) {
+            return reportError<DefinitionPtr>("definition already defined");
+        }
+        markupDef.fFileName = fFileName;
+        markupDef.fStart = includeDef.fStart;
+        markupDef.fContentStart = includeDef.fStart;
+        markupDef.fName = name;
+        markupDef.fContentEnd = includeDef.fContentEnd;
+        markupDef.fTerminator = includeDef.fTerminator;
+        markupDef.fParent = fParent;
+        markupDef.fLineCount = includeDef.fLineCount;
+        markupDef.fMarkType = markType;
+        markupDef.fKeyWord = includeDef.fKeyWord;
+        markupDef.fType = Definition::Type::kMark;
+        return &markupDef;
+    }
+
+    static KeyWord FindKey(const char* start, const char* end);
+    void keywordEnd();
+    void keywordStart(const char* keyword);
+    bool parseChar();
+    bool parseComment(const string& filename, const char* start, const char* end, int lineCount,
+            Definition* markupDef);
+    bool parseClass(Definition* def, IsStruct);
+    bool parseDefine();
+    bool parseEnum(Definition* child, Definition* markupDef);
+
+    bool parseFromFile(const char* path) override {
+        if (!INHERITED::parseSetup(path)) {
+            return false;
+        }
+        string name(path);
+        return parseInclude(name);
+    }
+
+    bool parseInclude(const string& name);
+    bool parseMember(Definition* child, Definition* markupDef);
+    bool parseMethod(Definition* child, Definition* markupDef);
+    bool parseObject(Definition* child, Definition* markupDef);
+    bool parseObjects(Definition* parent, Definition* markupDef);
+    bool parseTemplate();
+    bool parseTypedef();
+    bool parseUnion();
+
+    void popBracket() {
+        SkASSERT(Definition::Type::kBracket == fParent->fType);
+        this->popObject();
+        Bracket bracket = this->topBracket();
+        this->setBracketShortCuts(bracket);
+    }
+
+    void pushBracket(Bracket bracket) {
+        this->setBracketShortCuts(bracket);
+        fParent->fTokens.emplace_back(bracket, fChar, fLineCount, fParent);
+        Definition* container = &fParent->fTokens.back();
+        this->addDefinition(container);
+    }
+
+    void reset() override {
+        INHERITED::resetCommon();
+        fRootTopic = nullptr;
+        fInBrace = nullptr;
+        fIncludeWord = nullptr;
+        fPrev = '\0';
+        fInChar = false;
+        fInCharCommentString = false;
+        fInComment = false;
+        fInEnum = false;
+        fInFunction = false;
+        fInString = false;
+    }
+
+    void setBracketShortCuts(Bracket bracket) {
+        fInComment = Bracket::kSlashSlash == bracket || Bracket::kSlashStar == bracket;
+        fInString = Bracket::kString == bracket;
+        fInChar = Bracket::kChar == bracket;
+        fInCharCommentString = fInChar || fInComment || fInString;
+    }
+
+    Bracket topBracket() const {
+        Definition* parent = fParent;
+        while (parent && Definition::Type::kBracket != parent->fType) {
+            parent = parent->fParent;
+        }
+        return parent ? parent->fBracket : Bracket::kNone;
+    }
+
+    template <typename T>
+    string uniqueName(const unordered_map<string, T>& m, const string& typeName) {
+        string base(typeName.size() > 0 ? typeName : "_anonymous");
+        string name(base);
+        int anonCount = 1;
+        do {
+            auto iter = m.find(name);
+            if (iter == m.end()) {
+                return name;
+            }
+            name = base + '_';
+            name += to_string(++anonCount);
+        } while (true);
+        // should never get here
+        return string();
+    }
+
+    void validate() const;
+
+protected:
+    static void ValidateKeyWords();
+
+    struct DefinitionMap {
+        unordered_map<string, Definition>* fInclude;
+        MarkType fMarkType;
+    };
+    
+    DefinitionMap fMaps[Last_MarkType + 1];
+    unordered_map<string, Definition> fIncludeMap;
+    unordered_map<string, IClassDefinition> fIClassMap;
+    unordered_map<string, Definition> fIDefineMap;
+    unordered_map<string, Definition> fIEnumMap;
+    unordered_map<string, Definition> fIStructMap;
+    unordered_map<string, Definition> fITemplateMap;
+    unordered_map<string, Definition> fITypedefMap;
+    unordered_map<string, Definition> fIUnionMap;
+    Definition* fRootTopic;
+    Definition* fInBrace;
+    const char* fIncludeWord;
+    char fPrev;
+    bool fInChar;
+    bool fInCharCommentString;
+    bool fInComment;
+    bool fInEnum;
+    bool fInFunction;
+    bool fInString;
+
+    typedef ParserCommon INHERITED;
+};
+
+class IncludeWriter : public IncludeParser {
+public:
+    enum class Word {
+        kStart,
+        kCap,
+        kFirst,
+        kUnderline,
+        kMixed,
+    };
+
+    enum class PunctuationState {
+        kStart,
+        kDelimiter,
+        kPeriod,
+    };
+
+    enum class Wrote {
+        kNone,
+        kLF,
+        kChars,
+    };
+
+    IncludeWriter() : IncludeParser() {}
+    ~IncludeWriter() override {}
+
+    bool contentFree(int size, const char* data) const {
+        while (size > 0 && data[0] <= ' ') {
+            --size;
+            ++data;
+        }
+        while (size > 0 && data[size - 1] <= ' ') {
+            --size;
+        }
+        return 0 == size;
+    }
+
+    void enumHeaderOut(const RootDefinition* root, const Definition& child);
+    void enumMembersOut(const RootDefinition* root, const Definition& child);
+    void enumSizeItems(const Definition& child);
+    int lookupMethod(const PunctuationState punctuation, const Word word,
+            const int start, const int run, int lastWrite, const char last, 
+            const char* data);
+    int lookupReference(const PunctuationState punctuation, const Word word,
+            const int start, const int run, int lastWrite, const char last, 
+            const char* data);
+    void methodOut(const Definition* method);
+    bool populate(Definition* def, RootDefinition* root);
+    bool populate(BmhParser& bmhParser);
+
+    void reset() override {
+        INHERITED::resetCommon();
+        fBmhParser = nullptr;
+        fEnumDef = nullptr;
+        fStructDef = nullptr;
+        fAnonymousEnumCount = 1;
+        fInStruct = false;
+    }
+
+    string resolveMethod(const char* start, const char* end, bool first);
+    string resolveRef(const char* start, const char* end, bool first);
+    Wrote rewriteBlock(int size, const char* data);
+    void structMemberOut(const Definition* memberStart, const Definition& child);
+    void structOut(const Definition* root, const Definition& child,
+            const char* commentStart, const char* commentEnd);
+    void structSizeMembers(Definition& child);
+
+private:
+    BmhParser* fBmhParser;
+    Definition* fDeferComment;
+    const Definition* fEnumDef;
+    const Definition* fStructDef;
+    const char* fContinuation;  // used to construct paren-qualified method name
+    int fAnonymousEnumCount;
+    int fEnumItemValueTab;
+    int fEnumItemCommentTab;
+    int fStructMemberTab;
+    int fStructCommentTab;
+    bool fInStruct;
+
+    typedef IncludeParser INHERITED;
+};
+
+class FiddleParser : public ParserCommon {
+public:
+    FiddleParser(BmhParser* bmh) : ParserCommon()
+        , fBmhParser(bmh) {
+        this->reset();
+    }
+
+    Definition* findExample(const string& name) const;
+
+    bool parseFromFile(const char* path) override {
+        if (!INHERITED::parseSetup(path)) {
+            return false;
+        }
+        return parseFiddles();
+    }
+
+    void reset() override {
+        INHERITED::resetCommon();
+    }
+
+private:
+    bool parseFiddles();
+
+    BmhParser* fBmhParser;  // must be writable; writes example hash
+
+    typedef ParserCommon INHERITED;
+};
+
+class HackParser : public ParserCommon {
+public:
+    HackParser() : ParserCommon() {
+        this->reset();
+    }
+
+    bool parseFromFile(const char* path) override {
+        if (!INHERITED::parseSetup(path)) {
+            return false;
+        }
+        return hackFiles();
+    }
+
+    void reset() override {
+        INHERITED::resetCommon();
+    }
+
+private:
+    bool hackFiles();
+
+    typedef ParserCommon INHERITED;
+};
+
+class MdOut : public ParserCommon {
+public:
+    MdOut(const BmhParser& bmh) : ParserCommon()
+        , fBmhParser(bmh) {
+        this->reset();
+    }
+
+    bool buildReferences(const char* path, const char* outDir);
+private:
+    enum class TableState {
+        kNone,
+        kRow,
+        kColumn,
+    };
+
+    string addReferences(const char* start, const char* end, BmhParser::Resolvable );
+    bool buildRefFromFile(const char* fileName, const char* outDir);
+    void childrenOut(const Definition* def, const char* contentStart);
+    const Definition* isDefined(const TextParser& parser, const string& ref, bool report) const;
+    string linkName(const Definition* ) const;
+    string linkRef(const string& leadingSpaces, const Definition*, const string& ref) const;
+    void markTypeOut(Definition* );
+    void mdHeaderOut(int depth) { mdHeaderOutLF(depth, 2); } 
+    void mdHeaderOutLF(int depth, int lf);
+    bool parseFromFile(const char* path) override {
+        return true;
+    }
+
+    void reset() override {
+        INHERITED::resetCommon();
+        fMethod = nullptr;
+        fRoot = nullptr;
+        fTableState = TableState::kNone;
+        fHasFiddle = false;
+        fInDescription = false;
+        fInList = false;
+    }
+
+    BmhParser::Resolvable resolvable(MarkType markType) {
+        if ((MarkType::kExample == markType 
+                || MarkType::kFunction == markType) && fHasFiddle) {
+            return BmhParser::Resolvable::kNo;
+        }
+        return fBmhParser.fMaps[(int) markType].fResolve;
+    }
+
+    void resolveOut(const char* start, const char* end, BmhParser::Resolvable );
+
+    const BmhParser& fBmhParser;
+    Definition* fMethod;
+    RootDefinition* fRoot;
+    TableState fTableState;
+    bool fHasFiddle;
+    bool fInDescription;   // FIXME: for now, ignore unfound camelCase in description since it may
+                           // be defined in example which at present cannot be linked to
+    bool fInList;
+    typedef ParserCommon INHERITED;
+};
+
+
+// some methods cannot be trivially parsed; look for class-name / ~ / operator
+class MethodParser : public TextParser {
+public:
+    MethodParser(const string& className, const string& fileName,
+            const char* start, const char* end, int lineCount)
+        : TextParser(fileName, start, end, lineCount)
+        , fClassName(className) {
+    }
+
+    void skipToMethodStart() {
+        if (!fClassName.length()) {
+            this->skipToAlphaNum();
+            return;
+        }
+        while (!this->eof() && !isalnum(this->peek()) && '~' != this->peek()) {
+            this->next();
+        }
+    }
+
+    void skipToMethodEnd() {
+        if (this->eof()) {
+            return;
+        }
+        if (fClassName.length()) {
+            if ('~' == this->peek()) {
+                this->next();
+                if (!this->startsWith(fClassName.c_str())) {
+                    --fChar;
+                    return;
+                }
+            }
+            if (this->startsWith(fClassName.c_str()) || this->startsWith("operator")) {
+                const char* ptr = this->anyOf(" (");
+                if (ptr && '(' ==  *ptr) {
+                    this->skipToEndBracket(')');
+                    SkAssertResult(')' == this->next());
+                    return;
+                }
+            }
+        }
+        if (this->startsWith("Sk") && this->wordEndsWith(".h")) {  // allow include refs
+            this->skipToNonAlphaNum();
+        } else {
+            this->skipFullName();
+        }
+    }
+
+    bool wordEndsWith(const char* str) const {
+        const char* space = this->strnchr(' ', fEnd);
+        if (!space) {
+            return false;
+        }
+        size_t len = strlen(str);
+        if (space < fChar + len) {
+            return false;
+        }
+        return !strncmp(str, space - len, len);
+    }
+
+private:
+    string fClassName;
+    typedef TextParser INHERITED;
+};
+
+#endif
diff --git a/tools/bookmaker/fiddleParser.cpp b/tools/bookmaker/fiddleParser.cpp
new file mode 100644
index 0000000..d5cfcf4
--- /dev/null
+++ b/tools/bookmaker/fiddleParser.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bookmaker.h"
+
+static Definition* find_fiddle(Definition* def, const string& name) {
+    if (MarkType::kExample == def->fMarkType && name == def->fFiddle) {
+        return def;
+    }
+    for (auto& child : def->fChildren) {
+        Definition* result = find_fiddle(child, name);
+        if (result) {
+            return result;
+        }
+    }
+    return nullptr;
+}
+
+Definition* FiddleParser::findExample(const string& name) const {
+    for (const auto& topic : fBmhParser->fTopicMap) {
+        if (topic.second->fParent) {
+            continue;
+        }
+        Definition* def = find_fiddle(topic.second, name);
+        if (def) {
+            return def;
+        }
+    }
+    return nullptr;
+}
+
+bool FiddleParser::parseFiddles() {
+    if (!this->skipExact("{\n")) {
+        return false;
+    }
+    while (!this->eof()) {
+        if (!this->skipExact("  \"")) {
+            return false;
+        }
+        const char* nameLoc = fChar;
+        if (!this->skipToEndBracket("\"")) {
+            return false;
+        }
+        string name(nameLoc, fChar - nameLoc);
+        if (!this->skipExact("\": {\n")) {
+            return false;
+        }
+        if (!this->skipExact("    \"compile_errors\": [")) {
+            return false;
+        }
+        if (']' != this->peek()) {
+            // report compiler errors
+            int brackets = 1;
+            const char* errorStart = fChar;
+            do {
+                if ('[' == this->peek()) {
+                    ++brackets;
+                } else if (']' == this->peek()) {
+                    --brackets;
+                }
+            } while (!this->eof() && this->next() && brackets > 0);
+            SkDebugf("fiddle compile error in %s: %.*s\n", name.c_str(), (int) (fChar - errorStart),
+                    errorStart);
+        }
+        if (!this->skipExact("],\n")) {
+            return false;
+        }
+        if (!this->skipExact("    \"runtime_error\": \"")) {
+            return false;
+        }
+        if ('"' != this->peek()) {
+            const char* errorStart = fChar;
+            if (!this->skipToEndBracket('"')) {
+                return false;
+            }
+            SkDebugf("fiddle runtime error in %s: %.*s\n", name.c_str(), (int) (fChar - errorStart),
+                    errorStart);
+        }
+        if (!this->skipExact("\",\n")) {
+            return false;
+        }
+        if (!this->skipExact("    \"fiddleHash\": \"")) {
+            return false;
+        }
+        const char* hashStart = fChar;
+        if (!this->skipToEndBracket('"')) {
+            return false;
+        }
+        Definition* example = this->findExample(name);
+        if (!example) {
+            SkDebugf("missing example %s\n", name.c_str());
+        }
+        string hash(hashStart, fChar - hashStart);
+        if (example) {
+            example->fHash = hash;
+        }
+        if (!this->skipExact("\",\n")) {
+            return false;
+        }
+        if (!this->skipExact("    \"text\": \"")) {
+            return false;
+        }
+        if ('"' != this->peek()) {
+            const char* stdOutStart = fChar;
+            do {
+                if ('\\' == this->peek()) {
+                    this->next();
+                } else if ('"' == this->peek()) {
+                    break;
+                }
+            } while (!this->eof() && this->next());
+            const char* stdOutEnd = fChar;
+            if (example) {
+                bool foundStdOut = false;
+                for (auto& textOut : example->fChildren) {
+                    if (MarkType::kStdOut != textOut->fMarkType) {
+                        continue;
+                    }
+                    foundStdOut = true;
+                    bool foundVolatile = false;
+                    for (auto& stdOutChild : textOut->fChildren) {
+                         if (MarkType::kVolatile == stdOutChild->fMarkType) {
+                             foundVolatile = true;
+                             break;
+                         }
+                    }
+                    TextParser bmh(textOut);
+                    EscapeParser fiddle(stdOutStart, stdOutEnd);
+                    do {
+                        bmh.skipWhiteSpace();
+                        fiddle.skipWhiteSpace();
+                        const char* bmhEnd = bmh.trimmedLineEnd();
+                        const char* fiddleEnd = fiddle.trimmedLineEnd();
+                        ptrdiff_t bmhLen = bmhEnd - bmh.fChar;
+                        SkASSERT(bmhLen > 0);
+                        ptrdiff_t fiddleLen = fiddleEnd - fiddle.fChar;
+                        SkASSERT(fiddleLen > 0);
+                        if (bmhLen != fiddleLen) {
+                            if (!foundVolatile) {
+                                SkDebugf("mismatched stdout len in %s\n", name.c_str());
+                            }
+                        } else  if (strncmp(bmh.fChar, fiddle.fChar, fiddleLen)) {
+                            if (!foundVolatile) {
+                                SkDebugf("mismatched stdout text in %s\n", name.c_str());
+                            }
+                        }
+                        bmh.skipToLineStart();
+                        fiddle.skipToLineStart();
+                    } while (!bmh.eof() && !fiddle.eof());
+                    if (!foundStdOut) {
+                        SkDebugf("bmh %s missing stdout\n", name.c_str());
+                    } else if (!bmh.eof() || !fiddle.eof()) {
+                        if (!foundVolatile) {
+                            SkDebugf("%s mismatched stdout eof\n", name.c_str());
+                        }
+                    }
+                }
+            }
+        }
+        if (!this->skipExact("\"\n")) {
+            return false;
+        }
+        if (!this->skipExact("  }")) {
+            return false;
+        }
+        if ('\n' == this->peek()) {
+            break;
+        }
+        if (!this->skipExact(",\n")) {
+            return false;
+        }
+    }
+#if 0
+            // compare the text output with the expected output in the markup tree
+            this->skipToSpace();
+            SkASSERT(' ' == fChar[0]);
+            this->next();
+            const char* nameLoc = fChar;
+            this->skipToNonAlphaNum();
+            const char* nameEnd = fChar;
+            string name(nameLoc, nameEnd - nameLoc);
+            const Definition* example = this->findExample(name);
+            if (!example) {
+                return this->reportError<bool>("missing stdout name");
+            }
+            SkASSERT(':' == fChar[0]);
+            this->next();
+            this->skipSpace();
+            const char* stdOutLoc = fChar;
+            do {
+                this->skipToLineStart();
+            } while (!this->eof() && !this->startsWith("fiddles.htm:"));
+            const char* stdOutEnd = fChar;
+            for (auto& textOut : example->fChildren) {
+                if (MarkType::kStdOut != textOut->fMarkType) {
+                    continue;
+                }
+                TextParser bmh(textOut);
+                TextParser fiddle(fFileName, stdOutLoc, stdOutEnd, fLineCount);
+                do {
+                    bmh.skipWhiteSpace();
+                    fiddle.skipWhiteSpace();
+                    const char* bmhEnd = bmh.trimmedLineEnd();
+                    const char* fiddleEnd = fiddle.trimmedLineEnd();
+                    ptrdiff_t bmhLen = bmhEnd - bmh.fChar;
+                    SkASSERT(bmhLen > 0);
+                    ptrdiff_t fiddleLen = fiddleEnd - fiddle.fChar;
+                    SkASSERT(fiddleLen > 0);
+                    if (bmhLen != fiddleLen) {
+                        return this->reportError<bool>("mismatched stdout len");
+                    }
+                    if (strncmp(bmh.fChar, fiddle.fChar, fiddleLen)) {
+                        return this->reportError<bool>("mismatched stdout text");
+                    }
+                    bmh.skipToLineStart();
+                    fiddle.skipToLineStart();
+                } while (!bmh.eof() && !fiddle.eof());
+                if (!bmh.eof() || (!fiddle.eof() && !fiddle.startsWith("</pre>"))) {
+                    return this->reportError<bool>("mismatched stdout eof");
+                }
+                break;
+            }
+        }
+    }
+#endif
+    return true;
+}
diff --git a/tools/bookmaker/includeParser.cpp b/tools/bookmaker/includeParser.cpp
new file mode 100644
index 0000000..089dcb3
--- /dev/null
+++ b/tools/bookmaker/includeParser.cpp
@@ -0,0 +1,1733 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bookmaker.h"
+
+enum class KeyProperty {
+    kNone,
+    kClassSection,
+    kFunction,
+    kModifier,
+    kNumber,
+    kObject,
+    kPreprocessor,
+};
+
+struct IncludeKey {
+    const char* fName;
+    KeyWord fKeyWord;
+    KeyProperty fProperty;
+};
+
+const IncludeKey kKeyWords[] = {
+    { "",           KeyWord::kNone,         KeyProperty::kNone           },
+    { "bool",       KeyWord::kBool,         KeyProperty::kNumber         },
+    { "char",       KeyWord::kChar,         KeyProperty::kNumber         },
+    { "class",      KeyWord::kClass,        KeyProperty::kObject         },
+    { "const",      KeyWord::kConst,        KeyProperty::kModifier       },
+    { "constexpr",  KeyWord::kConstExpr,    KeyProperty::kModifier       },
+    { "define",     KeyWord::kDefine,       KeyProperty::kPreprocessor   },
+    { "double",     KeyWord::kDouble,       KeyProperty::kNumber         },
+    { "elif",       KeyWord::kElif,         KeyProperty::kPreprocessor   },
+    { "else",       KeyWord::kElse,         KeyProperty::kPreprocessor   },
+    { "endif",      KeyWord::kEndif,        KeyProperty::kPreprocessor   },
+    { "enum",       KeyWord::kEnum,         KeyProperty::kObject         },
+    { "float",      KeyWord::kFloat,        KeyProperty::kNumber         },
+    { "friend",     KeyWord::kFriend,       KeyProperty::kModifier       },
+    { "if",         KeyWord::kIf,           KeyProperty::kPreprocessor   },
+    { "ifdef",      KeyWord::kIfdef,        KeyProperty::kPreprocessor   },
+    { "ifndef",     KeyWord::kIfndef,       KeyProperty::kPreprocessor   },
+    { "include",    KeyWord::kInclude,      KeyProperty::kPreprocessor   },
+    { "inline",     KeyWord::kInline,       KeyProperty::kModifier       },
+    { "int",        KeyWord::kInt,          KeyProperty::kNumber         },
+    { "operator",   KeyWord::kOperator,     KeyProperty::kFunction       },
+    { "private",    KeyWord::kPrivate,      KeyProperty::kClassSection   },
+    { "protected",  KeyWord::kProtected,    KeyProperty::kClassSection   },
+    { "public",     KeyWord::kPublic,       KeyProperty::kClassSection   },
+    { "signed",     KeyWord::kSigned,       KeyProperty::kNumber         },
+    { "size_t",     KeyWord::kSize_t,       KeyProperty::kNumber         },
+    { "static",     KeyWord::kStatic,       KeyProperty::kModifier       },
+    { "struct",     KeyWord::kStruct,       KeyProperty::kObject         },
+    { "template",   KeyWord::kTemplate,     KeyProperty::kObject         },
+    { "typedef",    KeyWord::kTypedef,      KeyProperty::kObject         },
+    { "uint32_t",   KeyWord::kUint32_t,     KeyProperty::kNumber         },
+    { "union",      KeyWord::kUnion,        KeyProperty::kObject         },
+    { "unsigned",   KeyWord::kUnsigned,     KeyProperty::kNumber         },
+    { "void",       KeyWord::kVoid,         KeyProperty::kNumber         },
+};
+
+const size_t kKeyWordCount = SK_ARRAY_COUNT(kKeyWords);
+
+KeyWord IncludeParser::FindKey(const char* start, const char* end) {
+    int ch = 0;
+    for (size_t index = 0; index < kKeyWordCount; ) {
+        if (start[ch] > kKeyWords[index].fName[ch]) {
+            ++index;
+            if (ch > 0 && kKeyWords[index - 1].fName[ch - 1] < kKeyWords[index].fName[ch - 1]) {
+                return KeyWord::kNone;
+            }
+            continue;
+        }
+        if (start[ch] < kKeyWords[index].fName[ch]) {
+            return KeyWord::kNone;
+        }
+        ++ch;
+        if (start + ch >= end) {
+            return kKeyWords[index].fKeyWord;
+        }
+    }
+    return KeyWord::kNone;
+}
+
+void IncludeParser::ValidateKeyWords() {
+    for (size_t index = 1; index < kKeyWordCount; ++index) {
+        SkASSERT((int) kKeyWords[index - 1].fKeyWord + 1
+                == (int) kKeyWords[index].fKeyWord);
+        SkASSERT(0 > strcmp(kKeyWords[index - 1].fName, kKeyWords[index].fName));
+    }
+}
+
+void IncludeParser::addKeyword(KeyWord keyWord) {
+    fParent->fTokens.emplace_back(keyWord, fIncludeWord, fChar, fLineCount, fParent);
+    fIncludeWord = nullptr;
+    if (KeyProperty::kObject == kKeyWords[(int) keyWord].fProperty) {
+        Definition* def = &fParent->fTokens.back();
+        this->addDefinition(def);
+        if (KeyWord::kEnum == fParent->fKeyWord) {
+            fInEnum = true;
+        }
+    }
+}
+
+void IncludeParser::checkForMissingParams(const vector<string>& methodParams, 
+        const vector<string>& foundParams) {
+    for (auto& methodParam : methodParams) {
+        bool found = false;
+        for (auto& foundParam : foundParams) {
+            if (methodParam == foundParam) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            this->keywordStart("Param");
+            fprintf(fOut, "%s  ", methodParam.c_str());
+            this->keywordEnd();
+        }
+    }
+    for (auto& foundParam : foundParams) {
+        bool found = false;
+        for (auto& methodParam : methodParams) {
+            if (methodParam == foundParam) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            this->reportError("doxygen param does not match method declaration");
+        }
+    }
+}
+
+bool IncludeParser::checkForWord() {
+    if (!fIncludeWord) {
+        return true;
+    }
+    KeyWord keyWord = FindKey(fIncludeWord, fChar);
+    if (KeyWord::kNone != keyWord) {
+        if (KeyProperty::kPreprocessor != kKeyWords[(int) keyWord].fProperty) {
+            this->addKeyword(keyWord);
+            return true;
+        }
+    } else {
+        this->addWord();
+        return true;
+    }
+    Definition* poundDef = fParent;
+    if (!fParent) {
+        return reportError<bool>("expected parent");
+    }
+    if (Definition::Type::kBracket != poundDef->fType) {
+        return reportError<bool>("expected bracket");
+    }
+    if (Bracket::kPound != poundDef->fBracket) {
+        return reportError<bool>("expected preprocessor");
+    }
+    if (KeyWord::kNone != poundDef->fKeyWord) {
+        return reportError<bool>("already found keyword");
+    }
+    poundDef->fKeyWord = keyWord;
+    fIncludeWord = nullptr;
+    switch (keyWord) {
+        // these do not link to other # directives
+        case KeyWord::kDefine:
+        case KeyWord::kInclude:
+        break;
+        // these start a # directive link
+        case KeyWord::kIf:
+        case KeyWord::kIfdef:
+        case KeyWord::kIfndef:
+        break;
+        // these continue a # directive link
+        case KeyWord::kElif:
+        case KeyWord::kElse: {
+            this->popObject();  // pop elif
+            if (Bracket::kPound != fParent->fBracket) {
+                return this->reportError<bool>("expected preprocessor directive");
+            }
+            this->popBracket();  // pop if
+            poundDef->fParent = fParent;
+            this->addDefinition(poundDef);  // push elif back
+        } break;
+        // this ends a # directive link
+        case KeyWord::kEndif:
+        // FIXME : should this be calling popBracket() instead?
+            this->popObject();  // pop endif
+            if (Bracket::kPound != fParent->fBracket) {
+                return this->reportError<bool>("expected preprocessor directive");
+            }
+            this->popBracket();  // pop if/else
+        break;
+        default:
+            SkASSERT(0);
+    }
+    return true;
+}
+
+string IncludeParser::className() const {
+    string name(fParent->fName);
+    size_t slash = name.find_last_of("/");
+    if (string::npos == slash) {
+        slash = name.find_last_of("\\");
+    }
+    SkASSERT(string::npos != slash);
+    string result = name.substr(slash);
+    result = result.substr(1, result.size() - 3);
+    return result;
+}
+
+bool IncludeParser::crossCheck(BmhParser& bmhParser) {
+    string className = this->className();
+    string classPrefix = className + "::";
+    RootDefinition* root = &bmhParser.fClassMap[className];
+    root->clearVisited();
+    for (auto& classMapper : fIClassMap) {
+        if (className != classMapper.first
+                && classPrefix != classMapper.first.substr(0, classPrefix.length())) {
+            continue;
+        }
+        auto& classMap = classMapper.second;
+        auto& tokens = classMap.fTokens;
+        for (const auto& token : tokens) {
+            if (token.fPrivate) {
+                continue;
+            }
+            string fullName = classMapper.first + "::" + token.fName;
+            const Definition* def = root->find(fullName);
+            switch (token.fMarkType) {
+                case MarkType::kMethod: {
+                    if (0 == token.fName.find("internal_")
+                            || 0 == token.fName.find("Internal_")
+                            || 0 == token.fName.find("legacy_")
+                            || 0 == token.fName.find("temporary_")) {
+                        continue;
+                    }
+                    const char* methodID = bmhParser.fMaps[(int) token.fMarkType].fName;
+                    if (!def) {
+                        string paramName = className + "::";
+                        paramName += string(token.fContentStart,
+                                token.fContentEnd - token.fContentStart);
+                        def = root->find(paramName);
+                        if (!def && 0 == token.fName.find("operator")) {
+                            string operatorName = className + "::";
+                            TextParser oper("", token.fStart, token.fContentEnd, 0);
+                            const char* start = oper.strnstr("operator", token.fContentEnd);
+                            SkASSERT(start);
+                            oper.skipTo(start);
+                            oper.skipToEndBracket('(');
+                            int parens = 0;
+                            do {
+                                if ('(' == oper.peek()) {
+                                    ++parens;
+                                } else if (')' == oper.peek()) {
+                                    --parens;
+                                }
+                            } while (!oper.eof() && oper.next() && parens > 0);
+                            operatorName += string(start, oper.fChar - start);
+                            def = root->find(operatorName);
+                        }
+                    }
+                    if (!def) {
+                        int skip = !strncmp(token.fContentStart, "explicit ", 9) ? 9 : 0;
+                        skip = !strncmp(token.fContentStart, "virtual ", 8) ? 8 : skip;
+                        string constructorName = className + "::";
+                        constructorName += string(token.fContentStart + skip,
+                                token.fContentEnd - token.fContentStart - skip);
+                        def = root->find(constructorName);
+                    }
+                    if (!def && 0 == token.fName.find("SK_")) {
+                        string incName = token.fName + "()";
+                        string macroName = className + "::" + incName;
+                        def = root->find(macroName);
+                        if (def) {
+                            if (def->fName == incName) {
+                                def->fVisited = true;
+                                if ("SK_TO_STRING_NONVIRT" == token.fName) {
+                                    def = root->find(className + "::toString");
+                                    if (def) {
+                                        def->fVisited = true;
+                                    } else {
+                                        SkDebugf("missing toString bmh: %s\n", fullName.c_str());
+                                    }
+                                }
+                                break;
+                            } else {
+                                SkDebugf("method macro differs from bmh: %s\n", fullName.c_str());
+                            }
+                        }
+                    }
+                    if (!def) {
+                        bool allLower = true;
+                        for (size_t index = 0; index < token.fName.length(); ++index) {
+                            if (!islower(token.fName[index])) {
+                                allLower = false;
+                                break;
+                            }
+                        }
+                        if (allLower) {
+                            string lowerName = className + "::" + token.fName + "()";
+                            def = root->find(lowerName);
+                        }
+                    }
+                    if (!def) {
+                        SkDebugf("method missing from bmh: %s\n", fullName.c_str());
+                        break;
+                    }
+                    if (def->crossCheck(methodID, token)) {
+                        def->fVisited = true;
+                    } else {
+                       SkDebugf("method differs from bmh: %s\n", fullName.c_str());
+                    }
+                } break;
+                case MarkType::kComment:
+                    break;
+                case MarkType::kEnum: {
+                    if (!def) {
+                        // work backwards from first word to deduce #Enum name
+                        TextParser firstMember("", token.fStart, token.fContentEnd, 0);
+                        SkAssertResult(firstMember.skipName("enum"));
+                        SkAssertResult(firstMember.skipToEndBracket('{'));
+                        firstMember.next();
+                        firstMember.skipWhiteSpace();
+                        SkASSERT('k' == firstMember.peek());
+                        const char* savePos = firstMember.fChar;
+                        firstMember.skipToNonAlphaNum();
+                        const char* wordEnd = firstMember.fChar;
+                        firstMember.fChar = savePos; 
+                        const char* lastUnderscore = nullptr;
+                        do {
+                            if (!firstMember.skipToEndBracket('_')) {
+                                break;
+                            }
+                            if (firstMember.fChar > wordEnd) {
+                                break;
+                            }
+                            lastUnderscore = firstMember.fChar;
+                        } while (firstMember.next());
+                        if (lastUnderscore) {
+                            ++lastUnderscore;
+                            string anonName = className + "::" + string(lastUnderscore, 
+                                    wordEnd - lastUnderscore) + 's';
+                            def = root->find(anonName);
+                        }
+                        if (!def) {
+                            SkDebugf("enum missing from bmh: %s\n", fullName.c_str());
+                            break;
+                        }
+                    }
+                    def->fVisited = true;
+                    for (auto& child : def->fChildren) {
+                        if (MarkType::kCode == child->fMarkType) {
+                            def = child;
+                            break;
+                        }
+                    }
+                    if (MarkType::kCode != def->fMarkType) {
+                        SkDebugf("enum code missing from bmh: %s\n", fullName.c_str());
+                        break;
+                    }
+                    if (def->crossCheck(token)) {
+                        def->fVisited = true;
+                    } else {
+                       SkDebugf("enum differs from bmh: %s\n", def->fName.c_str());
+                    }
+                    for (auto& child : token.fChildren) {
+                        string constName = className + "::" + child->fName;
+                        def = root->find(constName);
+                        if (!def) {
+                            string innerName = classMapper.first + "::" + child->fName;
+                            def = root->find(innerName);
+                        }
+                        if (!def) {
+                            if (string::npos == child->fName.find("Legacy_")) {
+                                SkDebugf("const missing from bmh: %s\n", constName.c_str());
+                            }
+                        } else {
+                            def->fVisited = true;
+                        }
+                    }
+                    } break;
+                case MarkType::kMember:
+                    if (def) {
+                        def->fVisited = true;
+                    } else {
+                        SkDebugf("member missing from bmh: %s\n", fullName.c_str());
+                    }
+                    break;
+                default:
+                    SkASSERT(0);  // unhandled 
+                    break;
+            }
+        }
+    }
+    if (!root->dumpUnVisited()) {
+        SkDebugf("some struct elements not found; struct finding in includeParser is missing\n");
+    }
+    return true;
+}
+
+IClassDefinition* IncludeParser::defineClass(const Definition& includeDef,
+        const string& name) {
+    string className;
+    const Definition* test = fParent;
+    while (Definition::Type::kFileType != test->fType) {
+        if (Definition::Type::kMark == test->fType && KeyWord::kClass == test->fKeyWord) {
+            className = test->fName + "::";
+            break;
+        }
+        test = test->fParent;
+    }
+    className += name; 
+    unordered_map<string, IClassDefinition>& map = fIClassMap;
+    IClassDefinition& markupDef = map[className];
+    if (markupDef.fStart) {
+        typedef IClassDefinition* IClassDefPtr;
+        return INHERITED::reportError<IClassDefPtr>("class already defined");
+    }
+    markupDef.fFileName = fFileName;
+    markupDef.fStart = includeDef.fStart;
+    markupDef.fContentStart = includeDef.fStart;
+    markupDef.fName = className;
+    markupDef.fContentEnd = includeDef.fContentEnd;
+    markupDef.fTerminator = includeDef.fTerminator;
+    markupDef.fParent = fParent;
+    markupDef.fLineCount = fLineCount;
+    markupDef.fMarkType = KeyWord::kStruct == includeDef.fKeyWord ?
+            MarkType::kStruct : MarkType::kClass;
+    markupDef.fKeyWord = includeDef.fKeyWord;
+    markupDef.fType = Definition::Type::kMark;
+    fParent = &markupDef;
+    return &markupDef;
+}
+
+void IncludeParser::dumpClassTokens(IClassDefinition& classDef) {
+    auto& tokens = classDef.fTokens;
+    for (auto& token : tokens) {
+        if (Definition::Type::kMark == token.fType && MarkType::kComment == token.fMarkType) {
+            continue;
+        }
+        if (MarkType::kMember != token.fMarkType) {
+            fprintf(fOut, "%s",
+              "# ------------------------------------------------------------------------------\n");
+            fprintf(fOut, ""                                                                  "\n");
+        }
+        switch (token.fMarkType) {
+            case MarkType::kEnum:
+                fprintf(fOut, "#Enum %s"                                                     "\n",
+                        token.fName.c_str());
+                fprintf(fOut, ""                                                             "\n");
+                fprintf(fOut, "#Code"                                                        "\n");
+                fprintf(fOut, "    enum %s {"                                                "\n",
+                        token.fName.c_str());
+                for (auto& child : token.fChildren) {
+                    fprintf(fOut, "        %s %.*s"                                          "\n",
+                            child->fName.c_str(), child->length(), child->fContentStart);
+                }
+                fprintf(fOut, "    };"                                                       "\n");
+                fprintf(fOut, "##"                                                           "\n");
+                fprintf(fOut, ""                                                             "\n");
+                this->dumpComment(&token);
+                for (auto& child : token.fChildren) {
+                    fprintf(fOut, "#Const %s", child->fName.c_str());
+                    TextParser val(child);
+                    if (!val.eof()) {
+                        if ('=' == val.fStart[0] || ',' == val.fStart[0]) {
+                            val.next();
+                            val.skipSpace();
+                            const char* valEnd = val.anyOf(",\n");
+                            if (!valEnd) {
+                                valEnd = val.fEnd;
+                            }
+                            fprintf(fOut, " %.*s", (int) (valEnd - val.fStart), val.fStart);
+                        } else {
+                            fprintf(fOut, " %.*s", 
+                                    (int) (child->fContentEnd - child->fContentStart),
+                                    child->fContentStart);
+                        }
+                    }
+                    fprintf(fOut, ""                                                         "\n");
+                    for (auto& token : child->fTokens) {
+                        if (MarkType::kComment == token.fMarkType) {
+                            this->dumpComment(&token);
+                        }
+                    }
+                    fprintf(fOut, "##"                                                       "\n");
+                }
+                fprintf(fOut, ""                                                             "\n");
+            break;
+            case MarkType::kMethod:
+                fprintf(fOut, "#Method %.*s"                                                 "\n",
+                        token.length(), token.fStart);
+                lfAlways(1);
+                this->dumpComment(&token);
+            break;
+            case MarkType::kMember:
+                this->keywordStart("Member");
+                fprintf(fOut, "%.*s  %s  ", (int) (token.fContentEnd - token.fContentStart),
+                        token.fContentStart, token.fName.c_str());           
+                lfAlways(1);
+                for (auto child : token.fChildren) {
+                    fprintf(fOut, "%.*s", (int) (child->fContentEnd - child->fContentStart),
+                            child->fContentStart);
+                    lfAlways(1);
+                }
+                this->keywordEnd();
+                continue;
+            break;
+            default:
+                SkASSERT(0);
+        }
+        this->lf(2);
+        fprintf(fOut, "#Example"                                                             "\n");
+        fprintf(fOut, "##"                                                                   "\n");
+        fprintf(fOut, ""                                                                     "\n");
+        fprintf(fOut, "#ToDo incomplete ##"                                                  "\n");
+        fprintf(fOut, ""                                                                     "\n");
+        fprintf(fOut, "##"                                                                   "\n");
+        fprintf(fOut, ""                                                                     "\n");
+    }
+}
+void IncludeParser::dumpComment(Definition* token) {
+    fLineCount = token->fLineCount;
+    fChar = fLine = token->fContentStart;
+    fEnd = token->fContentEnd;
+    bool sawParam = false;
+    bool multiline = false;
+    bool sawReturn = false;
+    bool sawComment = false;
+    bool methodHasReturn = false;
+    vector<string> methodParams;
+    vector<string> foundParams;
+    Definition methodName;
+    TextParser methodParser(token->fFileName, token->fContentStart, token->fContentEnd,
+            token->fLineCount);
+    if (MarkType::kMethod == token->fMarkType) {
+        methodName.fName = string(token->fContentStart,
+                (int) (token->fContentEnd - token->fContentStart));
+        methodHasReturn = !methodParser.startsWith("void ")
+                && !methodParser.strnchr('~', methodParser.fEnd);
+        const char* paren = methodParser.strnchr('(', methodParser.fEnd);
+        const char* nextEnd = paren;
+        do {
+            string paramName;
+            methodParser.fChar = nextEnd + 1;
+            methodParser.skipSpace();
+            if (!methodName.nextMethodParam(&methodParser, &nextEnd, &paramName)) {
+                continue;
+            }
+            methodParams.push_back(paramName);
+        } while (')' != nextEnd[0]);
+    }
+    for (const auto& child : token->fTokens) {
+        if (Definition::Type::kMark == child.fType && MarkType::kComment == child.fMarkType) {
+            if ('@' == child.fContentStart[0]) {
+                TextParser parser(&child);
+                do {
+                    parser.next();
+                    if (parser.startsWith("param ")) {
+                        parser.skipWord("param");
+                        const char* parmStart = parser.fChar;
+                        parser.skipToSpace();
+                        string parmName = string(parmStart, (int) (parser.fChar - parmStart));
+                        parser.skipWhiteSpace();
+                        do {
+                            size_t nextComma = parmName.find(',');
+                            string piece;
+                            if (string::npos == nextComma) {
+                                piece = parmName;
+                                parmName = "";
+                            } else {
+                                piece = parmName.substr(0, nextComma);
+                                parmName = parmName.substr(nextComma + 1);
+                            }
+                            if (sawParam) {
+                                if (multiline) {
+                                    this->lfAlways(1);
+                                }
+                                this->keywordEnd();
+                            } else {
+                                if (sawComment) {
+                                    this->nl();
+                                }
+                                this->lf(2);
+                            }
+                            foundParams.emplace_back(piece);
+                            this->keywordStart("Param");
+                            fprintf(fOut, "%s  ", piece.c_str());
+                            fprintf(fOut, "%.*s", (int) (parser.fEnd - parser.fChar), parser.fChar);
+                            this->lfAlways(1);
+                            sawParam = true;
+                            sawComment = false;
+                        } while (parmName.length());
+                        parser.skipTo(parser.fEnd);
+                    } else if (parser.startsWith("return ") || parser.startsWith("returns ")) {
+                        parser.skipWord("return");
+                        if ('s' == parser.peek()) {
+                            parser.next();
+                        }
+                        if (sawParam) {
+                            if (multiline) {
+                                this->lfAlways(1);
+                            }
+                            this->keywordEnd();
+                        }
+                        this->checkForMissingParams(methodParams, foundParams);
+                        sawParam = false;
+                        sawComment = false;
+                        multiline = false;
+                        this->lf(2);
+                        this->keywordStart("Return");
+                        fprintf(fOut, "%.*s ", (int) (parser.fEnd - parser.fChar),
+                                parser.fChar);
+                        this->lfAlways(1);
+                        sawReturn = true;
+                        parser.skipTo(parser.fEnd);
+                    } else {
+                        this->reportError("unexpected doxygen directive");
+                    }
+                } while (!parser.eof());
+            } else {
+                if (sawComment) {
+                    this->nl();
+                }
+                this->lf(1);
+                fprintf(fOut, "%.*s ", child.length(), child.fContentStart);
+                sawComment = true;
+                if (sawParam || sawReturn) {
+                    multiline = true;
+                }
+            }
+        }
+    }
+    if (sawParam || sawReturn) {
+        if (multiline) {
+            this->lfAlways(1);
+        }
+        this->keywordEnd();
+    }
+    if (!sawReturn) {
+        if (!sawParam) {
+            if (sawComment) {
+                this->nl();
+            }
+            this->lf(2);
+        }
+        this->checkForMissingParams(methodParams, foundParams);
+    }
+    if (methodHasReturn != sawReturn) {
+        if (!methodHasReturn) {
+            this->reportError("unexpected doxygen return");
+        } else {
+            if (sawComment) {
+                this->nl();
+            }
+            this->lf(2);
+            this->keywordStart("Return");
+            this->keywordEnd();
+        }
+    }
+}
+
+    // dump equivalent markup 
+void IncludeParser::dumpTokens()  {
+    string skClassName = this->className();
+    string fileName = skClassName + ".bmh";
+    fOut = fopen(fileName.c_str(), "wb");
+    if (!fOut) {
+        SkDebugf("could not open output file %s\n", fileName.c_str());
+        return;
+    }
+    string prefixName = skClassName.substr(0, 2);
+    string topicName = skClassName.length() > 2 && isupper(skClassName[2]) &&
+        ("Sk" == prefixName || "Gr" == prefixName) ? skClassName.substr(2) : skClassName;
+    fprintf(fOut, "#Topic %s", topicName.c_str());
+    this->lfAlways(2);
+    fprintf(fOut, "#Class %s", skClassName.c_str());
+    this->lfAlways(2);
+    auto& classMap = fIClassMap[skClassName];
+    auto& tokens = classMap.fTokens;
+    for (auto& token : tokens) {
+        if (Definition::Type::kMark != token.fType || MarkType::kComment != token.fMarkType) {
+            continue;
+        }
+        fprintf(fOut, "%.*s", (int) (token.fContentEnd - token.fContentStart),
+                token.fContentStart);
+        this->lfAlways(1);
+    }
+    this->lf(2);
+    string className(skClassName.substr(2));
+    vector<string> sortedClasses;
+    size_t maxLen = 0;
+    for (const auto& oneClass : fIClassMap) {
+        if (skClassName + "::" != oneClass.first.substr(0, skClassName.length() + 2)) {
+            continue;
+        }
+        string structName = oneClass.first.substr(skClassName.length() + 2);
+        maxLen = SkTMax(maxLen, structName.length());
+        sortedClasses.emplace_back(structName);
+    }
+    fprintf(fOut, "#Topic Overview");
+    this->lfAlways(2);
+    fprintf(fOut, "#Subtopic %s_Structs", className.c_str());
+    this->lfAlways(1);
+    fprintf(fOut, "#Table");
+    this->lfAlways(1);
+    fprintf(fOut, "#Legend");
+    this->lfAlways(1);
+    fprintf(fOut, "# %-*s # description ##", (int) maxLen, "struct");
+    this->lfAlways(1);
+    fprintf(fOut, "#Legend ##");
+    this->lfAlways(1);
+    fprintf(fOut, "#Table ##");
+    this->lfAlways(1);
+    for (auto& name : sortedClasses) {
+        fprintf(fOut, "# %-*s # ##", (int) maxLen, name.c_str());
+        this->lfAlways(1);
+    }
+    fprintf(fOut, "#Subtopic ##");
+    this->lfAlways(2);
+    fprintf(fOut, "#Subtopic %s_Member_Functions", className.c_str());
+    this->lfAlways(1);
+    fprintf(fOut, "#Table");
+    this->lfAlways(1);
+    fprintf(fOut, "#Legend");
+    this->lfAlways(1);
+    maxLen = 0;
+    vector<string> sortedNames;
+    for (const auto& token : classMap.fTokens) {
+        if (Definition::Type::kMark != token.fType || MarkType::kMethod != token.fMarkType) {
+            continue;
+        }
+        const string& name = token.fName;
+        if (name.substr(0, 7) == "android" || string::npos != name.find("nternal_")) {
+            continue;
+        }
+        if (name[name.length() - 2] == '_' && isdigit(name[name.length() - 1])) {
+            continue;
+        }
+        size_t paren = name.find('(');
+        size_t funcLen = string::npos == paren ? name.length() : paren;
+        maxLen = SkTMax(maxLen, funcLen);
+        sortedNames.emplace_back(name);
+    }
+    std::sort(sortedNames.begin(), sortedNames.end());
+    fprintf(fOut, "# %-*s # description   ##" "\n",
+            (int) maxLen, "function");
+    fprintf(fOut, "#Legend ##"                                                               "\n");
+    for (auto& name : sortedNames) {
+        size_t paren = name.find('(');
+        size_t funcLen = string::npos == paren ? name.length() : paren;
+        fprintf(fOut, "# %-*s # ##"                                                          "\n",
+                (int) maxLen, name.substr(0, funcLen).c_str());
+    }
+    fprintf(fOut, "#Table ##"                                                                "\n");
+    fprintf(fOut, "#Subtopic ##"                                                             "\n");
+    fprintf(fOut, ""                                                                         "\n");
+    fprintf(fOut, "#Topic ##"                                                                "\n");
+    fprintf(fOut, ""                                                                         "\n");
+
+    for (auto& oneClass : fIClassMap) {
+        if (skClassName + "::" != oneClass.first.substr(0, skClassName.length() + 2)) {
+            continue;
+        }
+        string innerName = oneClass.first.substr(skClassName.length() + 2);
+        fprintf(fOut, "%s",
+            "# ------------------------------------------------------------------------------");
+        this->lfAlways(2);
+        fprintf(fOut, "#Struct %s", innerName.c_str());
+        this->lfAlways(2);
+        for (auto& token : oneClass.second.fTokens) {
+            if (Definition::Type::kMark != token.fType || MarkType::kComment != token.fMarkType) {
+                continue;
+            }
+            fprintf(fOut, "%.*s", (int) (token.fContentEnd - token.fContentStart),
+                    token.fContentStart);
+            this->lfAlways(1);
+        }
+        this->lf(2);
+        this->dumpClassTokens(oneClass.second);
+        this->lf(2);
+        fprintf(fOut, "#Struct %s ##", innerName.c_str());
+        this->lfAlways(2);
+    }
+    this->dumpClassTokens(classMap);
+    fprintf(fOut, "#Class %s ##"                                                             "\n",
+            skClassName.c_str());
+    fprintf(fOut, ""                                                                         "\n");
+    fprintf(fOut, "#Topic %s ##"                                                             "\n",
+            topicName.c_str());
+    fclose(fOut);
+}
+
+bool IncludeParser::findComments(const Definition& includeDef, Definition* markupDef) {
+    // add comment preceding class, if any
+    const Definition* parent = includeDef.fParent;
+    int index = includeDef.fParentIndex;
+    auto wordIter = parent->fTokens.begin();
+    std::advance(wordIter, index);
+    SkASSERT(&*wordIter == &includeDef);
+    while (parent->fTokens.begin() != wordIter) {
+        auto testIter = std::prev(wordIter);
+        if (Definition::Type::kWord != testIter->fType
+            && Definition::Type::kKeyWord != testIter->fType
+            && (Definition::Type::kBracket != testIter->fType
+            || Bracket::kAngle != testIter->fBracket)
+            && (Definition::Type::kPunctuation != testIter->fType
+            || Punctuation::kAsterisk != testIter->fPunctuation)) {
+            break;
+        }
+        wordIter = testIter;
+    }
+    auto commentIter = wordIter;
+    while (parent->fTokens.begin() != commentIter) {
+        auto testIter = std::prev(commentIter);
+        bool isComment = Definition::Type::kBracket == testIter->fType
+                && (Bracket::kSlashSlash == testIter->fBracket
+                || Bracket::kSlashStar == testIter->fBracket);
+        if (!isComment) {
+            break;
+        }
+        commentIter = testIter;
+    }
+    while (commentIter != wordIter) {
+        if (!this->parseComment(commentIter->fFileName, commentIter->fContentStart,
+                commentIter->fContentEnd, commentIter->fLineCount, markupDef)) {
+            return false;
+        }
+        commentIter = std::next(commentIter);
+    }
+    return true;
+}
+
+// caller calls reportError, so just return false here
+bool IncludeParser::parseClass(Definition* includeDef, IsStruct isStruct) {
+    SkASSERT(includeDef->fTokens.size() > 0);
+    if (includeDef->fTokens.size() == 1) {
+        return true;  // forward declaration only
+    }
+    // parse class header
+    auto iter = includeDef->fTokens.begin();
+    if (!strncmp(iter->fStart, "SK_API", iter->fContentEnd - iter->fStart)) {
+        // todo : documentation is ignoring this for now
+        iter = std::next(iter);
+    }
+    string nameStr(iter->fStart, iter->fContentEnd - iter->fStart);
+    includeDef->fName = nameStr;
+    do {
+        if (iter == includeDef->fTokens.end()) {
+            return false;
+        }
+        if ('{' == iter->fStart[0] && Definition::Type::kPunctuation == iter->fType) {
+            break;
+        }   
+    } while (static_cast<void>(iter = std::next(iter)), true);
+    if (Punctuation::kLeftBrace != iter->fPunctuation) {
+        return false;
+    }
+    IClassDefinition* markupDef = this->defineClass(*includeDef, nameStr);
+    if (!markupDef) {
+        return false;
+    }
+    markupDef->fStart = iter->fStart;
+    if (!this->findComments(*includeDef, markupDef)) {
+        return false;
+    }
+//    if (1 != includeDef->fChildren.size()) {
+//        return false;  // fix me: SkCanvasClipVisitor isn't correctly parsed
+//    }
+    includeDef = includeDef->fChildren.front();
+    iter = includeDef->fTokens.begin();
+    // skip until public
+    int publicIndex = 0;
+    if (IsStruct::kNo == isStruct) {
+        const char* publicName = kKeyWords[(int) KeyWord::kPublic].fName;
+        size_t publicLen = strlen(publicName);
+        while (iter != includeDef->fTokens.end()
+                && (publicLen != (size_t) (iter->fContentEnd - iter->fStart)
+                || strncmp(iter->fStart, publicName, publicLen))) {
+            iter = std::next(iter);
+            ++publicIndex;
+        }
+    }
+    auto childIter = includeDef->fChildren.begin();
+    while (childIter != includeDef->fChildren.end() && (*childIter)->fParentIndex < publicIndex) {
+        (*childIter)->fPrivate = true;
+        childIter = std::next(childIter);
+    }
+    int lastPublic = publicIndex;
+    const char* protectedName = kKeyWords[(int) KeyWord::kProtected].fName;
+    size_t protectedLen = strlen(protectedName);
+    const char* privateName = kKeyWords[(int) KeyWord::kPrivate].fName;
+    size_t privateLen = strlen(privateName);
+    while (iter != includeDef->fTokens.end()
+            && (protectedLen != (size_t) (iter->fContentEnd - iter->fStart)
+            || strncmp(iter->fStart, protectedName, protectedLen))
+            && (privateLen != (size_t) (iter->fContentEnd - iter->fStart)
+            || strncmp(iter->fStart, privateName, privateLen))) {
+        iter = std::next(iter);
+        ++lastPublic;
+    }
+    while (childIter != includeDef->fChildren.end() && (*childIter)->fParentIndex < lastPublic) {
+        Definition* child = *childIter;
+        if (!this->parseObject(child, markupDef)) {
+            return false;
+        }
+        childIter = std::next(childIter);
+    }
+    while (childIter != includeDef->fChildren.end()) {
+        (*childIter)->fPrivate = true;
+        childIter = std::next(childIter);
+    }
+    SkASSERT(fParent->fParent);
+    fParent = fParent->fParent;
+    return true;
+}
+
+bool IncludeParser::parseComment(const string& filename, const char* start, const char* end,
+        int lineCount, Definition* markupDef) {
+    TextParser parser(filename, start, end, lineCount);
+    // parse doxygen if present
+    if (parser.startsWith("**")) {
+        parser.next();
+        parser.next();
+        parser.skipWhiteSpace();
+        if ('\\' == parser.peek()) {
+            parser.next();
+            if (!parser.skipWord(kKeyWords[(int) markupDef->fKeyWord].fName)) {
+                return reportError<bool>("missing object type");
+            }
+            if (!parser.skipWord(markupDef->fName.c_str())) {
+                return reportError<bool>("missing object name");
+            }
+
+        }
+    }
+    // remove leading '*' if present
+    Definition* parent = markupDef->fTokens.size() ? &markupDef->fTokens.back() : markupDef;
+    while (!parser.eof() && parser.skipWhiteSpace()) {
+        while ('*' == parser.peek()) {
+            parser.next();
+            if (parser.eof()) {
+                break;
+            }
+            parser.skipWhiteSpace();
+        }
+        if (parser.eof()) {
+            break;
+        }
+        const char* lineEnd = parser.trimmedLineEnd();
+        markupDef->fTokens.emplace_back(MarkType::kComment, parser.fChar, lineEnd, 
+                parser.fLineCount, parent);
+        parser.skipToEndBracket('\n');
+    }
+    return true;
+}
+
+bool IncludeParser::parseDefine() {
+
+    return true;
+}
+
+bool IncludeParser::parseEnum(Definition* child, Definition* markupDef) {
+    string nameStr;
+    if (child->fTokens.size() > 0) {
+        auto token = child->fTokens.begin();
+        if (Definition::Type::kKeyWord == token->fType && KeyWord::kClass == token->fKeyWord) {
+            token = token->fTokens.begin();
+        }
+        if (Definition::Type::kWord == token->fType) {
+            nameStr += string(token->fStart, token->fContentEnd - token->fStart);
+        }
+    }
+    markupDef->fTokens.emplace_back(MarkType::kEnum, child->fContentStart, child->fContentEnd,
+        child->fLineCount, markupDef);
+    Definition* markupChild = &markupDef->fTokens.back();
+    SkASSERT(KeyWord::kNone == markupChild->fKeyWord);
+    markupChild->fKeyWord = KeyWord::kEnum;
+    TextParser enumName(child);
+    enumName.skipExact("enum ");
+    const char* nameStart = enumName.fChar;
+    enumName.skipToSpace();
+    markupChild->fName = markupDef->fName + "::" + 
+            string(nameStart, (size_t) (enumName.fChar - nameStart));
+    if (!this->findComments(*child, markupChild)) {
+        return false;
+    }
+    TextParser parser(child);
+    parser.skipToEndBracket('{');
+    const char* dataEnd;
+    do {
+        parser.next();
+        parser.skipWhiteSpace();
+        if ('}' == parser.peek()) {
+            break;
+        }
+        Definition* comment = nullptr;
+        // note that comment, if any, can be before or after (on the same line, though) as member
+        if ('#' == parser.peek()) {
+            // fixme: handle preprecessor, but just skip it for now
+            parser.skipToLineStart();
+        }
+        while (parser.startsWith("/*") || parser.startsWith("//")) {
+            parser.next();
+            const char* start = parser.fChar;
+            const char* end;
+            if ('*' == parser.peek()) {
+                end = parser.strnstr("*/", parser.fEnd);
+                parser.fChar = end;
+                parser.next();
+                parser.next();
+            } else {
+                end = parser.trimmedLineEnd();
+                parser.skipToLineStart();
+            }
+            markupChild->fTokens.emplace_back(MarkType::kComment, start, end, parser.fLineCount,
+                    markupChild);
+            comment = &markupChild->fTokens.back();
+            comment->fTerminator = end;
+            if (!this->parseComment(parser.fFileName, start, end, parser.fLineCount, comment)) {
+                return false;
+            }
+            parser.skipWhiteSpace();
+        }
+        parser.skipWhiteSpace();
+        const char* memberStart = parser.fChar;
+        if ('}' == memberStart[0]) {
+            break;
+        }
+        parser.skipToNonAlphaNum();
+        string memberName(memberStart, parser.fChar);
+        parser.skipWhiteSpace();
+        const char* dataStart = parser.fChar;
+        SkASSERT('=' == dataStart[0] || ',' == dataStart[0] || '}' == dataStart[0]
+                 || '/' == dataStart[0]);
+        dataEnd = parser.anyOf(",}");
+        markupChild->fTokens.emplace_back(MarkType::kMember, dataStart, dataEnd, parser.fLineCount,
+                markupChild);
+        Definition* member = &markupChild->fTokens.back();
+        member->fName = memberName;
+        if (comment) {
+            member->fChildren.push_back(comment);
+        }
+        markupChild->fChildren.push_back(member);
+        parser.skipToEndBracket(dataEnd[0]);
+    } while (',' == dataEnd[0]);
+    for (size_t index = 1; index < child->fChildren.size(); ++index) {
+        const Definition* follower = child->fChildren[index];
+        if (Definition::Type::kKeyWord == follower->fType) {
+            markupChild->fTokens.emplace_back(MarkType::kMember, follower->fContentStart, 
+                    follower->fContentEnd, follower->fLineCount, markupChild);
+            Definition* member = &markupChild->fTokens.back();
+            member->fName = follower->fName;
+            markupChild->fChildren.push_back(member);
+        }
+    }
+    IClassDefinition& classDef = fIClassMap[markupDef->fName];
+    SkASSERT(classDef.fStart);
+    string uniqueName = this->uniqueName(classDef.fEnums, nameStr);
+    markupChild->fName = uniqueName;
+    classDef.fEnums[uniqueName] = markupChild;
+    return true;
+}
+
+bool IncludeParser::parseInclude(const string& name) {
+    fParent = &fIncludeMap[name];
+    fParent->fName = name;
+    fParent->fFileName = fFileName;
+    fParent->fType = Definition::Type::kFileType;
+    fParent->fContentStart = fChar;
+    fParent->fContentEnd = fEnd;
+    // parse include file into tree
+    while (fChar < fEnd) {
+        if (!this->parseChar()) {
+            return false;
+        }
+    }
+    // parse tree and add named objects to maps
+    fParent = &fIncludeMap[name];
+    if (!this->parseObjects(fParent, nullptr)) {
+        return false;
+    }
+    return true;
+}
+
+bool IncludeParser::parseMember(Definition* child, Definition* markupDef) {
+    const char* typeStart = child->fChildren[0]->fContentStart;
+    markupDef->fTokens.emplace_back(MarkType::kMember, typeStart, child->fContentStart,
+        child->fLineCount, markupDef);
+    Definition* markupChild = &markupDef->fTokens.back();
+    TextParser nameParser(child);
+    nameParser.skipToNonAlphaNum();
+    string nameStr = string(child->fContentStart, nameParser.fChar - child->fContentStart);
+    IClassDefinition& classDef = fIClassMap[markupDef->fName];
+    string uniqueName = this->uniqueName(classDef.fMethods, nameStr);
+    markupChild->fName = uniqueName;
+    classDef.fMembers[uniqueName] = markupChild;
+    if (child->fParentIndex >= 2) {
+        auto comment = child->fParent->fTokens.begin();
+        std::advance(comment, child->fParentIndex - 2);
+        if (Definition::Type::kBracket == comment->fType
+                && (Bracket::kSlashStar == comment->fBracket
+                || Bracket::kSlashSlash == comment->fBracket)) {
+            TextParser parser(&*comment);
+            do {
+                parser.skipToAlpha();
+                if (parser.eof()) {
+                    break;
+                }
+                const char* start = parser.fChar;
+                const char* end = parser.trimmedBracketEnd('\n', OneLine::kYes);
+                if (Bracket::kSlashStar == comment->fBracket) {
+                    const char* commentEnd = parser.strnstr("*/", end);
+                    if (commentEnd) {
+                        end = commentEnd;
+                    }
+                }
+                markupDef->fTokens.emplace_back(MarkType::kComment, start, end, child->fLineCount,
+                        markupDef);
+                Definition* commentChild = &markupDef->fTokens.back();
+                markupChild->fChildren.emplace_back(commentChild);
+                parser.skipTo(end);
+            } while (!parser.eof());
+        }
+    }
+    return true;
+}
+
+bool IncludeParser::parseMethod(Definition* child, Definition* markupDef) {
+    auto tokenIter = child->fParent->fTokens.begin();
+    std::advance(tokenIter, child->fParentIndex);
+    tokenIter = std::prev(tokenIter);
+    string nameStr(tokenIter->fStart, tokenIter->fContentEnd - tokenIter->fStart);
+    while (tokenIter != child->fParent->fTokens.begin()) {
+        auto testIter = std::prev(tokenIter);
+        switch (testIter->fType) {
+            case Definition::Type::kWord:
+                goto keepGoing;
+            case Definition::Type::kKeyWord: {
+                KeyProperty keyProperty = kKeyWords[(int) testIter->fKeyWord].fProperty;
+                if (KeyProperty::kNumber == keyProperty || KeyProperty::kModifier == keyProperty) {
+                    goto keepGoing;
+                }
+            } break;
+            case Definition::Type::kBracket:
+                if (Bracket::kAngle == testIter->fBracket) {
+                    goto keepGoing;
+                }
+                break;
+            case Definition::Type::kPunctuation:
+                if (Punctuation::kSemicolon == testIter->fPunctuation
+                        || Punctuation::kLeftBrace == testIter->fPunctuation
+                        || Punctuation::kColon == testIter->fPunctuation) {
+                    break;
+                }
+            keepGoing:
+                tokenIter = testIter;
+                continue;
+            default:
+                break;
+        }
+        break;
+    }
+    tokenIter->fName = nameStr;
+    tokenIter->fMarkType = MarkType::kMethod;
+    auto testIter = child->fParent->fTokens.begin();
+    SkASSERT(child->fParentIndex > 0);
+    std::advance(testIter, child->fParentIndex - 1);
+    const char* start = tokenIter->fContentStart;
+    const char* end = tokenIter->fContentEnd;
+    const char kDebugCodeStr[] = "SkDEBUGCODE";
+    const size_t kDebugCodeLen = sizeof(kDebugCodeStr) - 1;
+    if (end - start == kDebugCodeLen && !strncmp(start, kDebugCodeStr, kDebugCodeLen)) {
+        std::advance(testIter, 1);
+        start = testIter->fContentStart + 1;
+        end = testIter->fContentEnd - 1;
+    } else {
+        end = testIter->fContentEnd;
+        while (testIter != child->fParent->fTokens.end()) {
+            testIter = std::next(testIter);
+            switch (testIter->fType) {
+                case Definition::Type::kPunctuation:
+                    SkASSERT(Punctuation::kSemicolon == testIter->fPunctuation
+                            || Punctuation::kLeftBrace == testIter->fPunctuation
+                            || Punctuation::kColon == testIter->fPunctuation);
+                    end = testIter->fStart;
+                    break;
+                case Definition::Type::kKeyWord: {
+                    KeyProperty keyProperty = kKeyWords[(int) testIter->fKeyWord].fProperty;
+                    if (KeyProperty::kNumber == keyProperty || KeyProperty::kModifier == keyProperty) {
+                        continue;
+                    }
+                    } break;
+                default:
+                    continue;
+            }
+            break;
+        }
+    }
+    markupDef->fTokens.emplace_back(MarkType::kMethod, start, end, tokenIter->fLineCount,
+            markupDef);
+    Definition* markupChild = &markupDef->fTokens.back();
+    // do find instead -- I wonder if there is a way to prevent this in c++
+    IClassDefinition& classDef = fIClassMap[markupDef->fName];
+    SkASSERT(classDef.fStart);
+    string uniqueName = this->uniqueName(classDef.fMethods, nameStr);
+    markupChild->fName = uniqueName;
+    if (!this->findComments(*child, markupChild)) {
+        return false;
+    }
+    classDef.fMethods[uniqueName] = markupChild;
+    return true;
+}
+
+void IncludeParser::keywordEnd() {
+    fprintf(fOut, "##");
+    this->lfAlways(1);
+}
+
+void IncludeParser::keywordStart(const char* keyword) {
+    this->lf(1);
+    fprintf(fOut, "#%s ", keyword);
+}
+
+bool IncludeParser::parseObjects(Definition* parent, Definition* markupDef) {
+    for (auto& child : parent->fChildren) {
+        if (!this->parseObject(child, markupDef)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool IncludeParser::parseObject(Definition* child, Definition* markupDef) {
+    // set up for error reporting
+    fLine = fChar = child->fStart;
+    fEnd = child->fContentEnd;
+    // todo: put original line number in child as well
+    switch (child->fType) {
+        case Definition::Type::kKeyWord:
+            switch (child->fKeyWord) {
+                case KeyWord::kClass: 
+                    if (!this->parseClass(child, IsStruct::kNo)) {
+                        return this->reportError<bool>("failed to parse class");
+                    }
+                    break;
+                case KeyWord::kEnum:
+                    if (!this->parseEnum(child, markupDef)) {
+                        return this->reportError<bool>("failed to parse enum");
+                    }
+                    break;
+                case KeyWord::kStruct:
+                    if (!this->parseClass(child, IsStruct::kYes)) {
+                        return this->reportError<bool>("failed to parse struct");
+                    }
+                    break;
+                case KeyWord::kTemplate:
+                    if (!this->parseTemplate()) {
+                        return this->reportError<bool>("failed to parse template");
+                    }
+                    break;
+                case KeyWord::kTypedef:
+                    if (!this->parseTypedef()) {
+                        return this->reportError<bool>("failed to parse typedef");
+                    }
+                    break;
+                case KeyWord::kUnion:
+                    if (!this->parseUnion()) {
+                        return this->reportError<bool>("failed to parse union");
+                    }
+                    break;
+                default:
+                    return this->reportError<bool>("unhandled keyword");
+            }
+            break;
+        case Definition::Type::kBracket:
+            switch (child->fBracket) {
+                case Bracket::kParen:
+                    if (!this->parseMethod(child, markupDef)) {
+                        return this->reportError<bool>("failed to parse method");
+                    }
+                    break;
+                case Bracket::kSlashSlash:
+                case Bracket::kSlashStar:
+                    // comments are picked up by parsing objects first
+                    break;
+                case Bracket::kPound:
+                    // special-case the #xxx xxx_DEFINED entries
+                    switch (child->fKeyWord) {
+                        case KeyWord::kIfndef:
+                        case KeyWord::kIfdef:
+                            if (child->boilerplateIfDef(fParent)) {
+                                if (!this->parseObjects(child, markupDef)) {
+                                    return false;
+                                }
+                                break;
+                            }
+                            goto preproError;
+                        case KeyWord::kDefine:
+                            if (child->boilerplateDef(fParent)) {
+                                break;
+                            }
+                            goto preproError;
+                        case KeyWord::kEndif:
+                            if (child->boilerplateEndIf()) {
+                                break;
+                            }
+                        case KeyWord::kInclude:
+                            // ignored for now
+                            break;
+                        case KeyWord::kElse:
+                        case KeyWord::kElif:
+                            // todo: handle these
+                            break;
+                        default:
+                        preproError:
+                            return this->reportError<bool>("unhandled preprocessor");
+                    }
+                    break;
+                case Bracket::kAngle:
+                    // pick up templated function pieces when method is found
+                    break;
+                default:
+                    return this->reportError<bool>("unhandled bracket");
+            }
+            break;
+        case Definition::Type::kWord:
+            if (MarkType::kMember != child->fMarkType) {
+                return this->reportError<bool>("unhandled word type");
+            }
+            if (!this->parseMember(child, markupDef)) {
+                return this->reportError<bool>("unparsable member");
+            }
+            break;
+        default:
+            return this->reportError<bool>("unhandled type");
+            break;
+    }
+    return true;
+}
+
+bool IncludeParser::parseTemplate() {
+
+    return true;
+}
+
+bool IncludeParser::parseTypedef() {
+
+    return true;
+}
+
+bool IncludeParser::parseUnion() {
+
+    return true;
+}
+
+bool IncludeParser::parseChar() {
+    char test = *fChar;
+    if ('\\' == fPrev) {
+        if ('\n' == test) {
+            ++fLineCount;
+            fLine = fChar + 1;
+        }
+        goto done;
+    }
+    switch (test) {
+        case '\n':
+            ++fLineCount;
+            fLine = fChar + 1;
+            if (fInChar) {
+                return reportError<bool>("malformed char");
+            }
+            if (fInString) {
+                return reportError<bool>("malformed string");
+            }
+            if (!this->checkForWord()) {
+                return false;
+            }
+            if (Bracket::kPound == this->topBracket()) {
+                KeyWord keyWord = fParent->fKeyWord;
+                if (KeyWord::kNone == keyWord) {
+                    return this->reportError<bool>("unhandled preprocessor directive");
+                }
+                if (KeyWord::kInclude == keyWord || KeyWord::kDefine == keyWord) {
+                    this->popBracket();
+                }
+            } else if (Bracket::kSlashSlash == this->topBracket()) {
+                this->popBracket();
+            }
+            break;
+        case '*':
+            if (!fInCharCommentString && '/' == fPrev) {
+                this->pushBracket(Bracket::kSlashStar);
+            }
+            if (!this->checkForWord()) {
+                return false;
+            }
+            if (!fInCharCommentString) {
+                this->addPunctuation(Punctuation::kAsterisk);
+            }
+            break;
+        case '/':
+            if ('*' == fPrev) {
+                if (!fInCharCommentString) {
+                    return reportError<bool>("malformed closing comment");
+                }
+                if (Bracket::kSlashStar == this->topBracket()) {
+                    this->popBracket();
+                }
+                break;
+            } 
+            if (!fInCharCommentString && '/' == fPrev) {
+                this->pushBracket(Bracket::kSlashSlash);
+                break;
+            }
+            if (!this->checkForWord()) {
+                return false;
+            }
+            break;
+        case '\'':
+            if (Bracket::kChar == this->topBracket()) {
+                this->popBracket();
+            } else if (!fInComment && !fInString) {
+                if (fIncludeWord) {
+                    return this->reportError<bool>("word then single-quote");
+                }
+                this->pushBracket(Bracket::kChar);
+            }
+            break;
+        case '\"':
+            if (Bracket::kString == this->topBracket()) {
+                this->popBracket();
+            } else if (!fInComment && !fInChar) {
+                if (fIncludeWord) {
+                    return this->reportError<bool>("word then double-quote");
+                }
+                this->pushBracket(Bracket::kString);
+            }
+            break;
+        case ':':
+        case '(':
+        case '[':
+        case '{': {
+            if (fInCharCommentString) {
+                break;
+            }
+            if (':' == test && (fInBrace || ':' == fChar[-1] || ':' == fChar[1])) {
+                break;
+            }
+            if (!fInBrace) {
+                if (!this->checkForWord()) {
+                    return false;
+                }
+                if (':' == test && !fInFunction) {
+                    break;
+                }
+                if ('{' == test) {
+                    this->addPunctuation(Punctuation::kLeftBrace);
+                } else if (':' == test) {
+                    this->addPunctuation(Punctuation::kColon);
+                }
+            }
+            if (fInBrace && '{' == test && Definition::Type::kBracket == fInBrace->fType
+                    && Bracket::kColon == fInBrace->fBracket) {
+                Definition* braceParent = fParent->fParent;
+                braceParent->fChildren.pop_back();
+                braceParent->fTokens.pop_back();
+                fParent = braceParent;
+                fInBrace = nullptr;
+            }
+            this->pushBracket(
+                    '(' == test ? Bracket::kParen :
+                    '[' == test ? Bracket::kSquare :
+                    '{' == test ? Bracket::kBrace :
+                                  Bracket::kColon);
+            if (!fInBrace
+                    && ('{' == test || (':' == test && ' ' >= fChar[1]))
+                    && fInFunction) {
+                fInBrace = fParent;
+            }
+            } break;
+        case '<':
+            if (fInCharCommentString || fInBrace) {
+                break;
+            }
+            if (!this->checkForWord()) {
+                return false;
+            }
+            if (fInEnum) {
+                break;
+            }
+            this->pushBracket(Bracket::kAngle);
+            break;
+        case ')':
+        case ']':
+        case '}': {
+            if (fInCharCommentString) {
+                break;
+            }
+            if (!fInBrace) {
+                if (!this->checkForWord()) {
+                    return false;
+                }
+            }
+            bool popBraceParent = fInBrace == fParent;
+            if ((')' == test ? Bracket::kParen :
+                    ']' == test ? Bracket::kSquare : Bracket::kBrace) == this->topBracket()) {
+                this->popBracket();
+                if (!fInFunction) {
+                    bool deprecatedMacro = false;
+                    if (')' == test) {
+                        auto iter = fParent->fTokens.end();
+                        bool lookForWord = false;
+                        while (fParent->fTokens.begin() != iter) {
+                            --iter;
+                            if (lookForWord) {
+                                if (Definition::Type::kWord != iter->fType) {
+                                    break;
+                                }
+                                string word(iter->fContentStart, iter->length());
+                                if ("SK_ATTR_EXTERNALLY_DEPRECATED" == word) {
+                                    deprecatedMacro = true;
+                                    // remove macro paren (would confuse method parsing later)
+                                    fParent->fTokens.pop_back();  
+                                    fParent->fChildren.pop_back();
+                                }
+                                break;
+                            }
+                            if (Definition::Type::kBracket != iter->fType) {
+                                break;
+                            }
+                            if (Bracket::kParen != iter->fBracket) {
+                                break;
+                            }
+                            lookForWord = true;
+                        }
+                    }
+                    fInFunction = ')' == test && !deprecatedMacro;
+                } else {
+                    fInFunction = '}' != test;
+                }
+            } else {
+                return reportError<bool>("malformed close bracket");
+            }
+            if (popBraceParent) {
+                Definition* braceParent = fInBrace->fParent;
+                braceParent->fChildren.pop_back();
+                braceParent->fTokens.pop_back();
+                fInBrace = nullptr;
+            }
+            } break;
+        case '>':
+            if (fInCharCommentString || fInBrace) {
+                break;
+            }
+            if (!this->checkForWord()) {
+                return false;
+            }
+            if (fInEnum) {
+                break;
+            }
+            if (Bracket::kAngle == this->topBracket()) {
+                this->popBracket();
+            } else {
+                return reportError<bool>("malformed close angle bracket");
+            }
+            break;
+        case '#': {
+            if (fInCharCommentString || fInBrace) {
+                break;
+            }
+            SkASSERT(!fIncludeWord);  // don't expect this, curious if it is triggered
+            this->pushBracket(Bracket::kPound);
+            break;
+        }
+        case '&':
+        case ',':
+        case ' ':
+            if (fInCharCommentString || fInBrace) {
+                break;
+            }
+            if (!this->checkForWord()) {
+                return false;
+            }
+            break;
+        case ';':
+            if (fInCharCommentString || fInBrace) {
+                break;
+            }
+            if (!this->checkForWord()) {
+                return false;
+            }
+            if (Definition::Type::kKeyWord == fParent->fType
+                    && KeyProperty::kObject == (kKeyWords[(int) fParent->fKeyWord].fProperty)) {
+                if (KeyWord::kEnum == fParent->fKeyWord) {
+                    fInEnum = false;
+                }
+                this->popObject();
+            } else if (Definition::Type::kBracket == fParent->fType
+                    && fParent->fParent && Definition::Type::kKeyWord == fParent->fParent->fType
+                    && KeyWord::kStruct == fParent->fParent->fKeyWord) {
+                list<Definition>::iterator baseIter = fParent->fTokens.end();
+                list<Definition>::iterator namedIter  = fParent->fTokens.end();
+                for (auto tokenIter = fParent->fTokens.end();
+                        fParent->fTokens.begin() != tokenIter--; ) {
+                    if (tokenIter->fLineCount == fLineCount) {
+                        if ('f' == tokenIter->fStart[0] && isupper(tokenIter->fStart[1])) {
+                            if (namedIter != fParent->fTokens.end()) {
+                                return reportError<bool>("found two named member tokens");
+                            }
+                            namedIter = tokenIter;
+                        }
+                        baseIter = tokenIter;
+                    } else {
+                        break;
+                    }
+                }
+                // FIXME: if a member definition spans multiple lines, this won't work
+                if (namedIter != fParent->fTokens.end()) {
+                    if (baseIter == namedIter) {
+                        return this->reportError<bool>("expected type before named token");
+                    }
+                    Definition* member = &*namedIter;
+                    member->fMarkType = MarkType::kMember;
+                    fParent->fChildren.push_back(member);
+                    for (auto nameType = baseIter; nameType != namedIter; ++nameType) {
+                        member->fChildren.push_back(&*nameType);
+                    }
+
+                }
+            } else if (fParent->fChildren.size() > 0) {
+                auto lastIter = fParent->fChildren.end();
+                Definition* priorEnum;
+                while (fParent->fChildren.begin() != lastIter) {
+                    std::advance(lastIter, -1);
+                    priorEnum = *lastIter;
+                    if (Definition::Type::kBracket != priorEnum->fType ||
+                            (Bracket::kSlashSlash != priorEnum->fBracket
+                            && Bracket::kSlashStar != priorEnum->fBracket)) {
+                        break;
+                    }
+                }
+                if (Definition::Type::kKeyWord == priorEnum->fType
+                        && KeyWord::kEnum == priorEnum->fKeyWord) {
+                    auto tokenWalker = fParent->fTokens.begin();
+                    std::advance(tokenWalker, priorEnum->fParentIndex);
+                    SkASSERT(KeyWord::kEnum == tokenWalker->fKeyWord);
+                    while (tokenWalker != fParent->fTokens.end()) {
+                        std::advance(tokenWalker, 1);
+                        if (Punctuation::kSemicolon == tokenWalker->fPunctuation) {
+                            break;
+                        }
+                    }
+                    while (tokenWalker != fParent->fTokens.end()) {
+                        std::advance(tokenWalker, 1);
+                        const Definition* test = &*tokenWalker;
+                        if (Definition::Type::kBracket != test->fType ||
+                                (Bracket::kSlashSlash != test->fBracket
+                                && Bracket::kSlashStar != test->fBracket)) {
+                            break;
+                        }
+                    }
+                    Definition* start = &*tokenWalker;
+                    bool foundExpected = true;
+                    for (KeyWord expected : {KeyWord::kStatic, KeyWord::kConstExpr, KeyWord::kInt}){
+                        const Definition* test = &*tokenWalker;
+                        if (expected != test->fKeyWord) {
+                            foundExpected = false;
+                            break;
+                        }
+                        if (tokenWalker == fParent->fTokens.end()) {
+                            break;
+                        }
+                        std::advance(tokenWalker, 1);
+                    }
+                    if (foundExpected && tokenWalker != fParent->fTokens.end()) {
+                        const char* nameStart = tokenWalker->fStart;
+                        std::advance(tokenWalker, 1);
+                        if (tokenWalker != fParent->fTokens.end()) {
+                            TextParser tp(fFileName, nameStart, tokenWalker->fStart, fLineCount);
+                            tp.skipToNonAlphaNum();
+                            start->fName = string(nameStart, tp.fChar - nameStart);
+                            start->fContentEnd = fChar;
+                            priorEnum->fChildren.emplace_back(start);
+                       }
+                    }
+                }
+            }
+            this->addPunctuation(Punctuation::kSemicolon);
+            fInFunction = false;
+            break;
+        case '~':
+            if (fInEnum) {
+                break;
+            }
+        case '0': case '1': case '2': case '3': case '4':
+        case '5': case '6': case '7': case '8': case '9':
+            // TODO: don't want to parse numbers, but do need to track for enum defs
+        //    break;
+        case 'A': case 'B': case 'C': case 'D': case 'E':
+        case 'F': case 'G': case 'H': case 'I': case 'J':
+        case 'K': case 'L': case 'M': case 'N': case 'O':
+        case 'P': case 'Q': case 'R': case 'S': case 'T':
+        case 'U': case 'V': case 'W': case 'X': case 'Y':
+        case 'Z': case '_':
+        case 'a': case 'b': case 'c': case 'd': case 'e':
+        case 'f': case 'g': case 'h': case 'i': case 'j':
+        case 'k': case 'l': case 'm': case 'n': case 'o':
+        case 'p': case 'q': case 'r': case 's': case 't':
+        case 'u': case 'v': case 'w': case 'x': case 'y':
+        case 'z': 
+            if (fInCharCommentString || fInBrace) {
+                break;
+            }
+            if (!fIncludeWord) {
+                fIncludeWord = fChar;
+            }
+            break;
+    }
+done:
+    fPrev = test;
+    ++fChar;
+    return true;
+}
+
+void IncludeParser::validate() const {
+    for (int index = 0; index <= (int) Last_MarkType; ++index) {
+        SkASSERT(fMaps[index].fMarkType == (MarkType) index);
+    }
+    IncludeParser::ValidateKeyWords();
+}
diff --git a/tools/bookmaker/includeWriter.cpp b/tools/bookmaker/includeWriter.cpp
new file mode 100644
index 0000000..5685f31
--- /dev/null
+++ b/tools/bookmaker/includeWriter.cpp
@@ -0,0 +1,1272 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bookmaker.h"
+
+void IncludeWriter::enumHeaderOut(const RootDefinition* root,
+        const Definition& child) {
+    const Definition* enumDef = nullptr;
+    const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
+            child.fContentStart;
+    this->writeBlockTrim((int) (bodyEnd - fStart), fStart);  // may write nothing
+    this->lf(2);
+    fDeferComment = nullptr;
+    fStart = child.fContentStart;
+    const auto& nameDef = child.fTokens.front();
+    string fullName;
+    if (nullptr != nameDef.fContentEnd) {
+        string enumName(nameDef.fContentStart,
+                (int) (nameDef.fContentEnd - nameDef.fContentStart));
+        fullName = root->fName + "::" + enumName;
+        enumDef = root->find(enumName);
+        if (!enumDef) {
+            enumDef = root->find(fullName);
+        }
+        SkASSERT(enumDef);
+        // child[0] should be #Code comment starts at child[0].fTerminator
+            // though skip until #Code is found (in case there's a #ToDo, etc)
+        // child[1] should be #Const comment ends at child[1].fStart
+        // comment becomes enum header (if any)
+    } else {
+        string enumName(root->fName);
+        enumName += "::_anonymous";
+        if (fAnonymousEnumCount > 1) {
+            enumName += '_' + to_string(fAnonymousEnumCount);
+        }
+        enumDef = root->find(enumName);
+        SkASSERT(enumDef);
+        ++fAnonymousEnumCount;
+    }
+    Definition* codeBlock = nullptr;
+    const char* commentStart = nullptr;
+    bool wroteHeader = false;
+    SkDEBUGCODE(bool foundConst = false);
+    for (auto test : enumDef->fChildren) {
+        if (MarkType::kCode == test->fMarkType) {
+            SkASSERT(!codeBlock);  // FIXME: check enum for correct order earlier
+            codeBlock = test;
+            commentStart = codeBlock->fTerminator;
+            continue;
+        }
+        if (!codeBlock) {
+            continue;
+        }
+        const char* commentEnd = test->fStart;
+        if (!wroteHeader &&
+                !this->contentFree((int) (commentEnd - commentStart), commentStart)) {
+            this->writeCommentHeader();
+            this->writeString("\\enum");
+            this->writeSpace();
+            this->writeString(fullName.c_str());
+            fIndent += 4;
+            this->lfcr();
+            wroteHeader = true;
+        }
+        this->rewriteBlock((int) (commentEnd - commentStart), commentStart);
+        if (MarkType::kAnchor == test->fMarkType) {
+            commentStart = test->fContentStart;
+            commentEnd = test->fChildren[0]->fStart;
+            this->writeSpace();
+            this->rewriteBlock((int) (commentEnd - commentStart), commentStart);
+            this->writeSpace();
+        }
+        commentStart = test->fTerminator;
+        if (MarkType::kConst == test->fMarkType) {
+            SkASSERT(codeBlock);  // FIXME: check enum for correct order earlier
+            SkDEBUGCODE(foundConst = true);
+            break;
+        }
+    }
+    SkASSERT(codeBlock);
+    SkASSERT(foundConst);
+    if (wroteHeader) {
+        fIndent -= 4;
+        this->lfcr();
+        this->writeCommentTrailer();
+    }
+    bodyEnd = child.fChildren[0]->fContentStart;
+    SkASSERT('{' == bodyEnd[0]);
+    ++bodyEnd;
+    this->lfcr();
+    this->writeBlock((int) (bodyEnd - fStart), fStart); // write include "enum Name {"
+    fIndent += 4;
+    this->singleLF();
+    fStart = bodyEnd;
+    fEnumDef = enumDef;
+}
+
+void IncludeWriter::enumMembersOut(const RootDefinition* root, const Definition& child) {
+    // iterate through include tokens and find how much remains for 1 line comments
+    // put ones that fit on same line, ones that are too big on preceding line?
+    const Definition* currentEnumItem = nullptr;
+    const char* commentStart = nullptr;
+    const char* lastEnd = nullptr;
+    int commentLen = 0;
+    enum class State {
+        kNoItem,
+        kItemName,
+        kItemValue,
+        kItemComment,
+    };
+    State state = State::kNoItem;
+    // can't use (auto& token : child.fTokens) 'cause we need state one past end 
+    auto tokenIter = child.fTokens.begin();
+    for (int onePast = 0; onePast < 2; onePast += tokenIter == child.fTokens.end()) {
+        const Definition* token = onePast ? nullptr : &*tokenIter++;
+        if (token && Definition::Type::kBracket == token->fType) {
+            if (Bracket::kSlashSlash == token->fBracket) {
+                fStart = token->fContentEnd;
+                continue;  // ignore old inline comments
+            }
+            if (Bracket::kSlashStar == token->fBracket) {
+                fStart = token->fContentEnd + 1;
+                continue;  // ignore old inline comments
+            }
+            SkASSERT(0); // incomplete
+        }
+        if (token && Definition::Type::kWord != token->fType) {
+            SkASSERT(0); // incomplete
+        }
+        if (token && State::kItemName == state) {
+            TextParser enumLine(token->fFileName, lastEnd,
+                    token->fContentStart, token->fLineCount);
+            const char* end = enumLine.anyOf(",}=");
+            SkASSERT(end);
+            state = '=' == *end ? State::kItemValue : State::kItemComment;
+            if (State::kItemValue == state) {  // write enum value
+                this->indentToColumn(fEnumItemValueTab);
+                this->writeString("=");
+                this->writeSpace();
+                lastEnd = token->fContentEnd;
+                this->writeBlock((int) (lastEnd - token->fContentStart),
+                        token->fContentStart); // write const value if any
+                continue;
+            }
+        }
+        if (token && State::kItemValue == state) {
+            TextParser valueEnd(token->fFileName, lastEnd,
+                    token->fContentStart, token->fLineCount);
+            const char* end = valueEnd.anyOf(",}");
+            if (!end) {  // write expression continuation
+                if (' ' == lastEnd[0]) {
+                    this->writeSpace();
+                }
+                this->writeBlock((int) (token->fContentEnd - lastEnd), lastEnd); 
+                continue;
+            }
+        }
+        if (State::kNoItem != state) {
+            this->writeString(",");
+            SkASSERT(currentEnumItem);
+            if (currentEnumItem->fShort) {
+                this->indentToColumn(fEnumItemCommentTab);
+                this->writeString("//!<");
+                this->writeSpace();
+                this->rewriteBlock(commentLen, commentStart);
+            }
+            if (onePast) {
+                fIndent -= 4;
+            }
+            this->lfcr();
+            if (token && State::kItemValue == state) {
+                fStart = token->fContentStart;
+            }
+            state = State::kNoItem;
+        }
+        SkASSERT(State::kNoItem == state);
+        if (onePast) {
+            break;
+        }
+        SkASSERT(token);
+        string itemName = root->fName + "::" + string(token->fContentStart,
+                (int) (token->fContentEnd - token->fContentStart));
+        for (auto& enumItem : fEnumDef->fChildren) {
+            if (MarkType::kConst != enumItem->fMarkType) {
+                continue;
+            }
+            if (itemName != enumItem->fName) {
+                continue;
+            }
+            currentEnumItem = enumItem;
+            break;
+        }
+        SkASSERT(currentEnumItem);
+        // if description fits, it goes after item
+        commentStart = currentEnumItem->fContentStart;
+        const char* commentEnd;
+        if (currentEnumItem->fChildren.size() > 0) {
+            commentEnd = currentEnumItem->fChildren[0]->fStart;
+        } else {
+            commentEnd = currentEnumItem->fContentEnd;
+        }
+        TextParser enumComment(fFileName, commentStart, commentEnd, currentEnumItem->fLineCount);
+        if (enumComment.skipToLineStart()) {  // skip const value
+            commentStart = enumComment.fChar;
+            commentLen = (int) (commentEnd - commentStart);
+        } else {
+            const Definition* privateDef = currentEnumItem->fChildren[0];
+            SkASSERT(MarkType::kPrivate == privateDef->fMarkType);
+            commentStart = privateDef->fContentStart;
+            commentLen = (int) (privateDef->fContentEnd - privateDef->fContentStart);
+        }
+        SkASSERT(commentLen > 0 && commentLen < 1000);
+        if (!currentEnumItem->fShort) {
+            this->writeCommentHeader();
+            fIndent += 4;
+            bool wroteLineFeed = Wrote::kLF == this->rewriteBlock(commentLen, commentStart);
+            fIndent -= 4;
+            if (wroteLineFeed || fColumn > 100 - 3 /* space * / */ ) {
+                this->lfcr();
+            } else {
+                this->writeSpace();
+            }
+            this->writeCommentTrailer();
+        }
+        lastEnd = token->fContentEnd;
+        this->lfcr();
+        if (',' == fStart[0]) {
+            ++fStart;
+        }
+        this->writeBlock((int) (lastEnd - fStart), fStart);  // enum item name
+        fStart = token->fContentEnd;
+        state = State::kItemName;
+    }
+}
+
+void IncludeWriter::enumSizeItems(const Definition& child) {
+    enum class State {
+        kNoItem,
+        kItemName,
+        kItemValue,
+        kItemComment,
+    };
+    State state = State::kNoItem;
+    int longestName = 0;
+    int longestValue = 0;
+    int valueLen = 0;
+    const char* lastEnd = nullptr;
+    SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
+    auto brace = child.fChildren[0];
+    SkASSERT(Bracket::kBrace == brace->fBracket);
+    for (auto& token : brace->fTokens) {
+        if (Definition::Type::kBracket == token.fType) {
+            if (Bracket::kSlashSlash == token.fBracket) {
+                continue;  // ignore old inline comments
+            }
+            if (Bracket::kSlashStar == token.fBracket) {
+                continue;  // ignore old inline comments
+            }
+            SkASSERT(0); // incomplete
+        }
+        if (Definition::Type::kWord != token.fType) {
+            SkASSERT(0); // incomplete
+        }
+        if (State::kItemName == state) {
+            TextParser enumLine(token.fFileName, lastEnd,
+                    token.fContentStart, token.fLineCount);
+            const char* end = enumLine.anyOf(",}=");
+            SkASSERT(end);
+            state = '=' == *end ? State::kItemValue : State::kItemComment;
+            if (State::kItemValue == state) {
+                valueLen = (int) (token.fContentEnd - token.fContentStart);
+                lastEnd = token.fContentEnd;
+                continue;
+            }
+        }
+        if (State::kItemValue == state) {
+            TextParser valueEnd(token.fFileName, lastEnd,
+                    token.fContentStart, token.fLineCount);
+            const char* end = valueEnd.anyOf(",}");
+            if (!end) {  // write expression continuation
+                valueLen += (int) (token.fContentEnd - lastEnd); 
+                continue;
+            }
+        }
+        if (State::kNoItem != state) {
+            longestValue = SkTMax(longestValue, valueLen);
+            state = State::kNoItem;
+        }
+        SkASSERT(State::kNoItem == state);
+        lastEnd = token.fContentEnd;
+        longestName = SkTMax(longestName, (int) (lastEnd - token.fContentStart));
+        state = State::kItemName;
+    }
+    if (State::kItemValue == state) {
+        longestValue = SkTMax(longestValue, valueLen);
+    }
+    fEnumItemValueTab = longestName + fIndent + 1 /* space before = */ ;
+    if (longestValue) {
+        longestValue += 3; /* = space , */
+    }
+    fEnumItemCommentTab = fEnumItemValueTab + longestValue + 1 /* space before //!< */ ;
+    // iterate through bmh children and see which comments fit on include lines
+    for (auto& enumItem : fEnumDef->fChildren) {
+        if (MarkType::kConst != enumItem->fMarkType) {
+            continue;
+        }
+        TextParser enumLine(enumItem);
+        enumLine.trimEnd();
+        enumLine.skipToLineStart(); // skip const value
+        const char* commentStart = enumLine.fChar;
+        enumLine.skipLine();
+        ptrdiff_t lineLen = enumLine.fChar - commentStart + 5 /* //!< space */ ;
+        if (!enumLine.eof()) {
+            enumLine.skipWhiteSpace();
+        }
+        enumItem->fShort = enumLine.eof() && fEnumItemCommentTab + lineLen < 100;
+    }
+}
+
+// walk children and output complete method doxygen description
+void IncludeWriter::methodOut(const Definition* method) {
+    fContinuation = nullptr;
+    fDeferComment = nullptr;
+    if (0 == fIndent) {
+        fIndent = 4;
+    }
+    this->writeCommentHeader();
+    fIndent += 4;
+    const char* commentStart = method->fContentStart;
+    int commentLen = (int) (method->fContentEnd - commentStart);
+    bool breakOut = false;
+    for (auto methodProp : method->fChildren) {
+        switch (methodProp->fMarkType) {
+            case MarkType::kDefinedBy:
+                commentStart = methodProp->fTerminator;
+                break;
+            case MarkType::kDeprecated: 
+            case MarkType::kPrivate:
+                commentLen = (int) (methodProp->fStart - commentStart);
+                if (commentLen > 0) {
+                    SkASSERT(commentLen < 1000);
+                    if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart)) {
+                        this->lfcr();
+                    }
+                }
+                commentStart = methodProp->fContentStart;
+                commentLen = (int) (methodProp->fContentEnd - commentStart);
+                if (commentLen > 0) {
+                    if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart)) {
+                        this->lfcr();
+                    }
+                }
+                commentStart = methodProp->fTerminator;
+                commentLen = (int) (method->fContentEnd - commentStart);
+            break;
+            default:
+                commentLen = (int) (methodProp->fStart - commentStart);
+                breakOut = true;
+        }
+        if (breakOut) {
+            break;
+        }
+    }
+    SkASSERT(commentLen > 0 && commentLen < 1000);
+    this->rewriteBlock(commentLen, commentStart);
+    // compute indention column
+    size_t column = 0;
+    bool hasParmReturn = false;
+    for (auto methodPart : method->fChildren) {
+        if (MarkType::kParam == methodPart->fMarkType) {
+            column = SkTMax(column, methodPart->fName.length());
+            hasParmReturn = true;
+        } else if (MarkType::kReturn == methodPart->fMarkType) {
+            hasParmReturn = true;
+        }
+    }
+    if (hasParmReturn) {
+        this->lf(2);
+        column += fIndent + sizeof("@return ");
+        int saveIndent = fIndent;
+        for (auto methodPart : method->fChildren) {
+            const char* partStart = methodPart->fContentStart;
+            const char* partEnd = methodPart->fContentEnd; 
+            if (MarkType::kParam == methodPart->fMarkType) {
+                this->writeString("@param");
+                this->writeSpace();
+                this->writeString(methodPart->fName.c_str());
+            } else if (MarkType::kReturn == methodPart->fMarkType) {
+                this->writeString("@return");
+            } else {
+                continue;
+            }
+            while ('\n' == partEnd[-1]) {
+                --partEnd;
+            }
+            while ('#' == partEnd[-1]) { // FIXME: so wrong; should not be before fContentEnd
+                --partEnd;
+            }
+            this->indentToColumn(column);
+            int partLen = (int) (partEnd - partStart);
+            SkASSERT(partLen > 0 && partLen < 200);
+            fIndent = column;
+            this->rewriteBlock(partLen, partStart);
+            fIndent = saveIndent;
+            this->lfcr();
+        }
+    } else {
+        this->lfcr();
+    }
+    fIndent -= 4;
+    this->lfcr();
+    this->writeCommentTrailer();
+}
+
+void IncludeWriter::structOut(const Definition* root, const Definition& child,
+        const char* commentStart, const char* commentEnd) {
+    this->writeCommentHeader();
+    this->writeString("\\");
+    SkASSERT(MarkType::kClass == child.fMarkType || MarkType::kStruct == child.fMarkType);
+    this->writeString(MarkType::kClass == child.fMarkType ? "class" : "struct");
+    this->writeSpace();
+    this->writeString(child.fName.c_str());
+    fIndent += 4;
+    this->lfcr();
+    this->rewriteBlock((int) (commentEnd - commentStart), commentStart);
+    fIndent -= 4;
+    this->lfcr();
+    this->writeCommentTrailer();
+}
+
+void IncludeWriter::structMemberOut(const Definition* memberStart, const Definition& child) {
+    const char* commentStart = nullptr;
+    ptrdiff_t commentLen = 0;
+    string name(child.fContentStart, (int) (child.fContentEnd - child.fContentStart));
+    bool isShort;
+    for (auto memberDef : fStructDef->fChildren)  {
+        if (memberDef->fName.length() - name.length() == memberDef->fName.find(name)) {
+            commentStart = memberDef->fContentStart;
+            commentLen = memberDef->fContentEnd - memberDef->fContentStart;
+            isShort = memberDef->fShort;
+            break;
+        }
+    }
+    if (!isShort) {
+        this->writeCommentHeader();
+        fIndent += 4;
+        bool wroteLineFeed = Wrote::kLF == this->rewriteBlock(commentLen, commentStart);
+        fIndent -= 4;
+        if (wroteLineFeed || fColumn > 100 - 3 /* space * / */ ) {
+            this->lfcr();
+        } else {
+            this->writeSpace();
+        }
+        this->writeCommentTrailer();
+    }
+    this->lfcr();
+    this->writeBlock((int) (memberStart->fContentEnd - memberStart->fContentStart),
+            memberStart->fContentStart);
+    this->indentToColumn(fStructMemberTab);
+    this->writeString(name.c_str());
+    this->writeString(";");
+    if (isShort) {
+        this->indentToColumn(fStructCommentTab);
+        this->writeString("//!<");
+        this->writeSpace();
+        this->rewriteBlock(commentLen, commentStart);
+        this->lfcr();
+    }
+}
+
+void IncludeWriter::structSizeMembers(Definition& child) {
+    int longestType = 0;
+    Definition* typeStart = nullptr;
+    int longestName = 0;
+    SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
+    bool inEnum = false;
+    auto brace = child.fChildren[0];
+    SkASSERT(Bracket::kBrace == brace->fBracket);
+    for (auto& token : brace->fTokens) {
+        if (Definition::Type::kBracket == token.fType) {
+            if (Bracket::kSlashSlash == token.fBracket) {
+                continue;  // ignore old inline comments
+            }
+            if (Bracket::kSlashStar == token.fBracket) {
+                continue;  // ignore old inline comments
+            }
+            if (Bracket::kParen == token.fBracket) {
+                break;
+            }
+            SkASSERT(0); // incomplete
+        }
+        if (Definition::Type::kKeyWord == token.fType) {
+            switch (token.fKeyWord) {
+                case KeyWord::kEnum:
+                    inEnum = true;
+                    break;
+                case KeyWord::kConst:
+                case KeyWord::kConstExpr:
+                case KeyWord::kStatic:
+                case KeyWord::kInt:
+                case KeyWord::kUint32_t:
+                case KeyWord::kSize_t:
+                case KeyWord::kFloat:
+                case KeyWord::kBool:
+                case KeyWord::kVoid:
+                    if (!typeStart) {
+                        typeStart = &token;
+                    }
+                    break;
+                default:
+                    break;
+            }
+            continue;
+        }
+        if (Definition::Type::kPunctuation == token.fType) {
+            if (inEnum) {
+                SkASSERT(Punctuation::kSemicolon == token.fPunctuation);
+                inEnum = false;
+            }
+            continue;
+        }
+        if (Definition::Type::kWord != token.fType) {
+            SkASSERT(0); // incomplete
+        }
+        if (MarkType::kMember == token.fMarkType) {
+            TextParser typeStr(token.fFileName, typeStart->fContentStart, token.fContentStart,
+                    token.fLineCount);
+            typeStr.trimEnd();
+            longestType = SkTMax(longestType, (int) (typeStr.fEnd - typeStart->fContentStart));
+            longestName = SkTMax(longestName, (int) (token.fContentEnd - token.fContentStart));
+            typeStart->fMemberStart = true;
+            typeStart = nullptr;
+            continue;
+        }
+        SkASSERT(MarkType::kNone == token.fMarkType);
+        if (!typeStart) {
+            typeStart = &token;
+        }
+    }
+    fStructMemberTab = longestType + fIndent + 1 /* space before name */ ;
+    fStructCommentTab = fStructMemberTab + longestName + 2 /* ; space */ ;
+    // iterate through bmh children and see which comments fit on include lines
+    for (auto& member : fStructDef->fChildren) {
+        if (MarkType::kMember != member->fMarkType) {
+            continue;
+        }
+        TextParser memberLine(member);
+        memberLine.trimEnd();
+        const char* commentStart = memberLine.fChar;
+        memberLine.skipLine();
+        ptrdiff_t lineLen = memberLine.fChar - commentStart + 5 /* //!< space */ ;
+        if (!memberLine.eof()) {
+            memberLine.skipWhiteSpace();
+        }
+        member->fShort = memberLine.eof() && fStructCommentTab + lineLen < 100;
+    }
+}
+
+bool IncludeWriter::populate(Definition* def, RootDefinition* root) {
+    // write bulk of original include up to class, method, enum, etc., excepting preceding comment
+    // find associated bmh object
+    // write any associated comments in Doxygen form
+    // skip include comment
+    // if there is a series of same named methods, write one set of comments, then write all methods
+    string methodName;
+    const Definition* method;
+    const Definition* clonedMethod = nullptr;
+    const Definition* memberStart = nullptr;
+    fContinuation = nullptr;
+    bool inStruct = false;
+    for (auto& child : def->fTokens) {
+        if (child.fPrivate) {
+            continue;
+        }
+        if (fContinuation) {
+            if (Definition::Type::kKeyWord == child.fType) {
+                if (KeyWord::kFriend == child.fKeyWord || KeyWord::kBool == child.fKeyWord) {
+                    continue;
+                }
+            }
+            if (Definition::Type::kBracket == child.fType && Bracket::kParen == child.fBracket) {
+                if (!clonedMethod) {
+                    continue;
+                }
+                int alternate = 1;
+                ptrdiff_t childLen = child.fContentEnd - child.fContentStart;
+                SkASSERT(')' == child.fContentStart[childLen]);
+                ++childLen;
+                do {
+                    TextParser params(clonedMethod->fFileName, clonedMethod->fStart, 
+                        clonedMethod->fContentStart, clonedMethod->fLineCount);
+                    params.skipToEndBracket('(');
+                    if (params.fEnd - params.fChar >= childLen &&
+                            !strncmp(params.fChar, child.fContentStart, childLen)) {
+                        this->methodOut(clonedMethod);
+                        break;
+                    }
+                    ++alternate;
+                    string alternateMethod = methodName + '_' + to_string(alternate);
+                    clonedMethod = root->find(alternateMethod);
+                } while (clonedMethod);
+                if (!clonedMethod) {
+                    return this->reportError<bool>("cloned method not found");
+                }
+                clonedMethod = nullptr;
+                continue;
+            }
+            if (Definition::Type::kWord == child.fType) {
+                if (clonedMethod) {
+                    continue;
+                }
+                size_t len = (size_t) (child.fContentEnd - child.fContentStart);
+                const char operatorStr[] = "operator";
+                size_t operatorLen = sizeof(operatorStr) - 1;
+                if (len >= operatorLen && !strncmp(child.fContentStart, operatorStr, operatorLen)) {
+                    fContinuation = child.fContentEnd;
+                    continue;
+                }
+            }
+            if (Definition::Type::kPunctuation == child.fType &&
+                    (Punctuation::kSemicolon == child.fPunctuation ||
+                    Punctuation::kLeftBrace == child.fPunctuation)) {
+                SkASSERT(fContinuation[0] == '(');
+                const char* continueEnd = child.fContentStart;
+                while (continueEnd > fContinuation && isspace(continueEnd[-1])) {
+                    --continueEnd;
+                }
+                methodName += string(fContinuation, continueEnd - fContinuation);
+                method = root->find(methodName);
+                if (!method) {
+                    fLineCount = child.fLineCount;
+                    fclose(fOut);  // so we can see what we've written so far
+                    return this->reportError<bool>("method not found");
+                }
+                this->methodOut(method);
+                continue;
+            }
+            methodName += "()";
+            method = root->find(methodName);
+            if (MarkType::kDefinedBy == method->fMarkType) {
+                method = method->fParent;
+            }
+            if (method) {
+                this->methodOut(method);
+                continue;
+            }
+            fLineCount = child.fLineCount;
+            fclose(fOut);  // so we can see what we've written so far
+            return this->reportError<bool>("method not found");
+        }
+        if (Bracket::kSlashSlash == child.fBracket || Bracket::kSlashStar == child.fBracket) {
+            if (!fDeferComment) {
+                fDeferComment = &child;
+            }
+            continue;
+        } 
+        if (MarkType::kMethod == child.fMarkType) {
+            const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
+                    child.fContentStart;
+            // FIXME: roll end-trimming into writeBlockTrim call
+            while (fStart < bodyEnd && ' ' >= bodyEnd[-1]) {
+                --bodyEnd;
+            }
+            int blockSize = (int) (bodyEnd - fStart);
+            if (blockSize) {
+                this->writeBlock(blockSize, fStart);
+            }
+            fStart = child.fContentStart;
+            methodName = root->fName + "::" + child.fName;
+            fContinuation = child.fContentEnd;
+            method = root->find(methodName);
+            if (!method) {
+                continue;
+            }
+            if (method->fCloned) {
+                clonedMethod = method;
+                continue;
+            }
+            this->methodOut(method);
+            continue;
+        } 
+        if (Definition::Type::kKeyWord == child.fType) {
+            const Definition* structDef = nullptr;
+            switch (child.fKeyWord) {
+                case KeyWord::kStruct:
+                    // if struct contains members, compute their name and comment tabs
+                    inStruct = fInStruct = child.fChildren.size() > 0;
+                    if (fInStruct) {
+                        fIndent += 4;
+                        fStructDef = root->find(child.fName);
+                        if (nullptr == structDef) {
+                            fStructDef = root->find(root->fName + "::" + child.fName);
+                        }
+                        this->structSizeMembers(child);
+                        fIndent -= 4;
+                    }
+                case KeyWord::kClass:
+                    if (child.fChildren.size() > 0) {
+                        const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
+                                child.fContentStart;
+                        this->writeBlock((int) (bodyEnd - fStart), fStart);
+                        fStart = child.fContentStart;
+                        if (child.fName == root->fName) {
+                            if (Definition* parent = root->fParent) {
+                                if (MarkType::kTopic == parent->fMarkType ||
+                                        MarkType::kSubtopic == parent->fMarkType) {
+                                    const char* commentStart = parent->fContentStart;
+                                    const char* commentEnd = root->fStart;
+                                    this->structOut(root, *root, commentStart, commentEnd);
+                                } else {
+                                    SkASSERT(0); // incomplete
+                                }
+                            } else {
+                                SkASSERT(0); // incomplete
+                            }
+                        } else {
+                            structDef = root->find(child.fName);
+                            if (nullptr == structDef) {
+                                structDef = root->find(root->fName + "::" + child.fName);
+                            }
+                            Definition* codeBlock = nullptr;
+                            Definition* nextBlock = nullptr;
+                            for (auto test : structDef->fChildren) {
+                                if (MarkType::kCode == test->fMarkType) {
+                                    SkASSERT(!codeBlock);  // FIXME: check enum for correct order earlier
+                                    codeBlock = test;
+                                    continue;
+                                }
+                                if (codeBlock) {
+                                    nextBlock = test;
+                                    break;
+                                }
+                            }
+                            SkASSERT(nextBlock);  // FIXME: check enum for correct order earlier
+                            const char* commentStart = codeBlock->fTerminator;
+                            const char* commentEnd = nextBlock->fStart;
+                            this->structOut(root, *structDef, commentStart, commentEnd);
+                        }
+                        fDeferComment = nullptr;
+                    } else {
+                       ; // empty forward reference, nothing to do here
+                    }
+                    break;
+                case KeyWord::kEnum: {
+                    this->fInEnum = true;
+                    this->enumHeaderOut(root, child);
+                    this->enumSizeItems(child);
+                } break;
+                case KeyWord::kConst:
+                case KeyWord::kConstExpr:
+                case KeyWord::kStatic:
+                case KeyWord::kInt:
+                case KeyWord::kUint32_t:
+                case KeyWord::kSize_t:
+                case KeyWord::kFloat:
+                case KeyWord::kBool:
+                case KeyWord::kVoid:
+                    if (!memberStart) {
+                        memberStart = &child;
+                    }
+                    break;
+                case KeyWord::kPublic:
+                case KeyWord::kPrivate:
+                case KeyWord::kProtected:
+                case KeyWord::kFriend:
+                    break;
+                default:
+                    SkASSERT(0);
+            }
+            if (structDef) {
+                TextParser structName(&child);
+                SkAssertResult(structName.skipToEndBracket('{'));
+                fStart = structName.fChar + 1;
+                this->writeBlock((int) (fStart - child.fStart), child.fStart);
+                this->lf(2);
+                fIndent += 4;
+                if (!this->populate(&child, const_cast<Definition*>(structDef)->asRoot())) {
+                    return false;
+                }
+                // output any remaining definitions at current indent level
+                const char* structEnd = child.fContentEnd;
+                SkAssertResult('}' == structEnd[-1]);
+                --structEnd;
+                this->writeBlock((int) (structEnd - fStart), fStart);
+                this->lf(2);
+                fStart = structEnd;
+                fIndent -= 4;
+                fContinuation = nullptr;
+                fDeferComment = nullptr;
+            } else {
+                if (!this->populate(&child, root)) {
+                    return false;
+                }
+            }
+            continue;
+        } 
+        if (Definition::Type::kBracket == child.fType) {
+            if (KeyWord::kEnum == child.fParent->fKeyWord) {
+                this->enumMembersOut(root, child);
+                this->writeString("};");
+                this->lf(2);
+                fStart = child.fParent->fContentEnd;
+                SkASSERT(';' == fStart[0]);
+                ++fStart;
+                fDeferComment = nullptr;
+                fInEnum = false;
+                continue;
+            } 
+            fDeferComment = nullptr;
+            if (!this->populate(&child, root)) {
+                return false;
+            }
+            continue;
+        }
+        if (Definition::Type::kWord == child.fType) {
+            if (MarkType::kMember == child.fMarkType) {
+                this->structMemberOut(memberStart, child);
+                fStart = child.fContentEnd + 1;
+                fDeferComment = nullptr;
+            }
+            if (child.fMemberStart) {
+                memberStart = &child;
+            }
+            continue;
+        }
+        if (Definition::Type::kPunctuation == child.fType) {
+            if (Punctuation::kSemicolon == child.fPunctuation) {
+                memberStart = nullptr;
+                if (inStruct) {
+                    fInStruct = false;
+                }
+                continue;
+            }
+            if (Punctuation::kLeftBrace == child.fPunctuation ||
+                    Punctuation::kColon == child.fPunctuation ||
+                    Punctuation::kAsterisk == child.fPunctuation
+                ) {
+                continue;
+            }
+        }
+    }
+    return true;
+}
+
+bool IncludeWriter::populate(BmhParser& bmhParser) {
+    bool allPassed = true;
+    for (auto& includeMapper : fIncludeMap) {
+        size_t lastSlash = includeMapper.first.rfind('/');
+        if (string::npos == lastSlash || lastSlash >= includeMapper.first.length() - 1) {
+            return this->reportError<bool>("malformed include name");
+        }
+        string fileName = includeMapper.first.substr(lastSlash + 1);
+        if (".h" != fileName.substr(fileName.length() - 2)) {
+            return this->reportError<bool>("expected fileName.h");
+        }
+        string skClassName = fileName.substr(0, fileName.length() - 2);
+        fOut = fopen(fileName.c_str(), "wb");
+        if (!fOut) {
+            SkDebugf("could not open output file %s\n", fileName.c_str());
+            return false;
+        }
+        if (bmhParser.fClassMap.end() == bmhParser.fClassMap.find(skClassName)) {
+            return this->reportError<bool>("could not find bmh class");
+        }
+        fBmhParser = &bmhParser;
+        RootDefinition* root = &bmhParser.fClassMap[skClassName];
+        fRootTopic = root->fParent;
+        root->clearVisited();
+        fStart = includeMapper.second.fContentStart;
+        fEnd = includeMapper.second.fContentEnd;
+        allPassed &= this->populate(&includeMapper.second, root);
+        this->writeBlock((int) (fEnd - fStart), fStart);
+        fIndent = 0;
+        this->lfcr();
+        this->writePending();
+        fclose(fOut);
+    }
+    return allPassed;
+}
+
+// change Xxx_Xxx to xxx xxx
+static string ConvertRef(const string str, bool first) {
+    string substitute;
+    for (char c : str) {
+        if ('_' == c) {
+            c = ' ';  // change Xxx_Xxx to xxx xxx
+        } else if (isupper(c) && !first) {
+            c = tolower(c);
+        }
+        substitute += c;
+        first = false;
+    }
+    return substitute;
+}
+
+// FIXME: buggy that this is called with strings containing spaces but resolveRef below is not..
+string IncludeWriter::resolveMethod(const char* start, const char* end, bool first) {
+    string methodname(start, end - start);
+    string substitute;
+    auto rootDefIter = fBmhParser->fMethodMap.find(methodname);
+    if (fBmhParser->fMethodMap.end() != rootDefIter) {
+        substitute = methodname + "()";
+    } else {
+        auto parent = fRootTopic->fChildren[0]->asRoot();
+        auto defRef = parent->find(parent->fName + "::" + methodname);
+        if (defRef && MarkType::kMethod == defRef->fMarkType) {
+            substitute = methodname + "()";
+        }
+    }
+    return substitute;
+}
+
+string IncludeWriter::resolveRef(const char* start, const char* end, bool first) {
+        // look up Xxx_Xxx 
+    string undername(start, end - start);
+    SkASSERT(string::npos == undername.find(' '));
+    const Definition* rootDef = nullptr;
+    {
+        auto rootDefIter = fBmhParser->fTopicMap.find(undername);
+        if (fBmhParser->fTopicMap.end() != rootDefIter) {
+            rootDef = rootDefIter->second;
+        } else {
+            string prefixedName = fRootTopic->fName + '_' + undername;
+            rootDefIter = fBmhParser->fTopicMap.find(prefixedName);
+            if (fBmhParser->fTopicMap.end() != rootDefIter) {
+                rootDef = rootDefIter->second;
+            } else {
+                auto aliasIter = fBmhParser->fAliasMap.find(undername);
+                if (fBmhParser->fAliasMap.end() != aliasIter) {
+                    rootDef = aliasIter->second->fParent;
+                } else if (!first) {
+                    for (const auto& external : fBmhParser->fExternals) {
+                        if (external.fName == undername) {
+                            return external.fName;
+                        }
+                    }
+                    SkDebugf("unfound: %s\n", undername.c_str());
+                }
+            }
+        }
+    }
+    string substitute;
+    if (rootDef) {
+        for (auto child : rootDef->fChildren) {
+            if (MarkType::kSubstitute == child->fMarkType) {
+                substitute = string(child->fContentStart,
+                        (int) (child->fContentEnd - child->fContentStart));
+                break;
+            }
+            if (MarkType::kClass == child->fMarkType ||
+                    MarkType::kStruct == child->fMarkType ||
+                    MarkType::kEnum == child->fMarkType ||
+                    MarkType::kEnumClass == child->fMarkType) {
+                substitute = child->fName;
+                if (MarkType::kEnum == child->fMarkType && fInEnum) {
+                    size_t parentClassEnd = substitute.find("::");
+                    SkASSERT(string::npos != parentClassEnd);
+                    substitute = substitute.substr(parentClassEnd + 2);
+                }
+                break;
+            }
+        }
+        if (!substitute.length()) {
+            auto parent = rootDef->fParent;
+            if (parent) {
+                if (MarkType::kClass == parent->fMarkType ||
+                        MarkType::kStruct == parent->fMarkType ||
+                        MarkType::kEnum == parent->fMarkType ||
+                        MarkType::kEnumClass == parent->fMarkType) {
+                    if (parent->fParent != fRootTopic) {
+                        substitute = parent->fName;
+                        size_t under = undername.find('_');
+                        SkASSERT(string::npos != under);
+                        string secondHalf(&undername[under], (size_t) (undername.length() - under));
+                        substitute += ConvertRef(secondHalf, false);
+                    } else {
+                        substitute += ConvertRef(undername, first);
+                    }
+                }
+            }
+        }
+    }
+ //   start here;
+    // first I thought first meant first word after period, but the below doesn't work
+//    if (first && isupper(start[0]) && substitute.length() > 0 && islower(substitute[0])) {
+//        substitute[0] = start[0];
+//    }
+    return substitute;
+}
+int IncludeWriter::lookupMethod(const PunctuationState punctuation, const Word word,
+        const int start, const int run, int lastWrite, const char last, const char* data) {
+    const int end = PunctuationState::kDelimiter == punctuation ||
+            PunctuationState::kPeriod == punctuation ? run - 1 : run;
+    string temp = this->resolveMethod(&data[start], &data[end], Word::kFirst == word);
+    if (temp.length()) {
+        if (start > lastWrite) {
+            SkASSERT(data[start - 1] >= ' ');
+            if (' ' == data[lastWrite]) {
+                this->writeSpace();
+            }
+            this->writeBlockTrim(start - lastWrite, &data[lastWrite]);
+            if (' ' == data[start - 1]) {
+                this->writeSpace();
+            }
+        }
+        SkASSERT(temp[temp.length() - 1] > ' ');
+        this->writeString(temp.c_str());
+        lastWrite = end;
+    }
+    return lastWrite;
+}
+
+int IncludeWriter::lookupReference(const PunctuationState punctuation, const Word word,
+        const int start, const int run, int lastWrite, const char last, const char* data) {
+    const int end = PunctuationState::kDelimiter == punctuation ||
+            PunctuationState::kPeriod == punctuation ? run - 1 : run;
+    string temp = this->resolveRef(&data[start], &data[end], Word::kFirst == word);
+    if (!temp.length()) {
+        if (Word::kFirst != word && '_' != last) {
+            temp = string(&data[start], (size_t) (end - start));
+            temp = ConvertRef(temp, false);
+        }
+    }                     
+    if (temp.length()) {
+        if (start > lastWrite) {
+            SkASSERT(data[start - 1] >= ' ');
+            if (' ' == data[lastWrite]) {
+                this->writeSpace();
+            }
+            this->writeBlockTrim(start - lastWrite, &data[lastWrite]);
+            if (' ' == data[start - 1]) {
+                this->writeSpace();
+            }
+        }
+        SkASSERT(temp[temp.length() - 1] > ' ');
+        this->writeString(temp.c_str());
+        lastWrite = end;
+    }
+    return lastWrite;
+}
+
+/* returns true if rewriteBlock wrote linefeeds */
+IncludeWriter::Wrote IncludeWriter::rewriteBlock(int size, const char* data) {
+    bool wroteLineFeeds = false;
+    while (size > 0 && data[0] <= ' ') {
+        --size;
+        ++data;
+    }
+    while (size > 0 && data[size - 1] <= ' ') {
+        --size;
+    }
+    if (0 == size) {
+        return Wrote::kNone;
+    }
+    int run = 0;
+    Word word = Word::kStart;
+    PunctuationState punctuation = PunctuationState::kStart;
+    int start = 0;
+    int lastWrite = 0;
+    int lineFeeds = 0;
+    int lastPrintable = 0;
+    char c = 0;
+    char last;
+    bool hasLower = false;
+    bool hasUpper = false;
+    bool hasSymbol = false;
+    while (run < size) {
+        last = c;
+        c = data[run];
+        SkASSERT(' ' <= c || '\n' == c);
+        if (lineFeeds && ' ' < c) {
+            if (lastPrintable >= lastWrite) {
+                if (' ' == data[lastWrite]) {
+                    this->writeSpace();
+                }
+                this->writeBlock(lastPrintable - lastWrite + 1, &data[lastWrite]);
+            }
+            if (lineFeeds > 1) {
+                this->lf(2);
+            }
+            this->lfcr(); // defer the indent until non-whitespace is seen
+            lastWrite = run;
+            lineFeeds = 0;
+        }
+        if (' ' < c) {
+            lastPrintable = run;
+        }
+        switch (c) {
+            case '\n':
+                ++lineFeeds;
+                wroteLineFeeds = true;
+            case ' ':
+                switch (word) {
+                    case Word::kStart:
+                        break;
+                    case Word::kUnderline:
+                    case Word::kCap:
+                    case Word::kFirst:
+                        if (!hasLower) {
+                            break;
+                        }
+                        lastWrite = this->lookupReference(punctuation, word, start, run,
+                                lastWrite, last, data);
+                        break;
+                    case Word::kMixed:
+                        if (hasUpper && hasLower && !hasSymbol) {
+                            lastWrite = this->lookupMethod(punctuation, word, start, run,
+                                    lastWrite, last, data);
+                        }
+                        break;
+                    default:
+                        SkASSERT(0);
+                }
+                punctuation = PunctuationState::kStart;
+                word = Word::kStart;
+                hasLower = false;
+                hasUpper = false;
+                hasSymbol = false;
+                break;
+            case '.':
+                switch (word) {
+                    case Word::kStart:
+                        punctuation = PunctuationState::kDelimiter;
+                    case Word::kCap:
+                    case Word::kFirst:
+                    case Word::kUnderline:
+                    case Word::kMixed:
+                        if (PunctuationState::kDelimiter == punctuation ||
+                                PunctuationState::kPeriod == punctuation) {
+                            word = Word::kMixed;
+                        }
+                        punctuation = PunctuationState::kPeriod;
+                        break;
+                    default:
+                        SkASSERT(0);
+                }
+                hasSymbol = true;
+                break;
+            case ',': case ';': case ':':
+                hasSymbol |= PunctuationState::kDelimiter == punctuation;
+                switch (word) {
+                    case Word::kStart:
+                        punctuation = PunctuationState::kDelimiter;
+                    case Word::kCap:
+                    case Word::kFirst:
+                    case Word::kUnderline:
+                    case Word::kMixed:
+                        if (PunctuationState::kDelimiter == punctuation ||
+                                PunctuationState::kPeriod == punctuation) {
+                            word = Word::kMixed;
+                        }
+                        punctuation = PunctuationState::kDelimiter;
+                        break;
+                    default:
+                        SkASSERT(0);
+                }
+                break;
+            case '\'': // possessive apostrophe isn't treated as delimiting punctation
+            case '=':
+            case '!':  // assumed not to be punctuation, but a programming symbol
+            case '&': case '>': case '<': case '{': case '}': case '/': case '*':
+                word = Word::kMixed;
+                hasSymbol = true;
+                break;
+            case '(':
+                if (' ' == last) {
+                    punctuation = PunctuationState::kDelimiter;
+                } else {
+                    word = Word::kMixed;
+                }
+                hasSymbol = true;
+                break;
+            case ')':   // assume word type has already been set
+                punctuation = PunctuationState::kDelimiter;
+                hasSymbol = true;
+                break;
+            case '_':
+                switch (word) {
+                    case Word::kStart:
+                        word = Word::kMixed;
+                        break;
+                    case Word::kCap:
+                    case Word::kFirst:
+                    case Word::kUnderline:
+                        word = Word::kUnderline;
+                        break;
+                    case Word::kMixed:
+                        break;
+                    default:
+                        SkASSERT(0);
+                }
+                break;
+            case 'A': case 'B': case 'C': case 'D': case 'E':
+            case 'F': case 'G': case 'H': case 'I': case 'J':
+            case 'K': case 'L': case 'M': case 'N': case 'O':
+            case 'P': case 'Q': case 'R': case 'S': case 'T':
+            case 'U': case 'V': case 'W': case 'X': case 'Y':
+            case 'Z':
+                switch (word) {
+                    case Word::kStart:
+                        word = PunctuationState::kStart == punctuation ? Word::kFirst : Word::kCap;
+                        start = run;
+                        break;
+                    case Word::kCap:
+                    case Word::kFirst:
+                        if (!isupper(last)) {
+                            word = Word::kMixed;
+                        }
+                        break;
+                    case Word::kUnderline:
+                        // some word in Xxx_XXX_Xxx can be all upper, but all can't: XXX_XXX
+                        if ('_' != last && !isupper(last)) {
+                            word = Word::kMixed;
+                        }
+                        break;
+                    case Word::kMixed:
+                        break;
+                    default:
+                        SkASSERT(0);
+                }
+                hasUpper = true;
+                if (PunctuationState::kPeriod == punctuation ||
+                        PunctuationState::kDelimiter == punctuation) {
+                    word = Word::kMixed;
+                } else {
+                    punctuation = PunctuationState::kStart;
+                }
+                break;
+            case 'a': case 'b': case 'c': case 'd': case 'e':
+            case 'f': case 'g': case 'h': case 'i': case 'j':
+            case 'k': case 'l': case 'm': case 'n': case 'o':
+            case 'p': case 'q': case 'r': case 's': case 't':
+            case 'u': case 'v': case 'w': case 'x': case 'y':
+            case 'z': 
+            case '0': case '1': case '2': case '3': case '4':
+            case '5': case '6': case '7': case '8': case '9':
+            case '-':
+                switch (word) {
+                    case Word::kStart:
+                        word = Word::kMixed;
+                        break;
+                    case Word::kMixed:
+                    case Word::kCap:
+                    case Word::kFirst:
+                    case Word::kUnderline:
+                        break;
+                    default:
+                        SkASSERT(0);
+                }
+                hasLower = true;
+                punctuation = PunctuationState::kStart;
+                break;
+            default:
+                SkASSERT(0);
+        }
+        ++run;
+    }
+    if ((word == Word::kCap || word == Word::kFirst || word == Word::kUnderline) && hasLower) {
+        lastWrite = this->lookupReference(punctuation, word, start, run, lastWrite, last, data);
+    }
+    if (run > lastWrite) {
+        if (' ' == data[lastWrite]) {
+            this->writeSpace();
+        }
+        this->writeBlock(run - lastWrite, &data[lastWrite]);
+    }
+    return wroteLineFeeds ? Wrote::kLF : Wrote::kChars;
+}
diff --git a/tools/bookmaker/mdOut.cpp b/tools/bookmaker/mdOut.cpp
new file mode 100644
index 0000000..a7737d6
--- /dev/null
+++ b/tools/bookmaker/mdOut.cpp
@@ -0,0 +1,929 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bookmaker.h"
+
+#include "SkOSFile.h"
+#include "SkOSPath.h"
+
+static void add_ref(const string& leadingSpaces, const string& ref, string* result) {
+    *result += leadingSpaces + ref;
+}
+
+// FIXME: preserve inter-line spaces and don't add new ones
+string MdOut::addReferences(const char* refStart, const char* refEnd,
+        BmhParser::Resolvable resolvable) {
+    string result;
+    MethodParser t(fRoot ? fRoot->fName : string(), fFileName, refStart, refEnd, fLineCount);
+    bool lineStart = true;
+    string ref;
+    string leadingSpaces;
+    do {
+        const char* base = t.fChar;
+        t.skipWhiteSpace();
+        const char* wordStart = t.fChar;
+        t.skipToMethodStart();
+        const char* start = t.fChar;
+        if (wordStart < start) {
+            if (lineStart) {
+                lineStart = false;
+            } else {
+                wordStart = base;
+            }
+            result += string(wordStart, start - wordStart);
+            if ('\n' != result.back()) {
+                while (start > wordStart && '\n' == start[-1]) {
+                    result += '\n';
+                    --start;
+                }
+            }
+        }
+        if (lineStart) {
+            lineStart = false;
+        } else {
+            leadingSpaces = string(base, wordStart - base);
+         }
+        t.skipToMethodEnd();
+        if (base == t.fChar) {
+            break;
+        }
+        if (start >= t.fChar) {
+            continue;
+        }
+        if (!t.eof() && '"' == t.peek() && start > wordStart && '"' == start[-1]) {
+            continue;
+        }
+        ref = string(start, t.fChar - start);
+        if (const Definition* def = this->isDefined(t, ref,
+                BmhParser::Resolvable::kOut != resolvable)) {
+            SkASSERT(def->fFiddle.length());
+            if (!t.eof() && '(' == t.peek() && t.strnchr(')', t.fEnd)) {
+                if (!t.skipToEndBracket(')')) {
+                    t.reportError("missing close paren");
+                    return result;
+                }
+                t.next();
+                string fullRef = string(start, t.fChar - start);
+                // if _2 etc alternates are defined, look for paren match
+                // may ignore () if ref is all lower case
+                // otherwise flag as error
+                int suffix = '2';
+                bool foundMatch = false;
+                const Definition* altDef = def;
+                while (altDef && suffix <= '9') {
+                    if ((foundMatch = altDef->paramsMatch(fullRef, ref))) {
+                        def = altDef;
+                        ref = fullRef;
+                        break;
+                    }
+                    string altTest = ref + '_';
+                    altTest += suffix++;
+                    altDef = this->isDefined(t, altTest, false);
+                }
+                if (suffix > '9') {
+                    t.reportError("too many alts");
+                    return result;
+                }
+                if (!foundMatch) {
+                    if (!(def = this->isDefined(t, fullRef, true))) {
+                        return result;
+                    }
+                    ref = fullRef;
+                }
+            }
+            result += linkRef(leadingSpaces, def, ref);
+            continue;
+        }
+        if (!t.eof() && '(' == t.peek()) {
+            if (!t.skipToEndBracket(')')) {
+                t.reportError("missing close paren");
+                return result;
+            }
+            t.next();
+            ref = string(start, t.fChar - start);
+            if (const Definition* def = this->isDefined(t, ref, true)) {
+                SkASSERT(def->fFiddle.length());
+                result += linkRef(leadingSpaces, def, ref);
+                continue;
+            }
+        }
+// class, struct, and enum start with capitals
+// methods may start with upper (static) or lower (most)
+
+        // see if this should have been a findable reference
+                 
+            // look for Sk / sk / SK ..
+        if (!ref.compare(0, 2, "Sk") && ref != "Skew" && ref != "Skews" &&
+              ref != "Skip" && ref != "Skips") {
+            t.reportError("missed Sk prefixed");
+            return result;
+        } 
+        if (!ref.compare(0, 2, "SK")) {
+            if (BmhParser::Resolvable::kOut != resolvable) {
+                t.reportError("missed SK prefixed");
+            }
+            return result;
+        } 
+        if (!isupper(start[0])) {
+            // TODO:
+            // look for all lowercase w/o trailing parens as mistaken method matches
+            // will also need to see if Example Description matches var in example
+            const Definition* def;
+            if (fMethod && (def = fMethod->hasParam(ref))) {
+                result += linkRef(leadingSpaces, def, ref);
+                continue;
+            } else if (!fInDescription && ref[0] != '0' 
+                    && string::npos != ref.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) {
+                // FIXME: see isDefined(); check to see if fXX is a member of xx.fXX
+                if (('f' != ref[0] && string::npos == ref.find("()"))
+                        || '.' != t.backup(ref.c_str())) {
+                    if (BmhParser::Resolvable::kOut != resolvable) {
+                        t.reportError("missed camelCase");
+                        return result;
+                    }
+                }
+            }
+            add_ref(leadingSpaces, ref, &result);
+            continue;
+        }
+        auto topicIter = fBmhParser.fTopicMap.find(ref);
+        if (topicIter != fBmhParser.fTopicMap.end()) {
+            result += linkRef(leadingSpaces, topicIter->second, ref);
+            continue;
+        }
+        bool startsSentence = t.sentenceEnd(start);
+        if (!t.eof() && ' ' != t.peek()) {
+            add_ref(leadingSpaces, ref, &result);
+            continue;
+        }
+        if (t.fChar + 1 >= t.fEnd || (!isupper(t.fChar[1]) && startsSentence)) {
+            add_ref(leadingSpaces, ref, &result);
+            continue;
+        }
+        if (isupper(t.fChar[1]) && startsSentence) {
+            TextParser next(t.fFileName, &t.fChar[1], t.fEnd, t.fLineCount);
+            string nextWord(next.fChar, next.wordEnd() - next.fChar);
+            if (this->isDefined(t, nextWord, true)) {
+                add_ref(leadingSpaces, ref, &result);
+                continue;
+            }
+        }
+        Definition* test = fRoot;
+        do {
+            if (!test->isRoot()) {
+                continue;
+            }
+            for (string prefix : { "_", "::" } ) {
+                RootDefinition* root = test->asRoot();
+                string prefixed = root->fName + prefix + ref;
+                if (const Definition* def = root->find(prefixed)) {
+                    result += linkRef(leadingSpaces, def, ref);
+                    goto found;
+                }
+            }
+        } while ((test = test->fParent));
+    found:
+        if (!test) {
+            if (BmhParser::Resolvable::kOut != resolvable) {
+                t.reportError("undefined reference");
+            }
+        }
+    } while (!t.eof());
+    return result;
+}
+
+bool MdOut::buildReferences(const char* fileOrPath, const char* outDir) {
+    if (!sk_isdir(fileOrPath)) {
+        if (!this->buildRefFromFile(fileOrPath, outDir)) {
+            SkDebugf("failed to parse %s\n", fileOrPath);
+            return false;
+        }
+    } else {
+        SkOSFile::Iter it(fileOrPath, ".bmh");
+        for (SkString file; it.next(&file); ) {
+            SkString p = SkOSPath::Join(fileOrPath, file.c_str());
+            const char* hunk = p.c_str();
+            if (!SkStrEndsWith(hunk, ".bmh")) {
+                continue;
+            }
+            if (SkStrEndsWith(hunk, "markup.bmh")) {  // don't look inside this for now
+                continue;
+            }
+            if (!this->buildRefFromFile(hunk, outDir)) {
+                SkDebugf("failed to parse %s\n", hunk);
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+bool MdOut::buildRefFromFile(const char* name, const char* outDir) {
+    fFileName = string(name);
+    string filename(name);
+    if (filename.substr(filename.length() - 4) == ".bmh") {
+        filename = filename.substr(0, filename.length() - 4);
+    }
+    size_t start = filename.length();
+    while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
+        --start;
+    }
+    string match = filename.substr(start);
+    string header = match;
+    filename = "bmh_" + match + ".md";
+    match += ".bmh";
+    fOut = nullptr;
+    for (const auto& topic : fBmhParser.fTopicMap) {
+        Definition* topicDef = topic.second;
+        if (topicDef->fParent) {
+            continue;
+        }
+        if (!topicDef->isRoot()) {
+            return this->reportError<bool>("expected root topic");
+        }
+        fRoot = topicDef->asRoot();
+        if (string::npos == fRoot->fFileName.rfind(match)) {
+            continue;
+        }
+        if (!fOut) {
+            string fullName(outDir);
+            if ('/' != fullName.back()) {
+                fullName += '/';
+            }
+            fullName += filename;
+            fOut = fopen(fullName.c_str(), "wb");
+            if (!fOut) {
+                SkDebugf("could not open output file %s\n", fullName.c_str());
+                return false;
+            }
+            fprintf(fOut, "Experimental %s", header.c_str());
+            this->lfAlways(1);
+            fprintf(fOut, "===");
+        }
+        this->markTypeOut(topicDef);
+    }
+    if (fOut) {
+        this->writePending();
+        fclose(fOut);
+        fOut = nullptr;
+    }
+    return true;
+}
+
+void MdOut::childrenOut(const Definition* def, const char* start) {
+    const char* end;
+    fLineCount = def->fLineCount;
+    if (def->isRoot()) {
+        fRoot = const_cast<RootDefinition*>(def->asRoot());
+    }
+    BmhParser::Resolvable resolvable = this->resolvable(def->fMarkType);
+    for (auto& child : def->fChildren) {
+        end = child->fStart;
+        if (BmhParser::Resolvable::kNo != resolvable) {
+            this->resolveOut(start, end, resolvable);
+        }
+        this->markTypeOut(child);
+        start = child->fTerminator;
+    }
+    if (BmhParser::Resolvable::kNo != resolvable) {
+        end = def->fContentEnd;
+        this->resolveOut(start, end, resolvable);
+    }
+}
+
+const Definition* MdOut::isDefined(const TextParser& parser, const string& ref, bool report) const {
+    auto rootIter = fBmhParser.fClassMap.find(ref);
+    if (rootIter != fBmhParser.fClassMap.end()) {
+        return &rootIter->second;
+    }
+    auto typedefIter = fBmhParser.fTypedefMap.find(ref);
+    if (typedefIter != fBmhParser.fTypedefMap.end()) {
+        return &typedefIter->second;
+    }
+    auto enumIter = fBmhParser.fEnumMap.find(ref);
+    if (enumIter != fBmhParser.fEnumMap.end()) {
+        return &enumIter->second;
+    }
+    auto constIter = fBmhParser.fConstMap.find(ref);
+    if (constIter != fBmhParser.fConstMap.end()) {
+        return &constIter->second;
+    }
+    auto methodIter = fBmhParser.fMethodMap.find(ref);
+    if (methodIter != fBmhParser.fMethodMap.end()) {
+        return &methodIter->second;
+    }
+    auto aliasIter = fBmhParser.fAliasMap.find(ref);
+    if (aliasIter != fBmhParser.fAliasMap.end()) {
+        return aliasIter->second;
+    }
+    for (const auto& external : fBmhParser.fExternals) {
+        if (external.fName == ref) {
+            return &external;
+        }
+    }
+    if (fRoot) {
+        if (ref == fRoot->fName) {
+            return fRoot;
+        }
+        if (const Definition* definition = fRoot->find(ref)) {
+            return definition;
+        }
+        Definition* test = fRoot;
+        do {
+            if (!test->isRoot()) {
+                continue;
+            }
+            RootDefinition* root = test->asRoot();
+            for (auto& leaf : root->fBranches) {
+                if (ref == leaf.first) {
+                    return leaf.second;
+                }
+                const Definition* definition = leaf.second->find(ref);
+                if (definition) {
+                    return definition;
+                }
+            }
+            for (string prefix : { "::", "_" } ) {
+                string prefixed = root->fName + prefix + ref;
+                if (const Definition* definition = root->find(prefixed)) {
+                    return definition;
+                }
+                if (isupper(prefixed[0])) {
+                    auto topicIter = fBmhParser.fTopicMap.find(prefixed);
+                    if (topicIter != fBmhParser.fTopicMap.end()) {
+                        return topicIter->second;
+                    }
+                }
+            }
+        } while ((test = test->fParent));
+    }
+    size_t doubleColon = ref.find("::");
+    if (string::npos != doubleColon) {
+        string className = ref.substr(0, doubleColon);
+        auto classIter = fBmhParser.fClassMap.find(className);
+        if (classIter != fBmhParser.fClassMap.end()) {
+            const RootDefinition& classDef = classIter->second;
+            const Definition* result = classDef.find(ref);
+            if (result) {
+                return result;
+            }
+        }
+
+    }
+    if (!ref.compare(0, 2, "SK") || !ref.compare(0, 3, "sk_")
+            || (('k' == ref[0] || 'g' == ref[0] || 'f' == ref[0]) &&
+                ref.length() > 1 && isupper(ref[1]))) {
+        // try with a prefix
+        if ('k' == ref[0]) {
+            for (auto const& iter : fBmhParser.fEnumMap) {
+                if (iter.second.find(ref)) {
+                    return &iter.second;
+                }
+            }
+        }
+        if ('f' == ref[0]) {
+            // FIXME : find def associated with prior, e.g.: r.fX where 'SkPoint r' was earlier
+                // need to have pushed last resolve on stack to do this
+                // for now, just try to make sure that it's there and error if not
+            if ('.' != parser.backup(ref.c_str())) {
+                parser.reportError("fX member undefined");
+                return nullptr;
+            }
+        } else {
+            if (report) {
+                parser.reportError("SK undefined");
+            }
+            return nullptr;
+        }
+    }
+    if (isupper(ref[0])) {
+        auto topicIter = fBmhParser.fTopicMap.find(ref);
+        if (topicIter != fBmhParser.fTopicMap.end()) {
+            return topicIter->second;
+        }
+        size_t pos = ref.find('_');
+        if (string::npos != pos) {
+            // see if it is defined by another base class
+            string className(ref, 0, pos);
+            auto classIter = fBmhParser.fClassMap.find(className);
+            if (classIter != fBmhParser.fClassMap.end()) {
+                if (const Definition* definition = classIter->second.find(ref)) {
+                    return definition;
+                }
+            }
+            auto enumIter = fBmhParser.fEnumMap.find(className);
+            if (enumIter != fBmhParser.fEnumMap.end()) {
+                if (const Definition* definition = enumIter->second.find(ref)) {
+                    return definition;
+                }
+            }
+            if (report) {
+                parser.reportError("_ undefined");
+            }
+            return nullptr;
+        }
+    }
+    return nullptr;
+}
+
+string MdOut::linkName(const Definition* ref) const {
+    string result = ref->fName;
+    size_t under = result.find('_');
+    if (string::npos != under) {
+        string classPart = result.substr(0, under);
+        string namePart = result.substr(under + 1, result.length());
+        if (fRoot && (fRoot->fName == classPart
+                || (fRoot->fParent && fRoot->fParent->fName == classPart))) {
+            result = namePart;
+        }
+    }
+    return result;
+}
+
+// for now, hard-code to html links
+// def should not include SkXXX_
+string MdOut::linkRef(const string& leadingSpaces, const Definition* def,
+        const string& ref) const {
+    string buildup;
+    const string* str = &def->fFiddle;
+    SkASSERT(str->length() > 0);
+    size_t under = str->find('_');
+    Definition* curRoot = fRoot;
+    string classPart = string::npos != under ? str->substr(0, under) : *str;
+    bool classMatch = curRoot->fName == classPart;
+    while (curRoot->fParent) {
+        curRoot = curRoot->fParent;
+        classMatch |= curRoot->fName == classPart;
+    }
+    const Definition* defRoot;
+    do {
+        defRoot = def;
+        if (!(def = def->fParent)) {
+            break;
+        }
+        classMatch |= def != defRoot && def->fName == classPart;
+    } while (true);
+    string namePart = string::npos != under ? str->substr(under + 1, str->length()) : *str;
+    SkASSERT(fRoot);
+    SkASSERT(fRoot->fFileName.length());
+    if (false && classMatch) {
+        str = &namePart;
+    } else if (true || (curRoot != defRoot && defRoot->isRoot())) {
+        string filename = defRoot->asRoot()->fFileName;
+        if (filename.substr(filename.length() - 4) == ".bmh") {
+            filename = filename.substr(0, filename.length() - 4);
+        }
+        size_t start = filename.length();
+        while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
+            --start;
+        }
+        buildup = "bmh_" + filename.substr(start) + "?cl=9919#"
+                + (classMatch ? namePart : *str);
+        str = &buildup;
+    }
+    string refOut(ref);
+    std::replace(refOut.begin(), refOut.end(), '_', ' ');
+    if (ref.length() > 2 && islower(ref[0]) && "()" == ref.substr(ref.length() - 2)) {
+        refOut = refOut.substr(0, refOut.length() - 2);
+    }
+    return leadingSpaces + "<a href=\"" + *str + "\">" + refOut + "</a>";
+}
+
+void MdOut::markTypeOut(Definition* def) {
+    string printable = def->printableName();
+    const char* textStart = def->fContentStart;
+    if (MarkType::kParam != def->fMarkType && MarkType::kConst != def->fMarkType &&
+            (!def->fParent || MarkType::kConst != def->fParent->fMarkType) &&
+            TableState::kNone != fTableState) {
+        this->writePending();
+        fprintf(fOut, "</table>");
+        this->lf(2);
+        fTableState = TableState::kNone;
+    }
+    switch (def->fMarkType) {
+        case MarkType::kAlias:
+            break;
+        case MarkType::kAnchor:
+            break;
+        case MarkType::kBug:
+            break;
+        case MarkType::kClass:
+            this->mdHeaderOut(1);
+            fprintf(fOut, "<a name=\"%s\"></a> Class %s", this->linkName(def).c_str(),
+                    def->fName.c_str());
+            this->lf(1);
+            break;
+        case MarkType::kCode:
+            this->lfAlways(2);
+            fprintf(fOut, "<pre style=\"padding: 1em 1em 1em 1em;"
+                    "width: 44em; background-color: #f0f0f0\">");
+            this->lf(1);
+            break;
+        case MarkType::kColumn:
+            this->writePending();
+            if (fInList) {
+                fprintf(fOut, "    <td>");
+            } else {
+                fprintf(fOut, "| ");
+            }
+            break;
+        case MarkType::kComment:
+            break;
+        case MarkType::kConst: {
+            if (TableState::kNone == fTableState) {
+                this->mdHeaderOut(3);
+                fprintf(fOut, "Constants\n"
+                        "\n"
+                        "<table>");
+                fTableState = TableState::kRow;
+                this->lf(1);
+            }
+            if (TableState::kRow == fTableState) {
+                this->writePending();
+                fprintf(fOut, "  <tr>");
+                this->lf(1);
+                fTableState = TableState::kColumn;
+            }
+            this->writePending();
+            fprintf(fOut, "    <td><a name=\"%s\"></a> <code><strong>%s </strong></code></td>",
+                    def->fName.c_str(), def->fName.c_str());
+            const char* lineEnd = strchr(textStart, '\n');
+            SkASSERT(lineEnd < def->fTerminator);
+            SkASSERT(lineEnd > textStart);
+            SkASSERT((int) (lineEnd - textStart) == lineEnd - textStart);
+            fprintf(fOut, "<td>%.*s</td>", (int) (lineEnd - textStart), textStart);
+            fprintf(fOut, "<td>");
+            textStart = lineEnd;
+        } break;
+        case MarkType::kDefine:
+            break;
+        case MarkType::kDefinedBy:
+            break;
+        case MarkType::kDeprecated:
+            break;
+        case MarkType::kDescription:
+            fInDescription = true;
+            this->writePending();
+            fprintf(fOut, "<div>");
+            break;
+        case MarkType::kDoxygen:
+            break;
+        case MarkType::kEnum:
+        case MarkType::kEnumClass:
+            this->mdHeaderOut(2);
+            fprintf(fOut, "<a name=\"%s\"></a> Enum %s", def->fName.c_str(), def->fName.c_str());
+            this->lf(2);
+            break;
+        case MarkType::kError:
+            break;
+        case MarkType::kExample: {
+            this->mdHeaderOut(3);
+            fprintf(fOut, "Example\n"
+                            "\n");
+            fHasFiddle = true;
+            const Definition* platform = def->hasChild(MarkType::kPlatform);
+            if (platform) {
+                TextParser platParse(platform);
+                fHasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
+            }
+            if (fHasFiddle) {
+                fprintf(fOut, "<div><fiddle-embed name=\"%s\">", def->fHash.c_str());
+            } else {
+                fprintf(fOut, "<pre style=\"padding: 1em 1em 1em 1em;"
+                        "width: 44em; background-color: #f0f0f0\">");
+                this->lf(1);
+            }
+            } break;
+        case MarkType::kExperimental:
+            break;
+        case MarkType::kExternal:
+            break;
+        case MarkType::kFile:
+            break;
+        case MarkType::kFormula:
+            break;
+        case MarkType::kFunction:
+            break;
+        case MarkType::kHeight:
+            break;
+        case MarkType::kImage:
+            break;
+        case MarkType::kLegend:
+            break;
+        case MarkType::kLink:
+            break;
+        case MarkType::kList:
+            fInList = true;
+            this->lfAlways(2);
+            fprintf(fOut, "<table>");
+            this->lf(1);
+            break;
+        case MarkType::kMarkChar:
+            fBmhParser.fMC = def->fContentStart[0];
+            break;
+        case MarkType::kMember: {
+            TextParser tp(def->fFileName, def->fStart, def->fContentStart, def->fLineCount);
+            tp.skipExact("#Member");
+            tp.skipWhiteSpace();
+            const char* end = tp.trimmedBracketEnd('\n', TextParser::OneLine::kYes);
+            this->lfAlways(2);
+            fprintf(fOut, "<code><strong>%.*s</strong></code>", (int) (end - tp.fChar), tp.fChar);
+            this->lf(2);
+            } break;
+        case MarkType::kMethod: {
+            string method_name = def->methodName();
+            string formattedStr = def->formatFunction();
+
+            if (!def->isClone()) {
+                this->lfAlways(2);
+                fprintf(fOut, "<a name=\"%s\"></a>", def->fiddleName().c_str());
+                this->mdHeaderOutLF(2, 1);
+                fprintf(fOut, "%s", method_name.c_str());
+                this->lf(2);
+            }
+
+            // TODO: put in css spec that we can define somewhere else (if markup supports that)
+            // TODO: 50em below should match limt = 80 in formatFunction()
+            this->writePending();
+            fprintf(fOut, "<pre style=\"padding: 1em 1em 1em 1em;"
+                                    "width: 50em; background-color: #f0f0f0\">\n"
+                            "%s\n"
+                            "</pre>",  formattedStr.c_str());
+            this->lf(2);
+            fTableState = TableState::kNone;
+            fMethod = def;
+            } break;
+        case MarkType::kNoExample:
+            break;
+        case MarkType::kParam: {
+            if (TableState::kNone == fTableState) {
+                this->mdHeaderOut(3);
+                fprintf(fOut, 
+                        "Parameters\n"
+                        "\n"
+                        "<table>"
+                        );
+                this->lf(1);
+                fTableState = TableState::kRow;
+            }
+            if (TableState::kRow == fTableState) {
+                fprintf(fOut, "  <tr>");
+                this->lf(1);
+                fTableState = TableState::kColumn;
+            }
+            TextParser paramParser(def->fFileName, def->fStart, def->fContentStart,
+                    def->fLineCount);
+            paramParser.skipWhiteSpace();
+            SkASSERT(paramParser.startsWith("#Param"));
+            paramParser.next(); // skip hash
+            paramParser.skipToNonAlphaNum(); // skip Param
+            paramParser.skipSpace();
+            const char* paramName = paramParser.fChar;
+            paramParser.skipToSpace();
+            fprintf(fOut, 
+                    "    <td><code><strong>%.*s </strong></code></td> <td>",
+                    (int) (paramParser.fChar - paramName), paramName);
+        } break;
+        case MarkType::kPlatform:
+            break;
+        case MarkType::kPrivate:
+            break;
+        case MarkType::kReturn:
+            this->mdHeaderOut(3);
+            fprintf(fOut, "Return Value");
+            this->lf(2);
+            break;
+        case MarkType::kRow:
+            if (fInList) {
+                fprintf(fOut, "  <tr>");
+                this->lf(1);
+            }
+            break;
+        case MarkType::kSeeAlso:
+            this->mdHeaderOut(3);
+            fprintf(fOut, "See Also");
+            this->lf(2);
+            break;
+        case MarkType::kStdOut: {
+            TextParser code(def);
+            this->mdHeaderOut(4);
+            fprintf(fOut, 
+                    "Example Output\n"
+                    "\n"
+                    "~~~~");
+            this->lfAlways(1);
+            code.skipSpace();
+            while (!code.eof()) {
+                const char* end = code.trimmedLineEnd();
+                fprintf(fOut, "%.*s\n", (int) (end - code.fChar), code.fChar);
+                code.skipToLineStart();
+            }
+            fprintf(fOut, "~~~~");
+            this->lf(2);
+            } break;
+        case MarkType::kStruct:
+            fRoot = def->asRoot();
+            this->mdHeaderOut(1);
+            fprintf(fOut, "<a name=\"%s\"></a> Struct %s", def->fName.c_str(), def->fName.c_str());
+            this->lf(1);
+            break;
+        case MarkType::kSubstitute:
+            break;
+        case MarkType::kSubtopic:
+            this->mdHeaderOut(2);
+            fprintf(fOut, "<a name=\"%s\"></a> %s", def->fName.c_str(), printable.c_str());
+            this->lf(2);
+            break;
+        case MarkType::kTable:
+            this->lf(2);
+            break;
+        case MarkType::kTemplate:
+            break;
+        case MarkType::kText:
+            break;
+        case MarkType::kTime:
+            break;
+        case MarkType::kToDo:
+            break;
+        case MarkType::kTopic:
+            this->mdHeaderOut(1);
+            fprintf(fOut, "<a name=\"%s\"></a> %s", this->linkName(def).c_str(),
+                    printable.c_str());
+            this->lf(1);
+            break;
+        case MarkType::kTrack:
+            // don't output children
+            return;
+        case MarkType::kTypedef:
+            break;
+        case MarkType::kUnion:
+            break;
+        case MarkType::kVolatile:
+            break;
+        case MarkType::kWidth:
+            break;
+        default:
+            SkDebugf("fatal error: MarkType::k%s unhandled in %s()\n",
+                    fBmhParser.fMaps[(int) def->fMarkType].fName, __func__);
+            SkASSERT(0); // handle everything
+            break;
+    }
+    this->childrenOut(def, textStart);
+    switch (def->fMarkType) {  // post child work, at least for tables
+        case MarkType::kCode:
+            this->writePending();
+            fprintf(fOut, "</pre>");
+            this->lf(2);
+            break;
+        case MarkType::kColumn:
+            if (fInList) {
+                this->writePending();
+                fprintf(fOut, "</td>");
+                this->lf(1);
+            } else {
+                fprintf(fOut, " ");
+            }
+            break;
+        case MarkType::kDescription:
+            this->writePending();
+            fprintf(fOut, "</div>");
+            fInDescription = false;
+            break;
+        case MarkType::kEnum:
+        case MarkType::kEnumClass:
+            this->lfAlways(2);
+            break;
+        case MarkType::kExample:
+            this->writePending();
+            if (fHasFiddle) {
+                fprintf(fOut, "</fiddle-embed></div>");
+            } else {
+                fprintf(fOut, "</pre>");
+            }
+            this->lf(2);
+            break;
+        case MarkType::kList:
+            fInList = false;
+            this->writePending();
+            fprintf(fOut, "</table>");
+            this->lf(2);
+            break;
+        case MarkType::kLegend: {
+            SkASSERT(def->fChildren.size() == 1);
+            const Definition* row = def->fChildren[0];
+            SkASSERT(MarkType::kRow == row->fMarkType);
+            size_t columnCount = row->fChildren.size();
+            SkASSERT(columnCount > 0);
+            this->writePending();
+            for (size_t index = 0; index < columnCount; ++index) {
+                fprintf(fOut, "| --- ");
+            }
+            fprintf(fOut, " |");
+            this->lf(1);
+            } break;
+        case MarkType::kMethod:
+            fMethod = nullptr;
+            this->lfAlways(2);
+            fprintf(fOut, "---");
+            this->lf(2);
+            break;
+        case MarkType::kConst:
+        case MarkType::kParam:
+            SkASSERT(TableState::kColumn == fTableState);
+            fTableState = TableState::kRow;
+            this->writePending();
+            fprintf(fOut, "</td>\n");
+            fprintf(fOut, "  </tr>");
+            this->lf(1);
+            break;
+        case MarkType::kReturn:
+        case MarkType::kSeeAlso:
+            this->lf(2);
+            break;
+        case MarkType::kRow:
+            if (fInList) {
+                fprintf(fOut, "  </tr>");
+            } else {
+                fprintf(fOut, "|");
+            }
+            this->lf(1);
+            break;
+        case MarkType::kStruct:
+            fRoot = fRoot->rootParent();
+            break;
+        case MarkType::kTable:
+            this->lf(2);
+            break;
+        case MarkType::kPrivate:
+            break;
+        default:
+            break;
+    }
+}
+
+void MdOut::mdHeaderOutLF(int depth, int lf) {
+    this->lfAlways(lf);
+    for (int index = 0; index < depth; ++index) {
+        fprintf(fOut, "#");
+    }
+    fprintf(fOut, " ");
+}
+
+void MdOut::resolveOut(const char* start, const char* end, BmhParser::Resolvable resolvable) {
+    // FIXME: this needs the markdown character present when the def was defined,
+    // not the last markdown character the parser would have seen...
+    while (fBmhParser.fMC == end[-1]) {
+        --end;
+    }
+    if (start >= end) {
+        return;
+    }
+    string resolved = this->addReferences(start, end, resolvable);
+    trim_end_spaces(resolved);
+    if (resolved.length()) {
+        TextParser paragraph(fFileName, &*resolved.begin(), &*resolved.end(), fLineCount);
+        TextParser original(fFileName, start, end, fLineCount);
+        while (!original.eof() && '\n' == original.peek()) {
+            original.next();
+        }
+        original.skipSpace();
+        while (!paragraph.eof()) {
+            paragraph.skipWhiteSpace();
+            const char* contentStart = paragraph.fChar;
+            paragraph.skipToEndBracket('\n');
+            ptrdiff_t lineLength = paragraph.fChar - contentStart;
+            if (lineLength) {
+                this->writePending();
+                fprintf(fOut, "%.*s", (int) lineLength, contentStart);
+            }
+            int linefeeds = 0;
+            while (lineLength > 0 && '\n' == contentStart[--lineLength]) {
+                ++linefeeds;
+            }
+            if (lineLength > 0) {
+                this->nl();
+            }
+            fLinefeeds += linefeeds;
+            if (paragraph.eof()) {
+                break;
+            }
+            if ('\n' == paragraph.next()) {
+                linefeeds = 1;
+                if (!paragraph.eof() && '\n' == paragraph.peek()) {
+                    linefeeds = 2;
+                }
+                this->lf(linefeeds);
+            }
+        }
+#if 0
+        while (end > start && end[0] == '\n') {
+            fprintf(fOut, "\n");
+            --end;
+        }
+#endif
+    }
+}
diff --git a/tools/bookmaker/parserCommon.cpp b/tools/bookmaker/parserCommon.cpp
new file mode 100644
index 0000000..cb55bcb
--- /dev/null
+++ b/tools/bookmaker/parserCommon.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bookmaker.h"
+
+bool ParserCommon::parseSetup(const char* path) {
+    this->reset();
+    sk_sp<SkData> data = SkData::MakeFromFileName(path);
+    if (nullptr == data.get()) {
+        SkDebugf("%s missing\n", path);
+        return false;
+    }
+    const char* rawText = (const char*) data->data();
+    bool hasCR = false;
+    size_t dataSize = data->size();
+    for (size_t index = 0; index < dataSize; ++index) {
+        if ('\r' == rawText[index]) {
+            hasCR = true;
+            break;
+        }
+    }
+    string name(path);
+    if (hasCR) {
+        vector<char> lfOnly;
+        for (size_t index = 0; index < dataSize; ++index) {
+            char ch = rawText[index];
+            if ('\r' == rawText[index]) {
+                ch = '\n';
+                if ('\n' == rawText[index + 1]) {
+                    ++index;
+                }
+            }
+            lfOnly.push_back(ch);
+        }
+        fLFOnly[name] = lfOnly;
+        dataSize = lfOnly.size();
+        rawText = &fLFOnly[name].front();
+    }
+    fRawData[name] = data;
+    fStart = rawText;
+    fLine = rawText;
+    fChar = rawText;
+    fEnd = rawText + dataSize;
+    fFileName = string(path);
+    fLineCount = 1;
+    return true;
+}
diff --git a/tools/bookmaker/spellCheck.cpp b/tools/bookmaker/spellCheck.cpp
new file mode 100644
index 0000000..e43a412
--- /dev/null
+++ b/tools/bookmaker/spellCheck.cpp
@@ -0,0 +1,455 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bookmaker.h"
+
+#include "SkOSFile.h"
+#include "SkOSPath.h"
+
+/* 
+things to do
+if cap word is beginning of sentence, add it to table as lower-case
+   word must have only a single initial capital
+
+if word is camel cased, look for :: matches on suffix
+
+when function crosses lines, whole thing isn't seen as a 'word' e.g., search for largeArc in path
+
+words in external not seen
+ */
+struct CheckEntry {
+    string fFile;
+    int fLine;
+    int fCount;
+};
+
+class SpellCheck : public ParserCommon {
+public:
+    SpellCheck(const BmhParser& bmh) : ParserCommon()
+        , fBmhParser(bmh) {
+        this->reset();
+    }
+    bool check(const char* match);
+    void report();
+private:
+    enum class TableState {
+        kNone,
+        kRow,
+        kColumn,
+    };
+
+    bool check(Definition* );
+    bool checkable(MarkType markType);
+    void childCheck(const Definition* def, const char* start);
+    void leafCheck(const char* start, const char* end);
+    bool parseFromFile(const char* path) override { return true; }
+    void printCheck(const string& str);
+
+    void reset() override {
+        INHERITED::resetCommon();
+        fMethod = nullptr;
+        fRoot = nullptr;
+        fTableState = TableState::kNone;
+        fInCode = false;
+        fInConst = false;
+        fInDescription = false;
+        fInStdOut = false;
+    }
+
+    void wordCheck(const string& str);
+    void wordCheck(ptrdiff_t len, const char* ch);
+
+    unordered_map<string, CheckEntry> fCode;
+    unordered_map<string, CheckEntry> fColons;
+    unordered_map<string, CheckEntry> fDigits;
+    unordered_map<string, CheckEntry> fDots;
+    unordered_map<string, CheckEntry> fParens;  // also hold destructors, operators
+    unordered_map<string, CheckEntry> fUnderscores;
+    unordered_map<string, CheckEntry> fWords;
+    const BmhParser& fBmhParser;
+    Definition* fMethod;
+    RootDefinition* fRoot;
+    TableState fTableState;
+    bool fInCode;
+    bool fInConst;
+    bool fInDescription;
+    bool fInStdOut;
+    typedef ParserCommon INHERITED;
+};
+
+/* This doesn't perform a traditional spell or grammar check, although
+   maybe it should. Instead it looks for words used uncommonly and lower
+   case words that match capitalized words that are not sentence starters.
+   It also looks for articles preceeding capitalized words and their
+   modifiers to try to maintain a consistent voice.
+   Maybe also look for passive verbs (e.g. 'is') and suggest active ones?
+ */
+void BmhParser::spellCheck(const char* match) const {
+    SpellCheck checker(*this);
+    checker.check(match);
+    checker.report();
+}
+
+bool SpellCheck::check(const char* match) {
+    for (const auto& topic : fBmhParser.fTopicMap) {
+        Definition* topicDef = topic.second;
+        if (topicDef->fParent) {
+            continue;
+        }
+        if (!topicDef->isRoot()) {
+            return this->reportError<bool>("expected root topic");
+        }
+        fRoot = topicDef->asRoot();
+        if (string::npos == fRoot->fFileName.rfind(match)) {
+            continue;
+        }
+       this->check(topicDef);
+    }
+    return true;
+}
+
+bool SpellCheck::check(Definition* def) {
+    fFileName = def->fFileName;
+    fLineCount = def->fLineCount;
+    string printable = def->printableName();
+    const char* textStart = def->fContentStart;
+    if (MarkType::kParam != def->fMarkType && MarkType::kConst != def->fMarkType &&
+            TableState::kNone != fTableState) {
+        fTableState = TableState::kNone;
+    }
+    switch (def->fMarkType) {
+        case MarkType::kAlias:
+            break;
+        case MarkType::kAnchor:
+            break;
+        case MarkType::kBug:
+            break;
+        case MarkType::kClass:
+            this->wordCheck(def->fName);
+            break;
+        case MarkType::kCode:
+            fInCode = true;
+            break;
+        case MarkType::kColumn:
+            break;
+        case MarkType::kComment:
+            break;
+        case MarkType::kConst: {
+            fInConst = true;
+            if (TableState::kNone == fTableState) {
+                fTableState = TableState::kRow;
+            }
+            if (TableState::kRow == fTableState) {
+                fTableState = TableState::kColumn;
+            }
+            this->wordCheck(def->fName);
+            const char* lineEnd = strchr(textStart, '\n');
+            this->wordCheck(lineEnd - textStart, textStart);
+            textStart = lineEnd;
+        } break;
+        case MarkType::kDefine:
+            break;
+        case MarkType::kDefinedBy:
+            break;
+        case MarkType::kDeprecated:
+            break;
+        case MarkType::kDescription:
+            fInDescription = true;
+            break;
+        case MarkType::kDoxygen:
+            break;
+        case MarkType::kEnum:
+        case MarkType::kEnumClass:
+            this->wordCheck(def->fName);
+            break;
+        case MarkType::kError:
+            break;
+        case MarkType::kExample:
+            break;
+        case MarkType::kExternal:
+            break;
+        case MarkType::kFile:
+            break;
+        case MarkType::kFormula:
+            break;
+        case MarkType::kFunction:
+            break;
+        case MarkType::kHeight:
+            break;
+        case MarkType::kImage:
+            break;
+        case MarkType::kLegend:
+            break;
+        case MarkType::kList:
+            break;
+        case MarkType::kMember:
+            break;
+        case MarkType::kMethod: {
+            string method_name = def->methodName();
+            string formattedStr = def->formatFunction();
+            if (!def->isClone()) {
+                this->wordCheck(method_name);
+            }
+            fTableState = TableState::kNone;
+            fMethod = def;
+            } break;
+        case MarkType::kParam: {
+            if (TableState::kNone == fTableState) {
+                fTableState = TableState::kRow;
+            }
+            if (TableState::kRow == fTableState) {
+                fTableState = TableState::kColumn;
+            }
+            TextParser paramParser(def->fFileName, def->fStart, def->fContentStart,
+                    def->fLineCount);
+            paramParser.skipWhiteSpace();
+            SkASSERT(paramParser.startsWith("#Param"));
+            paramParser.next(); // skip hash
+            paramParser.skipToNonAlphaNum(); // skip Param
+            paramParser.skipSpace();
+            const char* paramName = paramParser.fChar;
+            paramParser.skipToSpace();
+            fInCode = true;
+            this->wordCheck(paramParser.fChar - paramName, paramName);
+            fInCode = false;
+       } break;
+        case MarkType::kPlatform:
+            break;
+        case MarkType::kReturn:
+            break;
+        case MarkType::kRow:
+            break;
+        case MarkType::kSeeAlso:
+            break;
+        case MarkType::kStdOut: {
+            fInStdOut = true;
+            TextParser code(def);
+            code.skipSpace();
+            while (!code.eof()) {
+                const char* end = code.trimmedLineEnd();
+                this->wordCheck(end - code.fChar, code.fChar);
+                code.skipToLineStart();
+            }
+            fInStdOut = false;
+            } break;
+        case MarkType::kStruct:
+            fRoot = def->asRoot();
+            this->wordCheck(def->fName);
+            break;
+        case MarkType::kSubtopic:
+            this->printCheck(printable);
+            break;
+        case MarkType::kTable:
+            break;
+        case MarkType::kTemplate:
+            break;
+        case MarkType::kText:
+            break;
+        case MarkType::kTime:
+            break;
+        case MarkType::kToDo:
+            break;
+        case MarkType::kTopic:
+            this->printCheck(printable);
+            break;
+        case MarkType::kTrack:
+            // don't output children
+            return true;
+        case MarkType::kTypedef:
+            break;
+        case MarkType::kUnion:
+            break;
+        case MarkType::kWidth:
+            break;
+        default:
+            SkASSERT(0); // handle everything
+            break;
+    }
+    this->childCheck(def, textStart);
+    switch (def->fMarkType) {  // post child work, at least for tables
+        case MarkType::kCode:
+            fInCode = false;
+            break;
+        case MarkType::kColumn:
+            break;
+        case MarkType::kDescription:
+            fInDescription = false;
+            break;
+        case MarkType::kEnum:
+        case MarkType::kEnumClass:
+            break;
+        case MarkType::kExample:
+            break;
+        case MarkType::kLegend:
+            break;
+        case MarkType::kMethod:
+            fMethod = nullptr;
+            break;
+        case MarkType::kConst:
+            fInConst = false;
+        case MarkType::kParam:
+            SkASSERT(TableState::kColumn == fTableState);
+            fTableState = TableState::kRow;
+            break;
+        case MarkType::kReturn:
+        case MarkType::kSeeAlso:
+            break;
+        case MarkType::kRow:
+            break;
+        case MarkType::kStruct:
+            fRoot = fRoot->rootParent();
+            break;
+        case MarkType::kTable:
+            break;
+        default:
+            break;
+    }
+    return true;
+}
+
+bool SpellCheck::checkable(MarkType markType) {
+    return BmhParser::Resolvable::kYes == fBmhParser.fMaps[(int) markType].fResolve;
+}
+
+void SpellCheck::childCheck(const Definition* def, const char* start) {
+    const char* end;
+    fLineCount = def->fLineCount;
+    if (def->isRoot()) {
+        fRoot = const_cast<RootDefinition*>(def->asRoot());
+    }
+    for (auto& child : def->fChildren) {
+        end = child->fStart;
+        if (this->checkable(def->fMarkType)) {
+            this->leafCheck(start, end);
+        }
+        this->check(child);
+        start = child->fTerminator;
+    }
+    if (this->checkable(def->fMarkType)) {
+        end = def->fContentEnd;
+        this->leafCheck(start, end);
+    }
+}
+
+void SpellCheck::leafCheck(const char* start, const char* end) {
+    TextParser text("", start, end, fLineCount);
+    do {
+        const char* lineStart = text.fChar;
+        text.skipToAlpha();
+        if (text.eof()) {
+            break;
+        }
+        const char* wordStart = text.fChar;
+        text.fChar = lineStart;
+        text.skipTo(wordStart);  // advances line number
+        text.skipToNonAlphaNum();
+        fLineCount = text.fLineCount;
+        string word(wordStart, text.fChar - wordStart);
+        wordCheck(word);
+    } while (!text.eof());
+}
+
+void SpellCheck::printCheck(const string& str) {
+    string word;
+    for (std::stringstream stream(str); stream >> word; ) {
+        wordCheck(word);
+    }
+}
+
+void SpellCheck::report() {
+    for (auto iter : fWords) {
+        if (string::npos != iter.second.fFile.find("undocumented.bmh")) {
+            continue;
+        }
+        if (string::npos != iter.second.fFile.find("markup.bmh")) {
+            continue;
+        }
+        if (string::npos != iter.second.fFile.find("usingBookmaker.bmh")) {
+            continue;
+        }
+        if (iter.second.fCount == 1) {
+            SkDebugf("%s %s %d\n", iter.first.c_str(), iter.second.fFile.c_str(),
+                    iter.second.fLine);
+        }
+    }
+}
+
+void SpellCheck::wordCheck(const string& str) {
+    bool hasColon = false;
+    bool hasDot = false;
+    bool hasParen = false;
+    bool hasUnderscore = false;
+    bool sawDash = false;
+    bool sawDigit = false;
+    bool sawSpecial = false;
+    SkASSERT(str.length() > 0);
+    SkASSERT(isalpha(str[0]) || '~' == str[0]);
+    for (char ch : str) {
+        if (isalpha(ch) || '-' == ch) {
+            sawDash |= '-' == ch;
+            continue;
+        }
+        bool isColon = ':' == ch;
+        hasColon |= isColon;
+        bool isDot = '.' == ch;
+        hasDot |= isDot;
+        bool isParen = '(' == ch || ')' == ch || '~' == ch || '=' == ch || '!' == ch;
+        hasParen |= isParen;
+        bool isUnderscore = '_' == ch;
+        hasUnderscore |= isUnderscore;
+        if (isColon || isDot || isUnderscore || isParen) {
+            continue;
+        }
+        if (isdigit(ch)) {
+            sawDigit = true;
+            continue;
+        }
+        if ('&' == ch || ',' == ch || ' ' == ch) {
+            sawSpecial = true;
+            continue;
+        }
+        SkASSERT(0);
+    }
+    if (sawSpecial && !hasParen) {
+        SkASSERT(0);
+    }
+    bool inCode = fInCode;
+    if (hasUnderscore && isupper(str[0]) && ('S' != str[0] || 'K' != str[1])
+            && !hasColon && !hasDot && !hasParen && !fInStdOut && !inCode && !fInConst 
+            && !sawDigit && !sawSpecial && !sawDash) {
+        std::istringstream ss(str);
+        string token;
+        while (std::getline(ss, token, '_')) {
+            this->wordCheck(token);
+        }
+        return;
+    }
+    if (!hasColon && !hasDot && !hasParen && !hasUnderscore 
+            && !fInStdOut && !inCode && !fInConst && !sawDigit
+            && islower(str[0]) && isupper(str[1])) {
+        inCode = true;
+    }
+    auto& mappy = hasColon ? fColons : 
+                  hasDot ? fDots :
+                  hasParen ? fParens :
+                  hasUnderscore ? fUnderscores :
+                  fInStdOut || inCode || fInConst ? fCode :
+                  sawDigit ? fDigits : fWords;
+    auto iter = mappy.find(str);
+    if (mappy.end() != iter) {
+        iter->second.fCount += 1;
+    } else {
+        CheckEntry* entry = &mappy[str];
+        entry->fFile = fFileName;
+        entry->fLine = fLineCount;
+        entry->fCount = 1;
+    }
+}
+
+void SpellCheck::wordCheck(ptrdiff_t len, const char* ch) {
+    leafCheck(ch, ch + len);
+}