From 8032b983faaa8c76f81bf3cf028e9c64f4635478 Mon Sep 17 00:00:00 2001 From: Cary Clark Date: Fri, 28 Jul 2017 11:04:54 -0400 Subject: [PATCH] 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 Reviewed-by: Cary Clark --- BUILD.gn | 17 + docs/SkCanvas.bmh | 5721 ++++++++++++++++++++++++++++ docs/SkPaint.bmh | 5280 ++++++++++++++++++++++++++ docs/SkPath.bmh | 5801 +++++++++++++++++++++++++++++ docs/markup.bmh | 88 + docs/overview.bmh | 8 + docs/undocumented.bmh | 528 +++ docs/usingBookmaker.bmh | 95 + tools/bookmaker/bookmaker.cpp | 2198 +++++++++++ tools/bookmaker/bookmaker.h | 1844 +++++++++ tools/bookmaker/fiddleParser.cpp | 231 ++ tools/bookmaker/includeParser.cpp | 1733 +++++++++ tools/bookmaker/includeWriter.cpp | 1272 +++++++ tools/bookmaker/mdOut.cpp | 929 +++++ tools/bookmaker/parserCommon.cpp | 51 + tools/bookmaker/spellCheck.cpp | 455 +++ 16 files changed, 26251 insertions(+) create mode 100644 docs/SkCanvas.bmh create mode 100644 docs/SkPaint.bmh create mode 100644 docs/SkPath.bmh create mode 100644 docs/markup.bmh create mode 100644 docs/overview.bmh create mode 100644 docs/undocumented.bmh create mode 100644 docs/usingBookmaker.bmh create mode 100644 tools/bookmaker/bookmaker.cpp create mode 100644 tools/bookmaker/bookmaker.h create mode 100644 tools/bookmaker/fiddleParser.cpp create mode 100644 tools/bookmaker/includeParser.cpp create mode 100644 tools/bookmaker/includeWriter.cpp create mode 100644 tools/bookmaker/mdOut.cpp create mode 100644 tools/bookmaker/parserCommon.cpp create mode 100644 tools/bookmaker/spellCheck.cpp 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 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 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 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 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 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& 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 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 surface = SkSurface::MakeRasterN32Premul(5, 6); + SkCanvas* smallCanvas = surface->getCanvas(); + SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(3, 4); + sk_sp 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 c2 = + SkRasterHandleAllocator::MakeCanvas(std::unique_ptr( + 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 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 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& 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 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 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 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 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 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& 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 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& 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 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& 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 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 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& 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 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 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 blob = textBlobBuilder.make(); + paint.reset(); + canvas->drawTextBlob(blob.get(), 0, 0, paint); + } +## + +#ToDo incomplete ## + +## + +# ------------------------------------------------------------------------------ + +#Method void drawTextBlob(const sk_sp& 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 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 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& 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 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 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& 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 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& 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& 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 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& 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 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 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 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 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& 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 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 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 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 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 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 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 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 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 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 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 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 DummyRasterizer::CreateProc(SkReadBuffer&) { + return sk_make_sp(); + } + + #Function ## + void draw(SkCanvas* canvas) { + SkPaint paint; + DummyRasterizer dummy; + SkDebugf("nullptr %c= rasterizer\n", paint.getRasterizer() ? '!' : '='); + paint.setRasterizer(sk_make_sp()); + SkDebugf("nullptr %c= rasterizer\n", paint.getRasterizer() ? '!' : '='); + } + + #StdOut + nullptr == rasterizer + nullptr != rasterizer + ## + ## + +## + +#Method sk_sp 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 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 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 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 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 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 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 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 widths; + std::vector 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 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 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 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 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 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("
"); + int textSizeLoc = str.find(textSize) + strlen(textSize) + trailerSize; + const char* sizeStart = &str.c_str()[textSizeLoc]; + int textSizeEnd = SkStrFind(sizeStart, ""); + 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 +# 1 arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo) ## +# 2 parameter sets force MoveTo ## +# 3 start angle must be multiple of 90 degrees. ## +# 4 arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) ## +# 5 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 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 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 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 MakeCanvas(std::unique_ptr, 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 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 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("no #Return expected"); + } + if (foundReturn) { + return methodParser.reportError("multiple #Return markers"); + } + foundReturn = true; + } + if (expectReturn && !foundReturn && !foundException) { + return methodParser.reportError("missing #Return marker"); + } + const char* paren = methodParser.strnchr('(', methodParser.fEnd); + if (!paren) { + return methodParser.reportError("missing #Method function definition"); + } + const char* nextEnd = paren; + do { + string paramName; + methodParser.fChar = nextEnd + 1; + methodParser.skipSpace(); + if (!this->nextMethodParam(&methodParser, &nextEnd, ¶mName)) { + 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("multiple #Method param with same name"); + } + child->fVisited = true; + if (foundParam) { + TextParser paramError(child); + return methodParser.reportError("multiple #Param with same name"); + } + foundParam = true; + + } + if (!foundParam && !foundException) { + return methodParser.reportError("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("#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("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("#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("#Method missing param name"); + } + *paramName = string(paramStart, paramEnd - paramStart); + if (!paramName->length()) { + if (')' != nextEnd[0]) { + return methodParser->reportError("#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& 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("unnamed markup"); + } + if (typeNameBuilder.size() > 1) { + return this->reportError("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("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("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("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("mismatched topic"); + } + } else { + if (nullptr != defPtr) { + return this->reportError("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("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("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("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("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("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("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(errStr.c_str()); + }; + + if (markType == fParent->fMarkType) { + return true; + } + if (this->hasEndToken()) { + if (!fParent->fParent) { + return this->reportError("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("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("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("expected definition"); + } + if (this->peek() != fMC) { + vector 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("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("invalid parent"); + } + } + if (!this->skipName(fMaps[(int) markType].fName)) { + return this->reportError("illegal markup character"); + } + if (!this->skipSpace()) { + return this->reportError("unexpected end"); + } + bool expectEnd = true; + vector typeNameBuilder = this->typeName(markType, &expectEnd); + if (fCloned && MarkType::kMethod != markType && MarkType::kExample != markType + && !fAnonymous) { + return this->reportError("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(errorStr.c_str()); + } + if (!fParent || MarkType::kTable != fParent->fMarkType) { + return this->reportError("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("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("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("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("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("unexpected method name char"); + } + if (' ' == nameStart[0]) { + ++nameStart; + } + if (nameEnd <= nameStart) { + return this->reportError("missing method name"); + } + if (nameStart >= paren) { + return this->reportError("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("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("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("missing parent"); + } + if (definition != fParent) { + return this->reportError("definition end is not parent"); + } + if (!definition->fStart) { + return this->reportError("definition missing start"); + } + if (definition->fContentEnd) { + return this->reportError("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("expected end mark"); + } + this->next(); + if (fMC != this->peek()) { + return this->reportError("expected end mark"); + } + this->next(); + return true; +} + +bool BmhParser::skipToDefinitionEnd(MarkType markType) { + if (this->eof()) { + return this->reportError("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("unbalanced stack"); +} + +vector BmhParser::topicName() { + vector 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 BmhParser::typeName(MarkType markType, bool* checkEnd) { + fAnonymous = false; + fCloned = false; + vector 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("no comma needed"); + break; + } + if (fMC == ch) { + return builder; + } + if (!isalnum(ch) && '_' != ch && ':' != ch && '-' != ch) { + return this->reportError("unexpected char"); + } + if (':' == ch) { + // expect pair, and expect word to start with Sk + if (nameStart[0] != 'S' || nameStart[1] != 'k') { + return this->reportError("expected Sk"); + } + if (':' != this->peek()) { + return this->reportError("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("didn't expected kX"); + } + if (nameStart[0] == 'S' && nameStart[1] == 'k') { + return this->reportError("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 +#include +#include +#include +#include +#include +#include +#include + +// std::to_string isn't implemented on android +#include + +template +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(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(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 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 fTokens; + vector 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 fBranches; + unordered_map fLeaves; +private: + RootDefinition* fRootParent = nullptr; +}; + +struct IClassDefinition : public Definition { + unordered_map fEnums; + unordered_map fMembers; + unordered_map fMethods; + unordered_map 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> fRawData; + unordered_map> 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& 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 topicName(); + vector 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* fBmh; + MarkType fMarkType; + Resolvable fResolve; + Exemplary fExemplary; // worthy of an example + uint64_t fParentMask; + }; + + DefinitionMap fMaps[Last_MarkType + 1]; + forward_list fTopics; + forward_list fMarkup; + forward_list fExternals; + vector fInputFiles; + unordered_map fClassMap; + unordered_map fConstMap; + unordered_map fEnumMap; + unordered_map fMethodMap; + unordered_map fTypedefMap; + unordered_map fTopicMap; + unordered_map 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& methodParams, + const vector& 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* map = fMaps[(int) markType].fInclude; + if (!map) { + return reportError("invalid mark type"); + } + string name = this->uniqueName(*map, typeName); + Definition& markupDef = (*map)[name]; + if (markupDef.fStart) { + return reportError("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 + string uniqueName(const unordered_map& 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* fInclude; + MarkType fMarkType; + }; + + DefinitionMap fMaps[Last_MarkType + 1]; + unordered_map fIncludeMap; + unordered_map fIClassMap; + unordered_map fIDefineMap; + unordered_map fIEnumMap; + unordered_map fIStructMap; + unordered_map fITemplateMap; + unordered_map fITypedefMap; + unordered_map 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("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("mismatched stdout len"); + } + if (strncmp(bmh.fChar, fiddle.fChar, fiddleLen)) { + return this->reportError("mismatched stdout text"); + } + bmh.skipToLineStart(); + fiddle.skipToLineStart(); + } while (!bmh.eof() && !fiddle.eof()); + if (!bmh.eof() || (!fiddle.eof() && !fiddle.startsWith(""))) { + return this->reportError("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& methodParams, + const vector& 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("expected parent"); + } + if (Definition::Type::kBracket != poundDef->fType) { + return reportError("expected bracket"); + } + if (Bracket::kPound != poundDef->fBracket) { + return reportError("expected preprocessor"); + } + if (KeyWord::kNone != poundDef->fKeyWord) { + return reportError("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("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("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& map = fIClassMap; + IClassDefinition& markupDef = map[className]; + if (markupDef.fStart) { + typedef IClassDefinition* IClassDefPtr; + return INHERITED::reportError("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 methodParams; + vector 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, ¶mName)) { + 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 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 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(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("missing object type"); + } + if (!parser.skipWord(markupDef->fName.c_str())) { + return reportError("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("failed to parse class"); + } + break; + case KeyWord::kEnum: + if (!this->parseEnum(child, markupDef)) { + return this->reportError("failed to parse enum"); + } + break; + case KeyWord::kStruct: + if (!this->parseClass(child, IsStruct::kYes)) { + return this->reportError("failed to parse struct"); + } + break; + case KeyWord::kTemplate: + if (!this->parseTemplate()) { + return this->reportError("failed to parse template"); + } + break; + case KeyWord::kTypedef: + if (!this->parseTypedef()) { + return this->reportError("failed to parse typedef"); + } + break; + case KeyWord::kUnion: + if (!this->parseUnion()) { + return this->reportError("failed to parse union"); + } + break; + default: + return this->reportError("unhandled keyword"); + } + break; + case Definition::Type::kBracket: + switch (child->fBracket) { + case Bracket::kParen: + if (!this->parseMethod(child, markupDef)) { + return this->reportError("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("unhandled preprocessor"); + } + break; + case Bracket::kAngle: + // pick up templated function pieces when method is found + break; + default: + return this->reportError("unhandled bracket"); + } + break; + case Definition::Type::kWord: + if (MarkType::kMember != child->fMarkType) { + return this->reportError("unhandled word type"); + } + if (!this->parseMember(child, markupDef)) { + return this->reportError("unparsable member"); + } + break; + default: + return this->reportError("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("malformed char"); + } + if (fInString) { + return reportError("malformed string"); + } + if (!this->checkForWord()) { + return false; + } + if (Bracket::kPound == this->topBracket()) { + KeyWord keyWord = fParent->fKeyWord; + if (KeyWord::kNone == keyWord) { + return this->reportError("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("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("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("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("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("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::iterator baseIter = fParent->fTokens.end(); + list::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("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("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("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("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("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(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("malformed include name"); + } + string fileName = includeMapper.first.substr(lastSlash + 1); + if (".h" != fileName.substr(fileName.length() - 2)) { + return this->reportError("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("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("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(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 + "" + refOut + ""; +} + +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, ""); + 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, " Class %s", this->linkName(def).c_str(), + def->fName.c_str()); + this->lf(1); + break; + case MarkType::kCode: + this->lfAlways(2); + fprintf(fOut, "
");
+            this->lf(1);
+            break;
+        case MarkType::kColumn:
+            this->writePending();
+            if (fInList) {
+                fprintf(fOut, "    ");
+            } else {
+                fprintf(fOut, "| ");
+            }
+            break;
+        case MarkType::kComment:
+            break;
+        case MarkType::kConst: {
+            if (TableState::kNone == fTableState) {
+                this->mdHeaderOut(3);
+                fprintf(fOut, "Constants\n"
+                        "\n"
+                        "");
+                fTableState = TableState::kRow;
+                this->lf(1);
+            }
+            if (TableState::kRow == fTableState) {
+                this->writePending();
+                fprintf(fOut, "  ");
+                this->lf(1);
+                fTableState = TableState::kColumn;
+            }
+            this->writePending();
+            fprintf(fOut, "    ",
+                    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, "", (int) (lineEnd - textStart), textStart);
+            fprintf(fOut, "\n");
+            fprintf(fOut, "  ");
+            this->lf(1);
+            break;
+        case MarkType::kReturn:
+        case MarkType::kSeeAlso:
+            this->lf(2);
+            break;
+        case MarkType::kRow:
+            if (fInList) {
+                fprintf(fOut, "  ");
+            } 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 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 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 fCode;
+    unordered_map fColons;
+    unordered_map fDigits;
+    unordered_map fDots;
+    unordered_map fParens;  // also hold destructors, operators
+    unordered_map fUnderscores;
+    unordered_map 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("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(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);
+}
%s %.*s"); + textStart = lineEnd; + } break; + case MarkType::kDefine: + break; + case MarkType::kDefinedBy: + break; + case MarkType::kDeprecated: + break; + case MarkType::kDescription: + fInDescription = true; + this->writePending(); + fprintf(fOut, "
"); + break; + case MarkType::kDoxygen: + break; + case MarkType::kEnum: + case MarkType::kEnumClass: + this->mdHeaderOut(2); + fprintf(fOut, " 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, "
", def->fHash.c_str()); + } else { + fprintf(fOut, "
");
+                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, "");
+            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, "%.*s", (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, "", 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, "
\n"
+                            "%s\n"
+                            "
", 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" + "
" + ); + this->lf(1); + fTableState = TableState::kRow; + } + if (TableState::kRow == fTableState) { + fprintf(fOut, " "); + 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, + " "); + 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, " 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, " %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, " %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, ""); + this->lf(2); + break; + case MarkType::kColumn: + if (fInList) { + this->writePending(); + fprintf(fOut, ""); + this->lf(1); + } else { + fprintf(fOut, " "); + } + break; + case MarkType::kDescription: + this->writePending(); + fprintf(fOut, ""); + fInDescription = false; + break; + case MarkType::kEnum: + case MarkType::kEnumClass: + this->lfAlways(2); + break; + case MarkType::kExample: + this->writePending(); + if (fHasFiddle) { + fprintf(fOut, ""); + } else { + fprintf(fOut, ""); + } + this->lf(2); + break; + case MarkType::kList: + fInList = false; + this->writePending(); + fprintf(fOut, "
%.*s ", + (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, "
"); + 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, "