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.
- vec_io/__init__.py +38 -0
- vec_io/__main__.py +138 -0
- vec_io/arrays.py +83 -0
- vec_io/canvas.py +2086 -0
- vec_io/cells.py +513 -0
- vec_io/document.py +779 -0
- vec_io/draw.py +1013 -0
- vec_io/filter.py +787 -0
- vec_io/flate.py +59 -0
- vec_io/fonts/__init__.py +519 -0
- vec_io/fonts/cff.py +612 -0
- vec_io/fonts/cff2.py +603 -0
- vec_io/fonts/fontdb.py +465 -0
- vec_io/fonts/opentype.py +384 -0
- vec_io/fonts/sfnt.py +905 -0
- vec_io/fonts/variations.py +845 -0
- vec_io/fonts/woff2.py +246 -0
- vec_io/geometry.py +914 -0
- vec_io/image/__init__.py +232 -0
- vec_io/image/avif.py +184 -0
- vec_io/image/codecs.py +115 -0
- vec_io/image/jxl.py +440 -0
- vec_io/image/resource.py +269 -0
- vec_io/items.py +227 -0
- vec_io/lines.py +377 -0
- vec_io/native/__init__.py +329 -0
- vec_io/native/_api.py +712 -0
- vec_io/native/bin/linux-aarch64.so +0 -0
- vec_io/native/bin/linux-x86_64.so +0 -0
- vec_io/native/bin/macos-arm64.dylib +0 -0
- vec_io/native/bin/windows-arm64.dll +0 -0
- vec_io/native/bin/windows-x86_64.dll +0 -0
- vec_io/native/brotli.py +19 -0
- vec_io/native/deflate.py +21 -0
- vec_io/native/emit.py +293 -0
- vec_io/native/filters.py +205 -0
- vec_io/native/jpeg.py +89 -0
- vec_io/native/png.py +458 -0
- vec_io/native/woff2.py +24 -0
- vec_io/paint.py +769 -0
- vec_io/pdf/__init__.py +0 -0
- vec_io/pdf/images.py +205 -0
- vec_io/pdf/objects.py +147 -0
- vec_io/pdf/pdfa.py +163 -0
- vec_io/pdf/writer.py +208 -0
- vec_io/py.typed +0 -0
- vec_io/raster/__init__.py +530 -0
- vec_io/raster/groups.py +102 -0
- vec_io/raster/resources.py +400 -0
- vec_io/scene.py +672 -0
- vec_io/shapes.py +294 -0
- vec_io/simplify.py +402 -0
- vec_io/svg/__init__.py +104 -0
- vec_io/svg/css.py +355 -0
- vec_io/svg/filters.py +286 -0
- vec_io/svg/parse.py +341 -0
- vec_io/svg/render.py +1868 -0
- vec_io/svg/writer.py +480 -0
- vec_io/text/__init__.py +944 -0
- vec_io/text/breaking.py +583 -0
- vec_io/text/path.py +116 -0
- vec_io/threads.py +80 -0
- vec_io/units.py +105 -0
- vec_io-0.1.0.dist-info/METADATA +168 -0
- vec_io-0.1.0.dist-info/RECORD +68 -0
- vec_io-0.1.0.dist-info/WHEEL +4 -0
- vec_io-0.1.0.dist-info/entry_points.txt +2 -0
- 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)
|