vec-io 0.1.0__py3-none-any.whl

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 (68) hide show
  1. vec_io/__init__.py +38 -0
  2. vec_io/__main__.py +138 -0
  3. vec_io/arrays.py +83 -0
  4. vec_io/canvas.py +2086 -0
  5. vec_io/cells.py +513 -0
  6. vec_io/document.py +779 -0
  7. vec_io/draw.py +1013 -0
  8. vec_io/filter.py +787 -0
  9. vec_io/flate.py +59 -0
  10. vec_io/fonts/__init__.py +519 -0
  11. vec_io/fonts/cff.py +612 -0
  12. vec_io/fonts/cff2.py +603 -0
  13. vec_io/fonts/fontdb.py +465 -0
  14. vec_io/fonts/opentype.py +384 -0
  15. vec_io/fonts/sfnt.py +905 -0
  16. vec_io/fonts/variations.py +845 -0
  17. vec_io/fonts/woff2.py +246 -0
  18. vec_io/geometry.py +914 -0
  19. vec_io/image/__init__.py +232 -0
  20. vec_io/image/avif.py +184 -0
  21. vec_io/image/codecs.py +115 -0
  22. vec_io/image/jxl.py +440 -0
  23. vec_io/image/resource.py +269 -0
  24. vec_io/items.py +227 -0
  25. vec_io/lines.py +377 -0
  26. vec_io/native/__init__.py +329 -0
  27. vec_io/native/_api.py +712 -0
  28. vec_io/native/bin/linux-aarch64.so +0 -0
  29. vec_io/native/bin/linux-x86_64.so +0 -0
  30. vec_io/native/bin/macos-arm64.dylib +0 -0
  31. vec_io/native/bin/windows-arm64.dll +0 -0
  32. vec_io/native/bin/windows-x86_64.dll +0 -0
  33. vec_io/native/brotli.py +19 -0
  34. vec_io/native/deflate.py +21 -0
  35. vec_io/native/emit.py +293 -0
  36. vec_io/native/filters.py +205 -0
  37. vec_io/native/jpeg.py +89 -0
  38. vec_io/native/png.py +458 -0
  39. vec_io/native/woff2.py +24 -0
  40. vec_io/paint.py +769 -0
  41. vec_io/pdf/__init__.py +0 -0
  42. vec_io/pdf/images.py +205 -0
  43. vec_io/pdf/objects.py +147 -0
  44. vec_io/pdf/pdfa.py +163 -0
  45. vec_io/pdf/writer.py +208 -0
  46. vec_io/py.typed +0 -0
  47. vec_io/raster/__init__.py +530 -0
  48. vec_io/raster/groups.py +102 -0
  49. vec_io/raster/resources.py +400 -0
  50. vec_io/scene.py +672 -0
  51. vec_io/shapes.py +294 -0
  52. vec_io/simplify.py +402 -0
  53. vec_io/svg/__init__.py +104 -0
  54. vec_io/svg/css.py +355 -0
  55. vec_io/svg/filters.py +286 -0
  56. vec_io/svg/parse.py +341 -0
  57. vec_io/svg/render.py +1868 -0
  58. vec_io/svg/writer.py +480 -0
  59. vec_io/text/__init__.py +944 -0
  60. vec_io/text/breaking.py +583 -0
  61. vec_io/text/path.py +116 -0
  62. vec_io/threads.py +80 -0
  63. vec_io/units.py +105 -0
  64. vec_io-0.1.0.dist-info/METADATA +168 -0
  65. vec_io-0.1.0.dist-info/RECORD +68 -0
  66. vec_io-0.1.0.dist-info/WHEEL +4 -0
  67. vec_io-0.1.0.dist-info/entry_points.txt +2 -0
  68. vec_io-0.1.0.dist-info/licenses/LICENSE +28 -0
vec_io/__init__.py ADDED
@@ -0,0 +1,38 @@
1
+ """vec_io: create PDF files and images in Python, with no dependencies (a bundled C library
2
+ does the heavy lifting).
3
+
4
+ Basic usage::
5
+
6
+ import vec_io
7
+
8
+ doc = vec_io.Document(unit="mm")
9
+ page = doc.add_page(size="A4")
10
+ page.fill_rect(20, 20, 50, 30, fill="tomato")
11
+ page.fill_text("Hello", 20, 70)
12
+ doc.save("hello.pdf")
13
+ page.render(dpi=150).save("hello.png")
14
+
15
+ vec_io.convert("drawing.svg", "drawing.pdf")
16
+ """
17
+
18
+ from .canvas import Canvas
19
+ from .document import Bookmark, Document, Page
20
+ from .fonts.fontdb import FontDB
21
+ from .geometry import Matrix, Path
22
+ from .image import AVIF, JPEG, JXL, PNG, Image, ImagePolicy
23
+ from .filter import Filter
24
+ from .paint import (CMYK, Color, LinearGradient, Marker, Mask, Paint, PathMarker, Pattern,
25
+ RadialGradient, Spot, rgb255)
26
+ from .svg import SVGDocument, SVGParseError, convert
27
+ from .text import Content, Font, GlyphRun, Span
28
+ from .text.breaking import TextLayout
29
+
30
+ __all__ = ["convert", "SVGDocument", "SVGParseError",
31
+ "Document", "Page", "Canvas", "Bookmark",
32
+ "Color", "CMYK", "Spot", "rgb255", "Paint", "LinearGradient", "RadialGradient",
33
+ "Pattern", "Mask", "Marker", "PathMarker", "Filter",
34
+ "Matrix", "Path", "Font", "GlyphRun", "Span", "Content", "TextLayout", "FontDB",
35
+ "Image", "ImagePolicy", "PNG", "JPEG", "JXL", "AVIF",
36
+ "__version__"]
37
+
38
+ __version__ = "0.1.0"
vec_io/__main__.py ADDED
@@ -0,0 +1,138 @@
1
+ """Command line interface: ``python -m vec_io input.svg [output.pdf]``."""
2
+
3
+ import argparse
4
+ import os
5
+ import sys
6
+
7
+ from . import AVIF, JPEG, JXL, PNG, ImagePolicy, SVGParseError, __version__, convert
8
+ from .raster import RasterizationError
9
+
10
+
11
+ def main(argv=None):
12
+ parser = argparse.ArgumentParser(
13
+ prog="vec_io", description="Convert an SVG file to PDF (or PNG, JPEG, JPEG XL, AVIF).")
14
+ parser.add_argument("input", help="input SVG file, or '-' for stdin")
15
+ parser.add_argument("output", nargs="?",
16
+ help="output file ('-' for stdout); defaults to the "
17
+ "input name with the extension of --format")
18
+ parser.add_argument("--format", choices=["pdf", "png", "jpeg", "jxl", "avif"], default="pdf",
19
+ help="output format (default pdf)")
20
+ parser.add_argument("--no-compress", action="store_true",
21
+ help="write uncompressed content streams")
22
+ parser.add_argument("--compression", choices=["fast", "balanced", "best"],
23
+ default=None,
24
+ help="zlib level 1, zlib level 6 (default), or vec_io's near-optimal "
25
+ "Deflate; for PNG output, balanced and best also search the "
26
+ "smallest pixel layout; for JPEG output, fast skips trellis "
27
+ "quantization, best tries several progressive scan orders; for "
28
+ "JPEG XL and AVIF, the encoder's effort")
29
+ parser.add_argument("--font-dir", action="append", default=[], metavar="DIR",
30
+ help="extra directory to search for fonts (repeatable)")
31
+ parser.add_argument("--no-system-fonts", action="store_true",
32
+ help="do not use fonts installed on the system")
33
+ parser.add_argument("--filters", action="store_true",
34
+ help="apply SVG filter effects (rasterized)")
35
+ parser.add_argument("--quality", type=int, default=None, metavar="N",
36
+ help="store rasterized content as JPEG (JPEG XL with --jxl) of "
37
+ "quality N (1-100); with --format jpeg, jxl or avif, the "
38
+ "quality of the image; with --format png, a lossy palette")
39
+ parser.add_argument("--jxl", action="store_true",
40
+ help="write the PDF's images as JPEG XL (/JXLDecode, read by PDFium; "
41
+ "needs libjxl)")
42
+ parser.add_argument("--pdf-a", choices=["2b", "2u", "3b"], default=None,
43
+ help="write a PDF/A file of this conformance level")
44
+ parser.add_argument("--simplify", nargs="?", const="true", default=None, metavar="DPI",
45
+ help="leave out what later content hides at DPI (default 600); "
46
+ "'auto': only where it pays off")
47
+ parser.add_argument("--dpi", type=float, default=96,
48
+ help="resolution of image output (default 96: 1px per CSS px)")
49
+ parser.add_argument("--version", action="version", version="%(prog)s " + __version__)
50
+ args = parser.parse_args(argv)
51
+ if not args.dpi > 0:
52
+ parser.error("--dpi must be positive")
53
+ simplify = None
54
+ if args.simplify is not None:
55
+ if args.simplify.lower() == "auto":
56
+ simplify = "auto"
57
+ else:
58
+ try:
59
+ simplify = True if args.simplify == "true" else float(args.simplify)
60
+ except ValueError:
61
+ parser.error("--simplify takes a resolution in dpi or 'auto'")
62
+ if simplify is not True and not 0 < simplify < float("inf"):
63
+ parser.error("--simplify must be positive")
64
+ fmt = args.format
65
+ if fmt != "pdf":
66
+ pdf_only = [flag for flag, used in (("--pdf-a", args.pdf_a is not None),
67
+ ("--jxl", args.jxl),
68
+ ("--no-compress", args.no_compress))
69
+ if used]
70
+ if pdf_only:
71
+ parser.error(f"{', '.join(pdf_only)} cannot be used with --format {fmt}")
72
+ if args.quality is not None and not 1 <= args.quality <= 100:
73
+ parser.error("--quality must be between 1 and 100")
74
+
75
+ try:
76
+ if args.input == "-":
77
+ data = sys.stdin.buffer.read()
78
+ base_dir = os.getcwd()
79
+ else:
80
+ with open(args.input, "rb") as fh:
81
+ data = fh.read()
82
+ base_dir = os.path.dirname(os.path.abspath(args.input))
83
+ except OSError as exc:
84
+ parser.exit(1, f"vec_io: cannot read input: {exc}\n")
85
+
86
+ options = dict(base_dir=base_dir, font_dirs=args.font_dir,
87
+ system_fonts=not args.no_system_fonts, filters=args.filters,
88
+ simplify=simplify)
89
+ compression = args.compression or "balanced"
90
+ if fmt == "pdf":
91
+ options.update(compress=not args.no_compress, compression=compression,
92
+ pdf_a=args.pdf_a, images=ImagePolicy(quality=args.quality, jxl=args.jxl))
93
+ else:
94
+ options["dpi"] = args.dpi
95
+ if fmt == "png":
96
+ options["png"] = PNG(compression, args.quality)
97
+ elif fmt == "jpeg":
98
+ options["jpeg"] = JPEG(args.quality or 90, compression)
99
+ elif fmt == "jxl":
100
+ options["jxl"] = JXL(args.quality, compression)
101
+ else:
102
+ options["avif"] = AVIF(args.quality, compression)
103
+ try:
104
+ data = convert(data, format=fmt, **options)
105
+ except (SVGParseError, RasterizationError, ValueError) as exc:
106
+ parser.exit(1, f"vec_io: {exc}\n")
107
+
108
+ output = args.output
109
+ if output is None:
110
+ if args.input == "-":
111
+ output = "-"
112
+ else:
113
+ ext = {"pdf": ".pdf", "png": ".png", "jpeg": ".jpg", "jxl": ".jxl",
114
+ "avif": ".avif"}[fmt]
115
+ output = os.path.splitext(args.input)[0] + ext
116
+ if output == "-":
117
+ if sys.stdout is None: # file descriptor 1 is closed
118
+ parser.exit(1, "vec_io: cannot write output: standard output is closed\n")
119
+ try:
120
+ sys.stdout.buffer.write(data)
121
+ sys.stdout.buffer.flush()
122
+ except BrokenPipeError:
123
+ # The reader went away; keep Python's exit flush from failing too.
124
+ os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
125
+ return 1
126
+ except OSError as exc:
127
+ parser.exit(1, f"vec_io: cannot write output: {exc}\n")
128
+ else:
129
+ try:
130
+ with open(output, "wb") as fh:
131
+ fh.write(data)
132
+ except OSError as exc:
133
+ parser.exit(1, f"vec_io: cannot write output: {exc}\n")
134
+ return 0
135
+
136
+
137
+ if __name__ == "__main__":
138
+ sys.exit(main())
vec_io/arrays.py ADDED
@@ -0,0 +1,83 @@
1
+ """Array arguments: sequences of numbers (``None`` for missing values) or any object
2
+ exporting the buffer protocol (``array.array``, ``memoryview``, numpy arrays, ...). Buffers
3
+ are read through :class:`memoryview` and converted in C; numpy is never imported."""
4
+
5
+ import array
6
+ import math
7
+ import sys
8
+
9
+ from .native import emit
10
+
11
+ __all__ = ["doubles", "buffer_shape", "rows", "grid"]
12
+
13
+ _FORMATS = {"d": ("f", 8), "f": ("f", 4), "b": ("i", 1), "B": ("u", 1), "h": ("i", 2),
14
+ "H": ("u", 2), "i": ("i", 4), "I": ("u", 4), "l": ("i", 0), "L": ("u", 0),
15
+ "q": ("i", 8), "Q": ("u", 8), "n": ("i", 0), "N": ("u", 0), "?": ("b", 1)}
16
+
17
+
18
+ def _view(value):
19
+ """A memoryview of ``value`` when it has a numeric buffer, else ``None``."""
20
+ if isinstance(value, (str, bytes, bytearray)):
21
+ return None
22
+ try:
23
+ mv = memoryview(value)
24
+ except TypeError:
25
+ return None
26
+ fmt = mv.format.lstrip("@=<>!")
27
+ return mv if fmt in _FORMATS else None
28
+
29
+
30
+ def buffer_shape(value):
31
+ """Shape of a numeric buffer, or ``None`` for other values."""
32
+ mv = _view(value)
33
+ return None if mv is None else tuple(mv.shape)
34
+
35
+
36
+ def _convert(mv):
37
+ """All items of a numeric buffer as ``array('d')`` (row-major)."""
38
+ order = mv.format[0] if mv.format[0] in "@=<>!" else "@"
39
+ kind, size = _FORMATS[mv.format.lstrip("@=<>!")]
40
+ size = size or mv.itemsize
41
+ n = mv.nbytes // mv.itemsize if mv.itemsize else 0
42
+ little = order in "<" or (order in "@=" and sys.byteorder == "little")
43
+ if not n:
44
+ return array.array("d")
45
+ if kind == "f" and size == 8 and little == (sys.byteorder == "little"):
46
+ out = array.array("d")
47
+ out.frombytes(mv.tobytes() if not mv.c_contiguous else mv.cast("B"))
48
+ return out
49
+ data = mv if mv.c_contiguous else mv.tobytes()
50
+ return emit.to_doubles(data, kind, size, little, size, n)
51
+
52
+
53
+ def doubles(value):
54
+ """``value`` (a sequence or a 1-D buffer) as ``array('d')``; ``None`` becomes NaN."""
55
+ if isinstance(value, array.array) and value.typecode == "d":
56
+ return value
57
+ mv = _view(value)
58
+ if mv is not None:
59
+ if mv.ndim != 1:
60
+ raise ValueError("expected a 1-D sequence")
61
+ return _convert(mv)
62
+ nan = math.nan
63
+ return array.array("d", [nan if v is None else float(v) for v in value])
64
+
65
+
66
+ def rows(value):
67
+ """A 2-D buffer as a list of ``array('d')`` rows, or ``None`` for other values."""
68
+ mv = _view(value)
69
+ if mv is None or mv.ndim != 2:
70
+ return None
71
+ h, w = mv.shape
72
+ flat = _convert(mv)
73
+ return [flat[i * w:(i + 1) * w] for i in range(h)]
74
+
75
+
76
+ def grid(value):
77
+ """``(height, width, k, array('d'))`` of a 2-D (k = 1) or 3-D buffer, or ``None``."""
78
+ mv = _view(value)
79
+ if mv is None or mv.ndim not in (2, 3):
80
+ return None
81
+ h, w = mv.shape[:2]
82
+ k = mv.shape[2] if mv.ndim == 3 else 1
83
+ return h, w, k, _convert(mv)