We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Doesn't have support for outputting a2 to a0 paper sizes
I was hoping to output some large paper sizes to plot.

the error messages were the same for paper sizes a2 to a0

```
Traceback (most recent call last):
  File "/usr/local/bin/vpype", line 8, in <module>
    sys.exit(cli())
  File "/usr/local/lib/python3.8/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/vpype_cli/cli.py", line 74, in main
    return super().main(args=preprocess_argument_list(args), **extra)
  File "/usr/local/lib/python3.8/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.8/site-packages/click/core.py", line 1290, in invoke
    return _process_result(rv)
  File "/usr/local/lib/python3.8/site-packages/click/core.py", line 1224, in _process_result
    value = ctx.invoke(self.result_callback, value, **ctx.params)
  File "/usr/local/lib/python3.8/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/vpype_cli/cli.py", line 128, in process_pipeline
    execute_processors(processors)
  File "/usr/local/lib/python3.8/site-packages/vpype_cli/cli.py", line 212, in execute_processors
    state = proc(state)
  File "/usr/local/lib/python3.8/site-packages/vpype/decorators.py", line 150, in global_processor
    state.document = f(state.document, *args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/vpype_cli/write.py", line 199, in write
    page_size_px = convert_page_size(page_size)
  File "/usr/local/lib/python3.8/site-packages/vpype/utils.py", line 154, in convert_page_size
    raise ValueError(f"page size '{value}' unknown")
ValueError: page size 'a2' unknown
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `vpype/utils.py`
Content:
```
1 import logging
2 import math
3 import re
4 from typing import Callable, Dict, List, Tuple, Union
5 
6 import click
7 import numpy as np
8 
9 # REMINDER: anything added here must be added to docs/api.rst
10 __all__ = [
11     "UNITS",
12     "ANGLE_UNITS",
13     "PAGE_SIZES",
14     "LengthType",
15     "AngleType",
16     "PageSizeType",
17     "convert_length",
18     "convert_angle",
19     "convert_page_size",
20     "union",
21     # deprecated:
22     "PAGE_FORMATS",
23     "convert",
24     "convert_page_format",
25     "Length",
26 ]
27 
28 
29 def _mm_to_px(x: float, y: float) -> Tuple[float, float]:
30     return x * 96.0 / 25.4, y * 96.0 / 25.4
31 
32 
33 UNITS = {
34     "px": 1.0,
35     "in": 96.0,
36     "mm": 96.0 / 25.4,
37     "cm": 96.0 / 2.54,
38     "pc": 16.0,
39     "pt": 96.0 / 72.0,
40 }
41 
42 ANGLE_UNITS = {
43     "deg": 1.0,
44     "grad": 9.0 / 10.0,  # note: must be before "rad"!
45     "rad": 180.0 / math.pi,
46     "turn": 360.0,
47 }
48 
49 # page sizes in pixel
50 PAGE_SIZES = {
51     "tight": _mm_to_px(0, 0),
52     "a6": _mm_to_px(105.0, 148.0),
53     "a5": _mm_to_px(148.0, 210.0),
54     "a4": _mm_to_px(210.0, 297.0),
55     "a3": _mm_to_px(297.0, 420.0),
56     "letter": _mm_to_px(215.9, 279.4),
57     "legal": _mm_to_px(215.9, 355.6),
58     "executive": _mm_to_px(185.15, 266.7),
59     "tabloid": _mm_to_px(279.4, 431.8),
60 }
61 
62 # deprecated
63 PAGE_FORMATS = PAGE_SIZES
64 
65 
66 def _convert_unit(value: Union[str, float], units: Dict[str, float]) -> float:
67     """Converts a string with unit to a value"""
68     if isinstance(value, str):
69         value = value.strip().lower()
70         for unit, factor in units.items():
71             if value.endswith(unit):
72                 num = value.strip(unit)
73                 return (float(num) if len(num) > 0 else 1.0) * factor
74 
75     return float(value)
76 
77 
78 def convert_length(value: Union[str, float]) -> float:
79     """Convert a length optionally expressed as a string with unit to px value.
80 
81     Args:
82         value: value to convert
83 
84     Returns:
85         converted value
86 
87     Raises:
88         :class:`ValueError`
89     """
90     return _convert_unit(value, UNITS)
91 
92 
93 def convert(value: Union[str, float]) -> float:  # pragma: no cover
94     """Deprecated, use convert_length."""
95     logging.warning(
96         "!!! `vpype.convert()` is deprecated, use `vpype.convert_length()` instead."
97     )
98     return convert_length(value)
99 
100 
101 def convert_angle(value: Union[str, float]) -> float:
102     """Convert an angle optionally expressed as a string with unit to degrees.
103 
104     Args:
105         value: angle to convert
106 
107     Returns:
108         converted angle in degree
109 
110     Raises:
111         :class:`ValueError`
112     """
113     return _convert_unit(value, ANGLE_UNITS)
114 
115 
116 def convert_page_size(value: str) -> Tuple[float, float]:
117     """Converts a string with page size to dimension in pixels.
118 
119     The input can be either a known page size (see ``vpype write --help`` for a list) or
120     a page size descriptor in the form of "WxH" where both W and H can have units.
121 
122     Examples:
123 
124         Using a know page size::
125 
126             >>> import vpype
127             >>> vpype.convert_page_size("a3")
128             (1122.5196850393702, 1587.4015748031497)
129 
130         Using page size descriptor (no units, pixels are assumed)::
131 
132             >>> vpype.convert_page_size("100x200")
133             (100.0, 200.0)
134 
135         Using page size descriptor (explicit units)::
136 
137             >>> vpype.convert_page_size("1inx2in")
138             (96.0, 192.0)
139 
140     Args:
141         value: page size descriptor
142 
143     Returns:
144         the page size in CSS pixels
145     """
146     if value in PAGE_SIZES:
147         return PAGE_SIZES[value]
148 
149     match = re.match(
150         r"^(\d+\.?\d*)({0})?x(\d+\.?\d*)({0})?$".format("|".join(UNITS.keys())), value
151     )
152 
153     if not match:
154         raise ValueError(f"page size '{value}' unknown")
155 
156     x, x_unit, y, y_unit = match.groups()
157 
158     if not x_unit:
159         x_unit = y_unit if y_unit else "px"
160     if not y_unit:
161         y_unit = x_unit
162 
163     return float(x) * convert_length(x_unit), float(y) * convert_length(y_unit)
164 
165 
166 def convert_page_format(value: str) -> Tuple[float, float]:  # pragma: no cover
167     """Deprecated, use convert_page_size."""
168     logging.warning(
169         "!!! `vpype.convert_page_format()` is deprecated, use `vpype.convert_page_size()` "
170         "instead."
171     )
172     return convert_page_size(value)
173 
174 
175 class LengthType(click.ParamType):
176     """:class:`click.ParamType` sub-class to automatically converts a user-provided length
177     string (which may contain units) into a value in CSS pixel units. This class uses
178     :func:`convert_length` internally.
179 
180     Example::
181 
182         >>> import click
183         >>> import vpype_cli
184         >>> import vpype
185         >>> @vpype_cli.cli.command(group="my commands")
186         ... @click.argument("x", type=vpype.LengthType())
187         ... @click.option("-o", "--option", type=vpype.LengthType(), default="1mm")
188         ... @vpype.generator
189         ... def my_command(x: float, option: float):
190         ...     pass
191     """
192 
193     name = "length"
194 
195     def convert(self, value, param, ctx):
196         try:
197             return convert_length(value)
198         except ValueError:
199             self.fail(f"parameter {value} is an incorrect length")
200 
201 
202 class Length(LengthType):  # pragma: no cover
203     """Deprecated, use LengthType."""
204 
205     def __init__(self, *args, **kwargs):
206         super().__init__(*args, **kwargs)
207         logging.warning("!!! `vpype.Length` is deprecated, use `vpype.LengthType` instead.")
208 
209 
210 class AngleType(click.ParamType):
211     """:class:`click.ParamType` sub-class to automatically converts a user-provided angle
212     string (which may contain units) into a value in degrees. This class uses
213     :func:`convert_angle` internally.
214 
215     Example::
216 
217         >>> import click
218         >>> import vpype_cli
219         >>> import vpype
220         >>> @vpype_cli.cli.command(group="my commands")
221         ... @click.argument("angle", type=vpype.AngleType())
222         ... @vpype.generator
223         ... def my_command(angle: float):
224         ...     pass
225     """
226 
227     name = "angle"
228 
229     def convert(self, value, param, ctx):
230         try:
231             return convert_angle(value)
232         except ValueError:
233             self.fail(f"parameter {value} is an incorrect angle")
234 
235 
236 class PageSizeType(click.ParamType):
237     """:class:`click.ParamType` sub-class to automatically converts a user-provided page size
238     string into a tuple of float in CSS pixel units. See :func:`convert_page_size` for
239     information on the page size descriptor syntax.
240 
241     Example::
242 
243         >>> import click
244         >>> import vpype_cli
245         >>> import vpype
246         >>> @vpype_cli.cli.command(group="my commands")
247         ... @click.argument("fmt", type=vpype.PageSizeType())
248         ... @vpype.generator
249         ... def my_command(fmt: Tuple[float, float]):
250         ...     pass
251     """
252 
253     name = "PAGESIZE"
254 
255     def convert(self, value, param, ctx) -> Tuple[float, float]:
256         try:
257             return convert_page_size(value)
258         except ValueError:
259             self.fail(f"parameter {value} is not a valid page size")
260 
261 
262 def union(line: np.ndarray, keys: List[Callable[[np.ndarray], bool]]) -> bool:
263     """Returns True if every callables in ``keys`` return True (similar to ``all()``. This
264     function is typically used with :meth:`LineCollection.filter`.
265 
266     Args:
267         line: line to test
268         keys: list of callables
269 
270     Returns:
271         True if every callables return True
272     """
273     for key in keys:
274         if not key(line):
275             return False
276     return True
277
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/vpype/utils.py b/vpype/utils.py
--- a/vpype/utils.py
+++ b/vpype/utils.py
@@ -53,6 +53,9 @@
     "a5": _mm_to_px(148.0, 210.0),
     "a4": _mm_to_px(210.0, 297.0),
     "a3": _mm_to_px(297.0, 420.0),
+    "a2": _mm_to_px(420.0, 594.0),
+    "a1": _mm_to_px(594.0, 841.0),
+    "a0": _mm_to_px(841.0, 1189.0),
     "letter": _mm_to_px(215.9, 279.4),
     "legal": _mm_to_px(215.9, 355.6),
     "executive": _mm_to_px(185.15, 266.7),

{"golden_diff": "diff --git a/vpype/utils.py b/vpype/utils.py\n--- a/vpype/utils.py\n+++ b/vpype/utils.py\n@@ -53,6 +53,9 @@\n     \"a5\": _mm_to_px(148.0, 210.0),\n     \"a4\": _mm_to_px(210.0, 297.0),\n     \"a3\": _mm_to_px(297.0, 420.0),\n+    \"a2\": _mm_to_px(420.0, 594.0),\n+    \"a1\": _mm_to_px(594.0, 841.0),\n+    \"a0\": _mm_to_px(841.0, 1189.0),\n     \"letter\": _mm_to_px(215.9, 279.4),\n     \"legal\": _mm_to_px(215.9, 355.6),\n     \"executive\": _mm_to_px(185.15, 266.7),\n", "issue": "Doesn't have support for outputting a2 to a0 paper sizes\nI was hoping to output some large paper sizes to plot.\r\n\r\nthe error messages were the same for paper sizes a2 to a0\r\n\r\n```\r\nTraceback (most recent call last):\r\n  File \"/usr/local/bin/vpype\", line 8, in <module>\r\n    sys.exit(cli())\r\n  File \"/usr/local/lib/python3.8/site-packages/click/core.py\", line 829, in __call__\r\n    return self.main(*args, **kwargs)\r\n  File \"/usr/local/lib/python3.8/site-packages/vpype_cli/cli.py\", line 74, in main\r\n    return super().main(args=preprocess_argument_list(args), **extra)\r\n  File \"/usr/local/lib/python3.8/site-packages/click/core.py\", line 782, in main\r\n    rv = self.invoke(ctx)\r\n  File \"/usr/local/lib/python3.8/site-packages/click/core.py\", line 1290, in invoke\r\n    return _process_result(rv)\r\n  File \"/usr/local/lib/python3.8/site-packages/click/core.py\", line 1224, in _process_result\r\n    value = ctx.invoke(self.result_callback, value, **ctx.params)\r\n  File \"/usr/local/lib/python3.8/site-packages/click/core.py\", line 610, in invoke\r\n    return callback(*args, **kwargs)\r\n  File \"/usr/local/lib/python3.8/site-packages/vpype_cli/cli.py\", line 128, in process_pipeline\r\n    execute_processors(processors)\r\n  File \"/usr/local/lib/python3.8/site-packages/vpype_cli/cli.py\", line 212, in execute_processors\r\n    state = proc(state)\r\n  File \"/usr/local/lib/python3.8/site-packages/vpype/decorators.py\", line 150, in global_processor\r\n    state.document = f(state.document, *args, **kwargs)\r\n  File \"/usr/local/lib/python3.8/site-packages/vpype_cli/write.py\", line 199, in write\r\n    page_size_px = convert_page_size(page_size)\r\n  File \"/usr/local/lib/python3.8/site-packages/vpype/utils.py\", line 154, in convert_page_size\r\n    raise ValueError(f\"page size '{value}' unknown\")\r\nValueError: page size 'a2' unknown\r\n```\n", "before_files": [{"content": "import logging\nimport math\nimport re\nfrom typing import Callable, Dict, List, Tuple, Union\n\nimport click\nimport numpy as np\n\n# REMINDER: anything added here must be added to docs/api.rst\n__all__ = [\n    \"UNITS\",\n    \"ANGLE_UNITS\",\n    \"PAGE_SIZES\",\n    \"LengthType\",\n    \"AngleType\",\n    \"PageSizeType\",\n    \"convert_length\",\n    \"convert_angle\",\n    \"convert_page_size\",\n    \"union\",\n    # deprecated:\n    \"PAGE_FORMATS\",\n    \"convert\",\n    \"convert_page_format\",\n    \"Length\",\n]\n\n\ndef _mm_to_px(x: float, y: float) -> Tuple[float, float]:\n    return x * 96.0 / 25.4, y * 96.0 / 25.4\n\n\nUNITS = {\n    \"px\": 1.0,\n    \"in\": 96.0,\n    \"mm\": 96.0 / 25.4,\n    \"cm\": 96.0 / 2.54,\n    \"pc\": 16.0,\n    \"pt\": 96.0 / 72.0,\n}\n\nANGLE_UNITS = {\n    \"deg\": 1.0,\n    \"grad\": 9.0 / 10.0,  # note: must be before \"rad\"!\n    \"rad\": 180.0 / math.pi,\n    \"turn\": 360.0,\n}\n\n# page sizes in pixel\nPAGE_SIZES = {\n    \"tight\": _mm_to_px(0, 0),\n    \"a6\": _mm_to_px(105.0, 148.0),\n    \"a5\": _mm_to_px(148.0, 210.0),\n    \"a4\": _mm_to_px(210.0, 297.0),\n    \"a3\": _mm_to_px(297.0, 420.0),\n    \"letter\": _mm_to_px(215.9, 279.4),\n    \"legal\": _mm_to_px(215.9, 355.6),\n    \"executive\": _mm_to_px(185.15, 266.7),\n    \"tabloid\": _mm_to_px(279.4, 431.8),\n}\n\n# deprecated\nPAGE_FORMATS = PAGE_SIZES\n\n\ndef _convert_unit(value: Union[str, float], units: Dict[str, float]) -> float:\n    \"\"\"Converts a string with unit to a value\"\"\"\n    if isinstance(value, str):\n        value = value.strip().lower()\n        for unit, factor in units.items():\n            if value.endswith(unit):\n                num = value.strip(unit)\n                return (float(num) if len(num) > 0 else 1.0) * factor\n\n    return float(value)\n\n\ndef convert_length(value: Union[str, float]) -> float:\n    \"\"\"Convert a length optionally expressed as a string with unit to px value.\n\n    Args:\n        value: value to convert\n\n    Returns:\n        converted value\n\n    Raises:\n        :class:`ValueError`\n    \"\"\"\n    return _convert_unit(value, UNITS)\n\n\ndef convert(value: Union[str, float]) -> float:  # pragma: no cover\n    \"\"\"Deprecated, use convert_length.\"\"\"\n    logging.warning(\n        \"!!! `vpype.convert()` is deprecated, use `vpype.convert_length()` instead.\"\n    )\n    return convert_length(value)\n\n\ndef convert_angle(value: Union[str, float]) -> float:\n    \"\"\"Convert an angle optionally expressed as a string with unit to degrees.\n\n    Args:\n        value: angle to convert\n\n    Returns:\n        converted angle in degree\n\n    Raises:\n        :class:`ValueError`\n    \"\"\"\n    return _convert_unit(value, ANGLE_UNITS)\n\n\ndef convert_page_size(value: str) -> Tuple[float, float]:\n    \"\"\"Converts a string with page size to dimension in pixels.\n\n    The input can be either a known page size (see ``vpype write --help`` for a list) or\n    a page size descriptor in the form of \"WxH\" where both W and H can have units.\n\n    Examples:\n\n        Using a know page size::\n\n            >>> import vpype\n            >>> vpype.convert_page_size(\"a3\")\n            (1122.5196850393702, 1587.4015748031497)\n\n        Using page size descriptor (no units, pixels are assumed)::\n\n            >>> vpype.convert_page_size(\"100x200\")\n            (100.0, 200.0)\n\n        Using page size descriptor (explicit units)::\n\n            >>> vpype.convert_page_size(\"1inx2in\")\n            (96.0, 192.0)\n\n    Args:\n        value: page size descriptor\n\n    Returns:\n        the page size in CSS pixels\n    \"\"\"\n    if value in PAGE_SIZES:\n        return PAGE_SIZES[value]\n\n    match = re.match(\n        r\"^(\\d+\\.?\\d*)({0})?x(\\d+\\.?\\d*)({0})?$\".format(\"|\".join(UNITS.keys())), value\n    )\n\n    if not match:\n        raise ValueError(f\"page size '{value}' unknown\")\n\n    x, x_unit, y, y_unit = match.groups()\n\n    if not x_unit:\n        x_unit = y_unit if y_unit else \"px\"\n    if not y_unit:\n        y_unit = x_unit\n\n    return float(x) * convert_length(x_unit), float(y) * convert_length(y_unit)\n\n\ndef convert_page_format(value: str) -> Tuple[float, float]:  # pragma: no cover\n    \"\"\"Deprecated, use convert_page_size.\"\"\"\n    logging.warning(\n        \"!!! `vpype.convert_page_format()` is deprecated, use `vpype.convert_page_size()` \"\n        \"instead.\"\n    )\n    return convert_page_size(value)\n\n\nclass LengthType(click.ParamType):\n    \"\"\":class:`click.ParamType` sub-class to automatically converts a user-provided length\n    string (which may contain units) into a value in CSS pixel units. This class uses\n    :func:`convert_length` internally.\n\n    Example::\n\n        >>> import click\n        >>> import vpype_cli\n        >>> import vpype\n        >>> @vpype_cli.cli.command(group=\"my commands\")\n        ... @click.argument(\"x\", type=vpype.LengthType())\n        ... @click.option(\"-o\", \"--option\", type=vpype.LengthType(), default=\"1mm\")\n        ... @vpype.generator\n        ... def my_command(x: float, option: float):\n        ...     pass\n    \"\"\"\n\n    name = \"length\"\n\n    def convert(self, value, param, ctx):\n        try:\n            return convert_length(value)\n        except ValueError:\n            self.fail(f\"parameter {value} is an incorrect length\")\n\n\nclass Length(LengthType):  # pragma: no cover\n    \"\"\"Deprecated, use LengthType.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        logging.warning(\"!!! `vpype.Length` is deprecated, use `vpype.LengthType` instead.\")\n\n\nclass AngleType(click.ParamType):\n    \"\"\":class:`click.ParamType` sub-class to automatically converts a user-provided angle\n    string (which may contain units) into a value in degrees. This class uses\n    :func:`convert_angle` internally.\n\n    Example::\n\n        >>> import click\n        >>> import vpype_cli\n        >>> import vpype\n        >>> @vpype_cli.cli.command(group=\"my commands\")\n        ... @click.argument(\"angle\", type=vpype.AngleType())\n        ... @vpype.generator\n        ... def my_command(angle: float):\n        ...     pass\n    \"\"\"\n\n    name = \"angle\"\n\n    def convert(self, value, param, ctx):\n        try:\n            return convert_angle(value)\n        except ValueError:\n            self.fail(f\"parameter {value} is an incorrect angle\")\n\n\nclass PageSizeType(click.ParamType):\n    \"\"\":class:`click.ParamType` sub-class to automatically converts a user-provided page size\n    string into a tuple of float in CSS pixel units. See :func:`convert_page_size` for\n    information on the page size descriptor syntax.\n\n    Example::\n\n        >>> import click\n        >>> import vpype_cli\n        >>> import vpype\n        >>> @vpype_cli.cli.command(group=\"my commands\")\n        ... @click.argument(\"fmt\", type=vpype.PageSizeType())\n        ... @vpype.generator\n        ... def my_command(fmt: Tuple[float, float]):\n        ...     pass\n    \"\"\"\n\n    name = \"PAGESIZE\"\n\n    def convert(self, value, param, ctx) -> Tuple[float, float]:\n        try:\n            return convert_page_size(value)\n        except ValueError:\n            self.fail(f\"parameter {value} is not a valid page size\")\n\n\ndef union(line: np.ndarray, keys: List[Callable[[np.ndarray], bool]]) -> bool:\n    \"\"\"Returns True if every callables in ``keys`` return True (similar to ``all()``. This\n    function is typically used with :meth:`LineCollection.filter`.\n\n    Args:\n        line: line to test\n        keys: list of callables\n\n    Returns:\n        True if every callables return True\n    \"\"\"\n    for key in keys:\n        if not key(line):\n            return False\n    return True\n", "path": "vpype/utils.py"}], "after_files": [{"content": "import logging\nimport math\nimport re\nfrom typing import Callable, Dict, List, Tuple, Union\n\nimport click\nimport numpy as np\n\n# REMINDER: anything added here must be added to docs/api.rst\n__all__ = [\n    \"UNITS\",\n    \"ANGLE_UNITS\",\n    \"PAGE_SIZES\",\n    \"LengthType\",\n    \"AngleType\",\n    \"PageSizeType\",\n    \"convert_length\",\n    \"convert_angle\",\n    \"convert_page_size\",\n    \"union\",\n    # deprecated:\n    \"PAGE_FORMATS\",\n    \"convert\",\n    \"convert_page_format\",\n    \"Length\",\n]\n\n\ndef _mm_to_px(x: float, y: float) -> Tuple[float, float]:\n    return x * 96.0 / 25.4, y * 96.0 / 25.4\n\n\nUNITS = {\n    \"px\": 1.0,\n    \"in\": 96.0,\n    \"mm\": 96.0 / 25.4,\n    \"cm\": 96.0 / 2.54,\n    \"pc\": 16.0,\n    \"pt\": 96.0 / 72.0,\n}\n\nANGLE_UNITS = {\n    \"deg\": 1.0,\n    \"grad\": 9.0 / 10.0,  # note: must be before \"rad\"!\n    \"rad\": 180.0 / math.pi,\n    \"turn\": 360.0,\n}\n\n# page sizes in pixel\nPAGE_SIZES = {\n    \"tight\": _mm_to_px(0, 0),\n    \"a6\": _mm_to_px(105.0, 148.0),\n    \"a5\": _mm_to_px(148.0, 210.0),\n    \"a4\": _mm_to_px(210.0, 297.0),\n    \"a3\": _mm_to_px(297.0, 420.0),\n    \"a2\": _mm_to_px(420.0, 594.0),\n    \"a1\": _mm_to_px(594.0, 841.0),\n    \"a0\": _mm_to_px(841.0, 1189.0),\n    \"letter\": _mm_to_px(215.9, 279.4),\n    \"legal\": _mm_to_px(215.9, 355.6),\n    \"executive\": _mm_to_px(185.15, 266.7),\n    \"tabloid\": _mm_to_px(279.4, 431.8),\n}\n\n# deprecated\nPAGE_FORMATS = PAGE_SIZES\n\n\ndef _convert_unit(value: Union[str, float], units: Dict[str, float]) -> float:\n    \"\"\"Converts a string with unit to a value\"\"\"\n    if isinstance(value, str):\n        value = value.strip().lower()\n        for unit, factor in units.items():\n            if value.endswith(unit):\n                num = value.strip(unit)\n                return (float(num) if len(num) > 0 else 1.0) * factor\n\n    return float(value)\n\n\ndef convert_length(value: Union[str, float]) -> float:\n    \"\"\"Convert a length optionally expressed as a string with unit to px value.\n\n    Args:\n        value: value to convert\n\n    Returns:\n        converted value\n\n    Raises:\n        :class:`ValueError`\n    \"\"\"\n    return _convert_unit(value, UNITS)\n\n\ndef convert(value: Union[str, float]) -> float:  # pragma: no cover\n    \"\"\"Deprecated, use convert_length.\"\"\"\n    logging.warning(\n        \"!!! `vpype.convert()` is deprecated, use `vpype.convert_length()` instead.\"\n    )\n    return convert_length(value)\n\n\ndef convert_angle(value: Union[str, float]) -> float:\n    \"\"\"Convert an angle optionally expressed as a string with unit to degrees.\n\n    Args:\n        value: angle to convert\n\n    Returns:\n        converted angle in degree\n\n    Raises:\n        :class:`ValueError`\n    \"\"\"\n    return _convert_unit(value, ANGLE_UNITS)\n\n\ndef convert_page_size(value: str) -> Tuple[float, float]:\n    \"\"\"Converts a string with page size to dimension in pixels.\n\n    The input can be either a known page size (see ``vpype write --help`` for a list) or\n    a page size descriptor in the form of \"WxH\" where both W and H can have units.\n\n    Examples:\n\n        Using a know page size::\n\n            >>> import vpype\n            >>> vpype.convert_page_size(\"a3\")\n            (1122.5196850393702, 1587.4015748031497)\n\n        Using page size descriptor (no units, pixels are assumed)::\n\n            >>> vpype.convert_page_size(\"100x200\")\n            (100.0, 200.0)\n\n        Using page size descriptor (explicit units)::\n\n            >>> vpype.convert_page_size(\"1inx2in\")\n            (96.0, 192.0)\n\n    Args:\n        value: page size descriptor\n\n    Returns:\n        the page size in CSS pixels\n    \"\"\"\n    if value in PAGE_SIZES:\n        return PAGE_SIZES[value]\n\n    match = re.match(\n        r\"^(\\d+\\.?\\d*)({0})?x(\\d+\\.?\\d*)({0})?$\".format(\"|\".join(UNITS.keys())), value\n    )\n\n    if not match:\n        raise ValueError(f\"page size '{value}' unknown\")\n\n    x, x_unit, y, y_unit = match.groups()\n\n    if not x_unit:\n        x_unit = y_unit if y_unit else \"px\"\n    if not y_unit:\n        y_unit = x_unit\n\n    return float(x) * convert_length(x_unit), float(y) * convert_length(y_unit)\n\n\ndef convert_page_format(value: str) -> Tuple[float, float]:  # pragma: no cover\n    \"\"\"Deprecated, use convert_page_size.\"\"\"\n    logging.warning(\n        \"!!! `vpype.convert_page_format()` is deprecated, use `vpype.convert_page_size()` \"\n        \"instead.\"\n    )\n    return convert_page_size(value)\n\n\nclass LengthType(click.ParamType):\n    \"\"\":class:`click.ParamType` sub-class to automatically converts a user-provided length\n    string (which may contain units) into a value in CSS pixel units. This class uses\n    :func:`convert_length` internally.\n\n    Example::\n\n        >>> import click\n        >>> import vpype_cli\n        >>> import vpype\n        >>> @vpype_cli.cli.command(group=\"my commands\")\n        ... @click.argument(\"x\", type=vpype.LengthType())\n        ... @click.option(\"-o\", \"--option\", type=vpype.LengthType(), default=\"1mm\")\n        ... @vpype.generator\n        ... def my_command(x: float, option: float):\n        ...     pass\n    \"\"\"\n\n    name = \"length\"\n\n    def convert(self, value, param, ctx):\n        try:\n            return convert_length(value)\n        except ValueError:\n            self.fail(f\"parameter {value} is an incorrect length\")\n\n\nclass Length(LengthType):  # pragma: no cover\n    \"\"\"Deprecated, use LengthType.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        logging.warning(\"!!! `vpype.Length` is deprecated, use `vpype.LengthType` instead.\")\n\n\nclass AngleType(click.ParamType):\n    \"\"\":class:`click.ParamType` sub-class to automatically converts a user-provided angle\n    string (which may contain units) into a value in degrees. This class uses\n    :func:`convert_angle` internally.\n\n    Example::\n\n        >>> import click\n        >>> import vpype_cli\n        >>> import vpype\n        >>> @vpype_cli.cli.command(group=\"my commands\")\n        ... @click.argument(\"angle\", type=vpype.AngleType())\n        ... @vpype.generator\n        ... def my_command(angle: float):\n        ...     pass\n    \"\"\"\n\n    name = \"angle\"\n\n    def convert(self, value, param, ctx):\n        try:\n            return convert_angle(value)\n        except ValueError:\n            self.fail(f\"parameter {value} is an incorrect angle\")\n\n\nclass PageSizeType(click.ParamType):\n    \"\"\":class:`click.ParamType` sub-class to automatically converts a user-provided page size\n    string into a tuple of float in CSS pixel units. See :func:`convert_page_size` for\n    information on the page size descriptor syntax.\n\n    Example::\n\n        >>> import click\n        >>> import vpype_cli\n        >>> import vpype\n        >>> @vpype_cli.cli.command(group=\"my commands\")\n        ... @click.argument(\"fmt\", type=vpype.PageSizeType())\n        ... @vpype.generator\n        ... def my_command(fmt: Tuple[float, float]):\n        ...     pass\n    \"\"\"\n\n    name = \"PAGESIZE\"\n\n    def convert(self, value, param, ctx) -> Tuple[float, float]:\n        try:\n            return convert_page_size(value)\n        except ValueError:\n            self.fail(f\"parameter {value} is not a valid page size\")\n\n\ndef union(line: np.ndarray, keys: List[Callable[[np.ndarray], bool]]) -> bool:\n    \"\"\"Returns True if every callables in ``keys`` return True (similar to ``all()``. This\n    function is typically used with :meth:`LineCollection.filter`.\n\n    Args:\n        line: line to test\n        keys: list of callables\n\n    Returns:\n        True if every callables return True\n    \"\"\"\n    for key in keys:\n        if not key(line):\n            return False\n    return True\n", "path": "vpype/utils.py"}]}