aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Cary Clark <caryclark@skia.org>2017-07-28 11:04:54 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-07-28 15:30:38 +0000
commit8032b983faaa8c76f81bf3cf028e9c64f4635478 (patch)
treeed3be061ff02a99dab1b3e443d48b7f5c906417e
parentacaa607328fb0dfac0894d4a2fcdead520e696b3 (diff)
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>
-rw-r--r--BUILD.gn17
-rw-r--r--docs/SkCanvas.bmh5721
-rw-r--r--docs/SkPaint.bmh5280
-rw-r--r--docs/SkPath.bmh5801
-rw-r--r--docs/markup.bmh88
-rw-r--r--docs/overview.bmh8
-rw-r--r--docs/undocumented.bmh528
-rw-r--r--docs/usingBookmaker.bmh95
-rw-r--r--tools/bookmaker/bookmaker.cpp2198
-rw-r--r--tools/bookmaker/bookmaker.h1844
-rw-r--r--tools/bookmaker/fiddleParser.cpp231
-rw-r--r--tools/bookmaker/includeParser.cpp1733
-rw-r--r--tools/bookmaker/includeWriter.cpp1272
-rw-r--r--tools/bookmaker/mdOut.cpp929
-rw-r--r--tools/bookmaker/parserCommon.cpp51
-rw-r--r--tools/bookmaker/spellCheck.cpp455
16 files changed, 26251 insertions, 0 deletions
diff --git a/BUILD.gn b/BUILD.gn
index 5b06c1766b..5956f6729a 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1213,6 +1213,23 @@ if (skia_enable_tools) {
}
}
+ 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 0000000000..aefbae4d33
--- /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 0000000000..4adb6dadcf
--- /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 0000000000..8c414518e9
--- /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 0000000000..2127fc71f4
--- /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 0000000000..c6b0d1fef8
--- /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 0000000000..8aecc3168f
--- /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 0000000000..65a8df561f
--- /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 0000000000..3b67663000
--- /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 0000000000..8d9d14f6a5
--- /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 0000000000..d5cfcf425c
--- /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 0000000000..089dcb3658
--- /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 0000000000..5685f31339
--- /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 0000000000..a7737d6d70
--- /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 0000000000..cb55bcb640
--- /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 0000000000..e43a412eed
--- /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);
+}