vec-io 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vec_io-0.1.0/API.md +1413 -0
- vec_io-0.1.0/EXAMPLES.md +612 -0
- vec_io-0.1.0/LICENSE +28 -0
- vec_io-0.1.0/PKG-INFO +168 -0
- vec_io-0.1.0/README.md +153 -0
- vec_io-0.1.0/examples/bubble_chart.py +72 -0
- vec_io-0.1.0/examples/canvas_demo.py +197 -0
- vec_io-0.1.0/examples/drawing_demo.py +80 -0
- vec_io-0.1.0/examples/heatmap.py +79 -0
- vec_io-0.1.0/examples/scatter_plot.py +74 -0
- vec_io-0.1.0/examples/showcase.svg +168 -0
- vec_io-0.1.0/examples/text.svg +79 -0
- vec_io-0.1.0/examples/text_layout.py +56 -0
- vec_io-0.1.0/pyproject.toml +45 -0
- vec_io-0.1.0/tests/brotli/abc.br +0 -0
- vec_io-0.1.0/tests/brotli/abc.out.z +0 -0
- vec_io-0.1.0/tests/brotli/big.br +0 -0
- vec_io-0.1.0/tests/brotli/big.out.z +0 -0
- vec_io-0.1.0/tests/brotli/binary.br +0 -0
- vec_io-0.1.0/tests/brotli/binary.out.z +0 -0
- vec_io-0.1.0/tests/brotli/bits2.br +5 -0
- vec_io-0.1.0/tests/brotli/bits2.out.z +0 -0
- vec_io-0.1.0/tests/brotli/dictwords.br +0 -0
- vec_io-0.1.0/tests/brotli/dictwords.out.z +0 -0
- vec_io-0.1.0/tests/brotli/empty.br +1 -0
- vec_io-0.1.0/tests/brotli/empty.out.z +0 -0
- vec_io-0.1.0/tests/brotli/four.br +0 -0
- vec_io-0.1.0/tests/brotli/four.out.z +0 -0
- vec_io-0.1.0/tests/brotli/handmade.br +0 -0
- vec_io-0.1.0/tests/brotli/handmade.out.z +0 -0
- vec_io-0.1.0/tests/brotli/ints.br +0 -0
- vec_io-0.1.0/tests/brotli/ints.out.z +0 -0
- vec_io-0.1.0/tests/brotli/mixed.br +0 -0
- vec_io-0.1.0/tests/brotli/mixed.out.z +0 -0
- vec_io-0.1.0/tests/brotli/multi.br +0 -0
- vec_io-0.1.0/tests/brotli/multi.out.z +2 -0
- vec_io-0.1.0/tests/brotli/one.br +0 -0
- vec_io-0.1.0/tests/brotli/one.out.z +0 -0
- vec_io-0.1.0/tests/brotli/random.br +0 -0
- vec_io-0.1.0/tests/brotli/random.out.z +0 -0
- vec_io-0.1.0/tests/brotli/text11.br +0 -0
- vec_io-0.1.0/tests/brotli/text11.out.z +0 -0
- vec_io-0.1.0/tests/brotli/text_q2.br +0 -0
- vec_io-0.1.0/tests/brotli/text_q2.out.z +0 -0
- vec_io-0.1.0/tests/brotli/three.br +0 -0
- vec_io-0.1.0/tests/brotli/three.out.z +0 -0
- vec_io-0.1.0/tests/brotli/w10.br +0 -0
- vec_io-0.1.0/tests/brotli/w10.out.z +0 -0
- vec_io-0.1.0/tests/brotli/w24.br +0 -0
- vec_io-0.1.0/tests/brotli/w24.out.z +0 -0
- vec_io-0.1.0/tests/corpus/clipping.svg +8 -0
- vec_io-0.1.0/tests/corpus/css.svg +13 -0
- vec_io-0.1.0/tests/corpus/empty.svg +3 -0
- vec_io-0.1.0/tests/corpus/filters.svg +6 -0
- vec_io-0.1.0/tests/corpus/fonts_face.svg +5 -0
- vec_io-0.1.0/tests/corpus/gradients.svg +14 -0
- vec_io-0.1.0/tests/corpus/images.svg +5 -0
- vec_io-0.1.0/tests/corpus/markers.svg +6 -0
- vec_io-0.1.0/tests/corpus/masks.svg +7 -0
- vec_io-0.1.0/tests/corpus/opacity.svg +4 -0
- vec_io-0.1.0/tests/corpus/paths.svg +6 -0
- vec_io-0.1.0/tests/corpus/patterns.svg +7 -0
- vec_io-0.1.0/tests/corpus/rasterized.svg +4 -0
- vec_io-0.1.0/tests/corpus/shapes.svg +9 -0
- vec_io-0.1.0/tests/corpus/strokes.svg +10 -0
- vec_io-0.1.0/tests/corpus/structure.svg +7 -0
- vec_io-0.1.0/tests/corpus/text-wrap.svg +4 -0
- vec_io-0.1.0/tests/corpus/text_basic.svg +7 -0
- vec_io-0.1.0/tests/corpus/text_layout.svg +7 -0
- vec_io-0.1.0/tests/corpus/text_path.svg +6 -0
- vec_io-0.1.0/tests/corpus/text_unicode.svg +5 -0
- vec_io-0.1.0/tests/corpus/transforms.svg +7 -0
- vec_io-0.1.0/tests/corpus/units.svg +5 -0
- vec_io-0.1.0/tests/corpus/use_symbol.svg +8 -0
- vec_io-0.1.0/tests/corpus/viewbox.svg +8 -0
- vec_io-0.1.0/tests/fonts/PioTest-Bold.ttf +0 -0
- vec_io-0.1.0/tests/fonts/PioTest-Regular.ttf +0 -0
- vec_io-0.1.0/tests/fuzz_native.py +864 -0
- vec_io-0.1.0/tests/helpers.py +540 -0
- vec_io-0.1.0/tests/maps.py +90 -0
- vec_io-0.1.0/tests/scene_oracle.py +696 -0
- vec_io-0.1.0/tests/shapes.py +20 -0
- vec_io-0.1.0/tests/svg_oracle.py +917 -0
- vec_io-0.1.0/tests/test_api.py +1281 -0
- vec_io-0.1.0/tests/test_avif.py +112 -0
- vec_io-0.1.0/tests/test_canvas_features.py +302 -0
- vec_io-0.1.0/tests/test_cells.py +311 -0
- vec_io-0.1.0/tests/test_cmyk.py +334 -0
- vec_io-0.1.0/tests/test_codecs.py +33 -0
- vec_io-0.1.0/tests/test_compile.py +131 -0
- vec_io-0.1.0/tests/test_convert.py +435 -0
- vec_io-0.1.0/tests/test_deflate.py +57 -0
- vec_io-0.1.0/tests/test_features.py +342 -0
- vec_io-0.1.0/tests/test_filters.py +882 -0
- vec_io-0.1.0/tests/test_fonts_io.py +562 -0
- vec_io-0.1.0/tests/test_jxl.py +252 -0
- vec_io-0.1.0/tests/test_marker_runs.py +140 -0
- vec_io-0.1.0/tests/test_native.py +299 -0
- vec_io-0.1.0/tests/test_native_emit.py +197 -0
- vec_io-0.1.0/tests/test_native_jpeg.py +562 -0
- vec_io-0.1.0/tests/test_native_png.py +565 -0
- vec_io-0.1.0/tests/test_output.py +329 -0
- vec_io-0.1.0/tests/test_parsing.py +246 -0
- vec_io-0.1.0/tests/test_pdfa.py +302 -0
- vec_io-0.1.0/tests/test_raster.py +1113 -0
- vec_io-0.1.0/tests/test_scene.py +372 -0
- vec_io-0.1.0/tests/test_shapes.py +141 -0
- vec_io-0.1.0/tests/test_simplify.py +583 -0
- vec_io-0.1.0/tests/test_svg_semantics.py +278 -0
- vec_io-0.1.0/tests/test_svg_writer.py +214 -0
- vec_io-0.1.0/tests/test_text.py +174 -0
- vec_io-0.1.0/tests/test_textlayout.py +414 -0
- vec_io-0.1.0/tests/test_threads.py +162 -0
- vec_io-0.1.0/tests/test_units.py +69 -0
- vec_io-0.1.0/tests/test_varfonts.py +248 -0
- vec_io-0.1.0/tests/test_woff2.py +160 -0
- vec_io-0.1.0/tests/testfont.py +204 -0
- vec_io-0.1.0/tests/varfonts/VarTest.otf +0 -0
- vec_io-0.1.0/tests/varfonts/VarTest.ttf +0 -0
- vec_io-0.1.0/tests/woff2/make.py +163 -0
- vec_io-0.1.0/tests/woff2/pair.woff2 +0 -0
- vec_io-0.1.0/tests/woff2/rich-null.woff2 +0 -0
- vec_io-0.1.0/tests/woff2/rich.ttf +0 -0
- vec_io-0.1.0/tests/woff2/rich.woff2 +0 -0
- vec_io-0.1.0/tools/build_native.py +421 -0
- vec_io-0.1.0/tools/gen_api.py +311 -0
- vec_io-0.1.0/vec_io/__init__.py +38 -0
- vec_io-0.1.0/vec_io/__main__.py +138 -0
- vec_io-0.1.0/vec_io/arrays.py +83 -0
- vec_io-0.1.0/vec_io/canvas.py +2086 -0
- vec_io-0.1.0/vec_io/cells.py +513 -0
- vec_io-0.1.0/vec_io/document.py +779 -0
- vec_io-0.1.0/vec_io/draw.py +1013 -0
- vec_io-0.1.0/vec_io/filter.py +787 -0
- vec_io-0.1.0/vec_io/flate.py +59 -0
- vec_io-0.1.0/vec_io/fonts/__init__.py +519 -0
- vec_io-0.1.0/vec_io/fonts/cff.py +612 -0
- vec_io-0.1.0/vec_io/fonts/cff2.py +603 -0
- vec_io-0.1.0/vec_io/fonts/fontdb.py +465 -0
- vec_io-0.1.0/vec_io/fonts/opentype.py +384 -0
- vec_io-0.1.0/vec_io/fonts/sfnt.py +905 -0
- vec_io-0.1.0/vec_io/fonts/variations.py +845 -0
- vec_io-0.1.0/vec_io/fonts/woff2.py +246 -0
- vec_io-0.1.0/vec_io/geometry.py +914 -0
- vec_io-0.1.0/vec_io/image/__init__.py +232 -0
- vec_io-0.1.0/vec_io/image/avif.py +184 -0
- vec_io-0.1.0/vec_io/image/codecs.py +115 -0
- vec_io-0.1.0/vec_io/image/jxl.py +440 -0
- vec_io-0.1.0/vec_io/image/resource.py +269 -0
- vec_io-0.1.0/vec_io/items.py +227 -0
- vec_io-0.1.0/vec_io/lines.py +377 -0
- vec_io-0.1.0/vec_io/native/README.md +248 -0
- vec_io-0.1.0/vec_io/native/__init__.py +329 -0
- vec_io-0.1.0/vec_io/native/_api.py +712 -0
- vec_io-0.1.0/vec_io/native/bin/linux-aarch64.so +0 -0
- vec_io-0.1.0/vec_io/native/bin/linux-x86_64.so +0 -0
- vec_io-0.1.0/vec_io/native/bin/macos-arm64.dylib +0 -0
- vec_io-0.1.0/vec_io/native/bin/windows-arm64.dll +0 -0
- vec_io-0.1.0/vec_io/native/bin/windows-x86_64.dll +0 -0
- vec_io-0.1.0/vec_io/native/brotli.py +19 -0
- vec_io-0.1.0/vec_io/native/deflate.py +21 -0
- vec_io-0.1.0/vec_io/native/emit.py +293 -0
- vec_io-0.1.0/vec_io/native/filters.py +205 -0
- vec_io-0.1.0/vec_io/native/jpeg.py +89 -0
- vec_io-0.1.0/vec_io/native/manifest.json +49 -0
- vec_io-0.1.0/vec_io/native/png.py +458 -0
- vec_io-0.1.0/vec_io/native/src/alloc.c +185 -0
- vec_io-0.1.0/vec_io/native/src/brotli.c +716 -0
- vec_io-0.1.0/vec_io/native/src/brotli.h +16 -0
- vec_io-0.1.0/vec_io/native/src/brotli_dict.c +3950 -0
- vec_io-0.1.0/vec_io/native/src/crowding.c +246 -0
- vec_io-0.1.0/vec_io/native/src/cull.c +1358 -0
- vec_io-0.1.0/vec_io/native/src/data.c +245 -0
- vec_io-0.1.0/vec_io/native/src/deflate.c +930 -0
- vec_io-0.1.0/vec_io/native/src/emit.c +319 -0
- vec_io-0.1.0/vec_io/native/src/filters.c +1120 -0
- vec_io-0.1.0/vec_io/native/src/image.c +150 -0
- vec_io-0.1.0/vec_io/native/src/interp.c +5275 -0
- vec_io-0.1.0/vec_io/native/src/items.c +327 -0
- vec_io-0.1.0/vec_io/native/src/jpeg.h +32 -0
- vec_io-0.1.0/vec_io/native/src/jpeg_dec.c +894 -0
- vec_io-0.1.0/vec_io/native/src/jpeg_enc.c +1350 -0
- vec_io-0.1.0/vec_io/native/src/math.c +356 -0
- vec_io-0.1.0/vec_io/native/src/paint.c +787 -0
- vec_io-0.1.0/vec_io/native/src/path.c +356 -0
- vec_io-0.1.0/vec_io/native/src/pio.h +164 -0
- vec_io-0.1.0/vec_io/native/src/pio_api.h +671 -0
- vec_io-0.1.0/vec_io/native/src/png.c +959 -0
- vec_io-0.1.0/vec_io/native/src/quant.c +648 -0
- vec_io-0.1.0/vec_io/native/src/raster.h +434 -0
- vec_io-0.1.0/vec_io/native/src/res.c +396 -0
- vec_io-0.1.0/vec_io/native/src/rt.c +92 -0
- vec_io-0.1.0/vec_io/native/src/scan.c +655 -0
- vec_io-0.1.0/vec_io/native/src/scene.c +1515 -0
- vec_io-0.1.0/vec_io/native/src/scene.h +228 -0
- vec_io-0.1.0/vec_io/native/src/shade.c +293 -0
- vec_io-0.1.0/vec_io/native/src/shade.h +55 -0
- vec_io-0.1.0/vec_io/native/src/signatures.c +169 -0
- vec_io-0.1.0/vec_io/native/src/stroke.c +508 -0
- vec_io-0.1.0/vec_io/native/src/svgw.c +1776 -0
- vec_io-0.1.0/vec_io/native/src/woff2.c +377 -0
- vec_io-0.1.0/vec_io/native/woff2.py +24 -0
- vec_io-0.1.0/vec_io/paint.py +769 -0
- vec_io-0.1.0/vec_io/pdf/__init__.py +0 -0
- vec_io-0.1.0/vec_io/pdf/images.py +205 -0
- vec_io-0.1.0/vec_io/pdf/objects.py +147 -0
- vec_io-0.1.0/vec_io/pdf/pdfa.py +163 -0
- vec_io-0.1.0/vec_io/pdf/writer.py +208 -0
- vec_io-0.1.0/vec_io/py.typed +0 -0
- vec_io-0.1.0/vec_io/raster/__init__.py +530 -0
- vec_io-0.1.0/vec_io/raster/groups.py +102 -0
- vec_io-0.1.0/vec_io/raster/resources.py +400 -0
- vec_io-0.1.0/vec_io/scene.py +672 -0
- vec_io-0.1.0/vec_io/shapes.py +294 -0
- vec_io-0.1.0/vec_io/simplify.py +402 -0
- vec_io-0.1.0/vec_io/svg/__init__.py +104 -0
- vec_io-0.1.0/vec_io/svg/css.py +355 -0
- vec_io-0.1.0/vec_io/svg/filters.py +286 -0
- vec_io-0.1.0/vec_io/svg/parse.py +341 -0
- vec_io-0.1.0/vec_io/svg/render.py +1868 -0
- vec_io-0.1.0/vec_io/svg/writer.py +480 -0
- vec_io-0.1.0/vec_io/text/__init__.py +944 -0
- vec_io-0.1.0/vec_io/text/breaking.py +583 -0
- vec_io-0.1.0/vec_io/text/path.py +116 -0
- vec_io-0.1.0/vec_io/threads.py +80 -0
- vec_io-0.1.0/vec_io/units.py +105 -0
vec_io-0.1.0/API.md
ADDED
|
@@ -0,0 +1,1413 @@
|
|
|
1
|
+
# vec_io API
|
|
2
|
+
|
|
3
|
+
Complete reference. Worked examples are in [EXAMPLES.md](EXAMPLES.md).
|
|
4
|
+
|
|
5
|
+
1. [Conventions](#1-conventions)
|
|
6
|
+
2. [Converting SVG](#2-converting-svg)
|
|
7
|
+
3. [Command line](#3-command-line)
|
|
8
|
+
4. [Document and Page](#4-document-and-page)
|
|
9
|
+
5. [Canvas](#5-canvas)
|
|
10
|
+
6. [Text layout](#6-text-layout)
|
|
11
|
+
7. [Paints and recorded content](#7-paints-and-recorded-content)
|
|
12
|
+
8. [Markers](#8-markers)
|
|
13
|
+
9. [Lines, segments and areas](#9-lines-segments-and-areas)
|
|
14
|
+
10. [Cells](#10-cells)
|
|
15
|
+
11. [Drawings: `vec_io.draw`](#11-drawings-vec_iodraw)
|
|
16
|
+
12. [Rasterization](#12-rasterization)
|
|
17
|
+
13. [Simplification](#13-simplification)
|
|
18
|
+
14. [Filters](#14-filters)
|
|
19
|
+
15. [SVG support](#15-svg-support)
|
|
20
|
+
16. [Fonts](#16-fonts)
|
|
21
|
+
17. [Geometry: `Matrix`, `Path`](#17-geometry-matrix-path)
|
|
22
|
+
18. [Output](#18-output)
|
|
23
|
+
19. [Errors](#19-errors)
|
|
24
|
+
20. [Modules](#20-modules)
|
|
25
|
+
21. [Array arguments](#21-array-arguments)
|
|
26
|
+
|
|
27
|
+
## 1. Conventions
|
|
28
|
+
|
|
29
|
+
* **Coordinates.** Origin at the top-left corner, y pointing down, in every API. Angles are
|
|
30
|
+
in degrees, clockwise on screen.
|
|
31
|
+
* **Unit.** A document has one unit, `Document(unit=…)`: `pt` (default), `px` (CSS pixels,
|
|
32
|
+
1/96 in), `mm`, `cm`, `in`, `pc` or `q`. Page sizes, coordinates, line widths, font sizes,
|
|
33
|
+
`vec_io.draw` drawings drawn on its pages: all are in it. SVG input is in CSS pixels by
|
|
34
|
+
its own definition and is scaled into the page. `vec_io.units` parses CSS dimensions:
|
|
35
|
+
`split_length(value, units=None)` gives `(number, unit)` or raises `ValueError`;
|
|
36
|
+
`to_px(number, unit)` converts absolute units; `parse_angle(value, strict=False)` gives
|
|
37
|
+
degrees; `parse_length` is the lenient SVG form. Tables: `PX_PER_UNIT`, `ANGLE_UNITS`.
|
|
38
|
+
* **Colors.** A color is a CSS string (`"red"`, `"#rgb[a]"`, `"#rrggbb[aa]"`,
|
|
39
|
+
`"transparent"`, and the CSS Color 4 functions in modern or comma syntax: `rgb[a]()`,
|
|
40
|
+
`hsl[a]()`, `hwb()`, `lab()`, `lch()`, `oklab()`, `oklch()`, `color(<space> r g b)` with
|
|
41
|
+
`srgb`, `srgb-linear`, `display-p3`, `a98-rgb`, `prophoto-rgb`, `rec2020`, `xyz`,
|
|
42
|
+
`xyz-d50`, `xyz-d65`, and `device-cmyk(c m y k)`; each takes `/ alpha` and `none`); a
|
|
43
|
+
tuple `(r, g, b)` or `(r, g, b, a)` of numbers in [0, 1]; or a print color below.
|
|
44
|
+
Components outside [0, 1] raise `ValueError`: values on the 0–255 scale go through
|
|
45
|
+
`rgb255(r, g, b, alpha=1)` or a CSS string. Colors outside sRGB are gamut mapped as
|
|
46
|
+
CSS Color 4 specifies (chroma reduced in OKLCh).
|
|
47
|
+
* **Print colors.** `CMYK(c, m, y, k, alpha=1)` is a device CMYK color, components in
|
|
48
|
+
[0, 1]. `Spot(name, cmyk, tint=1, alpha=1)` is a spot ink: `cmyk` (a `CMYK` or 4-tuple)
|
|
49
|
+
is its appearance at full tint, used by devices without the ink. Both are written exactly
|
|
50
|
+
(`k`/`K` operators, a `Separation` color space) in every color mode. They are values of
|
|
51
|
+
their own, equal only to print colors of the same components; `rgb` and `rgba` are their
|
|
52
|
+
approximation, used where RGB is needed (rasterized pixels, image and SVG output).
|
|
53
|
+
Methods: `with_alpha(alpha)`, `css()`.
|
|
54
|
+
* **Color spaces.** `vec_io.paint.srgb_to_oklab(r, g, b)` and `oklab_to_srgb(L, a, b)`
|
|
55
|
+
convert between sRGB (gamma-encoded, 0–1; clipped on the way back) and Oklab;
|
|
56
|
+
`oklch_to_oklab(L, C, H)` takes the hue in degrees; `lchuv_to_srgb(L, C, H)` converts CIE
|
|
57
|
+
LCh(uv) (D65, lightness 0–100) to sRGB as R's `hcl()` does.
|
|
58
|
+
* **Sources.** Files are given as paths; images and SVGs may also be bytes or `data:` URIs.
|
|
59
|
+
Relative references resolve against `base_dir`; without it only `data:` URIs are loaded.
|
|
60
|
+
With it, any local file the process can read may be embedded (`../` paths, absolute
|
|
61
|
+
paths, `file:` URLs on this machine), so pass `base_dir=None` for untrusted SVG. Remote
|
|
62
|
+
URLs and `file:` URLs naming another host are never fetched.
|
|
63
|
+
* **Types.** The package is typed (`py.typed`). Aliases: `vec_io.Color` (a color argument),
|
|
64
|
+
`vec_io.Paint` (a color, gradient, pattern or `None`), `vec_io.Content` (text layout
|
|
65
|
+
content). Chainable methods return the object's own type.
|
|
66
|
+
* **Arrays.** Coordinates, sizes, per-item values and cell grids are sequences (`None`
|
|
67
|
+
for a missing value) or any object with the buffer protocol: `array.array`,
|
|
68
|
+
`memoryview`, numpy arrays (read without importing numpy).
|
|
69
|
+
* **C library.** Bulk work (writing content streams, rasterization, culling, PNG and JPEG
|
|
70
|
+
coding, filters) runs in a bundled C library, prebuilt for Linux x86_64/aarch64, Windows
|
|
71
|
+
x86_64/arm64 and macOS arm64. It releases the GIL. Elsewhere, features that need it
|
|
72
|
+
raise `vec_io.native.NativeLibraryError` (build it with `tools/build_native.py` and set
|
|
73
|
+
`VEC_IO_NATIVE_LIB`).
|
|
74
|
+
|
|
75
|
+
## 2. Converting SVG
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
convert(source, output=None, *, format=None, base_dir=None, font_db=None, font_dirs=None,
|
|
79
|
+
system_fonts=True, filters=False, pdf_a=None, compress=True, compression="balanced",
|
|
80
|
+
simplify=None, images=None, dpi=96, background=None, png=None, jpeg=None, jxl=None,
|
|
81
|
+
avif=None) -> bytes
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Converts an SVG document to PDF, PNG, JPEG, JPEG XL or AVIF, returns the bytes and writes
|
|
85
|
+
them to `output` (a path or a binary file object) when given. It is a `Document` in CSS
|
|
86
|
+
pixels with the SVG on its page, saved or rendered with the options below.
|
|
87
|
+
|
|
88
|
+
| Parameter | Meaning |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `source` | SVG text or bytes (gzip-compressed SVGZ too), a path, a binary file object, or an `SVGDocument`. A string not starting with `<` is a path. |
|
|
91
|
+
| `format` | `"pdf"`, `"png"`, `"jpeg"`, `"jxl"` or `"avif"`; default: the extension of an `output` path, else `"pdf"`. |
|
|
92
|
+
| `base_dir` | Directory of relative `<image>` and `@font-face` URLs; default: a source path's directory. |
|
|
93
|
+
| `font_db`, `font_dirs`, `system_fonts` | Fonts: a `FontDB`, or extra directories searched first and whether to use installed fonts ([Fonts](#16-fonts)). |
|
|
94
|
+
| `filters` | Apply SVG filters (rasterizes filtered elements; [Filters](#14-filters)). |
|
|
95
|
+
| `pdf_a` | PDF: `"2b"`, `"2u"` or `"3b"` ([PDF/A](#pdfa)). |
|
|
96
|
+
| `compress`, `compression`, `simplify`, `images` | PDF: the options of `Document.to_bytes` ([4](#4-document-and-page)). |
|
|
97
|
+
| `dpi`, `simplify`, `background` | Images: resolution (96: one pixel per CSS pixel), decimation, a color under the page (`None`: transparent, white for JPEG). |
|
|
98
|
+
| `png`, `jpeg`, `jxl`, `avif` | Images: the options of the format (`PNG(…)`, `JPEG(…)`, …; [5.9](#59-rendering-and-image-output)). |
|
|
99
|
+
|
|
100
|
+
`SVGDocument(source)` parses SVG once (`vec_io.svg.parse`); `SVGParseError` (a
|
|
101
|
+
`ValueError`) reports input that is not usable SVG.
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
vec_io.convert("drawing.svg", "drawing.pdf")
|
|
105
|
+
png = vec_io.convert(text, format="png", dpi=192, png=vec_io.PNG(quality=90))
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 3. Command line
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
vec_io INPUT [OUTPUT] (or: python -m vec_io …)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Argument | Meaning |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `INPUT` | SVG file, or `-` for stdin. |
|
|
117
|
+
| `OUTPUT` | Output file, or `-` for stdout. Default: `INPUT` with the extension of `--format` (`.pdf`, `.png`, `.jpg`, `.jxl`, `.avif`). |
|
|
118
|
+
| `--format FORMAT` | `pdf` (default), `png`, `jpeg`, `jxl` or `avif`. |
|
|
119
|
+
| `--no-compress` | Uncompressed content streams. |
|
|
120
|
+
| `--compression LEVEL` | `fast`, `balanced` (default) or `best`: the encoder's effort. |
|
|
121
|
+
| `--font-dir DIR` | Extra font directory (repeatable). |
|
|
122
|
+
| `--no-system-fonts` | Ignore installed fonts. |
|
|
123
|
+
| `--filters` | Apply SVG filters. |
|
|
124
|
+
| `--quality N` | PDF: store rasterized content as JPEG (JPEG XL with `--jxl`) of quality N (1–100); images: the quality of the image (JPEG default 90, JPEG XL and AVIF default lossless, PNG a lossy palette). |
|
|
125
|
+
| `--jxl` | Write the PDF's images as JPEG XL ([JPEG XL](#jpeg-xl)). |
|
|
126
|
+
| `--pdf-a LEVEL` | Write PDF/A: `2b`, `2u` or `3b`. |
|
|
127
|
+
| `--simplify [DPI]` | Leave out what later content hides at DPI (default 600); `--simplify auto`: only where it pays off. |
|
|
128
|
+
| `--dpi N` | Image resolution (default 96). |
|
|
129
|
+
| `--version` | Print the version. |
|
|
130
|
+
|
|
131
|
+
Exit status 1 on unreadable input, invalid SVG, malformed fonts or write errors (including
|
|
132
|
+
a closed standard output); status 2 on invalid arguments: `--dpi` or `--simplify` not
|
|
133
|
+
positive, `--quality` outside 1–100, or an image `--format` with `--pdf-a`, `--jxl` or
|
|
134
|
+
`--no-compress`.
|
|
135
|
+
|
|
136
|
+
## 4. Document and Page
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
Document(unit="pt", title=None, author=None, subject=None, keywords=None, creator=None,
|
|
140
|
+
font_dirs=None, system_fonts=True, base_dir=None, font_db=None, filters=False,
|
|
141
|
+
color_mode="rgb", ink_limit=None, icc_profile=None, pdf_a=None, creation_date=None)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
A document records what is drawn on its pages: commands, the images and fonts as they were
|
|
145
|
+
given, and what content asks of simplification. Outputs are made from that record when
|
|
146
|
+
asked for, with their own options: `save`/`to_bytes` write the PDF, `Page.render` renders a
|
|
147
|
+
page ([5.9](#59-rendering-and-image-output)). Both can be called any number of times, in
|
|
148
|
+
any order, and drawing may go on after either; the same record written with the same
|
|
149
|
+
options gives the same bytes.
|
|
150
|
+
|
|
151
|
+
The constructor takes document-level choices. `unit` is the document's unit
|
|
152
|
+
([Conventions](#1-conventions)). The metadata arguments fill the PDF document information.
|
|
153
|
+
`base_dir` resolves relative paths given to `draw_image`, `draw_svg`, `add_font`, `attach`
|
|
154
|
+
and `icc_profile` (default: the working directory), and the references of SVGs placed from
|
|
155
|
+
text or bytes. `filters` applies to SVGs placed with `draw_svg`. `font_db` shares a
|
|
156
|
+
`FontDB` between documents. `creation_date` (a `datetime`; naive means local time) is
|
|
157
|
+
written as the creation and modification date; PDF/A files default to the current time,
|
|
158
|
+
other files have no date unless one is given.
|
|
159
|
+
|
|
160
|
+
| Print parameter | Meaning |
|
|
161
|
+
|---|---|
|
|
162
|
+
| `color_mode` | `"rgb"` (default) or `"cmyk"`. In CMYK mode every RGB color, gradient, PNG image and rasterized group is converted to DeviceCMYK; only JPEG files keep their own color space. `CMYK` and `Spot` colors are written as given in both modes. |
|
|
163
|
+
| `ink_limit` | Maximum total ink in percent (e.g. `300`) for RGB colors converted in CMYK mode. |
|
|
164
|
+
| `icc_profile` | ICC profile (path or bytes) of the printing condition, written as the output intent (`GTS_PDFX`). |
|
|
165
|
+
|
|
166
|
+
The RGB to CMYK conversion is naive, not color-managed: K = 1 − max(R, G, B), with full gray
|
|
167
|
+
component replacement. Convert with a color-managed tool when exact print colors matter,
|
|
168
|
+
and pass `CMYK` values. The content of luminance masks is written in DeviceGray in CMYK
|
|
169
|
+
mode: only its luminosity matters, and viewers disagree on the luminosity of inks.
|
|
170
|
+
|
|
171
|
+
| Method | Description |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `add_page(width=None, height=None, size="A4", landscape=False)` | New `Page`: `width` and `height` in the document's unit, or a named `size`. |
|
|
174
|
+
| `add_bookmark(title, page, y=None, parent=None)` | Outline entry for `page` (a `Page` or index), optionally at `y`. Returns a `Bookmark`, usable as `parent`. |
|
|
175
|
+
| `add_font(data_or_path, family=None, weight=None, italic=None)` | Register a font file (path or bytes) under `family` (default: its own name). |
|
|
176
|
+
| `attach(name, data, mime=None, description=None, relationship="Unspecified")` | Embed a file (bytes or a path). `relationship` is the PDF/A-3 `AFRelationship`: `Source`, `Data`, `Alternative`, `Supplement` or `Unspecified`. Not allowed in PDF/A-2. |
|
|
177
|
+
| `to_bytes(*, compress=True, compression="balanced", simplify=None, images=None, font_hinting=False)` | The PDF as `bytes` (a document without pages gets an empty A4 page), with this output's options. |
|
|
178
|
+
| `save(file, **options)` | Write the PDF to a path or binary file object; `options` as `to_bytes`. |
|
|
179
|
+
| `pages` | The pages, in order. |
|
|
180
|
+
|
|
181
|
+
| PDF option | Meaning |
|
|
182
|
+
|---|---|
|
|
183
|
+
| `compress` | Compressed PDF 1.5 (default); `False` writes a readable PDF 1.4 layout ([Output](#18-output)). |
|
|
184
|
+
| `compression` | `"fast"`, `"balanced"` (default) or `"best"`: the effort of every compressed stream, of image samples and of JPEG and JPEG XL encoding. |
|
|
185
|
+
| `simplify` | `None` (default): only what the content asks for; `True` (600 dpi) or a resolution: leave out what later content hides, and decimate lines; `"auto"`: per bulk call, where it pays off ([Simplification](#13-simplification)). |
|
|
186
|
+
| `images` | An `ImagePolicy`: how images are stored ([Image encoding](#image-encoding)). |
|
|
187
|
+
| `font_hinting` | Keep the TrueType hinting in embedded subsets (default: left out, [Fonts](#16-fonts)). |
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
ImagePolicy(quality=None, jxl=False)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`quality` (1–100): rasterized content (rasterized groups, filtered elements) is stored lossy
|
|
194
|
+
at this quality, as JPEG, or as JPEG XL with `jxl`; `None`: lossless. A group's own
|
|
195
|
+
`quality` overrides it. `jxl`: write images as JPEG XL ([JPEG XL](#jpeg-xl)). Other images
|
|
196
|
+
keep their encoding.
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
doc = vec_io.Document(unit="mm")
|
|
200
|
+
page = doc.add_page(size="A4") # 210 x 297, in mm
|
|
201
|
+
doc.save("a.pdf", compression="best", simplify=True,
|
|
202
|
+
images=vec_io.ImagePolicy(quality=85))
|
|
203
|
+
page.render(dpi=192).save("a.png") # the same record, as an image
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`Page` is the `Canvas` of a page ([5](#5-canvas)) with `render` ([5.9](#59-rendering-and-image-output)),
|
|
207
|
+
`document` and `index`. Named sizes (portrait): `A0`–`A6`, `B4`, `B5`, `Letter`, `Legal`,
|
|
208
|
+
`Tabloid`, `Ledger`, `Executive`. `landscape=True` swaps width and height.
|
|
209
|
+
|
|
210
|
+
### PDF/A
|
|
211
|
+
|
|
212
|
+
`pdf_a="2b"`, `"2u"` or `"3b"` writes an archival PDF/A file (ISO 19005-2 and -3; output
|
|
213
|
+
checked with veraPDF). Compared with a normal file:
|
|
214
|
+
|
|
215
|
+
* Every font is embedded; `embed=False` is ignored (a standard 14 name resolves to its
|
|
216
|
+
metric alias, see [Fonts](#16-fonts)). A character that no available font covers raises
|
|
217
|
+
`ValueError`, since PDF/A forbids showing `.notdef`.
|
|
218
|
+
* An XMP metadata stream repeats the document information and the `pdfaid` level;
|
|
219
|
+
creation and modification dates are written.
|
|
220
|
+
* The output intent is an sRGB profile generated by vec_io in RGB mode. CMYK mode requires
|
|
221
|
+
`icc_profile` (a CMYK profile). The `icc_profile` of a PDF/A file must be an output
|
|
222
|
+
(`prtr`) or display (`mntr`) profile, version 2 or 4.
|
|
223
|
+
* Colors that do not match the output intent are converted: `CMYK` colors become RGB in RGB
|
|
224
|
+
mode, and a `Spot` color gets an RGB alternate. In CMYK mode, RGB JPEG files are tagged
|
|
225
|
+
with an sRGB ICC color space. A CMYK JPEG file in RGB mode raises `ValueError`.
|
|
226
|
+
* Link annotations are printable; images are never interpolated.
|
|
227
|
+
* `"3b"` allows `attach()`; the files are associated with the document (`/AF`).
|
|
228
|
+
|
|
229
|
+
`"2u"` also guarantees that text extraction gives Unicode; vec_io maps every glyph, so
|
|
230
|
+
`"2u"` costs nothing over `"2b"`.
|
|
231
|
+
|
|
232
|
+
## 5. Canvas
|
|
233
|
+
|
|
234
|
+
A `Canvas` draws on one page (a `Page` is one), in the document's unit. It follows the HTML
|
|
235
|
+
canvas model: set state attributes, build a path, then paint it. Paint arguments (`fill`,
|
|
236
|
+
`stroke`) default to the state (`fill_style`, `stroke_style`) and take any paint; `None`
|
|
237
|
+
paints nothing.
|
|
238
|
+
|
|
239
|
+
### 5.1 State
|
|
240
|
+
|
|
241
|
+
`push()` saves the drawing attributes, the transform and the clip on a stack, `pop()`
|
|
242
|
+
restores them; `with page.state():` does both around a body. Inside a group, mask or
|
|
243
|
+
pattern every `push()` needs its `pop()` in the same body; an unbalanced one raises
|
|
244
|
+
`RuntimeError`. `reset_state()` restores the defaults (not the transform).
|
|
245
|
+
|
|
246
|
+
| Attribute | Default | Values |
|
|
247
|
+
|---|---|---|
|
|
248
|
+
| `fill_style` | `"black"` | a paint: color, `LinearGradient`, `RadialGradient`, `Pattern` or `None` |
|
|
249
|
+
| `stroke_style` | `None` | a paint; `stroke()` strokes in black when it is `None` |
|
|
250
|
+
| `line_width` | `1.0` | the document's unit |
|
|
251
|
+
| `line_cap` | `"butt"` | `butt`, `round`, `square` |
|
|
252
|
+
| `line_join` | `"miter"` | `miter`, `round`, `bevel` |
|
|
253
|
+
| `miter_limit` | `4.0` | |
|
|
254
|
+
| `line_dash` | `None` | sequence of lengths |
|
|
255
|
+
| `line_dash_offset` | `0.0` | |
|
|
256
|
+
| `global_alpha` | `1.0` | multiplies every paint |
|
|
257
|
+
| `font_family` | `"sans-serif"` | CSS family list |
|
|
258
|
+
| `font_size` | `12.0` | the document's unit |
|
|
259
|
+
| `font_weight` | `"normal"` | `normal`, `bold`, `1`–`1000` |
|
|
260
|
+
| `font_style` | `"normal"` | `normal`, `italic`, `oblique`, `oblique 10deg` |
|
|
261
|
+
| `font_stretch` | `"normal"` | CSS keywords (`condensed`, …) or a percentage, `50%`–`200%` |
|
|
262
|
+
| `font_variation_settings` | `""` | `'"wght" 650, "GRAD" -50'` or `{"wght": 650}` ([variable fonts](#variable-fonts)) |
|
|
263
|
+
| `font_optical_sizing` | `"auto"` | `auto` (the `opsz` axis follows the font size) or `none` |
|
|
264
|
+
| `font_kerning` | `"auto"` | `auto`/`normal`: pair kerning; `none` |
|
|
265
|
+
| `font_embed` | `True` | `False`: a standard 14 font, not embedded, when the family names one ([Fonts](#16-fonts)) |
|
|
266
|
+
| `text_align` | `"start"` | `start`/`left`, `center`/`middle`, `end`/`right` |
|
|
267
|
+
| `letter_spacing` | `0.0` | |
|
|
268
|
+
| `word_spacing` | `0.0` | |
|
|
269
|
+
| `fill_rule` | `"nonzero"` | `nonzero`, `evenodd` |
|
|
270
|
+
|
|
271
|
+
Properties: `width`, `height` (page size in the document's unit); `font`, the
|
|
272
|
+
[`Font`](#61-shaping-without-a-page-vec_iotext) of the font attributes (setting it sets
|
|
273
|
+
them).
|
|
274
|
+
|
|
275
|
+
### 5.2 Transform
|
|
276
|
+
|
|
277
|
+
| Method | |
|
|
278
|
+
|---|---|
|
|
279
|
+
| `translate(tx, ty)` | |
|
|
280
|
+
| `scale(sx, sy=None)` | |
|
|
281
|
+
| `rotate(degrees, cx=0, cy=0)` | Clockwise around `(cx, cy)`. |
|
|
282
|
+
| `skew(x_degrees=0, y_degrees=0)` | |
|
|
283
|
+
| `transform(a, b, c, d, e, f)` or `transform(matrix)` | Concatenate a matrix. |
|
|
284
|
+
|
|
285
|
+
### 5.3 Paths and painting
|
|
286
|
+
|
|
287
|
+
The current path persists until `begin_path()`.
|
|
288
|
+
|
|
289
|
+
| Method | |
|
|
290
|
+
|---|---|
|
|
291
|
+
| `begin_path()` | Start a new path. |
|
|
292
|
+
| `move_to(x, y)`, `line_to(x, y)` | |
|
|
293
|
+
| `curve_to(x1, y1, x2, y2, x, y)` | Cubic Bézier. |
|
|
294
|
+
| `quad_to(qx, qy, x, y)` | Quadratic Bézier. |
|
|
295
|
+
| `arc(cx, cy, r, start_angle, end_angle, counterclockwise=False)` | A sweep of a full turn or more draws a full circle; angles must be finite. |
|
|
296
|
+
| `close_path()` | |
|
|
297
|
+
| `rect(x, y, width, height)`, `rounded_rect(x, y, width, height, rx, ry=None)` | |
|
|
298
|
+
| `circle(cx, cy, r)`, `ellipse(cx, cy, rx, ry)` | |
|
|
299
|
+
| `add_path(path)` | Append a `Path` or SVG path data. |
|
|
300
|
+
| `fill(paint=<fill_style>, rule=None)` | Fill the path. |
|
|
301
|
+
| `stroke(paint=<stroke_style>)` | Stroke the path (black when `stroke_style` is `None`). |
|
|
302
|
+
| `fill_stroke(fill=<state>, stroke=<state>)` | Fill then stroke. |
|
|
303
|
+
| `clip(path=None, rule=None)` | Intersect the clip with `path` (a `Path` or path data; default the current path), until the enclosing `pop()`; `with page.clip(path):` clips the body only. |
|
|
304
|
+
| `fill_rect(x, y, width, height, fill=<state>)`, `stroke_rect(x, y, width, height, stroke=<state>)` | Independent of the current path. |
|
|
305
|
+
| `draw_path(path, fill=<state>, stroke=<state>, fill_rule=None, markers=None)` | Paint a `Path` or path data with the state's line settings, then its `markers`. |
|
|
306
|
+
| `draw_path_markers(path, markers, stroke_width=None)` | `PathMarker`s at the vertices of a path: one for every vertex, or `(start, mid, end)` (each may be `None`). |
|
|
307
|
+
|
|
308
|
+
The path-building methods return the canvas, so calls chain.
|
|
309
|
+
|
|
310
|
+
A `PathMarker` is SVG's `<marker>`:
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
PathMarker(draw, width=3, height=3, ref=(0, 0), orient=0, units="stroke_width",
|
|
314
|
+
view_box=None, preserve_aspect_ratio=None, clip=True)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`draw(canvas)` draws the marker at each vertex, in its own coordinates and from the default
|
|
318
|
+
drawing state (or `draw` is a recorded `Marker`, [7](#7-paints-and-recorded-content),
|
|
319
|
+
placed as a form). The marker box is `width` × `height`; its point `ref` sits on the vertex;
|
|
320
|
+
`view_box` maps the marker's coordinates into the box. `orient` is an angle in degrees,
|
|
321
|
+
`"auto"` (halfway between the directions in and out) or `"auto-start-reverse"` (the
|
|
322
|
+
same, turned around at the first vertex); `units="stroke_width"` scales the marker by
|
|
323
|
+
`stroke_width` (default `line_width`), `"user"` does not; `clip` clips to the box.
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
arrow = vec_io.PathMarker(lambda c: c.draw_path("M0 0 L10 5 L0 10 Z"), 10, 10, ref=(10, 5),
|
|
327
|
+
orient="auto")
|
|
328
|
+
page.draw_path("M10 10 L80 20", fill=None, stroke="black", markers=(None, None, arrow))
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 5.4 Text
|
|
332
|
+
|
|
333
|
+
| Method | |
|
|
334
|
+
|---|---|
|
|
335
|
+
| `fill_text(text, x, y, fill=<state>)` | Baseline at `y`, aligned by `text_align`. |
|
|
336
|
+
| `stroke_text(text, x, y, stroke=<state>)` | The outlines. |
|
|
337
|
+
| `draw_text(text, x, y, fill=<state>, stroke=<state>)` | Fill and/or stroke. |
|
|
338
|
+
| `measure_text(text)` | Advance width. |
|
|
339
|
+
| `text_extent(text)` | `(width, ascent, descent)`: the advance width and the largest ascent and descent (positive) of the fonts used; for `""`, those of the current font. |
|
|
340
|
+
| `shape_text(text)` | The `GlyphRun` the calls above draw or measure ([`vec_io.text.shape`](#61-shaping-without-a-page-vec_iotext) with the state's font, spacing and kerning). |
|
|
341
|
+
| `layout_text(content, width, …)`, `text_box(content, x, y, width, …, fill=<state>)` | Paragraphs in a box: see [Text layout](#6-text-layout). |
|
|
342
|
+
| `fill_text_on_path(text, path, offset=0, side="left", method="align", fill=<state>)`, `draw_text_on_path(…, fill=<state>, stroke=<state>)` | Text along a `Path` or path data, as SVG's `<textPath>` (below). |
|
|
343
|
+
|
|
344
|
+
`text` is a string, rich content (a list of strings and `Span` objects,
|
|
345
|
+
[Text layout](#6-text-layout)) or a `GlyphRun`, drawn on one line (newlines become spaces).
|
|
346
|
+
Spans without a color use the fill. One line is shaped as a paragraph is: fonts chosen per
|
|
347
|
+
character, pairs kerned (`font_kerning`), words shaped one by one, so `measure_text(s)` is
|
|
348
|
+
the width of `s` laid out on one line. Fonts are embedded as needed ([Fonts](#16-fonts)).
|
|
349
|
+
|
|
350
|
+
Text on a path is shaped the same way and starts at `offset` along the path (a length, or
|
|
351
|
+
a percentage of its length: `"50%"`), aligned there by `text_align`. Each glyph is drawn
|
|
352
|
+
where the path is under its middle, rotated with the path there (a kerned glyph's middle
|
|
353
|
+
counts its kerning, as in SVG); glyphs whose middle is past either end are left out.
|
|
354
|
+
`side="right"` runs the path the other way, `method="stretch"` bends the glyph outlines
|
|
355
|
+
along it (drawn as paths, not text). A span's `baseline_shift` moves its glyphs off the
|
|
356
|
+
path; underlines and links of spans are not drawn.
|
|
357
|
+
|
|
358
|
+
### 5.5 Images and SVG
|
|
359
|
+
|
|
360
|
+
| Method | |
|
|
361
|
+
|---|---|
|
|
362
|
+
| `draw_image(source, x, y, width=None, height=None, preserve_aspect_ratio="xMidYMid meet", opacity=1.0, smooth=None)` | PNG, JPEG or JPEG XL from a path, bytes or `data:` URI. Without a size, one pixel per unit; with one dimension the aspect ratio is kept. `smooth=False` keeps pixels sharp when scaled. Returns `False` if the image cannot be loaded. |
|
|
363
|
+
| `draw_svg(svg, x=0, y=0, width=None, height=None, base_dir=None, clip=True)` | Place SVG text, bytes, a path or an `SVGDocument`. Default size: its CSS pixel size. `clip` clips to its viewport. |
|
|
364
|
+
|
|
365
|
+
A `vec_io.draw` drawing draws itself: `drawing.draw(page, x, y, width=None, height=None)`
|
|
366
|
+
([11](#11-drawings-vec_iodraw)).
|
|
367
|
+
|
|
368
|
+
### 5.6 Plots
|
|
369
|
+
|
|
370
|
+
`draw_markers(...)`: see [Markers](#8-markers). `draw_lines(...)`, `draw_segments(...)`,
|
|
371
|
+
`fill_between(...)`, `draw_rects(...)`, `fill_polygons(...)`: see
|
|
372
|
+
[Lines, segments and areas](#9-lines-segments-and-areas). `draw_cells(...)`: see
|
|
373
|
+
[Cells](#10-cells). The bulk calls share one vocabulary: `fill`,
|
|
374
|
+
`stroke`, `stroke_width` (default `line_width`) and `opacity`, each one value or a column of
|
|
375
|
+
one per item.
|
|
376
|
+
|
|
377
|
+
### 5.7 Compositing
|
|
378
|
+
|
|
379
|
+
Context managers; everything drawn in the body is affected.
|
|
380
|
+
|
|
381
|
+
| Method | |
|
|
382
|
+
|---|---|
|
|
383
|
+
| `group(opacity=1.0, rasterize=False, dpi=None, quality=None, simplify=None, filter=None)` | Transparency group. `filter`: a `Filter` run on the body's pixels at `dpi` ([Filters](#14-filters)). `rasterize=True` makes the PDF hold the body as an image at `dpi` (default 300; [Rasterization](#12-rasterization)). `quality`: 1–100 for JPEG (JPEG XL with `ImagePolicy(jxl=True)`), `False` for lossless, `None` for the output's `ImagePolicy`. `simplify`: see [Simplification](#13-simplification); with it and no opacity or `rasterize`, the body is drawn directly, not as a group. Drawing attributes are restored afterwards; if the body raises, nothing is drawn. |
|
|
384
|
+
| `rasterized(dpi=None, opacity=1.0, post_process=None, region=None, quality=None, simplify=None)` | Lower-level rasterization. `post_process(surface)` edits the pixels (premultiplied float32 RGBA in `surface.pixels`); the body is then rasterized while it is drawn, and both outputs show that image. `region` `(x0, y0, x1, y1)` fixes the rasterized area. |
|
|
385
|
+
| `clip(path, rule=None)` | The body clipped to `path` ([5.3](#53-paths-and-painting)). |
|
|
386
|
+
| `masked(mask)` | The body seen through a `Mask` ([7](#7-paints-and-recorded-content)): white shows it, black hides it. Drawing attributes are restored afterwards. |
|
|
387
|
+
|
|
388
|
+
`mask()`, `pattern()` and `marker()` record content to be used elsewhere
|
|
389
|
+
([7](#7-paints-and-recorded-content)).
|
|
390
|
+
|
|
391
|
+
```python
|
|
392
|
+
with page.mask() as fade: # recorded, not drawn
|
|
393
|
+
page.fill_rect(0, 0, 100, 50, fill=vec_io.LinearGradient(0, 0, 100, 0,
|
|
394
|
+
[(0, "white"), (1, "black")]))
|
|
395
|
+
with page.masked(fade):
|
|
396
|
+
page.fill_rect(0, 0, 100, 50, fill="red")
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### 5.8 Links
|
|
400
|
+
|
|
401
|
+
`link(x, y, width, height, url=None, page=None, page_y=None)`: a clickable rectangle that
|
|
402
|
+
opens `url` or goes to `page` (a `Page` of this document, or a page index, which may name a
|
|
403
|
+
page added later), optionally at `page_y`.
|
|
404
|
+
|
|
405
|
+
### 5.9 Rendering and image output
|
|
406
|
+
|
|
407
|
+
```python
|
|
408
|
+
page.render(dpi=96, simplify=None, background=None) -> Image
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Renders the page from what it records: images from their samples (nothing is encoded to be
|
|
412
|
+
decoded again), rasterized groups as vectors, at `dpi` (96: one pixel per CSS pixel).
|
|
413
|
+
`background` is a color put under the page, or `None` (transparent). `simplify` decimates
|
|
414
|
+
lines for this resolution; nothing is left out, since rasterizing hidden content costs less
|
|
415
|
+
than finding it ([Simplification](#13-simplification)). `dpi` must be positive.
|
|
416
|
+
|
|
417
|
+
`Image`: `width`, `height`, `dpi`, `samples` (8-bit RGB, not premultiplied), `alpha` (one
|
|
418
|
+
byte per pixel, `None` when every pixel is opaque), `opaque`; `rgba()` (4 bytes per pixel),
|
|
419
|
+
`flattened(background="white")` (opaque, on that color),
|
|
420
|
+
`encode(format="png", *, png=None, jpeg=None, jxl=None, avif=None)` (bytes of `"png"`,
|
|
421
|
+
`"jpeg"`, `"jxl"` or `"avif"`) and `save(file, format=None, **options)` (a path, whose
|
|
422
|
+
extension gives the format, or a binary file object with `format`). Each format takes its
|
|
423
|
+
own options:
|
|
424
|
+
|
|
425
|
+
| Options | |
|
|
426
|
+
|---|---|
|
|
427
|
+
| `PNG(compression="balanced", quality=None)` | lossless, or a lossy palette at `quality` (1–100) |
|
|
428
|
+
| `JPEG(quality=90, compression="balanced", progressive=True)` | transparent pixels on white |
|
|
429
|
+
| `JXL(quality=None, compression="balanced")` | lossless, or lossy at `quality`; needs libjxl |
|
|
430
|
+
| `AVIF(quality=None, compression="balanced")` | lossless, or lossy at `quality` (alpha lossless); needs libavif ([AVIF](#avif)) |
|
|
431
|
+
|
|
432
|
+
`compression` is the encoder's effort: zlib level 1 or 6 or vec_io's Deflate for PNG,
|
|
433
|
+
trellis quantization for JPEG, the JPEG XL effort (5, 7, 9) and the AVIF speed (8, 6, 4).
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
image = page.render(dpi=192, background="white")
|
|
437
|
+
image.save("plot.png", png=vec_io.PNG(quality=90))
|
|
438
|
+
data = image.encode("jpeg", jpeg=vec_io.JPEG(quality=85))
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
PNG: lossless by default, in the smallest layout that keeps every pixel (gray, palette of
|
|
442
|
+
1–8 bits, color key or alpha dropped when they apply), with the row filters that compress
|
|
443
|
+
best; `compression="fast"` keeps the samples' layout, `"best"` adds a wider filter search
|
|
444
|
+
and vec_io's Deflate. `quality` (1–100) writes a palette of at most 256 colors chosen in
|
|
445
|
+
Oklab: colors covering at least 0.1% of the pixels stay exact, smooth areas are dithered,
|
|
446
|
+
flat areas and edges are not, and an image that needs more colors for the quality, or that
|
|
447
|
+
is smaller lossless, is written lossless. On plots, `quality=90` halves the file
|
|
448
|
+
(SSIM ≥ 0.999).
|
|
449
|
+
|
|
450
|
+
JPEG: Robidoux's quantization tables, the quality scale calibrated so that each value looks
|
|
451
|
+
at least as good (SSIM) as the IJG scale of the specification's tables; `"balanced"` adds
|
|
452
|
+
trellis quantization, deringing of dark edges on white and sharp 4:2:0 color, `"best"` tries
|
|
453
|
+
four progressive scan orders, `"fast"` quantizes plainly. Progressive files are about 5%
|
|
454
|
+
smaller than baseline ones. On plots and photographs, balanced JPEGs are 9–14% smaller than
|
|
455
|
+
with the specification's tables at the same quality, and 20–36% smaller at the same SSIM.
|
|
456
|
+
|
|
457
|
+
## 6. Text layout
|
|
458
|
+
|
|
459
|
+
Paragraphs broken into lines inside a box.
|
|
460
|
+
|
|
461
|
+
```python
|
|
462
|
+
layout = page.layout_text(content, width, height=None, align="start", line_height=1.2,
|
|
463
|
+
overflow="visible", breaking="greedy", kerning=True,
|
|
464
|
+
ligatures=False, paragraph_spacing=0.0, first_indent=0.0)
|
|
465
|
+
layout.draw(x, y) # top-left corner of the box
|
|
466
|
+
layout = page.text_box(content, x, y, width, height=None, fill=<state>, **options)
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
`content` is a string or a list of strings and `Span` objects. A blank line separates
|
|
470
|
+
paragraphs, `\n` forces a line break, runs of spaces collapse. Lines break at spaces; after a
|
|
471
|
+
hyphen between letters; at a soft hyphen (U+00AD, shown as `-` only where the line breaks);
|
|
472
|
+
at U+200B; between CJK characters, except before closing punctuation and small kana and
|
|
473
|
+
after opening brackets. A word wider than the line is split between glyphs.
|
|
474
|
+
|
|
475
|
+
| Option | |
|
|
476
|
+
|---|---|
|
|
477
|
+
| `align` | `start`, `center`, `end` or `justify` (aliases `left`, `middle`, `right`). `justify` stretches or shrinks the spaces of every line except the last of a paragraph and lines ending at `\n`. |
|
|
478
|
+
| `line_height` | Line height as a multiple of the largest font size on the line. |
|
|
479
|
+
| `breaking` | `greedy`: fill each line in turn. `optimal`: Knuth–Plass, minimal total looseness over the paragraph. |
|
|
480
|
+
| `height`, `overflow` | `visible` (default): place every line. `clip`: place the lines that fit in `height` (`text_box` also clips to the box). `ellipsis`: same, and end the last placed line with `…`. |
|
|
481
|
+
| `kerning` | Pair kerning: GPOS `kern` feature, else the `kern` table. |
|
|
482
|
+
| `ligatures` | Standard ligatures (GSUB `liga`); off with letter spacing. A ligature extracts as its characters. |
|
|
483
|
+
| `paragraph_spacing` | Extra space between paragraphs. |
|
|
484
|
+
| `first_indent` | Indent of the first line of each paragraph. |
|
|
485
|
+
|
|
486
|
+
The text style comes from the canvas state: `font`, `fill_style` (or `text_box`'s `fill`),
|
|
487
|
+
`letter_spacing`, `word_spacing`, `global_alpha`; `font_kerning="none"` turns kerning off.
|
|
488
|
+
[`vec_io.text.layout`](#61-shaping-without-a-page-vec_iotext) does the same without a page.
|
|
489
|
+
|
|
490
|
+
```python
|
|
491
|
+
Span(text, font_family=None, font_size=None, font_weight=None, font_style=None,
|
|
492
|
+
color=None, url=None, letter_spacing=None, word_spacing=None, underline=False,
|
|
493
|
+
baseline_shift=None, font_scale=None)
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
A run of text with its own style; `None` inherits the canvas state. `url` makes the run a
|
|
497
|
+
link; `underline` draws a line under it. `font_scale` multiplies the size. `baseline_shift`
|
|
498
|
+
raises the run: `"super"` (0.33 em of the canvas font size), `"sub"` (−0.2 em) or a number
|
|
499
|
+
of em (positive up). Exponents: `["10", Span("5", baseline_shift="super", font_scale=0.7)]`.
|
|
500
|
+
|
|
501
|
+
| `TextLayout` member | |
|
|
502
|
+
|---|---|
|
|
503
|
+
| `draw(x, y)` | Draw with the box's top-left corner at `(x, y)`. Can be called several times. |
|
|
504
|
+
| `width`, `height` | Widest line; height of the placed lines. |
|
|
505
|
+
| `lines`, `baselines` | Number of lines; baseline of each, from the top of the box. |
|
|
506
|
+
| `overflow`, `remainder` | Whether text did not fit in `height`; that text as a list of `Span`, to lay out in the next column or page. |
|
|
507
|
+
| `line_text(k)` | Text of line `k`. |
|
|
508
|
+
|
|
509
|
+
Kerning and justification are written as `TJ` adjustments, so the text stays selectable and
|
|
510
|
+
searchable. Speed: 1,800 words in about 0.02 s (greedy) or 0.035 s (optimal), fonts already
|
|
511
|
+
loaded. Not supported: bidirectional text, complex-script shaping, hyphenation
|
|
512
|
+
dictionaries.
|
|
513
|
+
|
|
514
|
+
### 6.1 Shaping without a page: `vec_io.text`
|
|
515
|
+
|
|
516
|
+
The text of every path (Canvas, SVG text, paragraphs) is shaped by `vec_io.text`, whose
|
|
517
|
+
functions need only a font database and a font description, so text can be measured before
|
|
518
|
+
any page exists.
|
|
519
|
+
|
|
520
|
+
```python
|
|
521
|
+
from vec_io import text
|
|
522
|
+
|
|
523
|
+
font = text.Font("Inter, sans-serif", size=10, weight="bold")
|
|
524
|
+
run = text.shape("AVATAR", font) # GlyphRun; db=None: the default FontDB
|
|
525
|
+
width, ascent, descent = text.measure("AVATAR", font)
|
|
526
|
+
para = text.layout(content, font, width=200, align="justify") # Paragraphs
|
|
527
|
+
page.draw_text(run, x, y) # any canvas, any document
|
|
528
|
+
para.draw(page, x, y, fill="black")
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
| Name | |
|
|
532
|
+
|---|---|
|
|
533
|
+
| `Font(family="sans-serif", size=12, weight=400, style="normal", stretch="normal", variations=None, optical_sizing="auto", embed=True, unit=1)` | A font description: CSS values as for the canvas attributes; `embed=False` uses a standard 14 font when the family names one; `unit`: CSS px per unit of `size` (a number or a unit name, for `opsz`). Immutable and hashable; `replace(**changes)`. |
|
|
534
|
+
| `shape(content, font, db=None, *, kerning=True, ligatures=False, letter_spacing=0, word_spacing=0)` | `GlyphRun` of a string or rich content on one line. |
|
|
535
|
+
| `measure(content, font, db=None, **options)` | `Extent(width, ascent, descent)` of that run. |
|
|
536
|
+
| `layout(content, font, width, height=None, db=None, *, align, line_height, overflow, breaking, kerning, ligatures, paragraph_spacing, first_indent, letter_spacing, word_spacing)` | `Paragraphs`: the [layout](#6-text-layout) options; `draw(canvas, x, y, fill=None)`, `width`, `height`, `lines`, `baselines`, `overflow`, `remainder`, `line_text(k)`, `line_cells()` (placed `Line`s). |
|
|
537
|
+
| `GlyphRun` | `text`, `width`, `ascent`, `descent`, `extent`; `segments` (`Segment`: one face and style, with `gids`, `texts`, `chars`, `advances`, `adjusts`); `glyphs` (`Glyph`: `face`, `gid`, `advance`, `adjust`, `text`, `char`, `style`, `synth`). |
|
|
538
|
+
| `default_db()` | The process-wide `FontDB` (installed fonts and `VEC_IO_FONT_DIRS`). |
|
|
539
|
+
| `NoFontError` | `ValueError`: no outline font at all for the text. |
|
|
540
|
+
|
|
541
|
+
Lengths are in the unit of `size` (on a page: `page.font` carries the document's unit).
|
|
542
|
+
Runs of plain strings are memoized in the `FontDB`, per text, description and options (up to
|
|
543
|
+
20,000; adding fonts empties the memo): measure a label many times and draw it once for the
|
|
544
|
+
price of one shaping. Words are shaped one by one and spaces apart (no kerning around a
|
|
545
|
+
space). Speed: shaping a new 5-character label about 7 µs, a memoized one under 1 µs.
|
|
546
|
+
|
|
547
|
+
## 7. Paints and recorded content
|
|
548
|
+
|
|
549
|
+
```python
|
|
550
|
+
LinearGradient(x1, y1, x2, y2, stops=(), spread="pad", transform=None)
|
|
551
|
+
RadialGradient(cx, cy, r, stops=(), fx=None, fy=None, fr=0.0, spread="pad", transform=None)
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
* `stops`: `(offset, color)` or `(offset, color, opacity)`; `offset` in [0, 1].
|
|
555
|
+
`add_stop(offset, color, opacity=1.0)` appends one and returns the gradient.
|
|
556
|
+
* `spread`: `pad`, `reflect` or `repeat`.
|
|
557
|
+
* `transform`: a `Matrix` applied to the paint geometry.
|
|
558
|
+
* `RadialGradient`: `(fx, fy)` is the focal point, `fr` its radius.
|
|
559
|
+
|
|
560
|
+
Gradients with transparent stops are drawn with soft masks.
|
|
561
|
+
|
|
562
|
+
Content recorded on a canvas, in its current user space, to be used elsewhere; nothing is
|
|
563
|
+
drawn on the page while it is recorded, and its body starts from the default drawing state:
|
|
564
|
+
|
|
565
|
+
| With | Records | Used as |
|
|
566
|
+
|---|---|---|
|
|
567
|
+
| `with page.pattern(width, height, x=0, y=0, transform=None) as pattern:` | a `Pattern`: a `width` × `height` tile, its origin at the top-left corner; `x`, `y` offset the tiling, `transform` transforms it | a paint: `fill_style = pattern`, `fill=pattern` |
|
|
568
|
+
| `with page.mask() as mask:` | a `Mask`: what the body draws is the luminance (white keeps, black hides) | `with page.masked(mask):` |
|
|
569
|
+
| `with page.marker() as marker:` | a `Marker`: one marker centered on the origin | `draw_markers(xs, ys, marker)`; `PathMarker(marker, …)` ([5.3](#53-paths-and-painting)) |
|
|
570
|
+
|
|
571
|
+
## 8. Markers
|
|
572
|
+
|
|
573
|
+
```python
|
|
574
|
+
Canvas.draw_markers(xs, ys, marker=None, size=6.0, fill=<state>, stroke=<state>,
|
|
575
|
+
stroke_width=None, opacity=1.0, simplify=None, keep=False)
|
|
576
|
+
draw.Markers(xs, ys, shape=None, size=6.0, **style)
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Draws a marker at every `(xs[i], ys[i])`. `xs`, `ys` are sequences or buffers.
|
|
580
|
+
|
|
581
|
+
| Parameter | One value or one per point | Meaning |
|
|
582
|
+
|---|---|---|
|
|
583
|
+
| `marker` / `shape` | yes | A `Path` or SVG path data in a unit box centered on the origin; `None` (default) is the unit circle. Shapes are not named here (plot_io names them): a square is `"M-0.5 -0.5 H0.5 V0.5 H-0.5 Z"`. Equal paths are stored once. One `Marker` ([7](#7-paints-and-recorded-content)) is drawn as recorded (uniform markers only). |
|
|
584
|
+
| `size` | yes | Marker width: the unit box is scaled by it. |
|
|
585
|
+
| `fill`, `stroke` | yes | Colors. Per point: a list of colors or an `(n, 3)`/`(n, 4)` buffer of numbers in [0, 1]; `None` paints nothing. One paint for all may be a gradient or pattern, when nothing else varies. |
|
|
586
|
+
| `stroke_width` | yes | Stroke width; default `line_width`. |
|
|
587
|
+
| `opacity` | yes | Multiplies the fill and stroke alpha. |
|
|
588
|
+
| `simplify`, `keep` | — | `True` (600 dpi) or a resolution: leave out the markers that later ones hide; `"auto"`: only where it pays off; `keep=True` never ([Simplification](#13-simplification)). |
|
|
589
|
+
|
|
590
|
+
Output:
|
|
591
|
+
|
|
592
|
+
* PDF: each shape is stored once (per paint kind) and placed at every point.
|
|
593
|
+
* Rasterized: each marker is drawn as its vector version, in order, placed to 1/32 pixel. A
|
|
594
|
+
shape is rendered once per size, line style and offset and reused with any colors and
|
|
595
|
+
opacities. Timings in [Rasterization](#12-rasterization).
|
|
596
|
+
|
|
597
|
+
## 9. Lines, segments and areas
|
|
598
|
+
|
|
599
|
+
```python
|
|
600
|
+
Canvas.draw_lines(xs, ys, stroke=<stroke_style>, stroke_width=None, dash=None, opacity=1.0,
|
|
601
|
+
cap=None, join=None, simplify=None, interpolation="linear", missing="break",
|
|
602
|
+
keep=False)
|
|
603
|
+
Canvas.draw_segments(x0, y0, x1, y1, stroke=<stroke_style>, stroke_width=None, opacity=1.0,
|
|
604
|
+
cap=None, simplify=None, keep=False)
|
|
605
|
+
Canvas.fill_between(xs, y1, y2=0.0, fill=<fill_style>, stroke=None, stroke_width=None,
|
|
606
|
+
opacity=1.0, simplify=None, interpolation="linear", missing="break",
|
|
607
|
+
keep=False)
|
|
608
|
+
|
|
609
|
+
draw.Lines(xs, ys, interpolation="linear", missing="break", **style)
|
|
610
|
+
draw.Segments(x0, y0, x1, y1, **style)
|
|
611
|
+
draw.Area(xs, y1, y2=0.0, interpolation="linear", missing="break", **style)
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
| Function | Draws | One value or one per… |
|
|
615
|
+
|---|---|---|
|
|
616
|
+
| `draw_lines` | One polyline, or one per row of `ys` (a list of sequences or a 2-D buffer); `xs` is shared or one per series. | series: `stroke`, `stroke_width`, `dash`, `opacity`, `interpolation` |
|
|
617
|
+
| `draw_segments` | Independent segments `(x0[i], y0[i]) → (x1[i], y1[i])`. | segment: `stroke`, `stroke_width`, `opacity` |
|
|
618
|
+
| `fill_between` | The area between `y1` and `y2` (a sequence or a constant), optionally outlined with `stroke`. | — |
|
|
619
|
+
|
|
620
|
+
`stroke` defaults to `stroke_style`, black when it is `None`; `stroke_width` to
|
|
621
|
+
`line_width`.
|
|
622
|
+
|
|
623
|
+
* NaN or `None` coordinates break a line or split an area (`missing="break"`): each run of
|
|
624
|
+
finite points is drawn on its own. With `missing="skip"`, they are left out and the other
|
|
625
|
+
points joined. Segments and markers with one are skipped. For an area, a point is missing
|
|
626
|
+
when x, y1 or y2 is.
|
|
627
|
+
* `interpolation` between the points:
|
|
628
|
+
|
|
629
|
+
| Value | Drawn as |
|
|
630
|
+
|---|---|
|
|
631
|
+
| `"linear"` | straight segments |
|
|
632
|
+
| `"step-pre"`, `"step-post"`, `"step-mid"` | horizontal and vertical segments; y changes at the start, end or middle of each interval |
|
|
633
|
+
| `"monotone"` | Fritsch–Carlson curve: never overshoots between two points; where x is not strictly monotonic within a run, the spline |
|
|
634
|
+
| `"spline"` | centripetal Catmull–Rom curve (α = 0.5): no cusps or loops |
|
|
635
|
+
|
|
636
|
+
Curves pass through every point, one Bézier per interval, with one-sided tangents at the
|
|
637
|
+
ends of each run, computed in C. An area interpolates both edges alike; a constant `y2`
|
|
638
|
+
stays straight. Only linear series are decimated by `simplify`. A coordinate of
|
|
639
|
+
`draw_segments` given as a number applies to every segment.
|
|
640
|
+
* `cap`, `join` and `dash` (with `line_dash_offset`) default to the canvas state.
|
|
641
|
+
* `simplify=True` (600 dpi) or a dpi: lines and areas whose x values increase keep only the
|
|
642
|
+
first, last, lowest and highest point of each device-pixel column at that resolution (M4
|
|
643
|
+
decimation), made by each output for its own resolution. Lines, areas and segments that
|
|
644
|
+
later content hides are left out ([Simplification](#13-simplification)). `keep=True` keeps
|
|
645
|
+
every point.
|
|
646
|
+
|
|
647
|
+
Output: PDF coordinates are written in bulk by the C library, to 1/1000 pt
|
|
648
|
+
([Output](#18-output)). A 1M-point line takes 0.10 s and 4.7 MB; with `simplify=True`,
|
|
649
|
+
0.05 s and 0.1 MB. Rasterized: drawn by the general rasterizer, with the line's own joins,
|
|
650
|
+
caps and dashes.
|
|
651
|
+
|
|
652
|
+
| 300 dpi, 400 × 300 pt (including the vector output) | Time |
|
|
653
|
+
|---|---|
|
|
654
|
+
| `draw_lines`, 1M points | 0.21 s |
|
|
655
|
+
| `fill_between`, 1M points | 0.14 s |
|
|
656
|
+
| `draw_segments`, 100k segments with their own colors | 0.23 s |
|
|
657
|
+
|
|
658
|
+
### Rectangles and polygons
|
|
659
|
+
|
|
660
|
+
```python
|
|
661
|
+
Canvas.draw_rects(x, y, width, height, fill=<fill_style>, stroke=<stroke_style>,
|
|
662
|
+
stroke_width=None, opacity=1.0, simplify=None, keep=False)
|
|
663
|
+
Canvas.fill_polygons(paths, fill=<fill_style>, stroke=None, stroke_width=None,
|
|
664
|
+
opacity=1.0, fill_rule="nonzero", simplify=None, keep=False)
|
|
665
|
+
|
|
666
|
+
draw.Rects(x, y, width, height, **style)
|
|
667
|
+
draw.Polygons(paths, **style)
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
Many bars, cells, violins, filled contours or wedges in one call.
|
|
671
|
+
|
|
672
|
+
* **Rectangles:** `x`, `y`, `width` and `height` are sequences or numbers (a number applies
|
|
673
|
+
to every rectangle). A rectangle with a NaN or `None` value is skipped.
|
|
674
|
+
* **Polygons:** `paths` is a list of `Path` objects or SVG path data (curves allowed), or
|
|
675
|
+
`(xs, ys, ring_starts[, path_starts])` arrays for large inputs: ring k has the points
|
|
676
|
+
`ring_starts[k]` to `ring_starts[k + 1] - 1` (`ring_starts` ends with the total), path p
|
|
677
|
+
the rings `path_starts[p]` to `path_starts[p + 1] - 1` (default: all rings in one path).
|
|
678
|
+
The rings of a path are filled together with `fill_rule`, so holes work.
|
|
679
|
+
* **Paints:** `fill`, `stroke`, `stroke_width` and `opacity` take one value or one per item.
|
|
680
|
+
One paint for all items may be a gradient or a pattern; values per item need flat colors.
|
|
681
|
+
* **PDF:** with one paint, the geometry is written in bulk and painted once. With values per
|
|
682
|
+
item, one block sets only what changes from item to item.
|
|
683
|
+
* **`simplify`:** culling only ([Simplification](#13-simplification)).
|
|
684
|
+
|
|
685
|
+
10,000 rectangles take 0.001 s with one fill and 0.017 s with a fill each.
|
|
686
|
+
|
|
687
|
+
## 10. Cells
|
|
688
|
+
|
|
689
|
+
```python
|
|
690
|
+
Canvas.draw_cells(x_edges, y_edges, fill, palette=None, *, bounds=None, bad=None,
|
|
691
|
+
mode="image", opacity=1.0)
|
|
692
|
+
draw.Cells(x_edges, y_edges, fill, palette=None, bounds=None, bad=None, mode="image",
|
|
693
|
+
**keywords)
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
A grid of rectangles, each with its own color: heatmaps, 2-D histograms. vec_io has no
|
|
697
|
+
color maps: the caller turns values into colors, palette indices or positions (plot_io does
|
|
698
|
+
it in its own C library).
|
|
699
|
+
|
|
700
|
+
| Parameter | Meaning |
|
|
701
|
+
|---|---|
|
|
702
|
+
| `x_edges`, `y_edges` | Cell edges: columns + 1 and rows + 1 values, finite, strictly increasing or decreasing. Uneven edges need `cells` or `smooth` mode. |
|
|
703
|
+
| `fill` | The cells row by row, row 0 between `y_edges[0]` and `y_edges[1]`. Without `palette`: colors, an `(n, 3)` or `(n, 4)` buffer (or `(rows, columns, 3 or 4)`) of floats in [0, 1], or a sequence of n colors. With `palette`: palette indices, or in `smooth` mode positions in [0, 1] along the palette. Any shape with n values in all. |
|
|
704
|
+
| `palette` | A list of colors, or an `(m, 3)` or `(m, 4)` buffer. As indices: at most 256 colors (255 when some cells are bad). In `smooth` mode: a color table sampled evenly over [0, 1], interpolated linearly between entries. |
|
|
705
|
+
| `bounds` | `smooth` mode: `len(palette) - 1` increasing positions in [0, 1] between the palette's colors; each color is then constant over its interval, so steps stay sharp. |
|
|
706
|
+
| `bad` | Color of bad cells: a `None` color or a NaN component, a NaN or negative index, a NaN position. Default: none (the cell stays empty). |
|
|
707
|
+
| `mode` | `image` (one sharp image pixel per cell, `/Interpolate false`; even edges only), `cells` (rectangles, one per run of equal cells, grouped by color) or `smooth` (a type 5 mesh shading over the cell centres, constant to the outer edges). |
|
|
708
|
+
| `opacity` | Opacity of the whole grid. |
|
|
709
|
+
|
|
710
|
+
* **Colors.** Cells are drawn as 8-bit RGB with alpha. CMYK and spot colors keep their space
|
|
711
|
+
as palette entries in `cells` mode and as `bad` with a palette in `cells` and `smooth`
|
|
712
|
+
modes; elsewhere they are drawn as their RGB approximation.
|
|
713
|
+
* **Smooth mode.** With a palette, positions are interpolated between cell centres and then
|
|
714
|
+
colored: the palette is a PDF function (type 0, sampled; with `bounds`, type 3 stitching
|
|
715
|
+
constant pieces), so a step table stays sharp. Palette alpha is not used. Bad cells are
|
|
716
|
+
clipped out, their neighbours' mean filling the mesh, and painted with `bad`. Without a
|
|
717
|
+
palette the colors themselves are interpolated; they must be opaque (bad cells too).
|
|
718
|
+
* **Cells mode.** Opaque cells reach 2 % of their size into their right and lower
|
|
719
|
+
neighbours, so viewers show no seams.
|
|
720
|
+
* **PDF.** `image` mode writes one Indexed image (palette indices) or an RGB image with a
|
|
721
|
+
soft mask for translucent cells. A 500 × 500 grid takes 0.01 s and 73 KB as an image,
|
|
722
|
+
0.05 s and 631 KB as cells, 0.06 s and 1.3 MB smooth (`tools/bench.py cells`).
|
|
723
|
+
|
|
724
|
+
## 11. Drawings: `vec_io.draw`
|
|
725
|
+
|
|
726
|
+
A drawing is a tree of elements (drawsvg-style names) that draws itself onto a canvas: a
|
|
727
|
+
page of its own document (`Drawing.document`), any page or canvas (`Drawing.draw`), or SVG
|
|
728
|
+
written from what it draws (`Drawing.to_svg`, through the scene's SVG writer,
|
|
729
|
+
[18](#18-output)).
|
|
730
|
+
|
|
731
|
+
```python
|
|
732
|
+
import vec_io.draw as draw
|
|
733
|
+
|
|
734
|
+
d = draw.Drawing(400, 200, font_family="Helvetica, Arial, sans-serif")
|
|
735
|
+
d.append(draw.Rect(10, 10, 380, 180, rx=16,
|
|
736
|
+
fill=vec_io.LinearGradient(0, 0, 400, 0, [(0, "orange"), (1, "purple")])))
|
|
737
|
+
d.append(draw.Text("Hello", 48, 200, 120, text_anchor="middle", fill="white"))
|
|
738
|
+
d.save("hello.pdf"); d.save("hello.svg"); d.save("hello.png", dpi=192)
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### 11.1 Keywords
|
|
742
|
+
|
|
743
|
+
Style keywords are inherited by the children of a `Group`, as in SVG:
|
|
744
|
+
|
|
745
|
+
| Keyword | Canvas state |
|
|
746
|
+
|---|---|
|
|
747
|
+
| `fill`, `stroke` | `fill_style`, `stroke_style`: a paint, a `draw.Pattern`, or `None` (no paint); defaults: black fill, no stroke |
|
|
748
|
+
| `fill_opacity`, `stroke_opacity` | multiply the alpha of a fill or stroke color |
|
|
749
|
+
| `stroke_width`, `stroke_linecap`, `stroke_linejoin`, `stroke_miterlimit`, `stroke_dasharray`, `stroke_dashoffset` | `line_width`, `line_cap`, `line_join`, `miter_limit`, `line_dash`, `line_dash_offset` |
|
|
750
|
+
| `fill_rule` | `fill_rule` |
|
|
751
|
+
| `font_family`, `font_size`, `font_weight`, `font_style`, `font_stretch`, `letter_spacing`, `word_spacing`, `text_anchor` | the font attributes; `text_anchor` (`start`, `middle`, `end`) is `text_align` |
|
|
752
|
+
|
|
753
|
+
Element keywords (not inherited): `transform` (a `Matrix` or SVG transform text), `opacity`
|
|
754
|
+
(the element as one transparency group, folded into the paint when it has one), `clip_path`
|
|
755
|
+
(a shape element, `ClipPath`, `Path` or path data), `mask` (an element or `Mask` drawing the
|
|
756
|
+
luminance), `filter` (a `Filter`, run at `rasterize`'s dpi or 300), `rasterize` (`True`
|
|
757
|
+
or a dpi), `quality` (of a rasterized element), `simplify`. `Path`, `Line`, `Polyline` and
|
|
758
|
+
`Polygon` also take `marker_start`, `marker_mid`, `marker_end` and `markers` (all three):
|
|
759
|
+
`Marker` elements. Other keywords raise `TypeError`. `set(**keywords)` changes them (`None` inherits again,
|
|
760
|
+
except for `fill` and `stroke`, where it paints nothing).
|
|
761
|
+
|
|
762
|
+
### 11.2 Drawing
|
|
763
|
+
|
|
764
|
+
```python
|
|
765
|
+
Drawing(width, height, *children, view_box=None, unit="px", **style)
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
`width` × `height` in `unit`; `view_box=(x, y, w, h)` maps that box into it (aspect kept,
|
|
769
|
+
centered).
|
|
770
|
+
|
|
771
|
+
| Method | |
|
|
772
|
+
|---|---|
|
|
773
|
+
| `append(*elements)`, `extend(elements)`, `group(**keywords)` | Children; `group` is a context manager appending and yielding a `Group` (also on `Group`). |
|
|
774
|
+
| `draw(canvas, x=0, y=0, width=None, height=None, clip=True)` | Draw onto a canvas at `(x, y)`, scaled to `width` × `height` (default its own size, in the canvas's unit), clipped to its box. |
|
|
775
|
+
| `document(**options)` | A new `Document` in the drawing's unit with one page holding the drawing; `options`: of the `Document`. |
|
|
776
|
+
| `render(dpi=96, simplify=None, background=None, **options)` | The drawing as an `Image` ([5.9](#59-rendering-and-image-output)). |
|
|
777
|
+
| `to_svg(**options)` | SVG source of what the drawing draws. |
|
|
778
|
+
| `save(file, format=None, **options)` | PDF, SVG, PNG, JPEG, JPEG XL or AVIF by `format` or the extension; `options`: of the `Document`, and of the output (`compress`, `compression`, `simplify`, `images`, `font_hinting` for PDF; `dpi`, `simplify`, `background`, `png`, `jpeg`, `jxl`, `avif` for images). |
|
|
779
|
+
|
|
780
|
+
### 11.3 Elements
|
|
781
|
+
|
|
782
|
+
| Element | Signature |
|
|
783
|
+
|---|---|
|
|
784
|
+
| `Group` | `Group(*children, **keywords)` |
|
|
785
|
+
| `Rect` | `Rect(x, y, width, height, rx=None, ry=None, **keywords)` |
|
|
786
|
+
| `Circle`, `Ellipse` | `Circle(cx, cy, r, …)`, `Ellipse(cx, cy, rx, ry, …)` |
|
|
787
|
+
| `Line` | `Line(x1, y1, x2, y2, …)` (stroked only) |
|
|
788
|
+
| `Polyline`, `Polygon` | `Polyline(points, …)`; `points` as `[(x, y), …]` or flat numbers |
|
|
789
|
+
| `Path` | `Path(d="", …)`: SVG path data or a `vec_io.Path`, with the builder methods `M m L l H h V v C c S s Q q T t A a Z` (SVG semantics); `d` is the path data, `outline()` the `vec_io.Path` |
|
|
790
|
+
| `Text` | `Text(text, font_size, x=0, y=0, line_height=1.2, path=None, offset=0, side="left", method="align", …)`: a string (`\n` stacks lines `line_height` em apart) or rich content (strings, `TSpan`, `Span`) on one line; with `path` (a shape element, `Path` or path data), along it ([5.4](#54-text)) |
|
|
791
|
+
| `TSpan` | `TSpan(text, font_family=…, font_size=…, font_weight=…, font_style=…, fill=…, underline=…, baseline_shift=…, url=…)`: a styled run (a `Span`) |
|
|
792
|
+
| `TextBox` | `TextBox(content, x, y, width, height=None, …)`: wrapped text, box top-left at `(x, y)`; the [layout](#6-text-layout) options as keywords |
|
|
793
|
+
| `Image` | `Image(x, y, width, height, path=None, data=None, preserve_aspect_ratio="xMidYMid meet", smooth=None, …)`: PNG, JPEG or JPEG XL |
|
|
794
|
+
| `SVG` | `SVG(source, x=0, y=0, width=None, height=None, base_dir=None, …)`: an SVG document drawn in the box, with everything SVG has, CSS style sheets included (filters when enabled) |
|
|
795
|
+
| `Use` | `Use(element, x=0, y=0, …)`: the element drawn again, moved; its keywords apply where the element sets none |
|
|
796
|
+
| `Markers`, `Lines`, `Segments`, `Area`, `Rects`, `Polygons` | the bulk calls ([8](#8-markers), [9](#9-lines-segments-and-areas)); `fill`, `stroke`, `stroke_width` and `opacity` may be columns |
|
|
797
|
+
| `Cells` | `Cells(x_edges, y_edges, fill, palette=None, bounds=None, bad=None, mode="image", …)`: [cells](#10-cells) |
|
|
798
|
+
| `Pattern` | `Pattern(width, height, *children, x=0, y=0, transform=None)`: a tile drawn by its children, as `fill=` or `stroke=` |
|
|
799
|
+
| `ClipPath` | `ClipPath(*shapes)`: the union of their outlines, as `clip_path=` |
|
|
800
|
+
| `Mask` | `Mask(*children)`: a luminance mask, as `mask=` |
|
|
801
|
+
| `Marker` | `Marker(*children, width=3, height=3, ref=(0, 0), orient=0, units="stroke_width", view_box=None, preserve_aspect_ratio=None, clip=True, **style)`: a marker drawn by its children (with its own style keywords, not the path's) at the vertices of paths, as `marker_start=` … ([`PathMarker`](#53-paths-and-painting)) |
|
|
802
|
+
|
|
803
|
+
Gradients are vec_io's (`vec_io.LinearGradient`, `RadialGradient`, also importable from
|
|
804
|
+
`vec_io.draw`), as are `CMYK` and `Spot`.
|
|
805
|
+
|
|
806
|
+
## 12. Rasterization
|
|
807
|
+
|
|
808
|
+
Rasterizes a group into an image aligned with the page's pixel grid,
|
|
809
|
+
trimmed to its visible extent and clipped to the page. In CMYK documents the
|
|
810
|
+
group is rendered in CMYK.
|
|
811
|
+
|
|
812
|
+
| API | |
|
|
813
|
+
|---|---|
|
|
814
|
+
| `Canvas.group(rasterize=True, dpi=300)` | Canvas |
|
|
815
|
+
| `Group(..., rasterize=True)` or `rasterize=150` | `vec_io.draw` |
|
|
816
|
+
| `<g data-rasterize="true">` or `data-rasterize="150"` | SVG |
|
|
817
|
+
|
|
818
|
+
The group is recorded as it is drawn; each output makes what it needs. A PDF rasterizes
|
|
819
|
+
the body when it is written (once per `simplify` of the output, kept for later saves) and
|
|
820
|
+
holds the image; a group that paints nothing writes nothing. Image output
|
|
821
|
+
([`render`](#59-rendering-and-image-output)) draws the body as vectors, clipped to
|
|
822
|
+
`region`: the page is rasterized anyway, at its own resolution, so the result is sharper
|
|
823
|
+
and faster than an image rasterized first. Filtered elements (`post_process`) are
|
|
824
|
+
rasterized while they are drawn, and both outputs show that image.
|
|
825
|
+
|
|
826
|
+
### Image encoding
|
|
827
|
+
|
|
828
|
+
Images are kept as they were given (PNG rows a PDF can hold as they are, JPEG files,
|
|
829
|
+
samples) and encoded when a PDF is written, with that output's `compression` and
|
|
830
|
+
`ImagePolicy`; each image keeps its last encoding, so saving again with the same options
|
|
831
|
+
encodes nothing. Image output uses the samples.
|
|
832
|
+
|
|
833
|
+
| Setting | Stored as |
|
|
834
|
+
|---|---|
|
|
835
|
+
| default | lossless (`/FlateDecode`): the smallest of 8-bit RGB (or CMYK), gray and `/Indexed` with 1–8 bits, with the PNG row filters that compress best; an alpha of only 0 and 255 is a 1-bit soft mask (`compression="fast"`: 8-bit RGB, filtered when it helps) |
|
|
836
|
+
| `quality=1…100` | baseline JPEG (`/DCTDecode`) as [JPEG output](#59-rendering-and-image-output) at the output's `compression`; 4:4:4 chroma from quality 90, 4:2:0 below |
|
|
837
|
+
| `ImagePolicy(jxl=True)` | JPEG XL (`/JXLDecode`), lossless, or lossy at `quality`, with the alpha inside |
|
|
838
|
+
|
|
839
|
+
Transparency is always stored losslessly, as a separate soft mask; under
|
|
840
|
+
transparent pixels the JPEG holds neighboring colors, so edges do not ring.
|
|
841
|
+
JPEG suits photographic or smooth content (blurs, gradients, dense point
|
|
842
|
+
clouds); lossless suits flat colors and sharp edges.
|
|
843
|
+
|
|
844
|
+
`quality` is set per group (`Canvas.group`, `draw` keyword, SVG
|
|
845
|
+
`data-quality` on the element or an ancestor; `"false"` forces
|
|
846
|
+
lossless) or per output (`ImagePolicy(quality=…)`, `--quality`). It
|
|
847
|
+
also applies to filtered elements.
|
|
848
|
+
|
|
849
|
+
100,000 markers with per-point colors, rasterized at 300 dpi on a
|
|
850
|
+
400 × 300 pt area:
|
|
851
|
+
|
|
852
|
+
| Encoding | Time | PDF size |
|
|
853
|
+
|---|---|---|
|
|
854
|
+
| lossless | 0.28 s | 4.9 MB |
|
|
855
|
+
| JPEG 95 | 0.45 s | 3.4 MB |
|
|
856
|
+
| JPEG 85 | 0.31 s | 1.5 MB |
|
|
857
|
+
| JPEG 70 | 0.28 s | 1.3 MB |
|
|
858
|
+
|
|
859
|
+
The codecs are public in `vec_io.image.codecs`: `jpeg_encode(samples, width,
|
|
860
|
+
height, components, quality=90, progressive=True, compression="balanced")`,
|
|
861
|
+
`jpeg_decode(data)`, `png_encode(samples, width, height, components, dpi=None,
|
|
862
|
+
compression="balanced", quality=None, colors=None)` (`colors`: the most palette
|
|
863
|
+
entries), `png_decode(data)`,
|
|
864
|
+
`jxl_encode(samples, width, height, components, quality=None)`,
|
|
865
|
+
`jxl_decode(data)` and `avif_encode(samples, width, height, components,
|
|
866
|
+
quality=None)`, on 8-bit interleaved samples. An A4 page at 300 dpi encodes as JPEG 85 in 0.21 s
|
|
867
|
+
(balanced).
|
|
868
|
+
|
|
869
|
+
### JPEG XL
|
|
870
|
+
|
|
871
|
+
JPEG XL goes through the system's libjxl, 0.9 or later, loaded on first use
|
|
872
|
+
(`VEC_IO_LIBJXL` gives its path when it is not found by name);
|
|
873
|
+
`vec_io.image.codecs.jxl_available()` tells whether it was found.
|
|
874
|
+
|
|
875
|
+
**Reading.** JPEG XL images (`draw_image`, `<image>`, `feImage`,
|
|
876
|
+
`draw.Image`) are decoded with libjxl; without it they are skipped like any
|
|
877
|
+
unreadable image (`draw_image` returns `False`). In a document without
|
|
878
|
+
`jxl`, a recompressed JPEG (`cjxl photo.jpg`) is rebuilt and embedded as
|
|
879
|
+
that JPEG; other images are embedded as 8-bit samples (Flate) with alpha as
|
|
880
|
+
an SMask. The first frame of an animation is used. Lossy images are decoded
|
|
881
|
+
to sRGB; lossless ones keep their color space, written as `ICCBased` when it
|
|
882
|
+
is not sRGB.
|
|
883
|
+
|
|
884
|
+
**Writing.** With `ImagePolicy(jxl=True)` (`save`/`to_bytes`, `convert`, `--jxl`) every image is written as JPEG XL, as PDFium's JPEG XL support reads it: the
|
|
885
|
+
JPEG XL file as the stream, `/Filter /JXLDecode`, `/ColorSpace /DeviceGray`
|
|
886
|
+
or `/DeviceRGB` (`ICCBased` for other color spaces), `/BitsPerComponent 8`,
|
|
887
|
+
alpha inside the JPEG XL rather than in an SMask:
|
|
888
|
+
|
|
889
|
+
* rasterized content: lossless, or lossy at `quality`;
|
|
890
|
+
* PNG images, cells drawn as images and other samples: lossless;
|
|
891
|
+
* JPEG files: recompressed without loss (CMYK JPEGs stay JPEG);
|
|
892
|
+
* JPEG XL files: as they are.
|
|
893
|
+
|
|
894
|
+
ISO 32000 has no JPEG XL filter yet; `/JXLDecode` is the name PDFium's
|
|
895
|
+
implementation uses, and viewers without it show nothing for these images.
|
|
896
|
+
`jxl` cannot be combined with `pdf_a` or `color_mode="cmyk"`, and raises
|
|
897
|
+
`ValueError` when the PDF is written without libjxl. The encoder effort follows `compression`: 5
|
|
898
|
+
(`fast`), 7 (`balanced`) or 9 (`best`).
|
|
899
|
+
|
|
900
|
+
JPEG XL pays off most on photographs and flat graphics: a recompressed
|
|
901
|
+
JPEG file is about 25% smaller, and a 960 × 450 heatmap is 51 KB lossless
|
|
902
|
+
against 80 KB with Flate. On noise-like content it gains little and lossless
|
|
903
|
+
encoding is slow: 100,000 translucent markers of random colors and sizes
|
|
904
|
+
on 400 × 300 pt at 300 dpi take 4.4 s for 4.9 MB lossless
|
|
905
|
+
(Flate: 0.3 s, 5.8 MB) and 1.0 s for 2.1 MB at quality 85 (JPEG: 0.25 s,
|
|
906
|
+
2.2 MB).
|
|
907
|
+
|
|
908
|
+
### AVIF
|
|
909
|
+
|
|
910
|
+
AVIF is an export format only (`Image.encode("avif")`, `Image.save("….avif")`,
|
|
911
|
+
`convert(…, format="avif")`, `--format avif`, `vec_io.image.codecs.avif_encode`): PDF
|
|
912
|
+
has no AVIF filter. It goes through the system's libavif 1.x
|
|
913
|
+
(`libavif.so.16`, `libavif.16.dylib`, `avif.dll`; `VEC_IO_LIBAVIF` gives its
|
|
914
|
+
path) with whichever AV1 encoder libavif was built with; without it the
|
|
915
|
+
calls raise `vec_io.image.codecs.AVIFError`, and `avif_available()` tells.
|
|
916
|
+
libavif's options are fields of its structures, so vec_io checks their
|
|
917
|
+
layout against libavif 1.x's defaults before use and treats any other
|
|
918
|
+
layout as missing.
|
|
919
|
+
|
|
920
|
+
Images are 8-bit sRGB. Without `quality` they are lossless (4:4:4, identity
|
|
921
|
+
matrix); with it, lossy (BT.601, 4:4:4 from quality 90, 4:2:0 below).
|
|
922
|
+
Alpha is always lossless. Encoder speed follows `compression`: 8 (`fast`),
|
|
923
|
+
6 (`balanced`) or 4 (`best`). AVIF suits lossy output: a 960 × 450 heatmap
|
|
924
|
+
is 26 KB at quality 90 and 12 KB at 70, but 115 KB lossless, more than its
|
|
925
|
+
81 KB PNG.
|
|
926
|
+
|
|
927
|
+
### Rendering
|
|
928
|
+
|
|
929
|
+
The rasterizer runs what a page records, the scene its PDF content is written
|
|
930
|
+
from, number for number, so a rasterized group matches its vector version.
|
|
931
|
+
Coverage is exact (the area of each pixel covered, computed in fixed point);
|
|
932
|
+
fills, strokes (caps, joins, dashes), gradients, tiling patterns, images,
|
|
933
|
+
clipping, transparency groups and soft masks are supported, and every platform
|
|
934
|
+
computes identical pixels.
|
|
935
|
+
As in PDF viewers, strokes are at least one device pixel wide; images are
|
|
936
|
+
smoothed unless `smooth=False` (`/Interpolate false`) or magnified 4 times or
|
|
937
|
+
more, and minified images are averaged. A dash pattern with more than
|
|
938
|
+
5,000,000 visible dashes in one subpath raises `RasterizationError`.
|
|
939
|
+
|
|
940
|
+
The scene is compiled once into a display list: shapes scan converted, plain
|
|
941
|
+
forms (markers) rendered once per 1/32-pixel phase, gradients, patterns and
|
|
942
|
+
images prepared, transparency groups and soft masks as lists of their own.
|
|
943
|
+
Surfaces of 250,000 pixels or more, or with 64 KB of recorded content or more,
|
|
944
|
+
then replay the list in horizontal bands on worker threads, one per thread, cut
|
|
945
|
+
so that each holds about the same work. The markers of one `draw_markers` call
|
|
946
|
+
(one shape and size, one fill or a fill per point) are runs of 16-byte
|
|
947
|
+
instances grouped by rows; the pixels are those of the markers drawn one by
|
|
948
|
+
one.
|
|
949
|
+
`VEC_IO_THREADS` sets the number of threads: default the usable CPUs, at
|
|
950
|
+
most 8; `1` disables them. Pixels and bytes do not depend on it.
|
|
951
|
+
|
|
952
|
+
Text is drawn from the outlines of its fonts (TrueType or CFF), the ones the PDF
|
|
953
|
+
embeds (scenes hold glyph ids; their codes in each subset are written with the PDF),
|
|
954
|
+
wherever it is: groups, masks, patterns, markers, clipping paths, images. A
|
|
955
|
+
standard 14 font (`embed=False`) is drawn with the outlines of the installed font its name
|
|
956
|
+
resolves to as a family (metric aliases first, [Fonts](#16-fonts)) at the standard advance
|
|
957
|
+
widths; with no installed font at all that raises `RasterizationError`.
|
|
958
|
+
|
|
959
|
+
Marker timings, 100,000 points on a 400 × 300 pt area:
|
|
960
|
+
|
|
961
|
+
| Output | One colour | Per-point fill, size and a stroke | Per-point fill, one size |
|
|
962
|
+
|---|---|---|---|
|
|
963
|
+
| Vector PDF | 0.02 s (0.8 MB) | 0.08 s (2.6 MB) | 0.06 s (1.8 MB) |
|
|
964
|
+
| Rasterized, 150 dpi | 0.04 s | 0.21 s | 0.12 s |
|
|
965
|
+
| Rasterized, 300 dpi | 0.10 s | 0.35 s | 0.21 s |
|
|
966
|
+
|
|
967
|
+
Timings: 8 worker threads (see above); on one thread the 300 dpi row is
|
|
968
|
+
0.17 s, 1.12 s and 0.40 s. A page of 1,000,000 one-colour markers renders to
|
|
969
|
+
PNG at 150 dpi in 0.09 s (0.12 s with a fill per point); its display list
|
|
970
|
+
takes about 21 bytes per marker.
|
|
971
|
+
|
|
972
|
+
JPEG images are decoded when rasterized (baseline, extended and progressive;
|
|
973
|
+
arithmetic-coded and 12-bit JPEGs raise `RasterizationError`), once per image; a
|
|
974
|
+
1500 × 1000 JPEG decodes in about 0.01 s. PNG files are decoded when drawn.
|
|
975
|
+
|
|
976
|
+
## 13. Simplification
|
|
977
|
+
|
|
978
|
+
`simplify=True` (600 dpi) or a resolution leaves out what cannot be seen at
|
|
979
|
+
that resolution:
|
|
980
|
+
|
|
981
|
+
* Content that later content in the same scope hides is deleted whole:
|
|
982
|
+
painting operators with their path, marker placements, and single subpaths
|
|
983
|
+
of strokes (each segment of `draw_segments`). Deleted content changes no
|
|
984
|
+
pixel by more than half an 8-bit level in all, translucent covers
|
|
985
|
+
included. Content that paints nothing on the page (outside it, or clipped
|
|
986
|
+
away) goes too.
|
|
987
|
+
* Lines and areas whose x values increase are decimated (M4, see
|
|
988
|
+
[Lines, segments and areas](#9-lines-segments-and-areas)).
|
|
989
|
+
|
|
990
|
+
It is a choice of the output. Drawing records everything, with what each piece
|
|
991
|
+
of content asks for; writing the PDF decides what to leave out (culling runs
|
|
992
|
+
on the recorded commands, before anything is written), and image output only
|
|
993
|
+
decimates. The same page can be written with different settings.
|
|
994
|
+
|
|
995
|
+
| API | Scope |
|
|
996
|
+
|---|---|
|
|
997
|
+
| `Document.save(…, simplify=…)`, `to_bytes(simplify=…)` | every page, group, mask and pattern |
|
|
998
|
+
| `Page.render(…, simplify=…)` | decimation of the page's lines |
|
|
999
|
+
| `draw_markers`, `draw_segments`, `draw_lines`, `fill_between`, `draw_rects`, `fill_polygons` (`simplify=`) | the call |
|
|
1000
|
+
| `Canvas.group(simplify=…)` | the body, across calls |
|
|
1001
|
+
| `simplify=` on any `vec_io.draw` element | the element |
|
|
1002
|
+
| `data-simplify="true"`, `"600"` or `"auto"` on any SVG element | the element |
|
|
1003
|
+
| `convert(…, simplify=…)`, `--simplify [DPI\|auto]` | the document |
|
|
1004
|
+
|
|
1005
|
+
* `None` (default) follows the enclosing scope, and outside any the output's
|
|
1006
|
+
setting. A resolution given inside a culled scope merges into it (it still
|
|
1007
|
+
sets the call's decimation).
|
|
1008
|
+
* `False`, or `keep=True` on the bulk calls, keeps content whole: nothing in
|
|
1009
|
+
it is deleted or decimated (it can still hide earlier content).
|
|
1010
|
+
`group(simplify=False)` draws its body directly, not as a group.
|
|
1011
|
+
* Groups, masks and patterns are culled on their own when the PDF is written, with
|
|
1012
|
+
the output's setting and the scope they were drawn in, and kept whole in the page.
|
|
1013
|
+
* Image output (`render`, rasterized groups, `convert` to an image format,
|
|
1014
|
+
`--format png|jpeg|jxl|avif`) is not culled:
|
|
1015
|
+
rasterizing hidden content is faster than culling it, and the image is the
|
|
1016
|
+
same. Lines are still decimated: with the output's setting, in a rasterized
|
|
1017
|
+
group at its dpi when `simplify=True`.
|
|
1018
|
+
* Never deleted: text, paths that also clip, images, shadings, groups and
|
|
1019
|
+
inlined content. Content under a soft mask or painted with a pattern is
|
|
1020
|
+
deleted when hidden but hides nothing; images, shadings and groups hide
|
|
1021
|
+
nothing either. Under a soft mask, a scope opened by a call or an inline
|
|
1022
|
+
group does nothing.
|
|
1023
|
+
* SVG output ([18](#18-output)) writes content whole.
|
|
1024
|
+
|
|
1025
|
+
What changes: in a renderer that composites shapes one by one with partial
|
|
1026
|
+
coverage (vec_io and PDF viewers), the deleted content changes no pixel by
|
|
1027
|
+
more than 0.5/255 at the given resolution, except two kinds of pixels:
|
|
1028
|
+
|
|
1029
|
+
* Edges of stacked copies: a point drawn many times shows the copies' colors
|
|
1030
|
+
in its anti-aliased edge; culled, it shows its own edge.
|
|
1031
|
+
* Seams between covering shapes, through which a hidden shape leaked.
|
|
1032
|
+
|
|
1033
|
+
At other resolutions the same holds up to slivers smaller than a pixel.
|
|
1034
|
+
`tools/simplify_check.py` measures it on the scenes below (300 × 200 pt,
|
|
1035
|
+
600 dpi; time to draw and write, without and with `simplify`):
|
|
1036
|
+
|
|
1037
|
+
| Scene | PDF | Time | Max change | Pixels changed ≥ 1 / ≥ 8 levels |
|
|
1038
|
+
|---|---|---|---|---|
|
|
1039
|
+
| Gaussian 60k, sizes 1–4, 3 colors, opaque | 790 → 188 KB | 0.10 → 0.16 s | 47 | 0.013 / 0.003 % |
|
|
1040
|
+
| same, alpha 0.5 | 802 → 586 KB | 0.09 → 0.26 s | 1 | 0.37 / 0 % |
|
|
1041
|
+
| Binned 60k (duplicates), one color, opaque | 119 → 4 KB | 0.29 → 0.34 s | 212 | 1.0 / 0.94 % |
|
|
1042
|
+
| Binned 60k, 3 colors, alpha 0.3 | 159 → 56 KB | 0.32 → 0.42 s | 114 | 1.3 / 0.12 % |
|
|
1043
|
+
| Gaussian 60k, one color, white outline | 437 → 84 KB | 0.05 → 0.10 s | 1 | 0 / 0 % |
|
|
1044
|
+
| Segments 40k, one color | 528 → 313 KB | 0.08 → 0.12 s | 0 | 0 / 0 % |
|
|
1045
|
+
| Segments 40k, 3 colors | 582 → 365 KB | 0.08 → 0.13 s | 1 | 0.003 / 0 % |
|
|
1046
|
+
| Area, 60 lines, markers, clip, opacity group, text | 380 → 243 KB | 0.06 → 0.12 s | 1 | 0 / 0 % |
|
|
1047
|
+
| SVG: 20k `<use>` markers in a clipped group | 173 → 75 KB | 1.87 → 1.95 s | 1 | 0 / 0 % |
|
|
1048
|
+
|
|
1049
|
+
Culling 100,000 markers at 600 dpi on a 500 × 360 pt page takes 0.21 s
|
|
1050
|
+
with one size and 0.29 s with a size per point; 1,000,000 opaque markers of
|
|
1051
|
+
one size take about 0.6 s, at opacity 0.5 about 2.1 s. Culling runs on the
|
|
1052
|
+
recorded scene and gives the writer a keep map: per command whether it is
|
|
1053
|
+
written, and per marker, segment or subpath of a stroke whether it stays, so the
|
|
1054
|
+
content is written once. What each translucent marker covers is computed on the
|
|
1055
|
+
worker threads (see `VEC_IO_THREADS` in [Rendering](#rendering)); opaque ones
|
|
1056
|
+
are read from their cached sprite when needed. The decisions, which depend on
|
|
1057
|
+
each other, are made in order on one thread. A unit whose pixels later content
|
|
1058
|
+
already covers fully is deleted without its pixels being visited. The result
|
|
1059
|
+
does not depend on the number of threads.
|
|
1060
|
+
|
|
1061
|
+
### `simplify="auto"`
|
|
1062
|
+
|
|
1063
|
+
`"auto"` simplifies each bulk call only where it pays off. Culling costs about
|
|
1064
|
+
0.1–0.2 s per 100,000 markers, and its gain depends on how deeply markers stack;
|
|
1065
|
+
decimation is cheap but only removes points where there are many per pixel column.
|
|
1066
|
+
Every decision depends on the data alone, so the output stays deterministic.
|
|
1067
|
+
|
|
1068
|
+
| Call | `"auto"` does |
|
|
1069
|
+
|---|---|
|
|
1070
|
+
| `draw_markers` | Culls as `True` when there are at least 20,000 markers and an estimate finds at least 30 % of them hidden; otherwise draws them all. |
|
|
1071
|
+
| `draw_lines`, `fill_between` | Decimates as `True` a series with more than 2 points per device-pixel column over its x extent. |
|
|
1072
|
+
| `draw_segments`, `draw_rects`, `fill_polygons` | Nothing. |
|
|
1073
|
+
| In rasterized groups and image output | As `True`: decimation at the group's resolution, no culling. |
|
|
1074
|
+
|
|
1075
|
+
The estimate (one pass in C, about 2 ms per million markers) bins the marker
|
|
1076
|
+
centers into square cells of half the median marker size and, in each cell,
|
|
1077
|
+
counts all but the last k markers as hidden, where k is the number of layers
|
|
1078
|
+
of the median fill opacity α that hide what is under them within 0.5/255
|
|
1079
|
+
(k = 1 when opaque, 8 at α 0.5, 15 at α 0.3). Markers without a plain fill
|
|
1080
|
+
color (stroke only, gradients, patterns) are never culled by `"auto"`.
|
|
1081
|
+
|
|
1082
|
+
As a scope (`Document.save`, `Canvas.group`, `vec_io.draw` elements,
|
|
1083
|
+
`data-simplify`, `convert`, `--simplify auto`), `"auto"` is inherited by the
|
|
1084
|
+
bulk calls inside, each deciding for itself (when the output needs it); other content is not culled across calls,
|
|
1085
|
+
which only `True` or a resolution does. Inside a `True` scope, `"auto"` means
|
|
1086
|
+
that scope. Each decision is logged at debug level by the `vec_io` logger.
|
|
1087
|
+
|
|
1088
|
+
`tools/simplify_check.py --auto` compares the decisions with the rule they are
|
|
1089
|
+
calibrated to, cull exactly where culling saves at least 30 % of the bytes
|
|
1090
|
+
(600 dpi, sizes without simplify → with `True`; estimated hidden fraction):
|
|
1091
|
+
|
|
1092
|
+
| Scene | PDF | Estimate | `"auto"` |
|
|
1093
|
+
|---|---|---|---|
|
|
1094
|
+
| Gaussian 60k, sizes 1–4, 3 colors, opaque | 790 → 188 KB (−76 %) | 0.79 | culls |
|
|
1095
|
+
| same, alpha 0.5 | 802 → 586 KB (−27 %) | 0.14 | keeps |
|
|
1096
|
+
| Binned 60k (duplicates), one color, opaque | 119 → 4 KB (−97 %) | 0.99 | culls |
|
|
1097
|
+
| Binned 60k, 3 colors, alpha 0.3 | 159 → 56 KB (−65 %) | 0.89 | culls |
|
|
1098
|
+
| Gaussian 60k, one color, white outline | 437 → 84 KB (−81 %) | 0.84 | culls |
|
|
1099
|
+
| Uniform 2k, one color, opaque | 17 → 16 KB (−1 %) | 0.00 | keeps |
|
|
1100
|
+
| Uniform 200k, one color, alpha 0.3 | 1,490 → 1,432 KB (−4 %) | 0.00 | keeps |
|
|
1101
|
+
| Binned 1M, one color, opaque | 1,957 → 6 KB (−100 %) | 1.00 | culls |
|
|
1102
|
+
|
|
1103
|
+
On a million markers that it does not cull, `"auto"` adds 2 ms.
|
|
1104
|
+
|
|
1105
|
+
## 14. Filters
|
|
1106
|
+
|
|
1107
|
+
PDF has no filters: a filtered group is drawn into an image (at the group's `dpi`, 300 by
|
|
1108
|
+
default) and the filter runs on its pixels. PDF, image and SVG output show that image (the
|
|
1109
|
+
scene keeps the pixels, not the filter).
|
|
1110
|
+
|
|
1111
|
+
```python
|
|
1112
|
+
from vec_io import Filter
|
|
1113
|
+
from vec_io.filter import GaussianBlur, Merge, Offset
|
|
1114
|
+
|
|
1115
|
+
shadow = Filter(GaussianBlur(3, input="SourceAlpha"), Offset(4, 4, result="s"),
|
|
1116
|
+
Merge("s", "SourceGraphic"))
|
|
1117
|
+
with page.group(filter=shadow, dpi=300):
|
|
1118
|
+
page.fill_rect(20, 20, 100, 60, fill="tomato")
|
|
1119
|
+
d.append(draw.Rect(20, 20, 100, 60, fill="tomato", filter=shadow)) # vec_io.draw
|
|
1120
|
+
```
|
|
1121
|
+
|
|
1122
|
+
`Filter(*primitives, region=None)`: the primitives run in order; `region` `(x, y, width,
|
|
1123
|
+
height)` in user space is what the filter may paint, by default the bounds of what the group
|
|
1124
|
+
draws, 10 % wider and higher on each side. The primitives (`vec_io.filter`) are those of
|
|
1125
|
+
SVG's Filter Effects 1, with the same semantics:
|
|
1126
|
+
|
|
1127
|
+
| Primitive | Parameters |
|
|
1128
|
+
|---|---|
|
|
1129
|
+
| `GaussianBlur` | `std_deviation` (one value, or x and y) |
|
|
1130
|
+
| `Offset` | `dx`, `dy` (sub-pixel) |
|
|
1131
|
+
| `Flood` | `color`, `opacity` |
|
|
1132
|
+
| `DropShadow` | `dx=2`, `dy=2`, `std_deviation=2`, `color`, `opacity` |
|
|
1133
|
+
| `Composite` | `operator` (`over`, `in`, `out`, `atop`, `xor`, `lighter`, `arithmetic`), `input2`, `k1`…`k4` |
|
|
1134
|
+
| `Merge` | `*inputs`, the first at the bottom (`None`: the previous result) |
|
|
1135
|
+
| `Blend` | `mode` (the 16 CSS blend modes), `input2` |
|
|
1136
|
+
| `ColorMatrix` | `type` (`matrix` with 20 `values`, `saturate`, `hueRotate`, `luminanceToAlpha`), `values` |
|
|
1137
|
+
| `ComponentTransfer` | `r`, `g`, `b`, `a`: `Transfer(type, table, slope, intercept, amplitude, exponent, offset)` |
|
|
1138
|
+
| `Morphology` | `operator` (`erode`, `dilate`), `radius` |
|
|
1139
|
+
| `Tile` | — |
|
|
1140
|
+
| `ConvolveMatrix` | `kernel`, `order=3`, `divisor`, `bias`, `target_x`, `target_y`, `edge_mode`, `preserve_alpha` |
|
|
1141
|
+
| `Turbulence` | `base_frequency`, `octaves`, `seed`, `type` (`turbulence`, `fractalNoise`), `stitch`, `transform` |
|
|
1142
|
+
| `DiffuseLighting`, `SpecularLighting` | `light` (`DistantLight(azimuth, elevation)`, `PointLight(x, y, z)`, `SpotLight(x, y, z, points_at, specular_exponent, limiting_cone_angle)`), `surface_scale`, `constant`, (`exponent`), `color` |
|
|
1143
|
+
| `Image` | `source` (PNG, JPEG or JPEG XL bytes or a path), `preserve_aspect_ratio`, `smooth` |
|
|
1144
|
+
| `DisplacementMap` | `scale`, `x_channel`, `y_channel` (`R`, `G`, `B`, `A`), `input2` |
|
|
1145
|
+
|
|
1146
|
+
Every primitive also takes `input` (`"SourceGraphic"`, `"SourceAlpha"` or an earlier
|
|
1147
|
+
`result`; default the previous result; the other standard inputs are transparent),
|
|
1148
|
+
`result`, its subregion `x`, `y`, `width`, `height` (default: the union of its inputs'
|
|
1149
|
+
subregions, or the filter region) and `space` (`"linearRGB"`, the default, or `"sRGB"`).
|
|
1150
|
+
Lengths and positions are in the group's user space; one kernel pixel is one device pixel.
|
|
1151
|
+
The pixel work is C (`src/filters.c`).
|
|
1152
|
+
|
|
1153
|
+
SVG `filter` properties are read into the same `Filter` (`primitiveUnits`, `filterUnits`,
|
|
1154
|
+
`color-interpolation-filters`, `flood-color`, `lighting-color`; `feImage` references to
|
|
1155
|
+
elements are transparent; `kernelUnitLength` is ignored) and drawn through
|
|
1156
|
+
`group(filter=…)`. In SVG they are off by default, since they rasterize:
|
|
1157
|
+
|
|
1158
|
+
| Scope | Enable |
|
|
1159
|
+
|---|---|
|
|
1160
|
+
| Conversion | `convert(…, filters=True)`; CLI `--filters` |
|
|
1161
|
+
| Document | `Document(filters=True)` (SVGs placed with `draw_svg` and `draw.SVG`) |
|
|
1162
|
+
| Element and descendants | SVG `data-filters="true"` / `"false"`. Overrides the scope above. |
|
|
1163
|
+
|
|
1164
|
+
## 15. SVG support
|
|
1165
|
+
|
|
1166
|
+
| Area | Support |
|
|
1167
|
+
|---|---|
|
|
1168
|
+
| Shapes | `rect` (rounded), `circle`, `ellipse`, `line`, `polyline`, `polygon`, `path` (all commands, arcs) |
|
|
1169
|
+
| Structure | `g`, nested `svg`, `use`, `symbol`, `defs`, `a`, `switch`, `viewBox`, `preserveAspectRatio`, `transform`; a zero-size `viewBox` disables rendering; at most 300,000 elements are instantiated through `<use>` per document (further instances are skipped) |
|
|
1170
|
+
| Styling | presentation attributes, `style`, `<style>` (type, class, id, attribute selectors; descendant, child, sibling combinators; `:first-child`, `:not()` (selector lists); specificity; `!important`), `font` and `marker` shorthands, inheritance, `inherit`, `currentColor` |
|
|
1171
|
+
| CSS | `@media` evaluated for print; `<style media="…">` |
|
|
1172
|
+
| Painting | `fill`, `stroke` (CSS Color 4 colors, `device-cmyk()`), `fill-rule`, `stroke-width`/`-linecap`/`-linejoin`/`-miterlimit`/`-dasharray`/`-dashoffset`, `paint-order`, `opacity`, `fill-opacity`, `stroke-opacity` |
|
|
1173
|
+
| Paint servers | `linearGradient`, `radialGradient` (focal point, `href` inheritance, `gradientTransform`, `spreadMethod`, `stop-opacity`), `pattern` |
|
|
1174
|
+
| Compositing | group opacity, `clipPath` (any shapes and text, `objectBoundingBox`, nested), luminance `mask` |
|
|
1175
|
+
| Markers | `marker-start`/`-mid`/`-end`, `orient` (`auto`, `auto-start-reverse`, angles), `viewBox`, `markerUnits`, `overflow`; drawn through `PathMarker` |
|
|
1176
|
+
| Text | `text`, `tspan`, `tref`, `textPath` (`startOffset`, `side`, `path`, `method="stretch"`); `x`/`y`/`dx`/`dy`/`rotate` lists; `textLength`, `lengthAdjust`; `text-anchor`; `inline-size` (SVG 2 wrapping: lines aligned by `text-anchor`, the first baseline at `y`); `font` and `font-*` (`font-kerning` included); `letter-spacing`, `word-spacing`, `text-decoration` (applies to descendants, painted with the declaring element's fill and stroke), `dominant-baseline`, `baseline-shift` (`sub`, `super`, lengths, percentages; nested shifts add up), `xml:space` (inherited) |
|
|
1177
|
+
| Images | PNG (all color types and depths, interlacing, transparency), JPEG (embedded unchanged), [JPEG XL](#jpeg-xl) (with libjxl), SVG; `data:` URIs or local files; `image-rendering: pixelated`/`crisp-edges` |
|
|
1178
|
+
| Filters | see [Filters](#14-filters) |
|
|
1179
|
+
| vec_io attributes | `data-rasterize`, `data-simplify`, `data-quality`, `data-filters` |
|
|
1180
|
+
| Input | UTF-8 or declared encodings, SVGZ, common HTML entities |
|
|
1181
|
+
|
|
1182
|
+
Document size: `width`/`height` in any CSS unit, else `viewBox`, else
|
|
1183
|
+
300 × 150. `rem` refers to the font size of the root `<svg>`.
|
|
1184
|
+
|
|
1185
|
+
## 16. Fonts
|
|
1186
|
+
|
|
1187
|
+
Fonts are embedded as subsets, always. For each character, the font is the first of:
|
|
1188
|
+
|
|
1189
|
+
1. The listed families of `font-family`, in order: an `@font-face` font, a font added with
|
|
1190
|
+
`Document.add_font`, or an installed font (`font_dirs`, then `VEC_IO_FONT_DIRS`, then the
|
|
1191
|
+
system directories), weight, width and style matched as in CSS Fonts 4 (missing bold
|
|
1192
|
+
and italic faces are synthesized). A family that is not installed resolves to its
|
|
1193
|
+
first installed metric alias: Helvetica and Arial to Liberation Sans, Arimo, Nimbus
|
|
1194
|
+
Sans, TeX Gyre Heros…; Times and Times New Roman to Liberation Serif, Tinos, Nimbus
|
|
1195
|
+
Roman…; Courier and Courier New to Liberation Mono, Cousine, Nimbus Mono…; else the
|
|
1196
|
+
next family. Generic families resolve to the first installed family of their list
|
|
1197
|
+
(`serif`: Times New Roman, Liberation Serif, DejaVu Serif, …; `sans-serif`: Arial,
|
|
1198
|
+
Helvetica, Liberation Sans, DejaVu Sans, …; `monospace`: Courier New, Liberation Mono,
|
|
1199
|
+
DejaVu Sans Mono, …), else to the best installed face of all.
|
|
1200
|
+
2. The generic family the list implies (`serif` for Times-like names, `monospace` for
|
|
1201
|
+
Courier-like ones, else `sans-serif`).
|
|
1202
|
+
3. Any installed font that has the character (Greek, Cyrillic, CJK, …).
|
|
1203
|
+
4. The first font's `.notdef` glyph (a character no font has).
|
|
1204
|
+
|
|
1205
|
+
The standard 14 PDF fonts are used only when asked for: a family naming one (`Helvetica`,
|
|
1206
|
+
`Times-Roman`, `Courier-Bold`, `Symbol`, `ZapfDingbats`…, the variant chosen by weight and
|
|
1207
|
+
style) with `font_embed=False` (or `Font(embed=False)`): it is not embedded, covers
|
|
1208
|
+
WinAnsi (Symbol and ZapfDingbats theirs) with the Adobe metrics, and is not kerned; other
|
|
1209
|
+
characters take steps 1–4. SVG text is always embedded.
|
|
1210
|
+
|
|
1211
|
+
Text needs an outline font. With none available at all (no installed font, or
|
|
1212
|
+
`system_fonts=False` without `font_dirs`, `VEC_IO_FONT_DIRS`, `@font-face` or `add_font`),
|
|
1213
|
+
drawing or measuring text raises `NoFontError` (`vec_io.fonts.fontdb`, a `ValueError`) naming
|
|
1214
|
+
these remedies; documents without text need no font. A silent fallback to the standard 14
|
|
1215
|
+
fonts would make the PDF depend on the viewer's fonts, differ from vec_io's own images,
|
|
1216
|
+
and fail anyway in image output and PDF/A, which need outlines.
|
|
1217
|
+
|
|
1218
|
+
Font files: TrueType, OpenType (CFF), collections (`.ttc`), WOFF 1.0 and WOFF 2.0
|
|
1219
|
+
(`.woff2`, collections included; Brotli is decoded by the bundled C library),
|
|
1220
|
+
variable fonts included (TrueType `gvar` and CFF2).
|
|
1221
|
+
All fonts are subset to the glyphs used, renumbered in order of first use; the codes
|
|
1222
|
+
are given when the PDF is written, so a document drawn on after a save writes a larger
|
|
1223
|
+
subset next time. TrueType outlines are embedded as TrueType (`FontFile2`), without their
|
|
1224
|
+
hinting unless the output asks for it (`font_hinting=True`, [Document](#4-document-and-page));
|
|
1225
|
+
CFF outlines,
|
|
1226
|
+
name-keyed or CID-keyed, as a CID-keyed CFF (`FontFile3/CIDFontType0C`) with
|
|
1227
|
+
unused subroutines emptied. A 5 MB CJK OpenType font typically adds a few
|
|
1228
|
+
kilobytes.
|
|
1229
|
+
Text remains selectable and searchable (ToUnicode maps).
|
|
1230
|
+
Pair kerning (GPOS `kern`, else the `kern` table) applies to every text: one-line text,
|
|
1231
|
+
paragraphs and SVG text (`font-kerning: none` and `font_kerning="none"` turn it off).
|
|
1232
|
+
Standard ligatures (GSUB `liga`) apply to paragraphs and to `vec_io.text.shape` with
|
|
1233
|
+
`ligatures=True`; one-line Canvas text and SVG text show one glyph per character.
|
|
1234
|
+
A subset costs a few KB: a plot page's labels and bold title in Arial (two faces) add
|
|
1235
|
+
about 4 KB, compressed (20 KB with the hinting).
|
|
1236
|
+
|
|
1237
|
+
### Variable fonts
|
|
1238
|
+
|
|
1239
|
+
A variable font is embedded as a static instance at each position used: outlines
|
|
1240
|
+
(`gvar` with IUP, or CFF2 blends), advances (`HVAR`, else phantom points), metrics
|
|
1241
|
+
(`MVAR`), hinting (`cvt` with `cvar`) and kerning (`GPOS` device deltas) are
|
|
1242
|
+
varied; `GSUB`/`GPOS` feature variations (such as `rvrn`) apply. CFF2
|
|
1243
|
+
instances are written as CID-keyed CFF. The axes follow CSS:
|
|
1244
|
+
|
|
1245
|
+
| Axis | From |
|
|
1246
|
+
|---|---|
|
|
1247
|
+
| `wght` | `font_weight` / `font-weight` |
|
|
1248
|
+
| `wdth` | `font_stretch` / `font-stretch` (percent) |
|
|
1249
|
+
| `ital` | 1 for `italic` |
|
|
1250
|
+
| `slnt` | minus the `oblique` angle (14° when none is given) |
|
|
1251
|
+
| `opsz` | the font size in px, with `font_optical_sizing="auto"` |
|
|
1252
|
+
| any | `font_variation_settings` / `font-variation-settings`, applied last |
|
|
1253
|
+
|
|
1254
|
+
Values are clamped to the axes' ranges. Face matching treats a variable face as
|
|
1255
|
+
covering its whole `wght` and `wdth` ranges, and as italic when an `ital` or `slnt`
|
|
1256
|
+
axis allows it, so a variable font is preferred to synthetic bold or oblique. A
|
|
1257
|
+
position that matches a named instance uses its PostScript name (`NotoSans-Bold`);
|
|
1258
|
+
others are named after Adobe TN 5902 (`RobotoFlex_26.667opsz_650wght_85wdth`). An
|
|
1259
|
+
`@font-face` with a `font-weight` range (`100 900`) takes the font's own axis.
|
|
1260
|
+
Malformed variation data raises `FontError` when the font is used.
|
|
1261
|
+
|
|
1262
|
+
### Installed fonts
|
|
1263
|
+
|
|
1264
|
+
For machine-independent output, pass `system_fonts=False` and provide fonts
|
|
1265
|
+
with `font_dirs`, `@font-face` or `add_font`. `VEC_IO_FONT_DIRS` (directories separated by
|
|
1266
|
+
`os.pathsep`) adds font directories to every `FontDB`, also with `system_fonts=False`; the
|
|
1267
|
+
tests point it at their own font.
|
|
1268
|
+
|
|
1269
|
+
Font directories are scanned once per process. The name, weight and style of
|
|
1270
|
+
every font file are kept in a persistent index (`fonts.json` in
|
|
1271
|
+
`$XDG_CACHE_HOME/vec_io`, `~/Library/Caches/vec_io` or
|
|
1272
|
+
`%LOCALAPPDATA%\vec_io\Cache`), so later processes re-read only new or
|
|
1273
|
+
changed files (by size and modification time). `VEC_IO_FONT_CACHE=0` disables
|
|
1274
|
+
the index; another value is the path of the index file.
|
|
1275
|
+
|
|
1276
|
+
PDF/A documents embed every font: `embed=False` is ignored, and a character no font
|
|
1277
|
+
covers raises `ValueError`.
|
|
1278
|
+
|
|
1279
|
+
## 17. Geometry: `Matrix`, `Path`
|
|
1280
|
+
|
|
1281
|
+
```python
|
|
1282
|
+
Matrix(a=1, b=0, c=0, d=1, e=0, f=0) # x' = a x + c y + e, y' = b x + d y + f
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
Class methods `translate(tx, ty=0)`, `scale(sx, sy=None)`,
|
|
1286
|
+
`rotate(degrees, cx=0, cy=0)`, `skew_x(degrees)`, `skew_y(degrees)`.
|
|
1287
|
+
Methods: `multiply(other)` (applies `other` first), `apply(x, y)`,
|
|
1288
|
+
`inverse()` (`None` if singular), `determinant()`, `is_identity()`,
|
|
1289
|
+
`values()` → `(a, b, c, d, e, f)`.
|
|
1290
|
+
|
|
1291
|
+
```python
|
|
1292
|
+
Path(d=None) # SVG path data (made absolute cubic), another Path, or segments
|
|
1293
|
+
```
|
|
1294
|
+
|
|
1295
|
+
A packed path: `verbs` (a `bytearray` of `Path.MOVE`, `LINE`, `CURVE`, `CLOSE`) and
|
|
1296
|
+
`coords` (an `array('d')`: 2 per move or line, 6 per curve), recorded into scenes as they
|
|
1297
|
+
are. Segments are tuples `("M", x, y)`, `("L", x, y)`, `("C", x1, y1, x2, y2, x, y)`,
|
|
1298
|
+
`("Z",)`; iterating a path gives them, `segments` lists them. Methods (chainable):
|
|
1299
|
+
`move_to`, `line_to`, `curve_to`, `close`, `extend(other)`; `transformed(m)` (new path),
|
|
1300
|
+
`current_point()`, `to_svg(decimals=6)` (path data), `bbox()` → `(x0, y0, x1, y1)` or
|
|
1301
|
+
`None`, `is_empty()`, `area()`: the area filled with the nonzero rule (subpaths closed as for
|
|
1302
|
+
filling; holes, overlaps and self-intersections counted as filled), with curves flattened
|
|
1303
|
+
to 1/10,000 of the path's size; 0 for paths that fill nothing.
|
|
1304
|
+
|
|
1305
|
+
## 18. Output
|
|
1306
|
+
|
|
1307
|
+
* Compressed (default): PDF 1.5. Streams are zlib-compressed, small objects
|
|
1308
|
+
are packed into compressed object streams and the cross-reference table is
|
|
1309
|
+
a compressed xref stream; identical streams are written once. Tiny
|
|
1310
|
+
documents use whichever layout is smaller. On SVG files this halves the
|
|
1311
|
+
output size compared with a classic layout (median ratio 0.53).
|
|
1312
|
+
* `compression` of every compressed stream: `"fast"` (zlib level 1),
|
|
1313
|
+
`"balanced"` (zlib 6, default) or `"best"`: vec_io's Deflate (binary-tree match
|
|
1314
|
+
finder, near-optimal parsing iterated over the Huffman costs, block splitting),
|
|
1315
|
+
as small as libdeflate's best level and 3–6 % smaller than zlib's best (PNG rows,
|
|
1316
|
+
PDF streams). On a 17 MB content stream (a 1M-point line) `fast` is 6.5× faster
|
|
1317
|
+
than `balanced` and 16 % larger; on SVG conversions `best` writes 9 % less Flate
|
|
1318
|
+
data than `balanced`, in 1.6× the time of zlib's best. PNG data embedded as is is
|
|
1319
|
+
recompressed at `best`.
|
|
1320
|
+
* Streams of 1 MB or more are compressed in 1 MB chunks on worker threads
|
|
1321
|
+
(`VEC_IO_THREADS`, see [Rendering](#rendering)): each chunk is primed with
|
|
1322
|
+
the previous 32 KB, so the result is one zlib stream within 0.01 % of the
|
|
1323
|
+
serial size, and the same bytes for any number of threads.
|
|
1324
|
+
* `compress=False`: classic, human-readable PDF 1.4 layout, for debugging.
|
|
1325
|
+
* Every file carries a document `/ID` derived from its content.
|
|
1326
|
+
* Object numbers and resource names are given when a file is written: names per
|
|
1327
|
+
category in the order the resources were first used (`F1`, `GS1`, `Fm1`, `Im2`…),
|
|
1328
|
+
objects in the order they are reached; identical streams are written once.
|
|
1329
|
+
* Fonts are embedded as subsets of the glyphs drawn, in the order they were first drawn
|
|
1330
|
+
(scenes hold glyph ids; the subset codes are written with the PDF). TrueType subsets
|
|
1331
|
+
leave out the hinting by default (`fpgm`, `prep`, `cvt` and the glyph instructions):
|
|
1332
|
+
viewers rarely use it at screen resolutions, and vec_io's rasterizer never does.
|
|
1333
|
+
A plot page with Arial labels and a bold title is 5.4 KB without it, 21.8 KB with it
|
|
1334
|
+
(`font_hinting=True`); CFF subsets keep their hints (they are part of the
|
|
1335
|
+
charstrings).
|
|
1336
|
+
* SVG conversion: one page of the SVG's size, 1 px = 0.75 pt.
|
|
1337
|
+
* SVG output: `vec_io.svg.writer.page_svg(page)` (and `Drawing.to_svg`) writes what a page
|
|
1338
|
+
draws, read from its scene command by command by the C library, as the PDF writer reads
|
|
1339
|
+
it: paths with their fills and strokes, `<text>` with every glyph placed (`<tspan>` where
|
|
1340
|
+
kerning or adjustments move one; the faces named by family, weight and style), `<image>`
|
|
1341
|
+
(PNG and JPEG files as given, other images as PNG), gradients and tiling patterns, clips
|
|
1342
|
+
and soft masks (`<clipPath>`, `<mask>`), forms (marker shapes, groups) as `<defs>` reused
|
|
1343
|
+
by `<use>`, links as `<a>`; smooth cells' mesh becomes an image of about two samples per
|
|
1344
|
+
unit, and a filtered group the image it is drawn as. Colors are RGB (CMYK and spot colors
|
|
1345
|
+
as their approximation); content asking for simplification is written whole. Read back
|
|
1346
|
+
by vec_io, a page renders as it did (tests: mean difference under half an 8-bit level;
|
|
1347
|
+
marker sprites and meshes differ at edges). 200,000 markers take 20 ms (12 MB), a
|
|
1348
|
+
1M-point line 42 ms, 1.5–2.6× the PDF content writer.
|
|
1349
|
+
* Shapes and text stay vectors; only rasterized groups, filters and PNG
|
|
1350
|
+
output produce pixels.
|
|
1351
|
+
* Numbers: coordinates (paths, rectangles, marker positions, lines, areas,
|
|
1352
|
+
segments, cells) are written to 1/1000 pt or finer on the page:
|
|
1353
|
+
the decimals follow the largest scale of the transform, from 0 to 6 (3 for
|
|
1354
|
+
user units in points, 5 for inches). Transform matrices, line widths and
|
|
1355
|
+
dashes have 6 decimals, colors 4. Drawing records a scene (`scene.py`) whose numbers
|
|
1356
|
+
are rounded to these decimals when they are captured; the C library writes the content
|
|
1357
|
+
streams from it.
|
|
1358
|
+
* Identical inputs give identical bytes, and a document written again with the same
|
|
1359
|
+
options gives the same bytes. PDF/A files carry the current date (taken when the
|
|
1360
|
+
document is made) unless `creation_date` is given.
|
|
1361
|
+
* PDF/A-2b, 2u and 3b: see [PDF/A](#pdfa).
|
|
1362
|
+
* CMYK (`Document(color_mode="cmyk")`): see [Document](#4-document-and-page).
|
|
1363
|
+
Limits: the conversion is naive (not ICC-managed); JPEG files are kept
|
|
1364
|
+
in their own color space; a spot color in a gradient uses its CMYK
|
|
1365
|
+
alternate; inside masks, images (including heatmaps in `image` mode)
|
|
1366
|
+
stay in CMYK.
|
|
1367
|
+
|
|
1368
|
+
## 19. Errors
|
|
1369
|
+
|
|
1370
|
+
| Exception | Raised for |
|
|
1371
|
+
|---|---|
|
|
1372
|
+
| `vec_io.SVGParseError` (`ValueError`) | Input that is not a usable SVG document. |
|
|
1373
|
+
| `vec_io.fonts.sfnt.FontError` (`ValueError`) | Malformed or unsupported font data: from `Document.add_font`, and when writing or rasterizing a font whose glyph data is malformed. `@font-face` fonts that cannot be read are ignored. |
|
|
1374
|
+
| `vec_io.fonts.fontdb.NoFontError` (`ValueError`) | Text with no outline font available at all ([Fonts](#16-fonts)). |
|
|
1375
|
+
| `vec_io.raster.RasterizationError` | Content the rasterizer cannot draw (a standard 14 font with no installed font to draw it, too many dashes, unsupported shadings or JPEGs). |
|
|
1376
|
+
| `vec_io.native.NativeLibraryError` | The C library is not built for this platform or failed to load. |
|
|
1377
|
+
| `vec_io.image.codecs.JPEGError`, `JXLError`, `AVIFError`, `vec_io.image.resource.ImageError` (all `ValueError`) | Malformed or unsupported image data given to `vec_io.image.codecs`. |
|
|
1378
|
+
| `ValueError` | Invalid arguments: mismatched per-point lengths, marker names (markers are paths), invalid colors (components outside [0, 1] included), an unknown unit, cells whose `fill` does not match their edges, palette indices outside the palette, `quality` outside 1–100, `color_mode` other than `"rgb"`/`"cmyk"`, an unknown `pdf_a` level, PDF/A requirements that cannot be met (no font for a character, a missing or mismatched `icc_profile`, a CMYK JPEG in RGB mode, two spot colors with one name and different alternates, `attach` in PDF/A-2), `ink_limit` outside (0, 400], an `icc_profile` that is not a gray, RGB or CMYK ICC profile, a `Spot` alternate without 4 components, a link or bookmark to a page of another document or an out-of-range page index, non-finite page sizes or `arc` angles. |
|
|
1379
|
+
|
|
1380
|
+
## 20. Modules
|
|
1381
|
+
|
|
1382
|
+
| Module | Role |
|
|
1383
|
+
|---|---|
|
|
1384
|
+
| `document.py` | `Document`, `Page`: pages, metadata, outline, attachments, PDF/A, color settings; resources and writing |
|
|
1385
|
+
| `canvas.py` | `Canvas`: the recording API |
|
|
1386
|
+
| `scene.py` | what a page, form or pattern draws: binary commands, columns, resource handles; written by the C library |
|
|
1387
|
+
| `simplify.py` | what content asks of simplification, and each output's plan |
|
|
1388
|
+
| `items.py` | per-item arguments of bulk calls |
|
|
1389
|
+
| `paint.py`, `units.py` | colors (CSS, CMYK, spot, Oklab), gradients, patterns, masks, markers, path markers; length units |
|
|
1390
|
+
| `filter.py` | `Filter` and its primitives: the filter graph, run on a group's pixels |
|
|
1391
|
+
| `geometry.py` | `Matrix`, the packed `Path`, path data, arcs |
|
|
1392
|
+
| `lines.py`, `shapes.py`, `cells.py` | lines, segments and areas; rectangles and polygons; cells |
|
|
1393
|
+
| `text/` | shaping, measuring and layout from a font database (`Font`, `GlyphRun`, `Span`, `shape`, `measure`, `layout`); `text/breaking.py`: line breaking of paragraphs; `text/path.py`: glyphs along a path |
|
|
1394
|
+
| `fonts/` | standard and embedded fonts; `fontdb` (discovery and matching), `sfnt`, `cff`, `cff2`, `variations`, `opentype`, `woff2` |
|
|
1395
|
+
| `svg/` | `convert`; `parse` (XML, CSS cascade, `SVGDocument`), `css`, `render` (SVG semantics drawn through a `Canvas`), `filters` (`<filter>` as a `Filter`), `writer` (SVG output of scenes, written by the C library) |
|
|
1396
|
+
| `image/` | `Image` and the output options (`PNG`, `JPEG`, `JXL`, `AVIF`, `ImagePolicy`); `codecs`, `jxl`, `avif`, `resource` (loading, `ImageResource`) |
|
|
1397
|
+
| `pdf/` | `objects` (PDF values, numbered when written), `writer`, `images` (image XObjects), `pdfa` |
|
|
1398
|
+
| `raster/` | rasterizer bindings, resource tables, rasterized groups |
|
|
1399
|
+
| `draw.py` | drawings of elements |
|
|
1400
|
+
| `arrays.py`, `flate.py`, `threads.py` | array arguments ([§21](#21-array-arguments)); parallel zlib; worker threads (`VEC_IO_THREADS`) |
|
|
1401
|
+
| `native/` | the bundled C library: sources, binaries, generated bindings |
|
|
1402
|
+
|
|
1403
|
+
## 21. Array arguments
|
|
1404
|
+
|
|
1405
|
+
`vec_io.arrays` reads array arguments: sequences (`None` for missing values) or buffers, as
|
|
1406
|
+
everywhere ([Conventions](#1-conventions)). Buffers are converted in C, whatever their item
|
|
1407
|
+
type and byte order.
|
|
1408
|
+
|
|
1409
|
+
| Function | Returns |
|
|
1410
|
+
|---|---|
|
|
1411
|
+
| `doubles(values)` | a 1-D sequence or buffer as `array('d')`, `None` as NaN; an `array('d')` is returned as it is |
|
|
1412
|
+
|
|
1413
|
+
Statistics (binning, densities, contours, quantiles, scaling) and color maps are plot_io's.
|