Part 5 of 5

Creating a Cairo-Inspired Graphics Library for MetaTrader 5 (Part 5): Fill Rules, Holes and Borders

Creating a Cairo-Inspired Graphics Library for MetaTrader 5 (Part 5): Fill Rules, Holes and Borders

Why this article was written

After Part 4 the renderer draws well: arbitrary outlines, smooth anti-aliased edges and correct alpha compositing, all through a single Fill() call. What it still could not produce was an empty region inside a shape.

That gap is not cosmetic. It is the hole in the letter "A" and the letter "O". It is a frame border, a donut, a gauge, an outlined button, the unfilled part of a progress track. Part 5 adds the mechanism that makes holes possible — and the change to the rasterizer turns out to be remarkably small.

What is inside

Two fill rules answering one question. Both decide whether a pixel is inside the shape, and they disagree on purpose. Non-zero counts direction, so overlapping contours merge into a seamless union. Even-odd counts parity, so an inner contour punches a hole no matter which way it was wound.

How little the rasterizer changes. One enum, one parameter with a default value, and one ternary in the sweep loop. Part 4's behaviour stays the default, so nothing that already worked behaves differently.

Why parity works on a signed sum. The winding accumulator from Part 3 is signed. Testing it for oddness rather than for non-zero is the even-odd rule — the article explains why that is legitimate rather than a happy accident.

Holes by construction. Why this matters more than it first sounds: a real hole shows whatever is underneath it. The alternative is painting the hole in the background colour, which means guessing what the background is, and which breaks the moment anything moves behind the shape.

Borders, the wrong way and the right way. The wrong way draws the shape twice and leaves seams and double-alpha artifacts where the two strokes overlap. The right way builds the stroke from filled quads, so the fill path it already has does the work.

The failure case, shown rather than avoided. Mixed winding — contour directions that are not consistent — and what each rule does when it meets one.

An honest note on scope

The strokes in this part are built from quads, which is enough for straight segments. Joins, caps and curved strokes are not solved here.

State of the library after this part:

MQL5
Color.mqh      Part 1    the ARGB color type, uint 0xAARRGGBB
Surface.mqh    Part 1    a pixel buffer bound to one chart object
Path.mqh       Part 2    points, contours, MoveTo / LineTo / Close
Raster.mqh     Part 3    edges, winding, the first filled shape
Raster.mqh     Part 4    coverage, sub-scanlines, blend-over
Config.mqh     Part 4    CairoAaSamples, the runtime quality dial
Raster.mqh     Part 5    fill rules: non-zero and even-odd        <- today

More in this series

Building a real 2D renderer for MetaTrader 5 in pure MQL5 — anti-aliased shapes, transparency and gradients, with no DLLs and no external tools. About this series

  1. Part 1Why the terminal needs its own 2D-rendererSeptember 3, 2026
  2. Part 2Points, Contours and the PathSeptember 10, 2026
  3. Part 3Edges and the First Filled ShapeSeptember 28, 2026
  4. Part 4Anti-Aliasing, Coverage and CompositingSeptember 28, 2026
  5. Part 5Fill Rules, Holes and Borders — you are hereOctober 1, 2026

Continue reading on MQL5.com

The full article with complete source code is published on MQL5.com.

Read the full article