Skip to main content

MaLight — an English drawing toolkit (the English edition of Shenbi Maliang)

简体中文 | English

A complete English rewrite of Shenbi Maliang ("Magic Light"). The product and class name is MaLight; the package name malight = Ma(gic) + light (lower-case by Python convention, used for import): it echoes the original Chinese name and means "light of magic" while staying shorter than magiclight.

English-only API, bilingual comments and examples in the source, and one method name per SVG element (circle / rect / text / path / g / clipPath / linearGradient …), one .py file per class. Drawing needs zero third-party dependencies thanks to a tiny self-contained SVG backend (PNG/PDF export optionally uses cairosvg; text-to-path optionally uses fontTools and svgpathtools).

pip install malight                 # core: no dependencies
pip install malight[export]         # add PNG / PDF export (cairosvg)
pip install malight[full]           # everything optional

0.1.0 (first release): package renamed magicpen → malight; new filter factory pen.fx (Photoshop/Illustrator-style filters, chainable); new extension mechanism malight.ext. Old scripts with import magicpen still run (a compatibility shim ships in the repo), and python -m malight.compat old.py new.py migrates them.

Also in 0.1.0: the "enum + string" dual style everywhere (fonts / colours / option values, so typos can't slip through); new path helper package malight.pathkit (inspect the structure, drag anchors and control points, insert/delete points, save/load point data); every class file ships a runnable example; exports print the full path so you can copy it straight into a shell; runtime localisation malight.i18n (English by default, one line to switch to Chinese); a bilingual doc pair xxx.zh.md / xxx.en.md is generated next to every module — docs travel with the code; code comments and docstrings are bilingual; English pages contain no Chinese at all. Elements can be tweaked after creation: el.set_font_size(36) or el.font_size(36) (no-arg reads, one-arg sets, chainable; parameter names match the creating call, get_font_size() reads back); plus paint_order=PaintOrder.STROKE (stroke-first outlined text) and the new fx_engrave() carved-in filter; plus PageSetup for PDF paper/margins (pen.export_pdf(page=PageSetup("A4", margin=24)) — Chrome engine only; PNG is a screen screenshot and has no notion of paper). Filter / text-style module docs now embed rendered effect preview images (generated by tools/gen_previews.py).

0.2.0 (current): element templates el.to_template() + clone() (turn an element into a <symbol> template in place, stamp out <use> copies); asset embedding — fonts are subsetted automatically by default (only the glyphs actually drawn are packaged), with whole-file embed and local-link modes; images can be embedded or linked; new pen.svg_image() to place an SVG (vector, with editable text and colours); new import_svg_as_group imported group (movable / rotatable / scalable, filters supported); new coloured console message helpers (print_red / print_green … in malight.tools).

Quick start

from malight import Malight, Color

pen = Malight("hello", width=800, height=600)
pen.set_background_color("#f8f9fa")
pen.circle(200, 150, 80, fill_color=Color.RGB(30, 144, 255),   # <circle>
           stroke_color="navy", stroke_width=3)
pen.rect(300, 60, 160, 110, corner_radius=12, fill_color="gold")
pen.text(400, 50, "Hello malight", font_size=32, bold=True)
pen.circle(600, 300, 70, fill_color="#e63946",
           filter=pen.fx.shadow(6, 8, 6))          # a filter in one line
pen.finish()          # write the SVG
pen.export_png()      # optional PNG (needs cairosvg; filters need a browser)

Malight and MagicPen are two names for the same class — use either. Deeper usage lives in board/core.en.md.

Method name = SVG element name

malight method SVG element Notes
pen.circle(x, y, r) <circle> circle
pen.ellipse(x, y, (rx,ry), rotate=) <ellipse> ellipse (with rotation)
pen.rect(x, y, w, h) <rect> rectangle
pen.line(p1, p2) <line> line segment
pen.polyline(pts) / pen.polygon(pts) <polyline> / <polygon> polyline / polygon
pen.text(x, y, s) <text> text
pen.textPath(pts, s) <textPath> text along a path
pen.path(...) <path> paths (Bézier / arcs / turtle drawing / boolean ops)
pen.image(file, x, y) <image> bitmap (inlined as base64)
pen.svg_image(file, x, y) <image> SVG file: vector, and its text/colours can be edited
pen.g() <g> group
pen.symbol(id) + pen.use(id, x, y) <symbol> + <use> templates and reuse
pen.pattern(...) <pattern> tiling fill
pen.marker(...) <marker> arrowheads / line-end markers
pen.clipPath(shape, targets) <clipPath> clipping (alias pen.clip)
pen.mask(shape, targets) <mask> masking
pen.a(el, url) <a> hyperlink
pen.linearGradient(...) / pen.radialGradient(...) <linearGradient> / <radialGradient> gradients

Every legacy long name (draw_circle, write_text, create_linear_gradient, …) is kept as an alias, so old scripts keep working.

Enum + string, both accepted

Font names, colour names, option values with only a handful of choices — all have enums. Each enum subclasses str, so f"{Font.SIMHEI}" gives "SimHei" and comparison against a plain string works, which makes enums and strings fully interchangeable.

from malight import (Malight, Font, Color, ColorName, FontWeight,
                     StrokeCap, FillRule)

pen = Malight("demo")

# Fonts: three equivalent forms — enum / font name / font file
pen.text(50, 60, "enum",  font=Font.SIMHEI)                  # enum
pen.text(50, 100, "name", font="KaiTi")                      # any installed font name
pen.text(50, 140, "bold", font=Font.MS_YAHEI, weight=FontWeight.BOLD)
# A font file (.ttf/.otf/.ttc) embeds only the glyphs actually drawn by default
# (automatic subsetting), so text survives elsewhere and the SVG stays small;
# see "Fonts and images" below to embed the whole file or link it instead:
# pen.text(50, 180, "file", font="C:/myfonts/MyFont.ttf")

# Colours: enum (72 common names) + string + RGB all work
pen.circle(200, 200, 60, fill_color=ColorName.TOMATO)
pen.circle(340, 200, 60, fill_color="tomato")             # same thing
pen.circle(480, 200, 60, fill_color=Color.RGB(30, 144, 255))

# Options with few values: enums make typos impossible
pen.path(fill_color="none", stroke_color="#333", stroke_width=6,
         stroke_cap=StrokeCap.ROUND,       # line cap: ROUND / BUTT / SQUARE
         stroke_join="round")              # line join: strings accepted too
pen.rect(0, 0, 10, 10, fill_rule=FillRule.EVENODD)
pen.finish()

The full enum list (Font / ColorName / BlendMode / FontWeight / DashStyle / StrokeCap / StrokeJoin / FillRule / ArrowStyle / TextHAlign / PDFMode …), their members and their meaning: definitions.en.md; font lookup and embedding: fonts.en.md.

General yes/no values also have lowercase constants: YES NO ON OFF. Your IDE completes them, so there is nothing to memorise.

Local font files and local images are written into the SVG by default (they survive on any machine), but how much gets written is your choice:

Asset Default Alternatives
Font (font=<file>) FontEmbed.SUBSET - only the glyphs actually drawn EMBED the whole file / LINK the local path
Image (pen.image(...)) ImageEmbed.EMBED - base64 inlined LINK a relative path
from malight import Malight, FontEmbed, ImageEmbed, find_font_file

pen = Malight("poster", fonts="link", images="link")   # smallest files
pen.set_embed(fonts=FontEmbed.SUBSET)                  # back to the default

# No need to repeat the text: at finish() the library scans the glyphs used
pen.text(300, 100, "MaLight", font=find_font_file("MyFont"),
         font_size=40, h_align="middle")
pen.image("assets/bg.jpg", 0, 0, width=600, height=400)   # LINK writes a path

Measured on a 5.6 MB font (Android.ttf) drawing 12 characters:

Mode Resulting SVG
SUBSET (default) 4.5 KB
EMBED 7.5 MB
LINK 0.7 KB

The price of LINK: the font / image file must stay where it is (same machine, same folder layout). Copy the SVG alone and you lose the glyphs or the picture. Keep the defaults when the file has to travel. Images in LINK mode are referenced relative to the SVG folder, so PNG/PDF export still resolves them.

Full walk-through: examples/demo_embed.py; bundled fonts: assets/fonts/README.md.

Recolouring one SVG file (pen.svg_image)

pen.image() pastes an SVG as a bitmap: the contents sit inside base64 and cannot be touched. pen.svg_image() reads the source text into the element, so you can edit it — colours included:

icon = pen.svg_image("assets/icons/mark.svg", x=40, y=40, width=60)
icon.svg_colors()                           # ['#ffffff', '#4dabf7']: what the file really uses
icon.replace_color("#ffffff", "#ff0000")    # white -> red (#fff / white all match)
icon.replace_text("circle", "ellipse")      # plain text replacement

# one file, three colours: each element edits its own copy
for i, color in enumerate(("#e63946", "#2a9d8f", "#1d3557")):
    pen.svg_image("assets/icons/mark.svg", x=40 + i * 90, y=200,
                  width=70).replace_color("white", color)

replace_color swaps by colour, not by raw text: #ffffff / #FFF / rgb(255,255,255) / white all mean the same thing and match at once, while identifiers such as id="orange" stay untouched. A colour the file does not actually use is reported together with the colours it does use, instead of silently changing nothing.

Taking an SVG apart (import_svg_as_group / import_svg_as_symbol)

Recolouring belongs to the SVG image element above, the one that carries its own text. When you want to take an SVG apart and edit it shape by shape, use these two entry points instead: they hand you nodes.

g = pen.import_svg_as_group("assets/icons/mark.svg", x=40, y=40, scale=0.5)
g.bbox()                              # it is a real group: geometry included
g.translate(10, 0)
for node in g.walk():                 # walk every node and edit any attribute
    print(node.tag, node.attribs.get("fill"))

They carry no recolour methods: colour is a property of a graphic, not of the idea of grouping. Recolour through the tools functions on the node instead (<style> blocks included):

from malight.tools import replace_svg_node_color, svg_node_colors

svg_node_colors(g.node)               # ['#ffffff', '#4dabf7']
replace_svg_node_color(g.node, "white", "#ff0000")

tpl = pen.import_svg_as_symbol("assets/icons/mark.svg", id_="mark")
replace_svg_node_color(tpl.node, "white", "#ff0000")   # one edit, every <use> changes
pen.use("mark", x=200, y=40, width=80, height=60)

When each copy needs its own colours, keep using pen.svg_image() — a template is by definition "edit once, change everywhere".

Switchable language at runtime (English by default)

Library errors, notices and export messages are English by default. Switching to Chinese takes one line and no changes to your own code:

from malight import set_language, use_language, get_language, t

print(get_language())                # "en" (default)

set_language("zh")                   # switch everything to Chinese
set_language("zh-TW")                # region codes work; falls back to zh
set_language("auto")                 # follow the system locale

with use_language("en"):             # scope it to one block; restored on exit
    pen.export_png()

print(t("export.ok", kind="SVG", tail="", path="a.svg", size="1 KB"))
  • The MALIGHT_LANG=zh environment variable sets the language before the process starts — handy in containers and CI.
  • Lookup order is exact language code → same language family → English, so a missing message never raises.
  • Add your own language, or override individual keys, with malight.i18n.add_messages("ja", {...}).

The complete API and every message key: i18n.en.md.

Docs travel with the code

Every module has its own bilingual documentation right next to it — change the API, change the page beside it:

File Contents
malight/elements/path.py the source (its docstrings are the single source of truth)
malight/elements/path.zh.md Chinese page: summary + class/method tables + full runnable example
malight/elements/path.en.md the English page, with a one-click switch back to Chinese

Pages are generated, never hand-written:

python tools/gen_docs.py            # refresh all 46 modules' bilingual pages from docstrings
python tools/gen_docs.py --check    # for CI: exit code 1 when a page is out of date
python tools/gen_docs.py path       # refresh only modules whose name matches

That is why the pages can't drift from the code; the module index in the README below is rebuilt by the same script.

Module index

malight (top-level)

Module Description
compat Mapping tables and a script migrator from the Chinese API to the English one.
definitions Colors, fonts, paper sizes and every option enum.
ext The extension mechanism: register a third-party toolkit with @toolkit and mount it on a board.
fonts Font enum plus font lookup, embedding and subsetting helpers.
gradients Linear and radial gradient definitions.
i18n Runtime message localisation.
page Page setup for printing and PDF export: paper, margins and scaling.
svg_backend A tiny SVG element and serialisation backend (internal).
tools Image, export, geometry, filter and font helpers.

malight/board

Module Description
containers Container elements: g, symbol, use, pattern, marker and a.
core BoardCore: canvas setup, element registry, page config, backgrounds, lifecycle hooks and finish().
debug Debugging helpers: grids, frames, distance measurements and key points.
effects Clipping and masking with SVG clipPath and mask.
filters The filter factory behind pen.fx and pen.filter.
fx FilterChain, a chainable builder for Photoshop-style filter stacks.
gradients_mixin Gradients: SVG linearGradient and radialGradient, plus rainbow and gold shortcuts.
images Images: bitmap <image> placement and SVG import.
layout Arranging elements horizontally, vertically, in a grid or around a circle.
paths Paths and connectors: path, polyline, tables, arrows and wave lines.
repeat Repetition: grid, circular and linear.
shapes Basic shapes, each method named after its SVG element.
style The style helper (pen.style) for CSS classes, the global stylesheet and font embedding.
text_board Text: SVG text, textPath and text-to-path conversion.

malight/elements

Module Description
base The Element base class and the helpers shared by every element.
circle CircleElement, created by pen.circle.
clippath ClipPathElement: clip-path definitions.
ellipse EllipseElement, created by pen.ellipse.
group GroupElement: bundle elements so they transform and animate together.
image ImageElement: a bitmap image.
line LineElement, created by pen.line.
link LinkElement: click the element to open a URL.
marker MarkerElement: line-end decorations such as arrowheads.
mask MaskElement: control visibility by luminance.
path PathElement: move / line / curve / arc commands, turtle drawing and boolean operations.
pattern PatternElement: tiling fill textures.
polygon PolygonElement, created by pen.polygon.
polyline PolylineElement, created by pen.polyline.
rect RectElement, created by pen.rect.
svggroup SvgGroupElement: an SVG file imported as an editable group.
svgimage SVGImageElement: embed another SVG file as an image.
symbol TemplateElement: define a reusable symbol.
text TextElement, created by pen.text.
textpath TextPathElement: text laid out along a path.
use UseElement: reference a symbol or an already defined shape.

malight/pathkit

Module Description
editor PathEditor: read the structure, drag anchors and control points, insert or drop points, save point data.
htmleditor PathHTMLEditor: export a path as one self-contained HTML file you drag points in.
parser Parse an SVG path d string into structured segments, and render segments back to d.
point PathPoint: a single draggable anchor or control point.
segment PathSegment: one SVG path command in absolute coordinates, with geometry helpers.

Going deeper

Topic In one line Documentation
Path helper pathkit Turn a path into visible points: inspect the structure, drag anchors/controls, insert or delete points, reverse, scale, save point data pathkit/editor.md | point.md | segment.md
Filters pen.fx Photoshop/Illustrator-style filters, chainable, only visually stable effects board/fx.md | board/filters.md
Extensions malight.ext Add toolkits (charts, icons, effects) to the board without touching the source ext.md
Export engines Chrome and cairo back ends for PNG and PDF, full path printed tools.md
Enums and constants Colours, fonts, paper sizes and every option enum definitions.md
Compatibility Chinese-API → English-API mapping tables and a one-command migrator compat.md
Internal backend The tiny SVG element tree and serialiser (rarely needed directly) svg_backend.md

Path helper pathkit (see it, drag it)

PathElement has many commands and is hard to reason about visually. malight.pathkit turns a path into visible points: how many points each curve has, where they are, and what happens when you drag them — just like a pen tool.

from malight import Malight
from malight.pathkit import PathEditor

pen = Malight("path_demo", width=660, height=400)
p = pen.path(fill_color="none", stroke_color="#e63946", stroke_width=3)
p.move_to(60, 300)
p.cubic_to((120, 80), (260, 80), (320, 300))
p.quad_to((420, 120), (520, 300))

print(p.describe())                   # 1) inspect: start / control / end of each segment
ed = p.editor()                       # 2) get the editor (same as PathEditor(p))
ed.anchors[1].move_to(200, 60)        #    drag an anchor (writes back immediately)
ed.controls[0].move_by(0, -30)        #    drag a control point
ed.insert_anchor(ed.curve_indices()[0], 0.5)   # 3) insert a point, shape unchanged
ed.scale_all(0.9, 0.9, cx=300, cy=200)
ed.show(labels=True)                  # 4) visualise: squares = anchors, dots = controls
ed.save_json("points.json")           # 5) save points, compute elsewhere, load back
pen.finish()

PathElement also exposes convenience passthroughs: path.describe() / path.anchor_points() / path.control_points() / path.move_anchor(i, x, y) / path.move_control(seg, i, x, y) / path.show_points() / path.editor().

Filters pen.fx (Photoshop / Illustrator style)

8 ways to use them (they stack and can be shared; illustrated in assets/images/filters_preview.png - code on the left, rendered result on the right):

# Style Code
1 Element method chain (fx_* returns the element, chains forever) el.fx_inner_shadow(1, 1, 2, "#ffffff", 0.6).fx_emboss().fx_blur(1.5)
2 Stack by name (same as el.fx_blur(3)) el.fx("blur", 3)
3 Append any raw SVG filter primitive el.fx("custom", "feBlend", mode="screen", in2="SourceGraphic")
4 Continue the element's own chain el.fx_chain().blur(2).saturate(1.4)
5 Factory, one call (pass filter= straight in) filter=pen.fx.shadow(6, 8, 6)
6 Factory chain, bind later (one chain, many elements) f = pen.fx.chain().blur(1).shadow(5, 5, 4) → a.set_filter(f) / f.apply(b)
7 Stack / replace / clear (set_filter stacks by default) repeated set_filter stacks; merge=False replaces the chain; set_filter(None) clears
8 Low-level helpers (tools.create_*_filter) fid = create_glow_filter(pen, 6) + extra={"filter": "url(#%s)" % fid}
fx = pen.fx          # pen.filter is an alias

# stack them like Photoshop layer styles (inner shadow + emboss + slight blur)
pen.text(60, 200, "Ma", font_size=44, fill_color="#c23b22") \
   .fx_inner_shadow(1, 1, 2, "#ffffff", 0.6).fx_emboss().fx_blur(1.5)

# build one chain, share it with several elements
f = fx.chain().outline(3, "#fff").shadow(8, 10, 6).saturate(1.4)
pen.star(500, 400, 140, fill_color="#f4a261").set_filter(f)
pen.circle(620, 400, 90, fill_color="#2a9d8f").set_filter(f)
Category Methods Photoshop / Illustrator equivalent
Layer styles shadow inner_shadow glow inner_glow bevel engrave outline color_overlay drop shadow / inner shadow / outer glow / inner glow / bevel & emboss / engrave / stroke / colour overlay
Blur & sharpen blur sharpen motion_blur Gaussian blur / unsharp mask / motion blur
Stylise roughen noise emboss edge_detect(width) roughen / add noise / emboss / find edges (width > 1 thickens strokes)
Colour saturate hue_rotate brightness contrast gamma grayscale sepia invert posterize hue & saturation / brightness & contrast / curves / desaturate / invert / posterize

Every method returns a FilterChain, so chains keep building; f.apply(el), el.set_filter(f) (stacks by default, merge=False replaces the whole chain) and el.set_filter(None) (clear) are supported, and f.custom("feBlend", mode="screen", ...) appends any raw SVG primitive. Factory methods map one-to-one onto the element shortcuts (pen.fx.blur(x) is the same as el.fx_blur(x)). Filters render in browsers and any SVG-filter-aware viewer (cairosvg ignores filters when exporting PNG).

Type hints (PyCharm / VS Code completion)

All 314 public methods carry return-type annotations, so typing pen. shows what each method returns, and chained calls keep their hints:

p = pen.path(fill_color="none", stroke_width=2)   # p: PathElement
p.move_to(50, 50).line_to(200, 80).close()        # chainable: returns itself

c = pen.circle(100, 100, 50)                      # c: CircleElement
c.set_filter(pen.fx.shadow(6, 6, 5)).translate(10, 0)

g = pen.g()                                       # g: GroupElement
pen.linearGradient((0, 0), (1, 0), "red", "blue") # -> LinearGradient
pen.repeat_grid(tile, 3, 40, 2, 40)               # -> list[Element]
  • Element methods (move_to / translate / set_filter …) are annotated with TypeVar("_Self"), so subclasses infer their own type (PathElement.move_to() returns PathElement).
  • Board methods that return the board (resize / set_background_color / add_js) are annotated _Pen.
  • The package ships py.typed (PEP 561), so hints survive pip install malight.
  • examples/test_types.py checks for missing annotations, evaluability and runtime type agreement: forget an annotation on a new method and the test fails.

Export: Chrome and cairo engines

pen.finish()

pen.export_pdf()                                  # AUTO: Chrome if present, else cairo
pen.export_pdf("out.pdf", engine=PDFMode.CHROME)  # force Chrome (browser-identical, SVG filters included)
pen.export_pdf("out.pdf", engine=PDFMode.CAIROSVG)# force cairo (no browser needed)
pen.export_png(scale=2)                           # PNG AUTO: cairosvg if installed, else Chrome
pen.export_png(scale=2, mode=PNGMode.CHROME)      # PNG via Chrome (filters render correctly)

Exports print the full path, ready to copy (the return value is the same absolute path, so scripts can chain on it):

[malight] SVG exported (800x500) -> C:\proj\output\demo_basic.svg  [7.3 KB]
[malight] cairosvg not found, falling back to headless Chrome for PNG (better SVG filter support)
[malight] PNG exported (Chrome engine, 2x, filters intact) -> C:\proj\output\demo_basic.png  [45.9 KB]
[malight] PDF exported (Chrome engine) -> C:\proj\output\demo_basic.pdf  [88.3 KB]
Engine Requirement SVG filters (shadow/glow/blur/emboss) Best for
Chrome (headless) local Chrome/Edge full rendering filter-heavy artwork, browser-identical PDF
cairo cairosvg ignored pure vector work, no browser, fastest
  • Chrome is located automatically (registry → common install dirs → PATH → Edge). For portable builds set MALIGHT_CHROME=D:\chrome\chrome.exe.
  • PDF pages have zero margin and map 1:1 to canvas pixels; PNG honours scale exactly (a 480×320 canvas at scale=2 yields 960×640).
  • If neither engine is available you get a clear error with two installation suggestions, not a bare ModuleNotFoundError. The full comparison is in examples/demo_export.py and tools.md.

Compatibility and migration

  • Legacy Chinese scripts: python -m malight.compat old.py new.py (Chinese API → English short names, including import magicpen → import malight).
  • v1 English scripts: import magicpen still works (the shim at the repo root forwards automatically), but moving to from malight import Malight is recommended. The shim ships only in the source repository, not in the wheel.

Mapping tables and all rewrite rules: compat.md.

Examples

examples/ holds 13 runnable demos: demo_basic demo_path demo_effects demo_animation demo_arrange demo_advanced demo_fx (the whole filter family) demo_ext (extensions) demo_export (Chrome vs cairo). The board_games/ subdirectory collects board-game samples: chinese_chess.py (Xiangqi) and international_chess.py (chess). Plus two tests: test_fx_smoke.py (filter smoke test) and test_types.py (annotation audit).

Every class file also contains a runnable example (if __name__ == "__main__":). Click the green triangle in PyCharm, or run it from the command line, and it draws its result; the example covers every method of that class with step-by-step comments:

python malight/elements/circle.py     # circle: styles / transforms / animation / clone
python malight/elements/path.py       # path: every command + pathkit point dragging
python malight/elements/group.py      # group: nesting / transforms / bbox / clone
python malight/pathkit/editor.py      # path editor: inspect / drag / insert / save
python malight/pathkit/point.py       # draggable point: properties / move / unpack / eq
python malight/pathkit/segment.py     # path segment: parse / sample / measure / split
# ... or equivalently `python -m malight.elements.circle`

Runnable modules: malight/elements/*.py (20 element classes) plus malight/pathkit/*.py (parser / editor / point / segment). The "Full example" section of each module page is exactly this code.

Building the wheel

The repo ships build_release.py (run it from the project root, where pyproject.toml lives):

python build_release.py            # build dist/malight-x.y.z-py3-none-any.whl + .tar.gz and self-test
python build_release.py --check    # also run twine check on the metadata
python build_release.py --upload   # build, verify, then upload to PyPI (needs twine)
python build_release.py --offline  # don't auto-install build/twine; fall back to pip wheel

The full PyPI flow (wired into the script, or run by hand):

pip install -U twine
python build_release.py --check
twine upload dist/*                          # production
twine upload --repository testpypi dist/*    # verify on TestPyPI first
  • The single source of truth for the version is __version__ in malight/__init__.py; pyproject.toml reads it dynamically.
  • Core drawing has no dependencies; optional extras: pip install malight[export] (PNG/PDF), malight[fontpath] (text to path), malight[full].
  • Before releasing, the script creates a clean venv, installs the wheel and exercises drawing, filters and extensions to make sure it works out of the box.
  • The bilingual module .md files ship inside the sdist (for offline reading) but are excluded from the wheel.

A note on the bilingual docs

Both languages are first class. Every module ships two pages next to it — xxx.zh.md in Chinese and xxx.en.md in English — each with a one-click switch at the top, and the same content on both: summary, class and method tables, module notes and a full runnable example.

The English pages are entirely English: summaries, prose, tables and the example code. The one exception is the switch label naming the other language. A regression check fails the build if Chinese ever leaks into an English page.

The sources serve both languages at once: every docstring and every example comment carries both halves, separated by / — Chinese first, English second. The docs generator splits each pair per page, which is why the two pages can never drift apart. Runtime messages follow the language you set with malight.set_language(...).

License

See LICENSE.

Release files for malight 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for malight 0.2.0
File Size Uploaded
malight-0.2.0.tar.gz 1.5 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for malight 0.2.0
File Interpreter ABI Platform
malight-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 2.7 MB

Release files / malight-0.2.0.tar.gz

Download URL malight-0.2.0.tar.gz
Size 1.5 MB
Tags Source
SHA-256 checksum
How to use checksums
6c72dd4f1e42bcb217d021e302782680e0b53edce5a3b754ff809f56331f02ab
BLAKE2b-256 checksum
How to use checksums
91d799eb11372cf17c1cf38e6447e5ab5901f19a1bfe6f9ed7b93f8817060c65
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / malight-0.2.0-py3-none-any.whl

Download URL malight-0.2.0-py3-none-any.whl
Size 1.3 MB
Tags Python 3
SHA-256 checksum
How to use checksums
5281728700812379220fcc14dab35bc8131335a986f945e7000a61ffbb17b455
BLAKE2b-256 checksum
How to use checksums
74ca83612dfb452c96d31cca37e22fb3e58c5438f4a6cefbd2e91b6e34295668
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page