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.
Files changed (226) hide show
  1. vec_io-0.1.0/API.md +1413 -0
  2. vec_io-0.1.0/EXAMPLES.md +612 -0
  3. vec_io-0.1.0/LICENSE +28 -0
  4. vec_io-0.1.0/PKG-INFO +168 -0
  5. vec_io-0.1.0/README.md +153 -0
  6. vec_io-0.1.0/examples/bubble_chart.py +72 -0
  7. vec_io-0.1.0/examples/canvas_demo.py +197 -0
  8. vec_io-0.1.0/examples/drawing_demo.py +80 -0
  9. vec_io-0.1.0/examples/heatmap.py +79 -0
  10. vec_io-0.1.0/examples/scatter_plot.py +74 -0
  11. vec_io-0.1.0/examples/showcase.svg +168 -0
  12. vec_io-0.1.0/examples/text.svg +79 -0
  13. vec_io-0.1.0/examples/text_layout.py +56 -0
  14. vec_io-0.1.0/pyproject.toml +45 -0
  15. vec_io-0.1.0/tests/brotli/abc.br +0 -0
  16. vec_io-0.1.0/tests/brotli/abc.out.z +0 -0
  17. vec_io-0.1.0/tests/brotli/big.br +0 -0
  18. vec_io-0.1.0/tests/brotli/big.out.z +0 -0
  19. vec_io-0.1.0/tests/brotli/binary.br +0 -0
  20. vec_io-0.1.0/tests/brotli/binary.out.z +0 -0
  21. vec_io-0.1.0/tests/brotli/bits2.br +5 -0
  22. vec_io-0.1.0/tests/brotli/bits2.out.z +0 -0
  23. vec_io-0.1.0/tests/brotli/dictwords.br +0 -0
  24. vec_io-0.1.0/tests/brotli/dictwords.out.z +0 -0
  25. vec_io-0.1.0/tests/brotli/empty.br +1 -0
  26. vec_io-0.1.0/tests/brotli/empty.out.z +0 -0
  27. vec_io-0.1.0/tests/brotli/four.br +0 -0
  28. vec_io-0.1.0/tests/brotli/four.out.z +0 -0
  29. vec_io-0.1.0/tests/brotli/handmade.br +0 -0
  30. vec_io-0.1.0/tests/brotli/handmade.out.z +0 -0
  31. vec_io-0.1.0/tests/brotli/ints.br +0 -0
  32. vec_io-0.1.0/tests/brotli/ints.out.z +0 -0
  33. vec_io-0.1.0/tests/brotli/mixed.br +0 -0
  34. vec_io-0.1.0/tests/brotli/mixed.out.z +0 -0
  35. vec_io-0.1.0/tests/brotli/multi.br +0 -0
  36. vec_io-0.1.0/tests/brotli/multi.out.z +2 -0
  37. vec_io-0.1.0/tests/brotli/one.br +0 -0
  38. vec_io-0.1.0/tests/brotli/one.out.z +0 -0
  39. vec_io-0.1.0/tests/brotli/random.br +0 -0
  40. vec_io-0.1.0/tests/brotli/random.out.z +0 -0
  41. vec_io-0.1.0/tests/brotli/text11.br +0 -0
  42. vec_io-0.1.0/tests/brotli/text11.out.z +0 -0
  43. vec_io-0.1.0/tests/brotli/text_q2.br +0 -0
  44. vec_io-0.1.0/tests/brotli/text_q2.out.z +0 -0
  45. vec_io-0.1.0/tests/brotli/three.br +0 -0
  46. vec_io-0.1.0/tests/brotli/three.out.z +0 -0
  47. vec_io-0.1.0/tests/brotli/w10.br +0 -0
  48. vec_io-0.1.0/tests/brotli/w10.out.z +0 -0
  49. vec_io-0.1.0/tests/brotli/w24.br +0 -0
  50. vec_io-0.1.0/tests/brotli/w24.out.z +0 -0
  51. vec_io-0.1.0/tests/corpus/clipping.svg +8 -0
  52. vec_io-0.1.0/tests/corpus/css.svg +13 -0
  53. vec_io-0.1.0/tests/corpus/empty.svg +3 -0
  54. vec_io-0.1.0/tests/corpus/filters.svg +6 -0
  55. vec_io-0.1.0/tests/corpus/fonts_face.svg +5 -0
  56. vec_io-0.1.0/tests/corpus/gradients.svg +14 -0
  57. vec_io-0.1.0/tests/corpus/images.svg +5 -0
  58. vec_io-0.1.0/tests/corpus/markers.svg +6 -0
  59. vec_io-0.1.0/tests/corpus/masks.svg +7 -0
  60. vec_io-0.1.0/tests/corpus/opacity.svg +4 -0
  61. vec_io-0.1.0/tests/corpus/paths.svg +6 -0
  62. vec_io-0.1.0/tests/corpus/patterns.svg +7 -0
  63. vec_io-0.1.0/tests/corpus/rasterized.svg +4 -0
  64. vec_io-0.1.0/tests/corpus/shapes.svg +9 -0
  65. vec_io-0.1.0/tests/corpus/strokes.svg +10 -0
  66. vec_io-0.1.0/tests/corpus/structure.svg +7 -0
  67. vec_io-0.1.0/tests/corpus/text-wrap.svg +4 -0
  68. vec_io-0.1.0/tests/corpus/text_basic.svg +7 -0
  69. vec_io-0.1.0/tests/corpus/text_layout.svg +7 -0
  70. vec_io-0.1.0/tests/corpus/text_path.svg +6 -0
  71. vec_io-0.1.0/tests/corpus/text_unicode.svg +5 -0
  72. vec_io-0.1.0/tests/corpus/transforms.svg +7 -0
  73. vec_io-0.1.0/tests/corpus/units.svg +5 -0
  74. vec_io-0.1.0/tests/corpus/use_symbol.svg +8 -0
  75. vec_io-0.1.0/tests/corpus/viewbox.svg +8 -0
  76. vec_io-0.1.0/tests/fonts/PioTest-Bold.ttf +0 -0
  77. vec_io-0.1.0/tests/fonts/PioTest-Regular.ttf +0 -0
  78. vec_io-0.1.0/tests/fuzz_native.py +864 -0
  79. vec_io-0.1.0/tests/helpers.py +540 -0
  80. vec_io-0.1.0/tests/maps.py +90 -0
  81. vec_io-0.1.0/tests/scene_oracle.py +696 -0
  82. vec_io-0.1.0/tests/shapes.py +20 -0
  83. vec_io-0.1.0/tests/svg_oracle.py +917 -0
  84. vec_io-0.1.0/tests/test_api.py +1281 -0
  85. vec_io-0.1.0/tests/test_avif.py +112 -0
  86. vec_io-0.1.0/tests/test_canvas_features.py +302 -0
  87. vec_io-0.1.0/tests/test_cells.py +311 -0
  88. vec_io-0.1.0/tests/test_cmyk.py +334 -0
  89. vec_io-0.1.0/tests/test_codecs.py +33 -0
  90. vec_io-0.1.0/tests/test_compile.py +131 -0
  91. vec_io-0.1.0/tests/test_convert.py +435 -0
  92. vec_io-0.1.0/tests/test_deflate.py +57 -0
  93. vec_io-0.1.0/tests/test_features.py +342 -0
  94. vec_io-0.1.0/tests/test_filters.py +882 -0
  95. vec_io-0.1.0/tests/test_fonts_io.py +562 -0
  96. vec_io-0.1.0/tests/test_jxl.py +252 -0
  97. vec_io-0.1.0/tests/test_marker_runs.py +140 -0
  98. vec_io-0.1.0/tests/test_native.py +299 -0
  99. vec_io-0.1.0/tests/test_native_emit.py +197 -0
  100. vec_io-0.1.0/tests/test_native_jpeg.py +562 -0
  101. vec_io-0.1.0/tests/test_native_png.py +565 -0
  102. vec_io-0.1.0/tests/test_output.py +329 -0
  103. vec_io-0.1.0/tests/test_parsing.py +246 -0
  104. vec_io-0.1.0/tests/test_pdfa.py +302 -0
  105. vec_io-0.1.0/tests/test_raster.py +1113 -0
  106. vec_io-0.1.0/tests/test_scene.py +372 -0
  107. vec_io-0.1.0/tests/test_shapes.py +141 -0
  108. vec_io-0.1.0/tests/test_simplify.py +583 -0
  109. vec_io-0.1.0/tests/test_svg_semantics.py +278 -0
  110. vec_io-0.1.0/tests/test_svg_writer.py +214 -0
  111. vec_io-0.1.0/tests/test_text.py +174 -0
  112. vec_io-0.1.0/tests/test_textlayout.py +414 -0
  113. vec_io-0.1.0/tests/test_threads.py +162 -0
  114. vec_io-0.1.0/tests/test_units.py +69 -0
  115. vec_io-0.1.0/tests/test_varfonts.py +248 -0
  116. vec_io-0.1.0/tests/test_woff2.py +160 -0
  117. vec_io-0.1.0/tests/testfont.py +204 -0
  118. vec_io-0.1.0/tests/varfonts/VarTest.otf +0 -0
  119. vec_io-0.1.0/tests/varfonts/VarTest.ttf +0 -0
  120. vec_io-0.1.0/tests/woff2/make.py +163 -0
  121. vec_io-0.1.0/tests/woff2/pair.woff2 +0 -0
  122. vec_io-0.1.0/tests/woff2/rich-null.woff2 +0 -0
  123. vec_io-0.1.0/tests/woff2/rich.ttf +0 -0
  124. vec_io-0.1.0/tests/woff2/rich.woff2 +0 -0
  125. vec_io-0.1.0/tools/build_native.py +421 -0
  126. vec_io-0.1.0/tools/gen_api.py +311 -0
  127. vec_io-0.1.0/vec_io/__init__.py +38 -0
  128. vec_io-0.1.0/vec_io/__main__.py +138 -0
  129. vec_io-0.1.0/vec_io/arrays.py +83 -0
  130. vec_io-0.1.0/vec_io/canvas.py +2086 -0
  131. vec_io-0.1.0/vec_io/cells.py +513 -0
  132. vec_io-0.1.0/vec_io/document.py +779 -0
  133. vec_io-0.1.0/vec_io/draw.py +1013 -0
  134. vec_io-0.1.0/vec_io/filter.py +787 -0
  135. vec_io-0.1.0/vec_io/flate.py +59 -0
  136. vec_io-0.1.0/vec_io/fonts/__init__.py +519 -0
  137. vec_io-0.1.0/vec_io/fonts/cff.py +612 -0
  138. vec_io-0.1.0/vec_io/fonts/cff2.py +603 -0
  139. vec_io-0.1.0/vec_io/fonts/fontdb.py +465 -0
  140. vec_io-0.1.0/vec_io/fonts/opentype.py +384 -0
  141. vec_io-0.1.0/vec_io/fonts/sfnt.py +905 -0
  142. vec_io-0.1.0/vec_io/fonts/variations.py +845 -0
  143. vec_io-0.1.0/vec_io/fonts/woff2.py +246 -0
  144. vec_io-0.1.0/vec_io/geometry.py +914 -0
  145. vec_io-0.1.0/vec_io/image/__init__.py +232 -0
  146. vec_io-0.1.0/vec_io/image/avif.py +184 -0
  147. vec_io-0.1.0/vec_io/image/codecs.py +115 -0
  148. vec_io-0.1.0/vec_io/image/jxl.py +440 -0
  149. vec_io-0.1.0/vec_io/image/resource.py +269 -0
  150. vec_io-0.1.0/vec_io/items.py +227 -0
  151. vec_io-0.1.0/vec_io/lines.py +377 -0
  152. vec_io-0.1.0/vec_io/native/README.md +248 -0
  153. vec_io-0.1.0/vec_io/native/__init__.py +329 -0
  154. vec_io-0.1.0/vec_io/native/_api.py +712 -0
  155. vec_io-0.1.0/vec_io/native/bin/linux-aarch64.so +0 -0
  156. vec_io-0.1.0/vec_io/native/bin/linux-x86_64.so +0 -0
  157. vec_io-0.1.0/vec_io/native/bin/macos-arm64.dylib +0 -0
  158. vec_io-0.1.0/vec_io/native/bin/windows-arm64.dll +0 -0
  159. vec_io-0.1.0/vec_io/native/bin/windows-x86_64.dll +0 -0
  160. vec_io-0.1.0/vec_io/native/brotli.py +19 -0
  161. vec_io-0.1.0/vec_io/native/deflate.py +21 -0
  162. vec_io-0.1.0/vec_io/native/emit.py +293 -0
  163. vec_io-0.1.0/vec_io/native/filters.py +205 -0
  164. vec_io-0.1.0/vec_io/native/jpeg.py +89 -0
  165. vec_io-0.1.0/vec_io/native/manifest.json +49 -0
  166. vec_io-0.1.0/vec_io/native/png.py +458 -0
  167. vec_io-0.1.0/vec_io/native/src/alloc.c +185 -0
  168. vec_io-0.1.0/vec_io/native/src/brotli.c +716 -0
  169. vec_io-0.1.0/vec_io/native/src/brotli.h +16 -0
  170. vec_io-0.1.0/vec_io/native/src/brotli_dict.c +3950 -0
  171. vec_io-0.1.0/vec_io/native/src/crowding.c +246 -0
  172. vec_io-0.1.0/vec_io/native/src/cull.c +1358 -0
  173. vec_io-0.1.0/vec_io/native/src/data.c +245 -0
  174. vec_io-0.1.0/vec_io/native/src/deflate.c +930 -0
  175. vec_io-0.1.0/vec_io/native/src/emit.c +319 -0
  176. vec_io-0.1.0/vec_io/native/src/filters.c +1120 -0
  177. vec_io-0.1.0/vec_io/native/src/image.c +150 -0
  178. vec_io-0.1.0/vec_io/native/src/interp.c +5275 -0
  179. vec_io-0.1.0/vec_io/native/src/items.c +327 -0
  180. vec_io-0.1.0/vec_io/native/src/jpeg.h +32 -0
  181. vec_io-0.1.0/vec_io/native/src/jpeg_dec.c +894 -0
  182. vec_io-0.1.0/vec_io/native/src/jpeg_enc.c +1350 -0
  183. vec_io-0.1.0/vec_io/native/src/math.c +356 -0
  184. vec_io-0.1.0/vec_io/native/src/paint.c +787 -0
  185. vec_io-0.1.0/vec_io/native/src/path.c +356 -0
  186. vec_io-0.1.0/vec_io/native/src/pio.h +164 -0
  187. vec_io-0.1.0/vec_io/native/src/pio_api.h +671 -0
  188. vec_io-0.1.0/vec_io/native/src/png.c +959 -0
  189. vec_io-0.1.0/vec_io/native/src/quant.c +648 -0
  190. vec_io-0.1.0/vec_io/native/src/raster.h +434 -0
  191. vec_io-0.1.0/vec_io/native/src/res.c +396 -0
  192. vec_io-0.1.0/vec_io/native/src/rt.c +92 -0
  193. vec_io-0.1.0/vec_io/native/src/scan.c +655 -0
  194. vec_io-0.1.0/vec_io/native/src/scene.c +1515 -0
  195. vec_io-0.1.0/vec_io/native/src/scene.h +228 -0
  196. vec_io-0.1.0/vec_io/native/src/shade.c +293 -0
  197. vec_io-0.1.0/vec_io/native/src/shade.h +55 -0
  198. vec_io-0.1.0/vec_io/native/src/signatures.c +169 -0
  199. vec_io-0.1.0/vec_io/native/src/stroke.c +508 -0
  200. vec_io-0.1.0/vec_io/native/src/svgw.c +1776 -0
  201. vec_io-0.1.0/vec_io/native/src/woff2.c +377 -0
  202. vec_io-0.1.0/vec_io/native/woff2.py +24 -0
  203. vec_io-0.1.0/vec_io/paint.py +769 -0
  204. vec_io-0.1.0/vec_io/pdf/__init__.py +0 -0
  205. vec_io-0.1.0/vec_io/pdf/images.py +205 -0
  206. vec_io-0.1.0/vec_io/pdf/objects.py +147 -0
  207. vec_io-0.1.0/vec_io/pdf/pdfa.py +163 -0
  208. vec_io-0.1.0/vec_io/pdf/writer.py +208 -0
  209. vec_io-0.1.0/vec_io/py.typed +0 -0
  210. vec_io-0.1.0/vec_io/raster/__init__.py +530 -0
  211. vec_io-0.1.0/vec_io/raster/groups.py +102 -0
  212. vec_io-0.1.0/vec_io/raster/resources.py +400 -0
  213. vec_io-0.1.0/vec_io/scene.py +672 -0
  214. vec_io-0.1.0/vec_io/shapes.py +294 -0
  215. vec_io-0.1.0/vec_io/simplify.py +402 -0
  216. vec_io-0.1.0/vec_io/svg/__init__.py +104 -0
  217. vec_io-0.1.0/vec_io/svg/css.py +355 -0
  218. vec_io-0.1.0/vec_io/svg/filters.py +286 -0
  219. vec_io-0.1.0/vec_io/svg/parse.py +341 -0
  220. vec_io-0.1.0/vec_io/svg/render.py +1868 -0
  221. vec_io-0.1.0/vec_io/svg/writer.py +480 -0
  222. vec_io-0.1.0/vec_io/text/__init__.py +944 -0
  223. vec_io-0.1.0/vec_io/text/breaking.py +583 -0
  224. vec_io-0.1.0/vec_io/text/path.py +116 -0
  225. vec_io-0.1.0/vec_io/threads.py +80 -0
  226. 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.