Skip to content
Vector Output

Vector Output

Render one DrawTarget routine to PNG, SVG, and PDF.

Vector Output

Usage

from canvas.color import Color
from canvas.path import Path
from canvas.buffer import Canvas
from canvas.vector.draw_target import DrawTarget
from canvas.vector.pdf import PdfCanvas, write_pdf
from canvas.vector.svg import SvgCanvas, write_svg
from canvas.io.png import write_png
from canvas.text.render import draw_text, TextAlign
from canvas.text.font_discovery import FontWeight


def draw_scene[T: DrawTarget](mut target: T) raises:
    """Draw one scene through the trait, with no knowledge of which
    backend is behind it -- this exact function body produces both
    out_vector.png and out_vector.svg.
    """
    # Background panel and a baseline, via the two cheapest primitives.
    target.fill_rect(0, 0, 800, 500, Color(250, 250, 252))
    target.draw_line_aa(60, 420, 740, 420, Color(120, 120, 130), 2.0)

    # Bars, overlapping and translucent, so the blend is visible where
    # they cross rather than the last one simply winning.
    target.fill_rect(100, 240, 120, 180, Color(40, 100, 200, 190))
    target.fill_rect(180, 300, 120, 120, Color(220, 60, 120, 190))

    # A pie wedge and a donut segment -- the two arc primitives.
    target.fill_arc_aa(430.0, 150.0, 70.0, -1.6, 0.6, Color(80, 170, 120))
    target.fill_ring_sector_aa(
        620.0, 150.0, 34.0, 70.0, -1.6, 1.4, Color(240, 160, 40)
    )

    # Scatter points, plus the error ellipse around them. An ellipse is
    # the one shape `fill_path_aa` cannot stand in for exactly -- see
    # `DrawTarget`'s own docstring -- which is why both an ellipse fill
    # and an ellipse outline are on the trait.
    target.fill_ellipse_aa(300, 150, 90, 45, Color(40, 100, 200, 60))
    target.draw_ellipse_aa(300, 150, 90, 45, Color(40, 100, 200))
    for i in range(7):
        var x = 240 + i * 20
        var y = 130 + (i * 13) % 41
        target.fill_circle_aa(x, y, 5, Color(40, 100, 200, 220))

    # An area shape and its stroked outline, both from one Path.
    var area = Path()
    area.move_to(400.0, 420.0)
    area.line_to(460.0, 350.0)
    area.quad_curve_to(540.0, 300.0, 600.0, 360.0)
    area.line_to(660.0, 330.0)
    area.line_to(660.0, 420.0)
    area.close()
    target.fill_path_aa(area, Color(80, 170, 120, 120))
    target.stroke_path_aa(area, Color(50, 120, 80), 2.0)


def main() raises:
    var canvas = Canvas(800, 500, Color(255, 255, 255))
    draw_scene(canvas)
    draw_text(
        canvas,
        60,
        50,
        "canvas",
        Color(35, 38, 46),
        36.0,
        weight=FontWeight.BOLD,
    )
    draw_text(
        canvas, 60, 82, "2D drawing, pure Mojo", Color(120, 120, 130), 18.0
    )
    write_png(canvas, "examples/out_vector.png")

    var svg = SvgCanvas(800, 500)
    draw_scene(svg)
    svg.draw_text(
        60,
        50,
        "canvas",
        Color(35, 38, 46),
        36.0,
        TextAlign.LEFT,
        weight=FontWeight.BOLD,
    )
    svg.draw_text(
        60,
        82,
        "2D drawing, pure Mojo",
        Color(120, 120, 130),
        18.0,
        TextAlign.LEFT,
    )
    write_svg(svg, "examples/out_vector.svg")

    # And a PDF page, the third backend: text there is glyph outlines
    # through the same layout the raster backend uses.
    var pdf = PdfCanvas(800, 500)
    pdf.fill_rect(0, 0, 800, 500, Color(255, 255, 255))
    draw_scene(pdf)
    pdf.draw_text(
        60.0, 50.0, "canvas", Color(35, 38, 46), 36.0, weight=FontWeight.BOLD
    )
    pdf.draw_text(
        60.0, 82.0, "2D drawing, pure Mojo", Color(120, 120, 130), 18.0
    )
    write_pdf(pdf, "examples/out_vector.pdf")

    print("wrote examples/out_vector.png, .svg and .pdf")
    print("  same draw_scene() call, three backends")