# gCanvas

Definition: | include xpGUI.e gdx id = gCanvas([rtn redraw=NULL,] string attributes="", dword_seq args={})
Description: | Creates an interface element that is a canvas - a working area for your application. redraw: a procedure invoked whenever the canvas needs to be redrawn, see REDRAW below. For more information on the attributes and args parameters see gSetAttributes(). This is a paranormalised function. (see technicalia) Returns the identifier of the created element. A gCanvas is also used to implement gGraph, gList, and gTable.
pwa/p2js: | Supported.
See Also: | gGraph, gImage, gImage_from_XPM, gImageDraw, gList, gTable, gRedraw
Example: |
    -- demo\xpGUI\gCanvas.exw (simplified)
    include xpGUI.e

    procedure redraw(gdx canvas, integer w, h)
        gCanvasLine(canvas,0,h/2,w,h/2,-1,1,XPG_GREEN)
    --  ... more gCanvasLine/Arc/Text/Polygon/Circle
    --      plus one gImageDraw(gImage_from_XPM())
    end procedure

    gdx canvas = gCanvas(redraw),
        dialog = gDialog(canvas,`gCanvas`,`SIZE=240x163`)
    gSetAttribute(canvas,"BGCLR",XPG_PARCHMENT)
    gShow(dialog)
    gMainLoop()
Other examples include demo\xpGUI\GraphR.exw, aaline.exw, and scroller.exw
Colours: | The following constants are defined for convenience.
EG | Value/Names | EG | Value/Names
--- | --- | --- | ---
| `#000000` XPG_BLACK |  | `#4B0082` XPG_INDIGO
| `#000080` XPG_NAVY |  | `#800080` XPG_DARK_PURPLE
| `#0000FF` XPG_BLUE |  | `#911EB4` XPG_PURPLE
| `#4363D8` XPG_LIGHT_BLUE |  | `#F032E6` XPG_DARK_VIOLET
| `#008080` XPG_TEAL |  | `#EE82EE` XPG_VIOLET
| `#00C0C0` XPG_DARK_CYAN |  | `#FF00FF` XPG_MAGENTA
| `#00FFFF` XPG_CYAN |  | `#800000` XPG_DARK_RED
| `#008000` XPG_DARK_GREEN |  | `#FF0000` XPG_RED
| `#3CB44B` XPG_GREEN |  | `#404040` XPG_SLATE
| `#00FF00` XPG_LIGHT_GREEN |  | `#808080` XPG_DARK_GREY, XPG_DARK_GRAY
| `#808000` XPG_OLIVE |  | `#C0C0C0` XPG_GREY, XPG_GRAY, XPG_SILVER
| `#FF8C00` XPG_ORANGE |  | `#E4E4E4` XPG_LIGHT_GREY, XPG_LIGHT_GRAY
| `#FFBF00` XPG_AMBER |  | `#FFFFE0` XPG_PARCHMENT
| `#EBEB00` XPG_DARK_YELLOW |  | `#FAF8EF` XPG_LIGHT_PARCHMENT
| `#FFFF00` XPG_YELLOW |  | `#FFFFFF` XPG_WHITE
Note that for instance while yellow is a primary color, in the RGB/additive color model (as used by xpGUI) it is made by combining red and green, in other words quite unlike mixing blue and yellow paint to make green paint (since paints are/use the RYB/subtractive colour model), and in fact mixing blue and yellow in the RGB model end ups with white! The WinAPI backend actually uses BGR colour encodings, but xpGUI quietly maps to/from RGB automatically, a fact you may need to know when porting some (legacy) Windows-based code to xpGUI. Colour codes are often declared as atom, however integer is also fine except under 32-bit when the alpha channel is #40..#BF, which it probably only very rarely ever would be. All the standard colours are fully opaque, ie have an alpha channel of #00 (which may get flipped to/from #FF on some backends). string res = gGetColourName(atom colour) returns one of the (leftmost) names above, "#RRGGBB", or "-1". atom colour = rgba(atom red, green, blue, alpha=0) Values should be 0..255 (fractions discarded). An alpha of 0 is fully opaque, 255 is fully transparent. integer {red,green,blue,alpha} = to_rgba(atom colour) For example, to_rgba(XPG_YELLOW) yields {255,255,0,0}, ie red+green [as it is in the RGB model]. atom colour = hsv_to_rgba(atom h, s, v, a=0) h,s,v: hue (0..<360), saturation (0..1), value (0..1). For more info see https://en.wikipedia.org/wiki/HSL_and_HSV a: An alpha of 0.0 is fully opaque, 1.0 fully transparent.
Functions: | The following routines should only be used inside its REDRAW handler. Use gRedraw() to force a repaint. Non-1 style/width/colour transiently set LINESTYLE/LINEWIDTH/FGCLR, which can also be set as defaults for several subsequent calls, or overriden on each (in small font below, just to keep lines short). Note that pixels are 0-indexed, so on a 200x200 canvas valid co-ordinates are {0,0} to {199,199}, with any attempts to draw off-canvas pixels simply quietly ignored, and say a line from {50,50} to {-300,27000} just drawing the bit it can. All co-ordinates are in pixels, with fractions permitted, but simply ignored/truncated. gCanvasLine(gdx canvas, atom x1, y1, x2, y2, integer style=-1, width=-1, atom colour=-1) Draw a line from {x1,y1} to {x2,y2} (inclusive, zero-based). gCanvasArc(gdx canvas, atom xc, yc, w, h, angle1, angle2, integer flags=0, style=-1, width=-1, colour=-1) gCanvasCircle(gdx canvas, atom xc, yc, radius, bool filled=false, integer style=-1, width=-1, colour=-1) Co-ordinates are in pixels, angles in degrees, flags can be XPG_FILLED with XPG_CHORD or XPG_SECTOR. The angles define the arc start and end, but they are not the angle relative to the center, except for w==h aka a circle. The arc starts at the point (xc+(w/2)*cos(angle1), yc+(h/2)*sin(angle1)) and ditto ends/angle2 (always clockwise). A complete ellipse can be drawn using 0 and 360 as the angles. gCanvasCircle just invokes gCanvasArc(w=h=2*radius,0,360). If angle2 is less than angle1 it will be increased by 360 until it is greater than angle1. True(/false) can also be used in place of XPG_FILLED, and the latter passed to gCanvasCircle. Note that filled arcs (and cirles/chords/sectors) (currently) have bresenham edges under WinAPI (fixable, see technicalia). See the initial screenshot above (there’s a bUsePoly fudge in that for "", which achieves roughly what I plan to make it do automatically, one day). gCanvasCubicBezier(gdx canvas, atom x1, y1, xc1, yc1, xc2, yc2, x2, y2, colour=-1) gCanvasQuadBezier(gdx canvas, atom x1, y1, cx, cy, x2, y2, colour=-1) NB: additional parameters not yet finalised, migration from aaline.exw to xpGUI.e still outstanding Draws a curve from x1,y1 to x2,y2 using control points xc1,yc1 and xc2,yc2. The (simpler) quadratic version just invokes the cubic one using the same control point twice. gCanvasPolygon(gdx canvas, sequence poly, bool bFilled=true, integer colour=-1, style=-1, width=-1) poly: a list of vertices aka starting points of each segment to be drawn. If a segment is just {x,y} a straight line is drawn to the end point (ie next/first segment). If a segment is length 4 aka {x,y,cx,cy} a quadratic bezier is drawn, likewise length 6 a cubic bezier. The poly can also contain {} which makes it a multi-part polygon, such as a metal washer, polo mint or doughnut. An error occurs for any part containing less than three vertices, or any segment not length 0|2|4|6. Polygon filling occurs on the full collection of segments, rather than each individually. A filled polygon may not have a style other than XPG_CONTINUOUS or width other than 1. bFilled/colour/style/width: assumed to be self-explanatory. see|run demo/xpGUI/gCanvasPolygon.exw and/or PhixLogo.exw for some examples. sequence poly = gRotatePolygon(sequence poly, object angle) poly: as gCanvasPolygon above but less restrictive, so (eg) you can rotate a single point. angle: rotate all the points of poly. If angle is an atom, rotation occurs about poly[1][1..2], otherwise it must be a three-atom {angle,cx,cy} triplet with rotation about {cx,cy}. The angle should be specified in degrees, clockwise, usually but does not have to be 0..+/-360. [Be warned this seems significantly slower than anticipated, possibly only managing ~150 points per second, whereas I would have confidently predicted it was going to be 10 or even 100 times faster than that...] gCanvasRect(gdx canvas, atom xmin, xmax, ymin, ymax, bool bFill=false, integer rc=0, style=-1, width=-1, atom colour=-1) Draws a rectangle, optionally filled and/or with rounded corners. xmin/xmax/ymin/ymax: includive, so {0,0,0,0} draws a single pixel top-left. rc: 0 for square corners, else the radius of the a quadrant/arc to use. Unpredictable results when > w/2 or h/2. bFill/style/width/colour: assumed to be self-explanatory. Replaces cdCanvas[Rounded](Box|Rect)() of pGUI. gCanvasPixel(gdx canvas, atom x, y, colour=-1) Draws a single pixel in a specified colour or the current foreground colour if omitted or -1. (It is expected to be more common that a colour is provided on every call than using the latter defaulting.) gCanvasText(gdx canvas, atom x, y, string text, integer align=XPG_E, object angle=0, colour=-1, integer style=-1) Draws text, with full UTF-8 support, optionally realigned, rotated, and/or coloured. align: replaces cdCanvasSetTextAlignment() of pGUI (transiently): XPG_N XPG_S XPG_W XPG_E XPG_NW XPG_NE XPG_SW XPG_SE The longhand aliases XPG_NORTH, XPG_SOUTH, XPG_WEST, XPG_EAST (the default) can also be used, along with XPG_NORTHWEST, XPG_NORTHEAST, XPG_SOUTHWEST, XPG_SOUTHEAST, and XPG_CENTRE. (Technically XPG_CENTER is the wrong constant, but explicitly/deliberately treated as XPG_CENTRE here.) You might also imagine the red centre of XPG_C being shifted to one of the eight surrounding orange crosses. Should the text end up too close to {x,y} you are expected to explicitly adjust {x,y} to mimic a margin, and further it is fairly likely that certain fonts and/or backends may have subtly different requirements. string res = gGetAlignName(integer align) converts an alignnent to a human readable string. angle: in degrees, clockwise, replaces cdCanvasSetTextOrientation() of pGUI (transiently). Rotation normally occurs around the top left (XPG_NW) corner of wherever the text ended up. The rotation point may be overidden by specifying the angle as eg {XPG_SE,90}. Note rotation points are relative to text, and the logical inverse of align (aka txt->pt vs. pt->txt) [Arguably align is at fault, but north putting it below would just be completely counter-intuitive.] There are a full 81 combinations of align and rotation point for any given angle, which should suffice! Fairly obviously, (eg) align:=XPG_NE and angle:={XPG_SW,90} actually results in the text being XPG_SE of {x,y}, and likewise, the same align:=XPG_NE but angle:={XPG_SW,270} actually results in the text being XPG_NW of {x,y}. You may prefer to use the following diagram to select a rotation point (XPG_C is its own logical inverse, no others are.): colour: as usual, an atom other than -1 transiently sets FGCLR(), should colour be a {fore,back} pair such as {XPG_RED,XPG_BLUE} (note unlike other functions it is object not atom), then while WinAPI/DrawText copes, under GTK it does a gCanvasRect() or if angle!=0 a rotated gCanvasPolygon() first. style: may be XPG_NORMAL, XPG_BOLD, XPG_ITALIC, or XPG_BOLDITALIC. A transient set FONT occurs. Note that underline and strikeout are not supported (by most/all backends), you’d’ve to gCanvasLine() it. You may also use gGetTextExtent(), but applying any rotation/align to the results from that is down to you.
Attributes: |
? EXPAND | (non inheritable): The default value is "YES". The natural size is the size of 1 character. Note that an explicit SIZE on the gCanvas(/gGraph) itself or whatever container it expands into is strongly recommended. Said parent container may itself may be inheriting from or expanding into the size of its parent, and of course should any MAXSIZE or MINSIZE be in use, any (initial) size should not exceed those limits. Only the width is critical on a (non-expanding) gList, whereas a gTable has column widths in the column data itself, and the height of both can be deduced from the number of rows, unless that needs to be virtual (as next).
SCROLLSIZE | Specify or retrieve some (larger) virtual canvas of which the real canvas should show some (smaller) VIEWPORT. Were you to (eg) accidentally open a video file in xpEditer, and that treated it as a text file of 500K lines, some of which were 500K characters long, it might try (and fail) to create a 236TB canvas, of which at most 16MB (0.000006%) would ever be shown, and obviously even if something significantly smaller did actually work it could quite literally be millions of times slower than needed. The file demo/xpGUI/scroller.exw allows (after editing) the creation of a 138TB virtual canvas that only actually uses 1.05MB or 0.000001%. Such extreme cases probably aren’t of themselves all that useful, but not crashing (the whole machine), not hogging memory, and not running ridiculously slowly, probably are. If not set then (the [variable/non-constant] attribute) SIZE is used instead. It is whether SCROLLSIZE is larger than VIEWPORT (which also defaults from SIZE) that determines the presence or absence of scrollbars. Automatically handled on gList (see ROWS) and gTable (see the rows parameter). This setting is entirely independent of MAXSIZE and MINSIZE, and should you (for instance) want to centre a smaller SCROLLSIZE within a larger SIZE that is entirely the responsibility of the REDRAW handler, ditto honoring VIEWPORT. See demo/xpGUI/scroller.exw for an example (and be amazed at how trivial and easy all this actually is). Returns {w,h} with as just mentioned either being zero (their defaults) resorting to SIZE.
SCROLLPORT, VIEWPORT | Retrieve or specify which part of a larger virtual SCROLLSIZE should be shown on the actual canvas. SCROLLPORT and VIEWPORT and just aliases of each other and refer to exactly the same thing. Automatically handled on gList and gTable. Returns {x,y,w,h} where x,y are initially 0 and indicate the [virtual] pixel position of the top left corner of the canvas, and w,h are from SIZE, reduced to account for any visible scrollbars, with {0,0,w,h} being returned if SCROLLSIZE is not set, or it is smaller than the SIZE, but in the latter case not necessarily strictly enforced. The scrollbars are drawn as/when needed after the REDRAW handler returns, and that should of course draw the (virtual) contents determined by x,y in the (actual) rectangle defined by 0..w-1 and 0..h-1, with any attempts to draw pixels outside of that area being, as usual, simply quietly ignored (or, to be excessively pedantic, duly soon obliterated by a scrollbar). When setting this attribute, only the origin (first two elements) should be provided. Negative values are not permitted, however they can exceed the natural limits that using the scrollbars would normally attain, so that for instance a gList can "go to line" and show only a few lines, with "dead space" filling up the rest of the visible/actual canvas. Setting this attribute automatically updates the otherwise private scrollbars. Any and all handling of the latter is achieved via the VIEWPORT attribute. Settings that result in the entire visible area being "dead space" and/or off-canvas scrollbar thumbs are not specifically guarded against, consider yourself warned.
LINESTYLE | Default: XPG_CONTINUOUS(==1), valid styles are: XPG_DASHED XPG_DOTTED XPG_DASH_DOT XPG_DASH_DOT_DOT Note that values other than XPG_CONTINUOUS with a line width other than 1 may appear as rhomboids, and perhaps for that same reason native WinAPI/GTK do not support such, but while that effect may be less than ideal it is reasonably acceptable on slightly thicker lines, and fine on horizontal/vertical lines, and just seems churlish to remove it completely. The style may also be a sequence (of 1/0, see xpGUI.e) which sets XPG_CUSTOM_DASH(==6). (XPG_CUSTOM_DASH is currently set at the system level rather than per canvas, for no good reason, shout should that sting.) returns 1..5 but pattern not XPG_CUSTOM_DASH. Use string res = gGetLineStyleName(object style) to convert a style to a human readable string.
LINEWIDTH | As above, values other than 1 may cause line styles other than XPG_CONTINUOUS to appear as rhomboids. Default: 1. Implemented in JavaScript via ctx.lineWidth
also | ? ACTIVE, ? BGCLR, ? FGCLR, ? CANFOCUS, FONT, MINSIZE, MAXSIZE, SIZE, TIP, ? VISIBLE.
Handlers: |
REDRAW | A procedure that should be invoked when the canvas needs to be redrawn, and should retrieve/honour any VIEWPORT. (Obviously it is not necessary to fetch the VIEWPORT if no SCROLLSIZE was ever set.) procedure redraw(gdx canvas[,integer w, h]) canvas: identifier of the canvas element that needs drawing. w,h: (optional) equivalent to `integer {w,h} = gGetAttribute(canvas,"SIZE")` as the first line of a single-arg procedure. Usually specified on the initial gCanvas() call, but can also be set or replaced later as usual, ie gSetHandler(canvas,"REDRAW",redraw), however a fatal error occurs should the redraw routine (still) be NULL when the canvas is displayed. Note the background colour used for initially clearing the canvas should be set before the first paint event and is applied before redraw() is invoked, and automatically restored after (so you don’t have to). Any decision to change that initial clear setting should likewise therefore be made and performed outside of the redraw() routine.
IDROP | (optional) Intercept/override the drawing operations. function idrop(integer rid) rid: an xpGUI drawing operation such as gCanvasText, full list below. Returns: the original (rid) or your intercept version of that. When you write an intercept, you must replicate the exact signature, usually by copying from these help docs, including any defaults, as say iCanvasText(), which can then invoke the real gCanvasText(), if desired. Only useful on the canvas-derived controls gGraph, gList, and gTable, which have overidden the REDRAW handler. Allows an application to tweak/log the drawing primitives, for an example see demo\xpGUI\gTable.exw The current set of so-interceptable drawing operations is: gCanvasRect, gCanvasLine, gCanvasText. (the only one used by gList) More may be added as/when/if needed (search xpGUI.e for "IDROP", or better yet xpg_default_IDROP, to be sure). Note changes to the attributes BGCLR, are not intercepted and may need -1s replaced with gGetAttribute(), at least when logging for later use as opposed to minor/immediate tweaking.
also | CLICK, MOUSEMOVE, KEY: All common handlers are supported.
Technicalia | As this is a paranormalised function, and with no expectation that you should memorise this lot, except maybe "raa", you can assume that all of the following are potentially perfectly valid, with the fairly obvious restrictions that any parameters must be in strict raa-order, and no args without attributes:
                            gCanvas()
                            gCanvas(redraw)
                            gCanvas(attributes)
                            gCanvas(attributes, args)
                            gCanvas(redraw, attributes)
                            gCanvas(redraw, attributes, args)   -- (the full set)
Note in practice redraw is actually defined as object, and attributes as sequence rather than string, but they are all manually and thoroughly verified to be of the documented types, after being repositioned via xpGUI.e/paranormalise_raa(). Compatibility note: On a gCanvas (as shown) {0,0} is XPG_NW, logically matching (say) the {x,y} of gShow(), whereas on an IupCanvas() it was/is CD_SOUTH_WEST. In other words the vertical axis is inverted: "height-y" usually works, that is when porting existing code from pGUI/IUP to xpGUI, and in some cases, even better, deleting a "height-". ### Platform/backend considerations GTK, bless it, decrees the top left pixel to be at {0.5,0.5} and (eg) cairo_rectangle(0.5,0.5,0,0) ie width,height of 0,0, draws that one pixel. WinAPI, bless it, decrees the top left pixel to be at {0,0} and excludes the right/bottom edge, so Rectange(0,0,1,1), ie ltrb, "". Of course xpGUI abstracts such differences away, such that gCanvasRect(0,0,0,0) draws the one top left pixel, and maps everything for GTK and other backends appropriately, and for a canvas size of {w,h} valid pixel coordinates are 0..w-1 and 0..h-1. [I could perhaps have made them 1..w and 1..h, but I didn’t, and predict I’ll thank myself when I get round to OpenGL.] In the same fashion as slices, 1..1 is 1 wide/high, and (eg) 1..0 or 7..6 logically draw nothing, not yet officially supported, though adding a few scattered if>0 as/when/should they turn out to be helpful is not likely to be all that stressful. ### Scrollbars Note that the scrollbars are drawn and managed using pure hll Phix code and do not rely on any native functionality of the WinAPI, GTK, or JavaScript (etc) backends. It was, perhaps surprisingly, just all much simpler that way. Some platform/version specific tweaks may yet be required to match look and feel aspects, possibly optionally. Likewise the VIEWPORT is entirely unconnected with any similarly-named native feature of the various backends. One serendipitous outcome was that during development, clicking to one side of a scrollbar thumb and holding down did not stop once the thumb hit the mouse position, which it does on WinAPI/Arwen, but carried on like it does on GTK, which I prefer anyway, so I kept that. I didn’t particularly want and saw no need to replicate the 42 attributes that IUP uses to manage scrollbars, not that I’d stand in the way of any genuinely useful and specifically requested ones. Theoretically a gCanvas with no pixels outside the scrollbar area could be used as a standalone scrollbar [with optional resizer thumb?], though it would probably be much easier to fake (eg) a gButton on a gCanvas than modify the Layout Manager to respect a scrollbar or two (or ten) on top of everything else it is trying to achieve - and of course the scrollbars themselves should act as a pretty good model for faking any other controls: just be aware there is quite a bit going on, from mouse hover effects, timers, and keyboard handling, to clipping the various offsets and hiding things should resizing make them no longer valid or necessary. ### Anti-aliasing, bezier curves, and polygon filling I stumbled across a few useful images in my travels and thought I’d include them here, to show you and remind myself how all these things are supposed to work. While GTK and JavaScript have suitable methods already, the raw WinAPI (as far as I can tell) does not perform proper anti-aliasing (and suchlike), so the following should explain what it is supposed to do on all platforms, but really only applies (in actual code terms) to WinAPI, except for the filled polygons parts. A bresenham line An anti-aliased equivalent Shown magnified 35 times, the above shows some individual (square) pixels being used to imitate the theoretical cyan line. On the left we set seven pixels to the requested colour, in this case black, which results in a jagged/blocky line. On the right we set seven pairs of pixels to a mixture of foreground/background, making it look much smoother. On a steep line segment we’d show each intersection with a y unit axis as two horizontal pixels, whereas as above on a flat line segment we show each intersection with an x unit axis as two vertical pixels. By "unit axis" I really mean (mathematical, horizontal/vertical) lines through the logical centres of the pixels, rather than their edges. Of course it is not perfect, but can almost make a 1080p (1K) monitor look like a (non-anti-aliased) 4K UHD one. Every modern font (for example) is anti-aliased in much the same way, and you would soon grumble were they suddenly not. The above were nicked from Wikipedia, and should give you a rough idea of how bezier curves work, with the next diagram below hopefully clarifying things further. Note that a quadratic bezier is exactly the same as a cubic with (the same end points and) the two control points being the same. Just in case your brain works like mine, remember that polynomial equations have daft names: unlike the more sensible Monomial, Binomial, Trinomial, Quadronomial, and Pentanomial (matching bicycle, tricycle, and quad bike) which stipulate number of terms but not the highest power, polynomials of degree 0..4 are called Constant (eg 6), Linear (eg ax+b), Quadratic (eg ax^2+bx+c), Cubic (eg ax^3+bx^2+cx+d), and Biquadratic or Quartic (eg ax^4+bx^3+cx^2+dx+e). And yes, I know the Latin word for square is quadratum, but it was still a daft naming choice, at least for English speakers. Quadratic beziers (the first two above) are simpler than cubic beziers (the last two). xpGUI uses the de Casteljau method to repeatedly bisect a bezier curve until S/Q/E are within one pixel of each other, at which point it (deems it safe to treat as a straight line and) estimates the intersection with a unit x or y axis along the line from S to E, skipping any fragments that don’t actually cross a boundary. For any curve S,C1,C2,E we first find the midpoints L,M,R (via (a+b)/2). We then find the secondary midpoints N and P. The tertiary midpoint, Q, is on the curve, at the point where the two halves formed by S,L,N,Q and Q,P,R,E meet. A zoomed in comparison of bresenham and anti-aliased bezier curves. Such a line could of course be drawn in either direction. Notice how the lower/steep part is (naturally) constructed using more horizontal pairs of pixels than vertical, whereas the top/flat part is the other way round. (Originally I planned to only draw horizontals on steep fragments and only vertical on flat, as per Xiaolin Wu’s original (straight) line anti-aliasing algorithm, but drawing both turned out much nicer.) Filled polygons are achieved using a simple scanline method. First all y axis unit intersections are saved, using a micro fudge factor where needed to ensure no segment starts or ends on an exact y axis boundary, hence avoiding any need to handle special cases. (Consider horizontal scanlines on a diamond shape: direct hits on the side vertices should be counted as 1 hit (despite touching two lines), but direct hits on the top and bottom vertices should be counted as 0 or 2 hits, whereas mid-line hits simply don’t need any of that.) We sort on {y,x}, verify each y has an even number of hits, then drawline each such pair that have ceil(first) < floor(second). (Technically, a few scattered +1 and/or +0.5 under GTK make a bit of a mockery of that last statement, but the sentiment remains valid.) This is done after the anti-aliased outline, to leave outer smoothing effects intact but cover unwanted lighter inner pixels. Fairly obviously and as intended any such fill completely obliterates any previous content uniformly with the specified colour. Polygons can be composed of a mixture of straight and curved lines. While not recommended, complex (self-crossing) polygons are supported, and filled using an odd-even rule similar to cdCanvasSetFillMode(canvas,CD_EVENODD): the winding rule would only ever be applicable to polygons constructed from all straight edges, so that is not even attempted in xpGUI. Of somewhat more interest, multi-part polygons (with embedded {}, see demo\xpGUI\gCanvasPolygon.exw for some examples) do not shade any inner holes they might have. Just in case you were wondering, there is no scheme to interpolate hard corners (and if you don’t know what that means you can quite safely ignore it). Ideally, WinAPI would also use the polygon code for gCanvasArc/Circle with XPG_FILLED, that is if an automatic mapping to that is even possible (see https://pomax.github.io/bezierinfo/#circles_cubic for ideas), but there’s a manually fudged attempt in demo/xpGUI/gCanvas.exw (see bUsePoly). UPDATE: Obviously (after sleeping on it for about three weeks) I could do this, it is just a whole bunch of extra code I hadn’t planned for. Put simply, gCanvasArc() needs pretty much the same code as gCanvasPolygon(), and maybe slightly simpler. For that matter, gCanvasPolygon() should probably also permit gCanvasArc()-based parts, as noted above the angle parameter is not imlemented or even properly "designed", and gCanvasArc() should probably also have a rotate angle, such that 0 is flat as now and 90 (or PI/4) is effectively the same as swapping w and h, but all that’s gotta be left for another day, unless you yourself want to step up. Dots and dashes look a bif naff especially for curved lines, but that is definitively something for someone else to solve. There is of course slim to zero chance of ever achieving pixel-perfect identical results on GTK, JavaScript, and WinAPI, and almost certainly none whatsoever should any fonts/text be involved, but of course they should look pretty similar - let me know of anything blatently wrong, that is by more than a couple of stray pixels. Update: I stumbled across a post on TheCodeProject which showed how to draw (straight) XPG_DOTTED properly under WinAPI, which I incorporated, but have not found a GTK equivalent or even tested it under JavaScript yet.