ThomasTheMaker commited on
Commit
1394162
·
verified ·
1 Parent(s): 710f025

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. venv/lib/python3.10/site-packages/contourpy/__init__.py +285 -0
  2. venv/lib/python3.10/site-packages/contourpy/__pycache__/__init__.cpython-310.pyc +0 -0
  3. venv/lib/python3.10/site-packages/contourpy/__pycache__/_version.cpython-310.pyc +0 -0
  4. venv/lib/python3.10/site-packages/contourpy/__pycache__/array.cpython-310.pyc +0 -0
  5. venv/lib/python3.10/site-packages/contourpy/__pycache__/chunk.cpython-310.pyc +0 -0
  6. venv/lib/python3.10/site-packages/contourpy/__pycache__/convert.cpython-310.pyc +0 -0
  7. venv/lib/python3.10/site-packages/contourpy/__pycache__/dechunk.cpython-310.pyc +0 -0
  8. venv/lib/python3.10/site-packages/contourpy/__pycache__/enum_util.cpython-310.pyc +0 -0
  9. venv/lib/python3.10/site-packages/contourpy/__pycache__/typecheck.cpython-310.pyc +0 -0
  10. venv/lib/python3.10/site-packages/contourpy/__pycache__/types.cpython-310.pyc +0 -0
  11. venv/lib/python3.10/site-packages/contourpy/_contourpy.pyi +199 -0
  12. venv/lib/python3.10/site-packages/contourpy/_version.py +1 -0
  13. venv/lib/python3.10/site-packages/contourpy/array.py +261 -0
  14. venv/lib/python3.10/site-packages/contourpy/chunk.py +95 -0
  15. venv/lib/python3.10/site-packages/contourpy/convert.py +621 -0
  16. venv/lib/python3.10/site-packages/contourpy/dechunk.py +207 -0
  17. venv/lib/python3.10/site-packages/contourpy/enum_util.py +57 -0
  18. venv/lib/python3.10/site-packages/contourpy/py.typed +0 -0
  19. venv/lib/python3.10/site-packages/contourpy/typecheck.py +203 -0
  20. venv/lib/python3.10/site-packages/contourpy/types.py +13 -0
  21. venv/lib/python3.10/site-packages/contourpy/util/__init__.py +5 -0
  22. venv/lib/python3.10/site-packages/contourpy/util/__pycache__/__init__.cpython-310.pyc +0 -0
  23. venv/lib/python3.10/site-packages/contourpy/util/__pycache__/_build_config.cpython-310.pyc +0 -0
  24. venv/lib/python3.10/site-packages/contourpy/util/__pycache__/bokeh_renderer.cpython-310.pyc +0 -0
  25. venv/lib/python3.10/site-packages/contourpy/util/__pycache__/bokeh_util.cpython-310.pyc +0 -0
  26. venv/lib/python3.10/site-packages/contourpy/util/__pycache__/data.cpython-310.pyc +0 -0
  27. venv/lib/python3.10/site-packages/contourpy/util/__pycache__/mpl_renderer.cpython-310.pyc +0 -0
  28. venv/lib/python3.10/site-packages/contourpy/util/__pycache__/mpl_util.cpython-310.pyc +0 -0
  29. venv/lib/python3.10/site-packages/contourpy/util/__pycache__/renderer.cpython-310.pyc +0 -0
  30. venv/lib/python3.10/site-packages/contourpy/util/_build_config.py +60 -0
  31. venv/lib/python3.10/site-packages/contourpy/util/bokeh_renderer.py +339 -0
  32. venv/lib/python3.10/site-packages/contourpy/util/bokeh_util.py +74 -0
  33. venv/lib/python3.10/site-packages/contourpy/util/data.py +78 -0
  34. venv/lib/python3.10/site-packages/contourpy/util/mpl_renderer.py +535 -0
  35. venv/lib/python3.10/site-packages/contourpy/util/mpl_util.py +77 -0
  36. venv/lib/python3.10/site-packages/contourpy/util/renderer.py +166 -0
  37. venv/lib/python3.10/site-packages/filelock-3.20.0.dist-info/INSTALLER +1 -0
  38. venv/lib/python3.10/site-packages/filelock-3.20.0.dist-info/METADATA +42 -0
  39. venv/lib/python3.10/site-packages/filelock-3.20.0.dist-info/RECORD +25 -0
  40. venv/lib/python3.10/site-packages/filelock-3.20.0.dist-info/REQUESTED +0 -0
  41. venv/lib/python3.10/site-packages/filelock-3.20.0.dist-info/WHEEL +4 -0
  42. venv/lib/python3.10/site-packages/filelock-3.20.0.dist-info/licenses/LICENSE +24 -0
  43. venv/lib/python3.10/site-packages/fontTools/__init__.py +8 -0
  44. venv/lib/python3.10/site-packages/fontTools/__main__.py +35 -0
  45. venv/lib/python3.10/site-packages/fontTools/afmLib.py +439 -0
  46. venv/lib/python3.10/site-packages/fontTools/agl.py +0 -0
  47. venv/lib/python3.10/site-packages/fontTools/annotations.py +30 -0
  48. venv/lib/python3.10/site-packages/fontTools/cffLib/CFF2ToCFF.py +233 -0
  49. venv/lib/python3.10/site-packages/fontTools/cffLib/CFFToCFF2.py +305 -0
  50. venv/lib/python3.10/site-packages/fontTools/cffLib/__init__.py +0 -0
venv/lib/python3.10/site-packages/contourpy/__init__.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+
7
+ from contourpy._contourpy import (
8
+ ContourGenerator,
9
+ FillType,
10
+ LineType,
11
+ Mpl2005ContourGenerator,
12
+ Mpl2014ContourGenerator,
13
+ SerialContourGenerator,
14
+ ThreadedContourGenerator,
15
+ ZInterp,
16
+ max_threads,
17
+ )
18
+ from contourpy._version import __version__
19
+ from contourpy.chunk import calc_chunk_sizes
20
+ from contourpy.convert import (
21
+ convert_filled,
22
+ convert_lines,
23
+ convert_multi_filled,
24
+ convert_multi_lines,
25
+ )
26
+ from contourpy.dechunk import (
27
+ dechunk_filled,
28
+ dechunk_lines,
29
+ dechunk_multi_filled,
30
+ dechunk_multi_lines,
31
+ )
32
+ from contourpy.enum_util import as_fill_type, as_line_type, as_z_interp
33
+
34
+ if TYPE_CHECKING:
35
+ from typing import Any
36
+
37
+ from numpy.typing import ArrayLike
38
+
39
+ from ._contourpy import CoordinateArray, MaskArray
40
+
41
+ __all__ = [
42
+ "__version__",
43
+ "contour_generator",
44
+ "convert_filled",
45
+ "convert_lines",
46
+ "convert_multi_filled",
47
+ "convert_multi_lines",
48
+ "dechunk_filled",
49
+ "dechunk_lines",
50
+ "dechunk_multi_filled",
51
+ "dechunk_multi_lines",
52
+ "max_threads",
53
+ "FillType",
54
+ "LineType",
55
+ "ContourGenerator",
56
+ "Mpl2005ContourGenerator",
57
+ "Mpl2014ContourGenerator",
58
+ "SerialContourGenerator",
59
+ "ThreadedContourGenerator",
60
+ "ZInterp",
61
+ ]
62
+
63
+
64
+ # Simple mapping of algorithm name to class name.
65
+ _class_lookup: dict[str, type[ContourGenerator]] = {
66
+ "mpl2005": Mpl2005ContourGenerator,
67
+ "mpl2014": Mpl2014ContourGenerator,
68
+ "serial": SerialContourGenerator,
69
+ "threaded": ThreadedContourGenerator,
70
+ }
71
+
72
+
73
+ def _remove_z_mask(
74
+ z: ArrayLike | np.ma.MaskedArray[Any, Any] | None,
75
+ ) -> tuple[CoordinateArray, MaskArray | None]:
76
+ # Preserve mask if present.
77
+ z_array = np.ma.asarray(z, dtype=np.float64) # type: ignore[no-untyped-call]
78
+ z_masked = np.ma.masked_invalid(z_array, copy=False) # type: ignore[no-untyped-call]
79
+
80
+ if np.ma.is_masked(z_masked): # type: ignore[no-untyped-call]
81
+ mask = np.ma.getmask(z_masked) # type: ignore[no-untyped-call]
82
+ else:
83
+ mask = None
84
+
85
+ return np.ma.getdata(z_masked), mask # type: ignore[no-untyped-call]
86
+
87
+
88
+ def contour_generator(
89
+ x: ArrayLike | None = None,
90
+ y: ArrayLike | None = None,
91
+ z: ArrayLike | np.ma.MaskedArray[Any, Any] | None = None,
92
+ *,
93
+ name: str = "serial",
94
+ corner_mask: bool | None = None,
95
+ line_type: LineType | str | None = None,
96
+ fill_type: FillType | str | None = None,
97
+ chunk_size: int | tuple[int, int] | None = None,
98
+ chunk_count: int | tuple[int, int] | None = None,
99
+ total_chunk_count: int | None = None,
100
+ quad_as_tri: bool = False,
101
+ z_interp: ZInterp | str | None = ZInterp.Linear,
102
+ thread_count: int = 0,
103
+ ) -> ContourGenerator:
104
+ """Create and return a :class:`~.ContourGenerator` object.
105
+
106
+ The class and properties of the returned :class:`~.ContourGenerator` are determined by the
107
+ function arguments, with sensible defaults.
108
+
109
+ Args:
110
+ x (array-like of shape (ny, nx) or (nx,), optional): The x-coordinates of the ``z`` values.
111
+ May be 2D with the same shape as ``z.shape``, or 1D with length ``nx = z.shape[1]``.
112
+ If not specified are assumed to be ``np.arange(nx)``. Must be ordered monotonically.
113
+ y (array-like of shape (ny, nx) or (ny,), optional): The y-coordinates of the ``z`` values.
114
+ May be 2D with the same shape as ``z.shape``, or 1D with length ``ny = z.shape[0]``.
115
+ If not specified are assumed to be ``np.arange(ny)``. Must be ordered monotonically.
116
+ z (array-like of shape (ny, nx), may be a masked array): The 2D gridded values to calculate
117
+ the contours of. May be a masked array, and any invalid values (``np.inf`` or
118
+ ``np.nan``) will also be masked out.
119
+ name (str): Algorithm name, one of ``"serial"``, ``"threaded"``, ``"mpl2005"`` or
120
+ ``"mpl2014"``, default ``"serial"``.
121
+ corner_mask (bool, optional): Enable/disable corner masking, which only has an effect if
122
+ ``z`` is a masked array. If ``False``, any quad touching a masked point is masked out.
123
+ If ``True``, only the triangular corners of quads nearest these points are always masked
124
+ out, other triangular corners comprising three unmasked points are contoured as usual.
125
+ If not specified, uses the default provided by the algorithm ``name``.
126
+ line_type (LineType or str, optional): The format of contour line data returned from calls
127
+ to :meth:`~.ContourGenerator.lines`, specified either as a :class:`~.LineType` or its
128
+ string equivalent such as ``"SeparateCode"``.
129
+ If not specified, uses the default provided by the algorithm ``name``.
130
+ The relationship between the :class:`~.LineType` enum and the data format returned from
131
+ :meth:`~.ContourGenerator.lines` is explained at :ref:`line_type`.
132
+ fill_type (FillType or str, optional): The format of filled contour data returned from calls
133
+ to :meth:`~.ContourGenerator.filled`, specified either as a :class:`~.FillType` or its
134
+ string equivalent such as ``"OuterOffset"``.
135
+ If not specified, uses the default provided by the algorithm ``name``.
136
+ The relationship between the :class:`~.FillType` enum and the data format returned from
137
+ :meth:`~.ContourGenerator.filled` is explained at :ref:`fill_type`.
138
+ chunk_size (int or tuple(int, int), optional): Chunk size in (y, x) directions, or the same
139
+ size in both directions if only one value is specified.
140
+ chunk_count (int or tuple(int, int), optional): Chunk count in (y, x) directions, or the
141
+ same count in both directions if only one value is specified.
142
+ total_chunk_count (int, optional): Total number of chunks.
143
+ quad_as_tri (bool): Enable/disable treating quads as 4 triangles, default ``False``.
144
+ If ``False``, a contour line within a quad is a straight line between points on two of
145
+ its edges. If ``True``, each full quad is divided into 4 triangles using a virtual point
146
+ at the centre (mean x, y of the corner points) and a contour line is piecewise linear
147
+ within those triangles. Corner-masked triangles are not affected by this setting, only
148
+ full unmasked quads.
149
+ z_interp (ZInterp or str, optional): How to interpolate ``z`` values when determining where
150
+ contour lines intersect the edges of quads and the ``z`` values of the central points of
151
+ quads, specified either as a :class:`~contourpy.ZInterp` or its string equivalent such
152
+ as ``"Log"``. Default is ``ZInterp.Linear``.
153
+ thread_count (int): Number of threads to use for contour calculation, default 0. Threads can
154
+ only be used with an algorithm ``name`` that supports threads (currently only
155
+ ``name="threaded"``) and there must be at least the same number of chunks as threads.
156
+ If ``thread_count=0`` and ``name="threaded"`` then it uses the maximum number of threads
157
+ as determined by the C++11 call ``std::thread::hardware_concurrency()``. If ``name`` is
158
+ something other than ``"threaded"`` then the ``thread_count`` will be set to ``1``.
159
+
160
+ Return:
161
+ :class:`~.ContourGenerator`.
162
+
163
+ Note:
164
+ A maximum of one of ``chunk_size``, ``chunk_count`` and ``total_chunk_count`` may be
165
+ specified.
166
+
167
+ Warning:
168
+ The ``name="mpl2005"`` algorithm does not implement chunking for contour lines.
169
+ """
170
+ x = np.asarray(x, dtype=np.float64)
171
+ y = np.asarray(y, dtype=np.float64)
172
+ z, mask = _remove_z_mask(z)
173
+
174
+ # Check arguments: z.
175
+ if z.ndim != 2:
176
+ raise TypeError(f"Input z must be 2D, not {z.ndim}D")
177
+
178
+ if z.shape[0] < 2 or z.shape[1] < 2:
179
+ raise TypeError(f"Input z must be at least a (2, 2) shaped array, but has shape {z.shape}")
180
+
181
+ ny, nx = z.shape
182
+
183
+ # Check arguments: x and y.
184
+ if x.ndim != y.ndim:
185
+ raise TypeError(f"Number of dimensions of x ({x.ndim}) and y ({y.ndim}) do not match")
186
+
187
+ if x.ndim == 0:
188
+ x = np.arange(nx, dtype=np.float64)
189
+ y = np.arange(ny, dtype=np.float64)
190
+ x, y = np.meshgrid(x, y)
191
+ elif x.ndim == 1:
192
+ if len(x) != nx:
193
+ raise TypeError(f"Length of x ({len(x)}) must match number of columns in z ({nx})")
194
+ if len(y) != ny:
195
+ raise TypeError(f"Length of y ({len(y)}) must match number of rows in z ({ny})")
196
+ x, y = np.meshgrid(x, y)
197
+ elif x.ndim == 2:
198
+ if x.shape != z.shape:
199
+ raise TypeError(f"Shapes of x {x.shape} and z {z.shape} do not match")
200
+ if y.shape != z.shape:
201
+ raise TypeError(f"Shapes of y {y.shape} and z {z.shape} do not match")
202
+ else:
203
+ raise TypeError(f"Inputs x and y must be None, 1D or 2D, not {x.ndim}D")
204
+
205
+ # Check mask shape just in case.
206
+ if mask is not None and mask.shape != z.shape:
207
+ raise ValueError("If mask is set it must be a 2D array with the same shape as z")
208
+
209
+ # Check arguments: name.
210
+ if name not in _class_lookup:
211
+ raise ValueError(f"Unrecognised contour generator name: {name}")
212
+
213
+ # Check arguments: chunk_size, chunk_count and total_chunk_count.
214
+ y_chunk_size, x_chunk_size = calc_chunk_sizes(
215
+ chunk_size, chunk_count, total_chunk_count, ny, nx)
216
+
217
+ cls = _class_lookup[name]
218
+
219
+ # Check arguments: corner_mask.
220
+ if corner_mask is None:
221
+ # Set it to default, which is True if the algorithm supports it.
222
+ corner_mask = cls.supports_corner_mask()
223
+ elif corner_mask and not cls.supports_corner_mask():
224
+ raise ValueError(f"{name} contour generator does not support corner_mask=True")
225
+
226
+ # Check arguments: line_type.
227
+ if line_type is None:
228
+ line_type = cls.default_line_type
229
+ else:
230
+ line_type = as_line_type(line_type)
231
+
232
+ if not cls.supports_line_type(line_type):
233
+ raise ValueError(f"{name} contour generator does not support line_type {line_type}")
234
+
235
+ # Check arguments: fill_type.
236
+ if fill_type is None:
237
+ fill_type = cls.default_fill_type
238
+ else:
239
+ fill_type = as_fill_type(fill_type)
240
+
241
+ if not cls.supports_fill_type(fill_type):
242
+ raise ValueError(f"{name} contour generator does not support fill_type {fill_type}")
243
+
244
+ # Check arguments: quad_as_tri.
245
+ if quad_as_tri and not cls.supports_quad_as_tri():
246
+ raise ValueError(f"{name} contour generator does not support quad_as_tri=True")
247
+
248
+ # Check arguments: z_interp.
249
+ if z_interp is None:
250
+ z_interp = ZInterp.Linear
251
+ else:
252
+ z_interp = as_z_interp(z_interp)
253
+
254
+ if z_interp != ZInterp.Linear and not cls.supports_z_interp():
255
+ raise ValueError(f"{name} contour generator does not support z_interp {z_interp}")
256
+
257
+ # Check arguments: thread_count.
258
+ if thread_count not in (0, 1) and not cls.supports_threads():
259
+ raise ValueError(f"{name} contour generator does not support thread_count {thread_count}")
260
+
261
+ # Prepare args and kwargs for contour generator constructor.
262
+ args = [x, y, z, mask]
263
+ kwargs: dict[str, int | bool | LineType | FillType | ZInterp] = {
264
+ "x_chunk_size": x_chunk_size,
265
+ "y_chunk_size": y_chunk_size,
266
+ }
267
+
268
+ if name not in ("mpl2005", "mpl2014"):
269
+ kwargs["line_type"] = line_type
270
+ kwargs["fill_type"] = fill_type
271
+
272
+ if cls.supports_corner_mask():
273
+ kwargs["corner_mask"] = corner_mask
274
+
275
+ if cls.supports_quad_as_tri():
276
+ kwargs["quad_as_tri"] = quad_as_tri
277
+
278
+ if cls.supports_z_interp():
279
+ kwargs["z_interp"] = z_interp
280
+
281
+ if cls.supports_threads():
282
+ kwargs["thread_count"] = thread_count
283
+
284
+ # Create contour generator.
285
+ return cls(*args, **kwargs)
venv/lib/python3.10/site-packages/contourpy/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (9.75 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/__pycache__/_version.cpython-310.pyc ADDED
Binary file (195 Bytes). View file
 
venv/lib/python3.10/site-packages/contourpy/__pycache__/array.cpython-310.pyc ADDED
Binary file (9.58 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/__pycache__/chunk.cpython-310.pyc ADDED
Binary file (2.79 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/__pycache__/convert.cpython-310.pyc ADDED
Binary file (16.7 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/__pycache__/dechunk.cpython-310.pyc ADDED
Binary file (6.22 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/__pycache__/enum_util.cpython-310.pyc ADDED
Binary file (1.6 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/__pycache__/typecheck.cpython-310.pyc ADDED
Binary file (6.59 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/__pycache__/types.cpython-310.pyc ADDED
Binary file (385 Bytes). View file
 
venv/lib/python3.10/site-packages/contourpy/_contourpy.pyi ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import ClassVar, NoReturn, TypeAlias
2
+
3
+ import numpy as np
4
+ import numpy.typing as npt
5
+
6
+ import contourpy._contourpy as cpy
7
+
8
+ # Input numpy array types, the same as in common.h
9
+ CoordinateArray: TypeAlias = npt.NDArray[np.float64]
10
+ MaskArray: TypeAlias = npt.NDArray[np.bool_]
11
+ LevelArray: TypeAlias = npt.ArrayLike
12
+
13
+ # Output numpy array types, the same as in common.h
14
+ PointArray: TypeAlias = npt.NDArray[np.float64]
15
+ CodeArray: TypeAlias = npt.NDArray[np.uint8]
16
+ OffsetArray: TypeAlias = npt.NDArray[np.uint32]
17
+
18
+ # Types returned from filled()
19
+ FillReturn_OuterCode: TypeAlias = tuple[list[PointArray], list[CodeArray]]
20
+ FillReturn_OuterOffset: TypeAlias = tuple[list[PointArray], list[OffsetArray]]
21
+ FillReturn_ChunkCombinedCode: TypeAlias = tuple[list[PointArray | None], list[CodeArray | None]]
22
+ FillReturn_ChunkCombinedOffset: TypeAlias = tuple[list[PointArray | None], list[OffsetArray | None]]
23
+ FillReturn_ChunkCombinedCodeOffset: TypeAlias = tuple[list[PointArray | None], list[CodeArray | None], list[OffsetArray | None]]
24
+ FillReturn_ChunkCombinedOffsetOffset: TypeAlias = tuple[list[PointArray | None], list[OffsetArray | None], list[OffsetArray | None]]
25
+ FillReturn_Chunk: TypeAlias = FillReturn_ChunkCombinedCode | FillReturn_ChunkCombinedOffset | FillReturn_ChunkCombinedCodeOffset | FillReturn_ChunkCombinedOffsetOffset
26
+ FillReturn: TypeAlias = FillReturn_OuterCode | FillReturn_OuterOffset | FillReturn_Chunk
27
+
28
+ # Types returned from lines()
29
+ LineReturn_Separate: TypeAlias = list[PointArray]
30
+ LineReturn_SeparateCode: TypeAlias = tuple[list[PointArray], list[CodeArray]]
31
+ LineReturn_ChunkCombinedCode: TypeAlias = tuple[list[PointArray | None], list[CodeArray | None]]
32
+ LineReturn_ChunkCombinedOffset: TypeAlias = tuple[list[PointArray | None], list[OffsetArray | None]]
33
+ LineReturn_ChunkCombinedNan: TypeAlias = tuple[list[PointArray | None]]
34
+ LineReturn_Chunk: TypeAlias = LineReturn_ChunkCombinedCode | LineReturn_ChunkCombinedOffset | LineReturn_ChunkCombinedNan
35
+ LineReturn: TypeAlias = LineReturn_Separate | LineReturn_SeparateCode | LineReturn_Chunk
36
+
37
+
38
+ NDEBUG: int
39
+ __version__: str
40
+
41
+ class FillType:
42
+ ChunkCombinedCode: ClassVar[cpy.FillType]
43
+ ChunkCombinedCodeOffset: ClassVar[cpy.FillType]
44
+ ChunkCombinedOffset: ClassVar[cpy.FillType]
45
+ ChunkCombinedOffsetOffset: ClassVar[cpy.FillType]
46
+ OuterCode: ClassVar[cpy.FillType]
47
+ OuterOffset: ClassVar[cpy.FillType]
48
+ __members__: ClassVar[dict[str, cpy.FillType]]
49
+ def __eq__(self, other: object) -> bool: ...
50
+ def __getstate__(self) -> int: ...
51
+ def __hash__(self) -> int: ...
52
+ def __index__(self) -> int: ...
53
+ def __init__(self, value: int) -> None: ...
54
+ def __int__(self) -> int: ...
55
+ def __ne__(self, other: object) -> bool: ...
56
+ def __setstate__(self, state: int) -> NoReturn: ...
57
+ @property
58
+ def name(self) -> str: ...
59
+ @property
60
+ def value(self) -> int: ...
61
+
62
+ class LineType:
63
+ ChunkCombinedCode: ClassVar[cpy.LineType]
64
+ ChunkCombinedNan: ClassVar[cpy.LineType]
65
+ ChunkCombinedOffset: ClassVar[cpy.LineType]
66
+ Separate: ClassVar[cpy.LineType]
67
+ SeparateCode: ClassVar[cpy.LineType]
68
+ __members__: ClassVar[dict[str, cpy.LineType]]
69
+ def __eq__(self, other: object) -> bool: ...
70
+ def __getstate__(self) -> int: ...
71
+ def __hash__(self) -> int: ...
72
+ def __index__(self) -> int: ...
73
+ def __init__(self, value: int) -> None: ...
74
+ def __int__(self) -> int: ...
75
+ def __ne__(self, other: object) -> bool: ...
76
+ def __setstate__(self, state: int) -> NoReturn: ...
77
+ @property
78
+ def name(self) -> str: ...
79
+ @property
80
+ def value(self) -> int: ...
81
+
82
+ class ZInterp:
83
+ Linear: ClassVar[cpy.ZInterp]
84
+ Log: ClassVar[cpy.ZInterp]
85
+ __members__: ClassVar[dict[str, cpy.ZInterp]]
86
+ def __eq__(self, other: object) -> bool: ...
87
+ def __getstate__(self) -> int: ...
88
+ def __hash__(self) -> int: ...
89
+ def __index__(self) -> int: ...
90
+ def __init__(self, value: int) -> None: ...
91
+ def __int__(self) -> int: ...
92
+ def __ne__(self, other: object) -> bool: ...
93
+ def __setstate__(self, state: int) -> NoReturn: ...
94
+ @property
95
+ def name(self) -> str: ...
96
+ @property
97
+ def value(self) -> int: ...
98
+
99
+ def max_threads() -> int: ...
100
+
101
+ class ContourGenerator:
102
+ def create_contour(self, level: float) -> LineReturn: ...
103
+ def create_filled_contour(self, lower_level: float, upper_level: float) -> FillReturn: ...
104
+ def filled(self, lower_level: float, upper_level: float) -> FillReturn: ...
105
+ def lines(self, level: float) -> LineReturn: ...
106
+ def multi_filled(self, levels: LevelArray) -> list[FillReturn]: ...
107
+ def multi_lines(self, levels: LevelArray) -> list[LineReturn]: ...
108
+ @staticmethod
109
+ def supports_corner_mask() -> bool: ...
110
+ @staticmethod
111
+ def supports_fill_type(fill_type: FillType) -> bool: ...
112
+ @staticmethod
113
+ def supports_line_type(line_type: LineType) -> bool: ...
114
+ @staticmethod
115
+ def supports_quad_as_tri() -> bool: ...
116
+ @staticmethod
117
+ def supports_threads() -> bool: ...
118
+ @staticmethod
119
+ def supports_z_interp() -> bool: ...
120
+ @property
121
+ def chunk_count(self) -> tuple[int, int]: ...
122
+ @property
123
+ def chunk_size(self) -> tuple[int, int]: ...
124
+ @property
125
+ def corner_mask(self) -> bool: ...
126
+ @property
127
+ def fill_type(self) -> FillType: ...
128
+ @property
129
+ def line_type(self) -> LineType: ...
130
+ @property
131
+ def quad_as_tri(self) -> bool: ...
132
+ @property
133
+ def thread_count(self) -> int: ...
134
+ @property
135
+ def z_interp(self) -> ZInterp: ...
136
+ default_fill_type: cpy.FillType
137
+ default_line_type: cpy.LineType
138
+
139
+ class Mpl2005ContourGenerator(ContourGenerator):
140
+ def __init__(
141
+ self,
142
+ x: CoordinateArray,
143
+ y: CoordinateArray,
144
+ z: CoordinateArray,
145
+ mask: MaskArray,
146
+ *,
147
+ x_chunk_size: int = 0,
148
+ y_chunk_size: int = 0,
149
+ ) -> None: ...
150
+
151
+ class Mpl2014ContourGenerator(ContourGenerator):
152
+ def __init__(
153
+ self,
154
+ x: CoordinateArray,
155
+ y: CoordinateArray,
156
+ z: CoordinateArray,
157
+ mask: MaskArray,
158
+ *,
159
+ corner_mask: bool,
160
+ x_chunk_size: int = 0,
161
+ y_chunk_size: int = 0,
162
+ ) -> None: ...
163
+
164
+ class SerialContourGenerator(ContourGenerator):
165
+ def __init__(
166
+ self,
167
+ x: CoordinateArray,
168
+ y: CoordinateArray,
169
+ z: CoordinateArray,
170
+ mask: MaskArray,
171
+ *,
172
+ corner_mask: bool,
173
+ line_type: LineType,
174
+ fill_type: FillType,
175
+ quad_as_tri: bool,
176
+ z_interp: ZInterp,
177
+ x_chunk_size: int = 0,
178
+ y_chunk_size: int = 0,
179
+ ) -> None: ...
180
+ def _write_cache(self) -> NoReturn: ...
181
+
182
+ class ThreadedContourGenerator(ContourGenerator):
183
+ def __init__(
184
+ self,
185
+ x: CoordinateArray,
186
+ y: CoordinateArray,
187
+ z: CoordinateArray,
188
+ mask: MaskArray,
189
+ *,
190
+ corner_mask: bool,
191
+ line_type: LineType,
192
+ fill_type: FillType,
193
+ quad_as_tri: bool,
194
+ z_interp: ZInterp,
195
+ x_chunk_size: int = 0,
196
+ y_chunk_size: int = 0,
197
+ thread_count: int = 0,
198
+ ) -> None: ...
199
+ def _write_cache(self) -> None: ...
venv/lib/python3.10/site-packages/contourpy/_version.py ADDED
@@ -0,0 +1 @@
 
 
1
+ __version__ = "1.3.2"
venv/lib/python3.10/site-packages/contourpy/array.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from itertools import chain, pairwise
4
+ from typing import TYPE_CHECKING
5
+
6
+ import numpy as np
7
+
8
+ from contourpy.typecheck import check_code_array, check_offset_array, check_point_array
9
+ from contourpy.types import CLOSEPOLY, LINETO, MOVETO, code_dtype, offset_dtype, point_dtype
10
+
11
+ if TYPE_CHECKING:
12
+ import contourpy._contourpy as cpy
13
+
14
+
15
+ def codes_from_offsets(offsets: cpy.OffsetArray) -> cpy.CodeArray:
16
+ """Determine codes from offsets, assuming they all correspond to closed polygons.
17
+ """
18
+ check_offset_array(offsets)
19
+
20
+ n = offsets[-1]
21
+ codes = np.full(n, LINETO, dtype=code_dtype)
22
+ codes[offsets[:-1]] = MOVETO
23
+ codes[offsets[1:] - 1] = CLOSEPOLY
24
+ return codes
25
+
26
+
27
+ def codes_from_offsets_and_points(
28
+ offsets: cpy.OffsetArray,
29
+ points: cpy.PointArray,
30
+ ) -> cpy.CodeArray:
31
+ """Determine codes from offsets and points, using the equality of the start and end points of
32
+ each line to determine if lines are closed or not.
33
+ """
34
+ check_offset_array(offsets)
35
+ check_point_array(points)
36
+
37
+ codes = np.full(len(points), LINETO, dtype=code_dtype)
38
+ codes[offsets[:-1]] = MOVETO
39
+
40
+ end_offsets = offsets[1:] - 1
41
+ closed = np.all(points[offsets[:-1]] == points[end_offsets], axis=1)
42
+ codes[end_offsets[closed]] = CLOSEPOLY
43
+
44
+ return codes
45
+
46
+
47
+ def codes_from_points(points: cpy.PointArray) -> cpy.CodeArray:
48
+ """Determine codes for a single line, using the equality of the start and end points to
49
+ determine if the line is closed or not.
50
+ """
51
+ check_point_array(points)
52
+
53
+ n = len(points)
54
+ codes = np.full(n, LINETO, dtype=code_dtype)
55
+ codes[0] = MOVETO
56
+ if np.all(points[0] == points[-1]):
57
+ codes[-1] = CLOSEPOLY
58
+ return codes
59
+
60
+
61
+ def concat_codes(list_of_codes: list[cpy.CodeArray]) -> cpy.CodeArray:
62
+ """Concatenate a list of codes arrays into a single code array.
63
+ """
64
+ if not list_of_codes:
65
+ raise ValueError("Empty list passed to concat_codes")
66
+
67
+ return np.concatenate(list_of_codes, dtype=code_dtype)
68
+
69
+
70
+ def concat_codes_or_none(list_of_codes_or_none: list[cpy.CodeArray | None]) -> cpy.CodeArray | None:
71
+ """Concatenate a list of codes arrays or None into a single code array or None.
72
+ """
73
+ list_of_codes = [codes for codes in list_of_codes_or_none if codes is not None]
74
+ if list_of_codes:
75
+ return concat_codes(list_of_codes)
76
+ else:
77
+ return None
78
+
79
+
80
+ def concat_offsets(list_of_offsets: list[cpy.OffsetArray]) -> cpy.OffsetArray:
81
+ """Concatenate a list of offsets arrays into a single offset array.
82
+ """
83
+ if not list_of_offsets:
84
+ raise ValueError("Empty list passed to concat_offsets")
85
+
86
+ n = len(list_of_offsets)
87
+ cumulative = np.cumsum([offsets[-1] for offsets in list_of_offsets], dtype=offset_dtype)
88
+ ret: cpy.OffsetArray = np.concatenate(
89
+ (list_of_offsets[0], *(list_of_offsets[i+1][1:] + cumulative[i] for i in range(n-1))),
90
+ dtype=offset_dtype,
91
+ )
92
+ return ret
93
+
94
+
95
+ def concat_offsets_or_none(
96
+ list_of_offsets_or_none: list[cpy.OffsetArray | None],
97
+ ) -> cpy.OffsetArray | None:
98
+ """Concatenate a list of offsets arrays or None into a single offset array or None.
99
+ """
100
+ list_of_offsets = [offsets for offsets in list_of_offsets_or_none if offsets is not None]
101
+ if list_of_offsets:
102
+ return concat_offsets(list_of_offsets)
103
+ else:
104
+ return None
105
+
106
+
107
+ def concat_points(list_of_points: list[cpy.PointArray]) -> cpy.PointArray:
108
+ """Concatenate a list of point arrays into a single point array.
109
+ """
110
+ if not list_of_points:
111
+ raise ValueError("Empty list passed to concat_points")
112
+
113
+ return np.concatenate(list_of_points, dtype=point_dtype)
114
+
115
+
116
+ def concat_points_or_none(
117
+ list_of_points_or_none: list[cpy.PointArray | None],
118
+ ) -> cpy.PointArray | None:
119
+ """Concatenate a list of point arrays or None into a single point array or None.
120
+ """
121
+ list_of_points = [points for points in list_of_points_or_none if points is not None]
122
+ if list_of_points:
123
+ return concat_points(list_of_points)
124
+ else:
125
+ return None
126
+
127
+
128
+ def concat_points_or_none_with_nan(
129
+ list_of_points_or_none: list[cpy.PointArray | None],
130
+ ) -> cpy.PointArray | None:
131
+ """Concatenate a list of points or None into a single point array or None, with NaNs used to
132
+ separate each line.
133
+ """
134
+ list_of_points = [points for points in list_of_points_or_none if points is not None]
135
+ if list_of_points:
136
+ return concat_points_with_nan(list_of_points)
137
+ else:
138
+ return None
139
+
140
+
141
+ def concat_points_with_nan(list_of_points: list[cpy.PointArray]) -> cpy.PointArray:
142
+ """Concatenate a list of points into a single point array with NaNs used to separate each line.
143
+ """
144
+ if not list_of_points:
145
+ raise ValueError("Empty list passed to concat_points_with_nan")
146
+
147
+ if len(list_of_points) == 1:
148
+ return list_of_points[0]
149
+ else:
150
+ nan_spacer = np.full((1, 2), np.nan, dtype=point_dtype)
151
+ list_of_points = [list_of_points[0],
152
+ *list(chain(*((nan_spacer, x) for x in list_of_points[1:])))]
153
+ return concat_points(list_of_points)
154
+
155
+
156
+ def insert_nan_at_offsets(points: cpy.PointArray, offsets: cpy.OffsetArray) -> cpy.PointArray:
157
+ """Insert NaNs into a point array at locations specified by an offset array.
158
+ """
159
+ check_point_array(points)
160
+ check_offset_array(offsets)
161
+
162
+ if len(offsets) <= 2:
163
+ return points
164
+ else:
165
+ nan_spacer = np.array([np.nan, np.nan], dtype=point_dtype)
166
+ # Convert offsets to int64 to avoid numpy error when mixing signed and unsigned ints.
167
+ return np.insert(points, offsets[1:-1].astype(np.int64), nan_spacer, axis=0)
168
+
169
+
170
+ def offsets_from_codes(codes: cpy.CodeArray) -> cpy.OffsetArray:
171
+ """Determine offsets from codes using locations of MOVETO codes.
172
+ """
173
+ check_code_array(codes)
174
+
175
+ return np.append(np.nonzero(codes == MOVETO)[0], len(codes)).astype(offset_dtype)
176
+
177
+
178
+ def offsets_from_lengths(list_of_points: list[cpy.PointArray]) -> cpy.OffsetArray:
179
+ """Determine offsets from lengths of point arrays.
180
+ """
181
+ if not list_of_points:
182
+ raise ValueError("Empty list passed to offsets_from_lengths")
183
+
184
+ return np.cumsum([0] + [len(line) for line in list_of_points], dtype=offset_dtype)
185
+
186
+
187
+ def outer_offsets_from_list_of_codes(list_of_codes: list[cpy.CodeArray]) -> cpy.OffsetArray:
188
+ """Determine outer offsets from codes using locations of MOVETO codes.
189
+ """
190
+ if not list_of_codes:
191
+ raise ValueError("Empty list passed to outer_offsets_from_list_of_codes")
192
+
193
+ return np.cumsum([0] + [np.count_nonzero(codes == MOVETO) for codes in list_of_codes],
194
+ dtype=offset_dtype)
195
+
196
+
197
+ def outer_offsets_from_list_of_offsets(list_of_offsets: list[cpy.OffsetArray]) -> cpy.OffsetArray:
198
+ """Determine outer offsets from a list of offsets.
199
+ """
200
+ if not list_of_offsets:
201
+ raise ValueError("Empty list passed to outer_offsets_from_list_of_offsets")
202
+
203
+ return np.cumsum([0] + [len(offsets)-1 for offsets in list_of_offsets], dtype=offset_dtype)
204
+
205
+
206
+ def remove_nan(points: cpy.PointArray) -> tuple[cpy.PointArray, cpy.OffsetArray]:
207
+ """Remove NaN from a points array, also return the offsets corresponding to the NaN removed.
208
+ """
209
+ check_point_array(points)
210
+
211
+ nan_offsets = np.nonzero(np.isnan(points[:, 0]))[0]
212
+ if len(nan_offsets) == 0:
213
+ return points, np.array([0, len(points)], dtype=offset_dtype)
214
+ else:
215
+ points = np.delete(points, nan_offsets, axis=0)
216
+ nan_offsets -= np.arange(len(nan_offsets))
217
+ offsets: cpy.OffsetArray = np.empty(len(nan_offsets)+2, dtype=offset_dtype)
218
+ offsets[0] = 0
219
+ offsets[1:-1] = nan_offsets
220
+ offsets[-1] = len(points)
221
+ return points, offsets
222
+
223
+
224
+ def split_codes_by_offsets(codes: cpy.CodeArray, offsets: cpy.OffsetArray) -> list[cpy.CodeArray]:
225
+ """Split a code array at locations specified by an offset array into a list of code arrays.
226
+ """
227
+ check_code_array(codes)
228
+ check_offset_array(offsets)
229
+
230
+ if len(offsets) > 2:
231
+ return np.split(codes, offsets[1:-1])
232
+ else:
233
+ return [codes]
234
+
235
+
236
+ def split_points_by_offsets(
237
+ points: cpy.PointArray,
238
+ offsets: cpy.OffsetArray,
239
+ ) -> list[cpy.PointArray]:
240
+ """Split a point array at locations specified by an offset array into a list of point arrays.
241
+ """
242
+ check_point_array(points)
243
+ check_offset_array(offsets)
244
+
245
+ if len(offsets) > 2:
246
+ return np.split(points, offsets[1:-1])
247
+ else:
248
+ return [points]
249
+
250
+
251
+ def split_points_at_nan(points: cpy.PointArray) -> list[cpy.PointArray]:
252
+ """Split a points array at NaNs into a list of point arrays.
253
+ """
254
+ check_point_array(points)
255
+
256
+ nan_offsets = np.nonzero(np.isnan(points[:, 0]))[0]
257
+ if len(nan_offsets) == 0:
258
+ return [points]
259
+ else:
260
+ nan_offsets = np.concatenate(([-1], nan_offsets, [len(points)]))
261
+ return [points[s+1:e] for s, e in pairwise(nan_offsets)]
venv/lib/python3.10/site-packages/contourpy/chunk.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import math
4
+
5
+
6
+ def calc_chunk_sizes(
7
+ chunk_size: int | tuple[int, int] | None,
8
+ chunk_count: int | tuple[int, int] | None,
9
+ total_chunk_count: int | None,
10
+ ny: int,
11
+ nx: int,
12
+ ) -> tuple[int, int]:
13
+ """Calculate chunk sizes.
14
+
15
+ Args:
16
+ chunk_size (int or tuple(int, int), optional): Chunk size in (y, x) directions, or the same
17
+ size in both directions if only one is specified. Cannot be negative.
18
+ chunk_count (int or tuple(int, int), optional): Chunk count in (y, x) directions, or the
19
+ same count in both directions if only one is specified. If less than 1, set to 1.
20
+ total_chunk_count (int, optional): Total number of chunks. If less than 1, set to 1.
21
+ ny (int): Number of grid points in y-direction.
22
+ nx (int): Number of grid points in x-direction.
23
+
24
+ Return:
25
+ tuple(int, int): Chunk sizes (y_chunk_size, x_chunk_size).
26
+
27
+ Note:
28
+ Zero or one of ``chunk_size``, ``chunk_count`` and ``total_chunk_count`` should be
29
+ specified.
30
+ """
31
+ if sum([chunk_size is not None, chunk_count is not None, total_chunk_count is not None]) > 1:
32
+ raise ValueError("Only one of chunk_size, chunk_count and total_chunk_count should be set")
33
+
34
+ if nx < 2 or ny < 2:
35
+ raise ValueError(f"(ny, nx) must be at least (2, 2), not ({ny}, {nx})")
36
+
37
+ if total_chunk_count is not None:
38
+ max_chunk_count = (nx-1)*(ny-1)
39
+ total_chunk_count = min(max(total_chunk_count, 1), max_chunk_count)
40
+ if total_chunk_count == 1:
41
+ chunk_size = 0
42
+ elif total_chunk_count == max_chunk_count:
43
+ chunk_size = (1, 1)
44
+ else:
45
+ factors = two_factors(total_chunk_count)
46
+ if ny > nx:
47
+ chunk_count = factors
48
+ else:
49
+ chunk_count = (factors[1], factors[0])
50
+
51
+ if chunk_count is not None:
52
+ if isinstance(chunk_count, tuple):
53
+ y_chunk_count, x_chunk_count = chunk_count
54
+ else:
55
+ y_chunk_count = x_chunk_count = chunk_count
56
+ x_chunk_count = min(max(x_chunk_count, 1), nx-1)
57
+ y_chunk_count = min(max(y_chunk_count, 1), ny-1)
58
+ chunk_size = (math.ceil((ny-1) / y_chunk_count), math.ceil((nx-1) / x_chunk_count))
59
+
60
+ if chunk_size is None:
61
+ y_chunk_size = x_chunk_size = 0
62
+ elif isinstance(chunk_size, tuple):
63
+ y_chunk_size, x_chunk_size = chunk_size
64
+ else:
65
+ y_chunk_size = x_chunk_size = chunk_size
66
+
67
+ if x_chunk_size < 0 or y_chunk_size < 0:
68
+ raise ValueError("chunk_size cannot be negative")
69
+
70
+ return y_chunk_size, x_chunk_size
71
+
72
+
73
+ def two_factors(n: int) -> tuple[int, int]:
74
+ """Split an integer into two integer factors.
75
+
76
+ The two factors will be as close as possible to the sqrt of n, and are returned in decreasing
77
+ order. Worst case returns (n, 1).
78
+
79
+ Args:
80
+ n (int): The integer to factorize, must be positive.
81
+
82
+ Return:
83
+ tuple(int, int): The two factors of n, in decreasing order.
84
+ """
85
+ if n < 0:
86
+ raise ValueError(f"two_factors expects positive integer not {n}")
87
+
88
+ i = math.ceil(math.sqrt(n))
89
+ while n % i != 0:
90
+ i -= 1
91
+ j = n // i
92
+ if i > j:
93
+ return i, j
94
+ else:
95
+ return j, i
venv/lib/python3.10/site-packages/contourpy/convert.py ADDED
@@ -0,0 +1,621 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from itertools import pairwise
4
+ from typing import TYPE_CHECKING, cast
5
+
6
+ import numpy as np
7
+
8
+ from contourpy._contourpy import FillType, LineType
9
+ import contourpy.array as arr
10
+ from contourpy.enum_util import as_fill_type, as_line_type
11
+ from contourpy.typecheck import check_filled, check_lines
12
+ from contourpy.types import MOVETO, offset_dtype
13
+
14
+ if TYPE_CHECKING:
15
+ import contourpy._contourpy as cpy
16
+
17
+
18
+ def _convert_filled_from_OuterCode(
19
+ filled: cpy.FillReturn_OuterCode,
20
+ fill_type_to: FillType,
21
+ ) -> cpy.FillReturn:
22
+ if fill_type_to == FillType.OuterCode:
23
+ return filled
24
+ elif fill_type_to == FillType.OuterOffset:
25
+ return (filled[0], [arr.offsets_from_codes(codes) for codes in filled[1]])
26
+
27
+ if len(filled[0]) > 0:
28
+ points = arr.concat_points(filled[0])
29
+ codes = arr.concat_codes(filled[1])
30
+ else:
31
+ points = None
32
+ codes = None
33
+
34
+ if fill_type_to == FillType.ChunkCombinedCode:
35
+ return ([points], [codes])
36
+ elif fill_type_to == FillType.ChunkCombinedOffset:
37
+ return ([points], [None if codes is None else arr.offsets_from_codes(codes)])
38
+ elif fill_type_to == FillType.ChunkCombinedCodeOffset:
39
+ outer_offsets = None if points is None else arr.offsets_from_lengths(filled[0])
40
+ ret1: cpy.FillReturn_ChunkCombinedCodeOffset = ([points], [codes], [outer_offsets])
41
+ return ret1
42
+ elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
43
+ if codes is None:
44
+ ret2: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
45
+ else:
46
+ offsets = arr.offsets_from_codes(codes)
47
+ outer_offsets = arr.outer_offsets_from_list_of_codes(filled[1])
48
+ ret2 = ([points], [offsets], [outer_offsets])
49
+ return ret2
50
+ else:
51
+ raise ValueError(f"Invalid FillType {fill_type_to}")
52
+
53
+
54
+ def _convert_filled_from_OuterOffset(
55
+ filled: cpy.FillReturn_OuterOffset,
56
+ fill_type_to: FillType,
57
+ ) -> cpy.FillReturn:
58
+ if fill_type_to == FillType.OuterCode:
59
+ separate_codes = [arr.codes_from_offsets(offsets) for offsets in filled[1]]
60
+ return (filled[0], separate_codes)
61
+ elif fill_type_to == FillType.OuterOffset:
62
+ return filled
63
+
64
+ if len(filled[0]) > 0:
65
+ points = arr.concat_points(filled[0])
66
+ offsets = arr.concat_offsets(filled[1])
67
+ else:
68
+ points = None
69
+ offsets = None
70
+
71
+ if fill_type_to == FillType.ChunkCombinedCode:
72
+ return ([points], [None if offsets is None else arr.codes_from_offsets(offsets)])
73
+ elif fill_type_to == FillType.ChunkCombinedOffset:
74
+ return ([points], [offsets])
75
+ elif fill_type_to == FillType.ChunkCombinedCodeOffset:
76
+ if offsets is None:
77
+ ret1: cpy.FillReturn_ChunkCombinedCodeOffset = ([None], [None], [None])
78
+ else:
79
+ codes = arr.codes_from_offsets(offsets)
80
+ outer_offsets = arr.offsets_from_lengths(filled[0])
81
+ ret1 = ([points], [codes], [outer_offsets])
82
+ return ret1
83
+ elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
84
+ if points is None:
85
+ ret2: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
86
+ else:
87
+ outer_offsets = arr.outer_offsets_from_list_of_offsets(filled[1])
88
+ ret2 = ([points], [offsets], [outer_offsets])
89
+ return ret2
90
+ else:
91
+ raise ValueError(f"Invalid FillType {fill_type_to}")
92
+
93
+
94
+ def _convert_filled_from_ChunkCombinedCode(
95
+ filled: cpy.FillReturn_ChunkCombinedCode,
96
+ fill_type_to: FillType,
97
+ ) -> cpy.FillReturn:
98
+ if fill_type_to == FillType.ChunkCombinedCode:
99
+ return filled
100
+ elif fill_type_to == FillType.ChunkCombinedOffset:
101
+ codes = [None if codes is None else arr.offsets_from_codes(codes) for codes in filled[1]]
102
+ return (filled[0], codes)
103
+ else:
104
+ raise ValueError(
105
+ f"Conversion from {FillType.ChunkCombinedCode} to {fill_type_to} not supported")
106
+
107
+
108
+ def _convert_filled_from_ChunkCombinedOffset(
109
+ filled: cpy.FillReturn_ChunkCombinedOffset,
110
+ fill_type_to: FillType,
111
+ ) -> cpy.FillReturn:
112
+ if fill_type_to == FillType.ChunkCombinedCode:
113
+ chunk_codes: list[cpy.CodeArray | None] = []
114
+ for points, offsets in zip(*filled):
115
+ if points is None:
116
+ chunk_codes.append(None)
117
+ else:
118
+ if TYPE_CHECKING:
119
+ assert offsets is not None
120
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
121
+ return (filled[0], chunk_codes)
122
+ elif fill_type_to == FillType.ChunkCombinedOffset:
123
+ return filled
124
+ else:
125
+ raise ValueError(
126
+ f"Conversion from {FillType.ChunkCombinedOffset} to {fill_type_to} not supported")
127
+
128
+
129
+ def _convert_filled_from_ChunkCombinedCodeOffset(
130
+ filled: cpy.FillReturn_ChunkCombinedCodeOffset,
131
+ fill_type_to: FillType,
132
+ ) -> cpy.FillReturn:
133
+ if fill_type_to == FillType.OuterCode:
134
+ separate_points = []
135
+ separate_codes = []
136
+ for points, codes, outer_offsets in zip(*filled):
137
+ if points is not None:
138
+ if TYPE_CHECKING:
139
+ assert codes is not None
140
+ assert outer_offsets is not None
141
+ separate_points += arr.split_points_by_offsets(points, outer_offsets)
142
+ separate_codes += arr.split_codes_by_offsets(codes, outer_offsets)
143
+ return (separate_points, separate_codes)
144
+ elif fill_type_to == FillType.OuterOffset:
145
+ separate_points = []
146
+ separate_offsets = []
147
+ for points, codes, outer_offsets in zip(*filled):
148
+ if points is not None:
149
+ if TYPE_CHECKING:
150
+ assert codes is not None
151
+ assert outer_offsets is not None
152
+ separate_points += arr.split_points_by_offsets(points, outer_offsets)
153
+ separate_codes = arr.split_codes_by_offsets(codes, outer_offsets)
154
+ separate_offsets += [arr.offsets_from_codes(codes) for codes in separate_codes]
155
+ return (separate_points, separate_offsets)
156
+ elif fill_type_to == FillType.ChunkCombinedCode:
157
+ ret1: cpy.FillReturn_ChunkCombinedCode = (filled[0], filled[1])
158
+ return ret1
159
+ elif fill_type_to == FillType.ChunkCombinedOffset:
160
+ all_offsets = [None if codes is None else arr.offsets_from_codes(codes)
161
+ for codes in filled[1]]
162
+ ret2: cpy.FillReturn_ChunkCombinedOffset = (filled[0], all_offsets)
163
+ return ret2
164
+ elif fill_type_to == FillType.ChunkCombinedCodeOffset:
165
+ return filled
166
+ elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
167
+ chunk_offsets: list[cpy.OffsetArray | None] = []
168
+ chunk_outer_offsets: list[cpy.OffsetArray | None] = []
169
+ for codes, outer_offsets in zip(*filled[1:]):
170
+ if codes is None:
171
+ chunk_offsets.append(None)
172
+ chunk_outer_offsets.append(None)
173
+ else:
174
+ if TYPE_CHECKING:
175
+ assert outer_offsets is not None
176
+ offsets = arr.offsets_from_codes(codes)
177
+ outer_offsets = np.array([np.nonzero(offsets == oo)[0][0] for oo in outer_offsets],
178
+ dtype=offset_dtype)
179
+ chunk_offsets.append(offsets)
180
+ chunk_outer_offsets.append(outer_offsets)
181
+ ret3: cpy.FillReturn_ChunkCombinedOffsetOffset = (
182
+ filled[0], chunk_offsets, chunk_outer_offsets,
183
+ )
184
+ return ret3
185
+ else:
186
+ raise ValueError(f"Invalid FillType {fill_type_to}")
187
+
188
+
189
+ def _convert_filled_from_ChunkCombinedOffsetOffset(
190
+ filled: cpy.FillReturn_ChunkCombinedOffsetOffset,
191
+ fill_type_to: FillType,
192
+ ) -> cpy.FillReturn:
193
+ if fill_type_to == FillType.OuterCode:
194
+ separate_points = []
195
+ separate_codes = []
196
+ for points, offsets, outer_offsets in zip(*filled):
197
+ if points is not None:
198
+ if TYPE_CHECKING:
199
+ assert offsets is not None
200
+ assert outer_offsets is not None
201
+ codes = arr.codes_from_offsets_and_points(offsets, points)
202
+ outer_offsets = offsets[outer_offsets]
203
+ separate_points += arr.split_points_by_offsets(points, outer_offsets)
204
+ separate_codes += arr.split_codes_by_offsets(codes, outer_offsets)
205
+ return (separate_points, separate_codes)
206
+ elif fill_type_to == FillType.OuterOffset:
207
+ separate_points = []
208
+ separate_offsets = []
209
+ for points, offsets, outer_offsets in zip(*filled):
210
+ if points is not None:
211
+ if TYPE_CHECKING:
212
+ assert offsets is not None
213
+ assert outer_offsets is not None
214
+ if len(outer_offsets) > 2:
215
+ separate_offsets += [offsets[s:e+1] - offsets[s] for s, e in
216
+ pairwise(outer_offsets)]
217
+ else:
218
+ separate_offsets.append(offsets)
219
+ separate_points += arr.split_points_by_offsets(points, offsets[outer_offsets])
220
+ return (separate_points, separate_offsets)
221
+ elif fill_type_to == FillType.ChunkCombinedCode:
222
+ chunk_codes: list[cpy.CodeArray | None] = []
223
+ for points, offsets, outer_offsets in zip(*filled):
224
+ if points is None:
225
+ chunk_codes.append(None)
226
+ else:
227
+ if TYPE_CHECKING:
228
+ assert offsets is not None
229
+ assert outer_offsets is not None
230
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
231
+ ret1: cpy.FillReturn_ChunkCombinedCode = (filled[0], chunk_codes)
232
+ return ret1
233
+ elif fill_type_to == FillType.ChunkCombinedOffset:
234
+ return (filled[0], filled[1])
235
+ elif fill_type_to == FillType.ChunkCombinedCodeOffset:
236
+ chunk_codes = []
237
+ chunk_outer_offsets: list[cpy.OffsetArray | None] = []
238
+ for points, offsets, outer_offsets in zip(*filled):
239
+ if points is None:
240
+ chunk_codes.append(None)
241
+ chunk_outer_offsets.append(None)
242
+ else:
243
+ if TYPE_CHECKING:
244
+ assert offsets is not None
245
+ assert outer_offsets is not None
246
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
247
+ chunk_outer_offsets.append(offsets[outer_offsets])
248
+ ret2: cpy.FillReturn_ChunkCombinedCodeOffset = (filled[0], chunk_codes, chunk_outer_offsets)
249
+ return ret2
250
+ elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
251
+ return filled
252
+ else:
253
+ raise ValueError(f"Invalid FillType {fill_type_to}")
254
+
255
+
256
+ def convert_filled(
257
+ filled: cpy.FillReturn,
258
+ fill_type_from: FillType | str,
259
+ fill_type_to: FillType | str,
260
+ ) -> cpy.FillReturn:
261
+ """Convert filled contours from one :class:`~.FillType` to another.
262
+
263
+ Args:
264
+ filled (sequence of arrays): Filled contour polygons to convert, such as those returned by
265
+ :meth:`.ContourGenerator.filled`.
266
+ fill_type_from (FillType or str): :class:`~.FillType` to convert from as enum or
267
+ string equivalent.
268
+ fill_type_to (FillType or str): :class:`~.FillType` to convert to as enum or string
269
+ equivalent.
270
+
271
+ Return:
272
+ Converted filled contour polygons.
273
+
274
+ When converting non-chunked fill types (``FillType.OuterCode`` or ``FillType.OuterOffset``) to
275
+ chunked ones, all polygons are placed in the first chunk. When converting in the other
276
+ direction, all chunk information is discarded. Converting a fill type that is not aware of the
277
+ relationship between outer boundaries and contained holes (``FillType.ChunkCombinedCode`` or
278
+ ``FillType.ChunkCombinedOffset``) to one that is will raise a ``ValueError``.
279
+
280
+ .. versionadded:: 1.2.0
281
+ """
282
+ fill_type_from = as_fill_type(fill_type_from)
283
+ fill_type_to = as_fill_type(fill_type_to)
284
+
285
+ check_filled(filled, fill_type_from)
286
+
287
+ if fill_type_from == FillType.OuterCode:
288
+ if TYPE_CHECKING:
289
+ filled = cast(cpy.FillReturn_OuterCode, filled)
290
+ return _convert_filled_from_OuterCode(filled, fill_type_to)
291
+ elif fill_type_from == FillType.OuterOffset:
292
+ if TYPE_CHECKING:
293
+ filled = cast(cpy.FillReturn_OuterOffset, filled)
294
+ return _convert_filled_from_OuterOffset(filled, fill_type_to)
295
+ elif fill_type_from == FillType.ChunkCombinedCode:
296
+ if TYPE_CHECKING:
297
+ filled = cast(cpy.FillReturn_ChunkCombinedCode, filled)
298
+ return _convert_filled_from_ChunkCombinedCode(filled, fill_type_to)
299
+ elif fill_type_from == FillType.ChunkCombinedOffset:
300
+ if TYPE_CHECKING:
301
+ filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled)
302
+ return _convert_filled_from_ChunkCombinedOffset(filled, fill_type_to)
303
+ elif fill_type_from == FillType.ChunkCombinedCodeOffset:
304
+ if TYPE_CHECKING:
305
+ filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled)
306
+ return _convert_filled_from_ChunkCombinedCodeOffset(filled, fill_type_to)
307
+ elif fill_type_from == FillType.ChunkCombinedOffsetOffset:
308
+ if TYPE_CHECKING:
309
+ filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled)
310
+ return _convert_filled_from_ChunkCombinedOffsetOffset(filled, fill_type_to)
311
+ else:
312
+ raise ValueError(f"Invalid FillType {fill_type_from}")
313
+
314
+
315
+ def _convert_lines_from_Separate(
316
+ lines: cpy.LineReturn_Separate,
317
+ line_type_to: LineType,
318
+ ) -> cpy.LineReturn:
319
+ if line_type_to == LineType.Separate:
320
+ return lines
321
+ elif line_type_to == LineType.SeparateCode:
322
+ separate_codes = [arr.codes_from_points(line) for line in lines]
323
+ return (lines, separate_codes)
324
+ elif line_type_to == LineType.ChunkCombinedCode:
325
+ if not lines:
326
+ ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
327
+ else:
328
+ points = arr.concat_points(lines)
329
+ offsets = arr.offsets_from_lengths(lines)
330
+ codes = arr.codes_from_offsets_and_points(offsets, points)
331
+ ret1 = ([points], [codes])
332
+ return ret1
333
+ elif line_type_to == LineType.ChunkCombinedOffset:
334
+ if not lines:
335
+ ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
336
+ else:
337
+ ret2 = ([arr.concat_points(lines)], [arr.offsets_from_lengths(lines)])
338
+ return ret2
339
+ elif line_type_to == LineType.ChunkCombinedNan:
340
+ if not lines:
341
+ ret3: cpy.LineReturn_ChunkCombinedNan = ([None],)
342
+ else:
343
+ ret3 = ([arr.concat_points_with_nan(lines)],)
344
+ return ret3
345
+ else:
346
+ raise ValueError(f"Invalid LineType {line_type_to}")
347
+
348
+
349
+ def _convert_lines_from_SeparateCode(
350
+ lines: cpy.LineReturn_SeparateCode,
351
+ line_type_to: LineType,
352
+ ) -> cpy.LineReturn:
353
+ if line_type_to == LineType.Separate:
354
+ # Drop codes.
355
+ return lines[0]
356
+ elif line_type_to == LineType.SeparateCode:
357
+ return lines
358
+ elif line_type_to == LineType.ChunkCombinedCode:
359
+ if not lines[0]:
360
+ ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
361
+ else:
362
+ ret1 = ([arr.concat_points(lines[0])], [arr.concat_codes(lines[1])])
363
+ return ret1
364
+ elif line_type_to == LineType.ChunkCombinedOffset:
365
+ if not lines[0]:
366
+ ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
367
+ else:
368
+ ret2 = ([arr.concat_points(lines[0])], [arr.offsets_from_lengths(lines[0])])
369
+ return ret2
370
+ elif line_type_to == LineType.ChunkCombinedNan:
371
+ if not lines[0]:
372
+ ret3: cpy.LineReturn_ChunkCombinedNan = ([None],)
373
+ else:
374
+ ret3 = ([arr.concat_points_with_nan(lines[0])],)
375
+ return ret3
376
+ else:
377
+ raise ValueError(f"Invalid LineType {line_type_to}")
378
+
379
+
380
+ def _convert_lines_from_ChunkCombinedCode(
381
+ lines: cpy.LineReturn_ChunkCombinedCode,
382
+ line_type_to: LineType,
383
+ ) -> cpy.LineReturn:
384
+ if line_type_to in (LineType.Separate, LineType.SeparateCode):
385
+ separate_lines = []
386
+ for points, codes in zip(*lines):
387
+ if points is not None:
388
+ if TYPE_CHECKING:
389
+ assert codes is not None
390
+ split_at = np.nonzero(codes == MOVETO)[0]
391
+ if len(split_at) > 1:
392
+ separate_lines += np.split(points, split_at[1:])
393
+ else:
394
+ separate_lines.append(points)
395
+ if line_type_to == LineType.Separate:
396
+ return separate_lines
397
+ else:
398
+ separate_codes = [arr.codes_from_points(line) for line in separate_lines]
399
+ return (separate_lines, separate_codes)
400
+ elif line_type_to == LineType.ChunkCombinedCode:
401
+ return lines
402
+ elif line_type_to == LineType.ChunkCombinedOffset:
403
+ chunk_offsets = [None if codes is None else arr.offsets_from_codes(codes)
404
+ for codes in lines[1]]
405
+ return (lines[0], chunk_offsets)
406
+ elif line_type_to == LineType.ChunkCombinedNan:
407
+ points_nan: list[cpy.PointArray | None] = []
408
+ for points, codes in zip(*lines):
409
+ if points is None:
410
+ points_nan.append(None)
411
+ else:
412
+ if TYPE_CHECKING:
413
+ assert codes is not None
414
+ offsets = arr.offsets_from_codes(codes)
415
+ points_nan.append(arr.insert_nan_at_offsets(points, offsets))
416
+ return (points_nan,)
417
+ else:
418
+ raise ValueError(f"Invalid LineType {line_type_to}")
419
+
420
+
421
+ def _convert_lines_from_ChunkCombinedOffset(
422
+ lines: cpy.LineReturn_ChunkCombinedOffset,
423
+ line_type_to: LineType,
424
+ ) -> cpy.LineReturn:
425
+ if line_type_to in (LineType.Separate, LineType.SeparateCode):
426
+ separate_lines = []
427
+ for points, offsets in zip(*lines):
428
+ if points is not None:
429
+ if TYPE_CHECKING:
430
+ assert offsets is not None
431
+ separate_lines += arr.split_points_by_offsets(points, offsets)
432
+ if line_type_to == LineType.Separate:
433
+ return separate_lines
434
+ else:
435
+ separate_codes = [arr.codes_from_points(line) for line in separate_lines]
436
+ return (separate_lines, separate_codes)
437
+ elif line_type_to == LineType.ChunkCombinedCode:
438
+ chunk_codes: list[cpy.CodeArray | None] = []
439
+ for points, offsets in zip(*lines):
440
+ if points is None:
441
+ chunk_codes.append(None)
442
+ else:
443
+ if TYPE_CHECKING:
444
+ assert offsets is not None
445
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
446
+ return (lines[0], chunk_codes)
447
+ elif line_type_to == LineType.ChunkCombinedOffset:
448
+ return lines
449
+ elif line_type_to == LineType.ChunkCombinedNan:
450
+ points_nan: list[cpy.PointArray | None] = []
451
+ for points, offsets in zip(*lines):
452
+ if points is None:
453
+ points_nan.append(None)
454
+ else:
455
+ if TYPE_CHECKING:
456
+ assert offsets is not None
457
+ points_nan.append(arr.insert_nan_at_offsets(points, offsets))
458
+ return (points_nan,)
459
+ else:
460
+ raise ValueError(f"Invalid LineType {line_type_to}")
461
+
462
+
463
+ def _convert_lines_from_ChunkCombinedNan(
464
+ lines: cpy.LineReturn_ChunkCombinedNan,
465
+ line_type_to: LineType,
466
+ ) -> cpy.LineReturn:
467
+ if line_type_to in (LineType.Separate, LineType.SeparateCode):
468
+ separate_lines = []
469
+ for points in lines[0]:
470
+ if points is not None:
471
+ separate_lines += arr.split_points_at_nan(points)
472
+ if line_type_to == LineType.Separate:
473
+ return separate_lines
474
+ else:
475
+ separate_codes = [arr.codes_from_points(points) for points in separate_lines]
476
+ return (separate_lines, separate_codes)
477
+ elif line_type_to == LineType.ChunkCombinedCode:
478
+ chunk_points: list[cpy.PointArray | None] = []
479
+ chunk_codes: list[cpy.CodeArray | None] = []
480
+ for points in lines[0]:
481
+ if points is None:
482
+ chunk_points.append(None)
483
+ chunk_codes.append(None)
484
+ else:
485
+ points, offsets = arr.remove_nan(points)
486
+ chunk_points.append(points)
487
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
488
+ return (chunk_points, chunk_codes)
489
+ elif line_type_to == LineType.ChunkCombinedOffset:
490
+ chunk_points = []
491
+ chunk_offsets: list[cpy.OffsetArray | None] = []
492
+ for points in lines[0]:
493
+ if points is None:
494
+ chunk_points.append(None)
495
+ chunk_offsets.append(None)
496
+ else:
497
+ points, offsets = arr.remove_nan(points)
498
+ chunk_points.append(points)
499
+ chunk_offsets.append(offsets)
500
+ return (chunk_points, chunk_offsets)
501
+ elif line_type_to == LineType.ChunkCombinedNan:
502
+ return lines
503
+ else:
504
+ raise ValueError(f"Invalid LineType {line_type_to}")
505
+
506
+
507
+ def convert_lines(
508
+ lines: cpy.LineReturn,
509
+ line_type_from: LineType | str,
510
+ line_type_to: LineType | str,
511
+ ) -> cpy.LineReturn:
512
+ """Convert contour lines from one :class:`~.LineType` to another.
513
+
514
+ Args:
515
+ lines (sequence of arrays): Contour lines to convert, such as those returned by
516
+ :meth:`.ContourGenerator.lines`.
517
+ line_type_from (LineType or str): :class:`~.LineType` to convert from as enum or
518
+ string equivalent.
519
+ line_type_to (LineType or str): :class:`~.LineType` to convert to as enum or string
520
+ equivalent.
521
+
522
+ Return:
523
+ Converted contour lines.
524
+
525
+ When converting non-chunked line types (``LineType.Separate`` or ``LineType.SeparateCode``) to
526
+ chunked ones (``LineType.ChunkCombinedCode``, ``LineType.ChunkCombinedOffset`` or
527
+ ``LineType.ChunkCombinedNan``), all lines are placed in the first chunk. When converting in the
528
+ other direction, all chunk information is discarded.
529
+
530
+ .. versionadded:: 1.2.0
531
+ """
532
+ line_type_from = as_line_type(line_type_from)
533
+ line_type_to = as_line_type(line_type_to)
534
+
535
+ check_lines(lines, line_type_from)
536
+
537
+ if line_type_from == LineType.Separate:
538
+ if TYPE_CHECKING:
539
+ lines = cast(cpy.LineReturn_Separate, lines)
540
+ return _convert_lines_from_Separate(lines, line_type_to)
541
+ elif line_type_from == LineType.SeparateCode:
542
+ if TYPE_CHECKING:
543
+ lines = cast(cpy.LineReturn_SeparateCode, lines)
544
+ return _convert_lines_from_SeparateCode(lines, line_type_to)
545
+ elif line_type_from == LineType.ChunkCombinedCode:
546
+ if TYPE_CHECKING:
547
+ lines = cast(cpy.LineReturn_ChunkCombinedCode, lines)
548
+ return _convert_lines_from_ChunkCombinedCode(lines, line_type_to)
549
+ elif line_type_from == LineType.ChunkCombinedOffset:
550
+ if TYPE_CHECKING:
551
+ lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines)
552
+ return _convert_lines_from_ChunkCombinedOffset(lines, line_type_to)
553
+ elif line_type_from == LineType.ChunkCombinedNan:
554
+ if TYPE_CHECKING:
555
+ lines = cast(cpy.LineReturn_ChunkCombinedNan, lines)
556
+ return _convert_lines_from_ChunkCombinedNan(lines, line_type_to)
557
+ else:
558
+ raise ValueError(f"Invalid LineType {line_type_from}")
559
+
560
+
561
+ def convert_multi_filled(
562
+ multi_filled: list[cpy.FillReturn],
563
+ fill_type_from: FillType | str,
564
+ fill_type_to: FillType | str,
565
+ ) -> list[cpy.FillReturn]:
566
+ """Convert multiple sets of filled contours from one :class:`~.FillType` to another.
567
+
568
+ Args:
569
+ multi_filled (nested sequence of arrays): Filled contour polygons to convert, such as those
570
+ returned by :meth:`.ContourGenerator.multi_filled`.
571
+ fill_type_from (FillType or str): :class:`~.FillType` to convert from as enum or
572
+ string equivalent.
573
+ fill_type_to (FillType or str): :class:`~.FillType` to convert to as enum or string
574
+ equivalent.
575
+
576
+ Return:
577
+ Converted sets filled contour polygons.
578
+
579
+ When converting non-chunked fill types (``FillType.OuterCode`` or ``FillType.OuterOffset``) to
580
+ chunked ones, all polygons are placed in the first chunk. When converting in the other
581
+ direction, all chunk information is discarded. Converting a fill type that is not aware of the
582
+ relationship between outer boundaries and contained holes (``FillType.ChunkCombinedCode`` or
583
+ ``FillType.ChunkCombinedOffset``) to one that is will raise a ``ValueError``.
584
+
585
+ .. versionadded:: 1.3.0
586
+ """
587
+ fill_type_from = as_fill_type(fill_type_from)
588
+ fill_type_to = as_fill_type(fill_type_to)
589
+
590
+ return [convert_filled(filled, fill_type_from, fill_type_to) for filled in multi_filled]
591
+
592
+
593
+ def convert_multi_lines(
594
+ multi_lines: list[cpy.LineReturn],
595
+ line_type_from: LineType | str,
596
+ line_type_to: LineType | str,
597
+ ) -> list[cpy.LineReturn]:
598
+ """Convert multiple sets of contour lines from one :class:`~.LineType` to another.
599
+
600
+ Args:
601
+ multi_lines (nested sequence of arrays): Contour lines to convert, such as those returned by
602
+ :meth:`.ContourGenerator.multi_lines`.
603
+ line_type_from (LineType or str): :class:`~.LineType` to convert from as enum or
604
+ string equivalent.
605
+ line_type_to (LineType or str): :class:`~.LineType` to convert to as enum or string
606
+ equivalent.
607
+
608
+ Return:
609
+ Converted set of contour lines.
610
+
611
+ When converting non-chunked line types (``LineType.Separate`` or ``LineType.SeparateCode``) to
612
+ chunked ones (``LineType.ChunkCombinedCode``, ``LineType.ChunkCombinedOffset`` or
613
+ ``LineType.ChunkCombinedNan``), all lines are placed in the first chunk. When converting in the
614
+ other direction, all chunk information is discarded.
615
+
616
+ .. versionadded:: 1.3.0
617
+ """
618
+ line_type_from = as_line_type(line_type_from)
619
+ line_type_to = as_line_type(line_type_to)
620
+
621
+ return [convert_lines(lines, line_type_from, line_type_to) for lines in multi_lines]
venv/lib/python3.10/site-packages/contourpy/dechunk.py ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, cast
4
+
5
+ from contourpy._contourpy import FillType, LineType
6
+ from contourpy.array import (
7
+ concat_codes_or_none,
8
+ concat_offsets_or_none,
9
+ concat_points_or_none,
10
+ concat_points_or_none_with_nan,
11
+ )
12
+ from contourpy.enum_util import as_fill_type, as_line_type
13
+ from contourpy.typecheck import check_filled, check_lines
14
+
15
+ if TYPE_CHECKING:
16
+ import contourpy._contourpy as cpy
17
+
18
+
19
+ def dechunk_filled(filled: cpy.FillReturn, fill_type: FillType | str) -> cpy.FillReturn:
20
+ """Return the specified filled contours with chunked data moved into the first chunk.
21
+
22
+ Filled contours that are not chunked (``FillType.OuterCode`` and ``FillType.OuterOffset``) and
23
+ those that are but only contain a single chunk are returned unmodified. Individual polygons are
24
+ unchanged, they are not geometrically combined.
25
+
26
+ Args:
27
+ filled (sequence of arrays): Filled contour data, such as returned by
28
+ :meth:`.ContourGenerator.filled`.
29
+ fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` as enum or string
30
+ equivalent.
31
+
32
+ Return:
33
+ Filled contours in a single chunk.
34
+
35
+ .. versionadded:: 1.2.0
36
+ """
37
+ fill_type = as_fill_type(fill_type)
38
+
39
+ if fill_type in (FillType.OuterCode, FillType.OuterOffset):
40
+ # No-op if fill_type is not chunked.
41
+ return filled
42
+
43
+ check_filled(filled, fill_type)
44
+ if len(filled[0]) < 2:
45
+ # No-op if just one chunk.
46
+ return filled
47
+
48
+ if TYPE_CHECKING:
49
+ filled = cast(cpy.FillReturn_Chunk, filled)
50
+ points = concat_points_or_none(filled[0])
51
+
52
+ if fill_type == FillType.ChunkCombinedCode:
53
+ if TYPE_CHECKING:
54
+ filled = cast(cpy.FillReturn_ChunkCombinedCode, filled)
55
+ if points is None:
56
+ ret1: cpy.FillReturn_ChunkCombinedCode = ([None], [None])
57
+ else:
58
+ ret1 = ([points], [concat_codes_or_none(filled[1])])
59
+ return ret1
60
+ elif fill_type == FillType.ChunkCombinedOffset:
61
+ if TYPE_CHECKING:
62
+ filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled)
63
+ if points is None:
64
+ ret2: cpy.FillReturn_ChunkCombinedOffset = ([None], [None])
65
+ else:
66
+ ret2 = ([points], [concat_offsets_or_none(filled[1])])
67
+ return ret2
68
+ elif fill_type == FillType.ChunkCombinedCodeOffset:
69
+ if TYPE_CHECKING:
70
+ filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled)
71
+ if points is None:
72
+ ret3: cpy.FillReturn_ChunkCombinedCodeOffset = ([None], [None], [None])
73
+ else:
74
+ outer_offsets = concat_offsets_or_none(filled[2])
75
+ ret3 = ([points], [concat_codes_or_none(filled[1])], [outer_offsets])
76
+ return ret3
77
+ elif fill_type == FillType.ChunkCombinedOffsetOffset:
78
+ if TYPE_CHECKING:
79
+ filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled)
80
+ if points is None:
81
+ ret4: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
82
+ else:
83
+ outer_offsets = concat_offsets_or_none(filled[2])
84
+ ret4 = ([points], [concat_offsets_or_none(filled[1])], [outer_offsets])
85
+ return ret4
86
+ else:
87
+ raise ValueError(f"Invalid FillType {fill_type}")
88
+
89
+
90
+ def dechunk_lines(lines: cpy.LineReturn, line_type: LineType | str) -> cpy.LineReturn:
91
+ """Return the specified contour lines with chunked data moved into the first chunk.
92
+
93
+ Contour lines that are not chunked (``LineType.Separate`` and ``LineType.SeparateCode``) and
94
+ those that are but only contain a single chunk are returned unmodified. Individual lines are
95
+ unchanged, they are not geometrically combined.
96
+
97
+ Args:
98
+ lines (sequence of arrays): Contour line data, such as returned by
99
+ :meth:`.ContourGenerator.lines`.
100
+ line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` as enum or string
101
+ equivalent.
102
+
103
+ Return:
104
+ Contour lines in a single chunk.
105
+
106
+ .. versionadded:: 1.2.0
107
+ """
108
+ line_type = as_line_type(line_type)
109
+
110
+ if line_type in (LineType.Separate, LineType.SeparateCode):
111
+ # No-op if line_type is not chunked.
112
+ return lines
113
+
114
+ check_lines(lines, line_type)
115
+ if len(lines[0]) < 2:
116
+ # No-op if just one chunk.
117
+ return lines
118
+
119
+ if TYPE_CHECKING:
120
+ lines = cast(cpy.LineReturn_Chunk, lines)
121
+
122
+ if line_type == LineType.ChunkCombinedCode:
123
+ if TYPE_CHECKING:
124
+ lines = cast(cpy.LineReturn_ChunkCombinedCode, lines)
125
+ points = concat_points_or_none(lines[0])
126
+ if points is None:
127
+ ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
128
+ else:
129
+ ret1 = ([points], [concat_codes_or_none(lines[1])])
130
+ return ret1
131
+ elif line_type == LineType.ChunkCombinedOffset:
132
+ if TYPE_CHECKING:
133
+ lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines)
134
+ points = concat_points_or_none(lines[0])
135
+ if points is None:
136
+ ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
137
+ else:
138
+ ret2 = ([points], [concat_offsets_or_none(lines[1])])
139
+ return ret2
140
+ elif line_type == LineType.ChunkCombinedNan:
141
+ if TYPE_CHECKING:
142
+ lines = cast(cpy.LineReturn_ChunkCombinedNan, lines)
143
+ points = concat_points_or_none_with_nan(lines[0])
144
+ ret3: cpy.LineReturn_ChunkCombinedNan = ([points],)
145
+ return ret3
146
+ else:
147
+ raise ValueError(f"Invalid LineType {line_type}")
148
+
149
+
150
+ def dechunk_multi_filled(
151
+ multi_filled: list[cpy.FillReturn],
152
+ fill_type: FillType | str,
153
+ ) -> list[cpy.FillReturn]:
154
+ """Return multiple sets of filled contours with chunked data moved into the first chunks.
155
+
156
+ Filled contours that are not chunked (``FillType.OuterCode`` and ``FillType.OuterOffset``) and
157
+ those that are but only contain a single chunk are returned unmodified. Individual polygons are
158
+ unchanged, they are not geometrically combined.
159
+
160
+ Args:
161
+ multi_filled (nested sequence of arrays): Filled contour data, such as returned by
162
+ :meth:`.ContourGenerator.multi_filled`.
163
+ fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` as enum or string
164
+ equivalent.
165
+
166
+ Return:
167
+ Multiple sets of filled contours in a single chunk.
168
+
169
+ .. versionadded:: 1.3.0
170
+ """
171
+ fill_type = as_fill_type(fill_type)
172
+
173
+ if fill_type in (FillType.OuterCode, FillType.OuterOffset):
174
+ # No-op if fill_type is not chunked.
175
+ return multi_filled
176
+
177
+ return [dechunk_filled(filled, fill_type) for filled in multi_filled]
178
+
179
+
180
+ def dechunk_multi_lines(
181
+ multi_lines: list[cpy.LineReturn],
182
+ line_type: LineType | str,
183
+ ) -> list[cpy.LineReturn]:
184
+ """Return multiple sets of contour lines with all chunked data moved into the first chunks.
185
+
186
+ Contour lines that are not chunked (``LineType.Separate`` and ``LineType.SeparateCode``) and
187
+ those that are but only contain a single chunk are returned unmodified. Individual lines are
188
+ unchanged, they are not geometrically combined.
189
+
190
+ Args:
191
+ multi_lines (nested sequence of arrays): Contour line data, such as returned by
192
+ :meth:`.ContourGenerator.multi_lines`.
193
+ line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` as enum or string
194
+ equivalent.
195
+
196
+ Return:
197
+ Multiple sets of contour lines in a single chunk.
198
+
199
+ .. versionadded:: 1.3.0
200
+ """
201
+ line_type = as_line_type(line_type)
202
+
203
+ if line_type in (LineType.Separate, LineType.SeparateCode):
204
+ # No-op if line_type is not chunked.
205
+ return multi_lines
206
+
207
+ return [dechunk_lines(lines, line_type) for lines in multi_lines]
venv/lib/python3.10/site-packages/contourpy/enum_util.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from contourpy._contourpy import FillType, LineType, ZInterp
4
+
5
+
6
+ def as_fill_type(fill_type: FillType | str) -> FillType:
7
+ """Coerce a FillType or string value to a FillType.
8
+
9
+ Args:
10
+ fill_type (FillType or str): Value to convert.
11
+
12
+ Return:
13
+ FillType: Converted value.
14
+ """
15
+ if isinstance(fill_type, str):
16
+ try:
17
+ return FillType.__members__[fill_type]
18
+ except KeyError as e:
19
+ raise ValueError(f"'{fill_type}' is not a valid FillType") from e
20
+ else:
21
+ return fill_type
22
+
23
+
24
+ def as_line_type(line_type: LineType | str) -> LineType:
25
+ """Coerce a LineType or string value to a LineType.
26
+
27
+ Args:
28
+ line_type (LineType or str): Value to convert.
29
+
30
+ Return:
31
+ LineType: Converted value.
32
+ """
33
+ if isinstance(line_type, str):
34
+ try:
35
+ return LineType.__members__[line_type]
36
+ except KeyError as e:
37
+ raise ValueError(f"'{line_type}' is not a valid LineType") from e
38
+ else:
39
+ return line_type
40
+
41
+
42
+ def as_z_interp(z_interp: ZInterp | str) -> ZInterp:
43
+ """Coerce a ZInterp or string value to a ZInterp.
44
+
45
+ Args:
46
+ z_interp (ZInterp or str): Value to convert.
47
+
48
+ Return:
49
+ ZInterp: Converted value.
50
+ """
51
+ if isinstance(z_interp, str):
52
+ try:
53
+ return ZInterp.__members__[z_interp]
54
+ except KeyError as e:
55
+ raise ValueError(f"'{z_interp}' is not a valid ZInterp") from e
56
+ else:
57
+ return z_interp
venv/lib/python3.10/site-packages/contourpy/py.typed ADDED
File without changes
venv/lib/python3.10/site-packages/contourpy/typecheck.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, cast
4
+
5
+ import numpy as np
6
+
7
+ from contourpy import FillType, LineType
8
+ from contourpy.enum_util import as_fill_type, as_line_type
9
+ from contourpy.types import MOVETO, code_dtype, offset_dtype, point_dtype
10
+
11
+ if TYPE_CHECKING:
12
+ import contourpy._contourpy as cpy
13
+
14
+
15
+ # Minimalist array-checking functions that check dtype, ndims and shape only.
16
+ # They do not walk the arrays to check the contents for performance reasons.
17
+ def check_code_array(codes: Any) -> None:
18
+ if not isinstance(codes, np.ndarray):
19
+ raise TypeError(f"Expected numpy array not {type(codes)}")
20
+ if codes.dtype != code_dtype:
21
+ raise ValueError(f"Expected numpy array of dtype {code_dtype} not {codes.dtype}")
22
+ if not (codes.ndim == 1 and len(codes) > 1):
23
+ raise ValueError(f"Expected numpy array of shape (?,) not {codes.shape}")
24
+ if codes[0] != MOVETO:
25
+ raise ValueError(f"First element of code array must be {MOVETO}, not {codes[0]}")
26
+
27
+
28
+ def check_offset_array(offsets: Any) -> None:
29
+ if not isinstance(offsets, np.ndarray):
30
+ raise TypeError(f"Expected numpy array not {type(offsets)}")
31
+ if offsets.dtype != offset_dtype:
32
+ raise ValueError(f"Expected numpy array of dtype {offset_dtype} not {offsets.dtype}")
33
+ if not (offsets.ndim == 1 and len(offsets) > 1):
34
+ raise ValueError(f"Expected numpy array of shape (?,) not {offsets.shape}")
35
+ if offsets[0] != 0:
36
+ raise ValueError(f"First element of offset array must be 0, not {offsets[0]}")
37
+
38
+
39
+ def check_point_array(points: Any) -> None:
40
+ if not isinstance(points, np.ndarray):
41
+ raise TypeError(f"Expected numpy array not {type(points)}")
42
+ if points.dtype != point_dtype:
43
+ raise ValueError(f"Expected numpy array of dtype {point_dtype} not {points.dtype}")
44
+ if not (points.ndim == 2 and points.shape[1] ==2 and points.shape[0] > 1):
45
+ raise ValueError(f"Expected numpy array of shape (?, 2) not {points.shape}")
46
+
47
+
48
+ def _check_tuple_of_lists_with_same_length(
49
+ maybe_tuple: Any,
50
+ tuple_length: int,
51
+ allow_empty_lists: bool = True,
52
+ ) -> None:
53
+ if not isinstance(maybe_tuple, tuple):
54
+ raise TypeError(f"Expected tuple not {type(maybe_tuple)}")
55
+ if len(maybe_tuple) != tuple_length:
56
+ raise ValueError(f"Expected tuple of length {tuple_length} not {len(maybe_tuple)}")
57
+ for maybe_list in maybe_tuple:
58
+ if not isinstance(maybe_list, list):
59
+ msg = f"Expected tuple to contain {tuple_length} lists but found a {type(maybe_list)}"
60
+ raise TypeError(msg)
61
+ lengths = [len(item) for item in maybe_tuple]
62
+ if len(set(lengths)) != 1:
63
+ msg = f"Expected {tuple_length} lists with same length but lengths are {lengths}"
64
+ raise ValueError(msg)
65
+ if not allow_empty_lists and lengths[0] == 0:
66
+ raise ValueError(f"Expected {tuple_length} non-empty lists")
67
+
68
+
69
+ def check_filled(filled: cpy.FillReturn, fill_type: FillType | str) -> None:
70
+ fill_type = as_fill_type(fill_type)
71
+
72
+ if fill_type == FillType.OuterCode:
73
+ if TYPE_CHECKING:
74
+ filled = cast(cpy.FillReturn_OuterCode, filled)
75
+ _check_tuple_of_lists_with_same_length(filled, 2)
76
+ for i, (points, codes) in enumerate(zip(*filled)):
77
+ check_point_array(points)
78
+ check_code_array(codes)
79
+ if len(points) != len(codes):
80
+ raise ValueError(f"Points and codes have different lengths in polygon {i}")
81
+ elif fill_type == FillType.OuterOffset:
82
+ if TYPE_CHECKING:
83
+ filled = cast(cpy.FillReturn_OuterOffset, filled)
84
+ _check_tuple_of_lists_with_same_length(filled, 2)
85
+ for i, (points, offsets) in enumerate(zip(*filled)):
86
+ check_point_array(points)
87
+ check_offset_array(offsets)
88
+ if offsets[-1] != len(points):
89
+ raise ValueError(f"Inconsistent points and offsets in polygon {i}")
90
+ elif fill_type == FillType.ChunkCombinedCode:
91
+ if TYPE_CHECKING:
92
+ filled = cast(cpy.FillReturn_ChunkCombinedCode, filled)
93
+ _check_tuple_of_lists_with_same_length(filled, 2, allow_empty_lists=False)
94
+ for chunk, (points_or_none, codes_or_none) in enumerate(zip(*filled)):
95
+ if points_or_none is not None and codes_or_none is not None:
96
+ check_point_array(points_or_none)
97
+ check_code_array(codes_or_none)
98
+ if len(points_or_none) != len(codes_or_none):
99
+ raise ValueError(f"Points and codes have different lengths in chunk {chunk}")
100
+ elif not (points_or_none is None and codes_or_none is None):
101
+ raise ValueError(f"Inconsistent Nones in chunk {chunk}")
102
+ elif fill_type == FillType.ChunkCombinedOffset:
103
+ if TYPE_CHECKING:
104
+ filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled)
105
+ _check_tuple_of_lists_with_same_length(filled, 2, allow_empty_lists=False)
106
+ for chunk, (points_or_none, offsets_or_none) in enumerate(zip(*filled)):
107
+ if points_or_none is not None and offsets_or_none is not None:
108
+ check_point_array(points_or_none)
109
+ check_offset_array(offsets_or_none)
110
+ if offsets_or_none[-1] != len(points_or_none):
111
+ raise ValueError(f"Inconsistent points and offsets in chunk {chunk}")
112
+ elif not (points_or_none is None and offsets_or_none is None):
113
+ raise ValueError(f"Inconsistent Nones in chunk {chunk}")
114
+ elif fill_type == FillType.ChunkCombinedCodeOffset:
115
+ if TYPE_CHECKING:
116
+ filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled)
117
+ _check_tuple_of_lists_with_same_length(filled, 3, allow_empty_lists=False)
118
+ for i, (points_or_none, codes_or_none, outer_offsets_or_none) in enumerate(zip(*filled)):
119
+ if (points_or_none is not None and codes_or_none is not None and
120
+ outer_offsets_or_none is not None):
121
+ check_point_array(points_or_none)
122
+ check_code_array(codes_or_none)
123
+ check_offset_array(outer_offsets_or_none)
124
+ if len(codes_or_none) != len(points_or_none):
125
+ raise ValueError(f"Points and codes have different lengths in chunk {i}")
126
+ if outer_offsets_or_none[-1] != len(codes_or_none):
127
+ raise ValueError(f"Inconsistent codes and outer_offsets in chunk {i}")
128
+ elif not (points_or_none is None and codes_or_none is None and
129
+ outer_offsets_or_none is None):
130
+ raise ValueError(f"Inconsistent Nones in chunk {i}")
131
+ elif fill_type == FillType.ChunkCombinedOffsetOffset:
132
+ if TYPE_CHECKING:
133
+ filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled)
134
+ _check_tuple_of_lists_with_same_length(filled, 3, allow_empty_lists=False)
135
+ for i, (points_or_none, offsets_or_none, outer_offsets_or_none) in enumerate(zip(*filled)):
136
+ if (points_or_none is not None and offsets_or_none is not None and
137
+ outer_offsets_or_none is not None):
138
+ check_point_array(points_or_none)
139
+ check_offset_array(offsets_or_none)
140
+ check_offset_array(outer_offsets_or_none)
141
+ if offsets_or_none[-1] != len(points_or_none):
142
+ raise ValueError(f"Inconsistent points and offsets in chunk {i}")
143
+ if outer_offsets_or_none[-1] != len(offsets_or_none) - 1:
144
+ raise ValueError(f"Inconsistent offsets and outer_offsets in chunk {i}")
145
+ elif not (points_or_none is None and offsets_or_none is None and
146
+ outer_offsets_or_none is None):
147
+ raise ValueError(f"Inconsistent Nones in chunk {i}")
148
+ else:
149
+ raise ValueError(f"Invalid FillType {fill_type}")
150
+
151
+
152
+ def check_lines(lines: cpy.LineReturn, line_type: LineType | str) -> None:
153
+ line_type = as_line_type(line_type)
154
+
155
+ if line_type == LineType.Separate:
156
+ if TYPE_CHECKING:
157
+ lines = cast(cpy.LineReturn_Separate, lines)
158
+ if not isinstance(lines, list):
159
+ raise TypeError(f"Expected list not {type(lines)}")
160
+ for points in lines:
161
+ check_point_array(points)
162
+ elif line_type == LineType.SeparateCode:
163
+ if TYPE_CHECKING:
164
+ lines = cast(cpy.LineReturn_SeparateCode, lines)
165
+ _check_tuple_of_lists_with_same_length(lines, 2)
166
+ for i, (points, codes) in enumerate(zip(*lines)):
167
+ check_point_array(points)
168
+ check_code_array(codes)
169
+ if len(points) != len(codes):
170
+ raise ValueError(f"Points and codes have different lengths in line {i}")
171
+ elif line_type == LineType.ChunkCombinedCode:
172
+ if TYPE_CHECKING:
173
+ lines = cast(cpy.LineReturn_ChunkCombinedCode, lines)
174
+ _check_tuple_of_lists_with_same_length(lines, 2, allow_empty_lists=False)
175
+ for chunk, (points_or_none, codes_or_none) in enumerate(zip(*lines)):
176
+ if points_or_none is not None and codes_or_none is not None:
177
+ check_point_array(points_or_none)
178
+ check_code_array(codes_or_none)
179
+ if len(points_or_none) != len(codes_or_none):
180
+ raise ValueError(f"Points and codes have different lengths in chunk {chunk}")
181
+ elif not (points_or_none is None and codes_or_none is None):
182
+ raise ValueError(f"Inconsistent Nones in chunk {chunk}")
183
+ elif line_type == LineType.ChunkCombinedOffset:
184
+ if TYPE_CHECKING:
185
+ lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines)
186
+ _check_tuple_of_lists_with_same_length(lines, 2, allow_empty_lists=False)
187
+ for chunk, (points_or_none, offsets_or_none) in enumerate(zip(*lines)):
188
+ if points_or_none is not None and offsets_or_none is not None:
189
+ check_point_array(points_or_none)
190
+ check_offset_array(offsets_or_none)
191
+ if offsets_or_none[-1] != len(points_or_none):
192
+ raise ValueError(f"Inconsistent points and offsets in chunk {chunk}")
193
+ elif not (points_or_none is None and offsets_or_none is None):
194
+ raise ValueError(f"Inconsistent Nones in chunk {chunk}")
195
+ elif line_type == LineType.ChunkCombinedNan:
196
+ if TYPE_CHECKING:
197
+ lines = cast(cpy.LineReturn_ChunkCombinedNan, lines)
198
+ _check_tuple_of_lists_with_same_length(lines, 1, allow_empty_lists=False)
199
+ for _chunk, points_or_none in enumerate(lines[0]):
200
+ if points_or_none is not None:
201
+ check_point_array(points_or_none)
202
+ else:
203
+ raise ValueError(f"Invalid LineType {line_type}")
venv/lib/python3.10/site-packages/contourpy/types.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+
5
+ # dtypes of arrays returned by ContourPy.
6
+ point_dtype = np.float64
7
+ code_dtype = np.uint8
8
+ offset_dtype = np.uint32
9
+
10
+ # Kind codes used in Matplotlib Paths.
11
+ MOVETO = 1
12
+ LINETO = 2
13
+ CLOSEPOLY = 79
venv/lib/python3.10/site-packages/contourpy/util/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from contourpy.util._build_config import build_config
4
+
5
+ __all__ = ["build_config"]
venv/lib/python3.10/site-packages/contourpy/util/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (312 Bytes). View file
 
venv/lib/python3.10/site-packages/contourpy/util/__pycache__/_build_config.cpython-310.pyc ADDED
Binary file (1.79 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/util/__pycache__/bokeh_renderer.cpython-310.pyc ADDED
Binary file (13.8 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/util/__pycache__/bokeh_util.cpython-310.pyc ADDED
Binary file (2.21 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/util/__pycache__/data.cpython-310.pyc ADDED
Binary file (2.72 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/util/__pycache__/mpl_renderer.cpython-310.pyc ADDED
Binary file (18.6 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/util/__pycache__/mpl_util.cpython-310.pyc ADDED
Binary file (3.12 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/util/__pycache__/renderer.cpython-310.pyc ADDED
Binary file (6.01 kB). View file
 
venv/lib/python3.10/site-packages/contourpy/util/_build_config.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # _build_config.py.in is converted into _build_config.py during the meson build process.
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def build_config() -> dict[str, str]:
7
+ """
8
+ Return a dictionary containing build configuration settings.
9
+
10
+ All dictionary keys and values are strings, for example ``False`` is
11
+ returned as ``"False"``.
12
+
13
+ .. versionadded:: 1.1.0
14
+ """
15
+ return dict(
16
+ # Python settings
17
+ python_version="3.10",
18
+ python_install_dir=r"/usr/local/lib/python3.10/site-packages/",
19
+ python_path=r"/tmp/build-env-dfa9bu3d/bin/python",
20
+
21
+ # Package versions
22
+ contourpy_version="1.3.2",
23
+ meson_version="1.7.2",
24
+ mesonpy_version="0.17.1",
25
+ pybind11_version="2.13.6",
26
+
27
+ # Misc meson settings
28
+ meson_backend="ninja",
29
+ build_dir=r"/project/.mesonpy-0roiogjz/lib/contourpy/util",
30
+ source_dir=r"/project/lib/contourpy/util",
31
+ cross_build="False",
32
+
33
+ # Build options
34
+ build_options=r"-Dbuildtype=release -Db_ndebug=if-release -Db_vscrt=md -Dvsenv=True --native-file=/project/.mesonpy-0roiogjz/meson-python-native-file.ini",
35
+ buildtype="release",
36
+ cpp_std="c++17",
37
+ debug="False",
38
+ optimization="3",
39
+ vsenv="True",
40
+ b_ndebug="if-release",
41
+ b_vscrt="from_buildtype",
42
+
43
+ # C++ compiler
44
+ compiler_name="gcc",
45
+ compiler_version="10.2.1",
46
+ linker_id="ld.bfd",
47
+ compile_command="c++",
48
+
49
+ # Host machine
50
+ host_cpu="x86_64",
51
+ host_cpu_family="x86_64",
52
+ host_cpu_endian="little",
53
+ host_cpu_system="linux",
54
+
55
+ # Build machine, same as host machine if not a cross_build
56
+ build_cpu="x86_64",
57
+ build_cpu_family="x86_64",
58
+ build_cpu_endian="little",
59
+ build_cpu_system="linux",
60
+ )
venv/lib/python3.10/site-packages/contourpy/util/bokeh_renderer.py ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import io
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from bokeh.io import export_png, export_svg, show
7
+ from bokeh.io.export import get_screenshot_as_png
8
+ from bokeh.layouts import gridplot
9
+ from bokeh.models.annotations.labels import Label
10
+ from bokeh.palettes import Category10
11
+ from bokeh.plotting import figure
12
+ import numpy as np
13
+
14
+ from contourpy.enum_util import as_fill_type, as_line_type
15
+ from contourpy.util.bokeh_util import filled_to_bokeh, lines_to_bokeh
16
+ from contourpy.util.renderer import Renderer
17
+
18
+ if TYPE_CHECKING:
19
+ from bokeh.core.enums import OutputBackendType
20
+ from bokeh.models import GridPlot
21
+ from bokeh.palettes import Palette
22
+ from numpy.typing import ArrayLike
23
+ from selenium.webdriver.remote.webdriver import WebDriver
24
+
25
+ from contourpy import FillType, LineType
26
+ from contourpy._contourpy import FillReturn, LineReturn
27
+
28
+
29
+ class BokehRenderer(Renderer):
30
+ """Utility renderer using Bokeh to render a grid of plots over the same (x, y) range.
31
+
32
+ Args:
33
+ nrows (int, optional): Number of rows of plots, default ``1``.
34
+ ncols (int, optional): Number of columns of plots, default ``1``.
35
+ figsize (tuple(float, float), optional): Figure size in inches (assuming 100 dpi), default
36
+ ``(9, 9)``.
37
+ show_frame (bool, optional): Whether to show frame and axes ticks, default ``True``.
38
+ want_svg (bool, optional): Whether output is required in SVG format or not, default
39
+ ``False``.
40
+
41
+ Warning:
42
+ :class:`~.BokehRenderer`, unlike :class:`~.MplRenderer`, needs to be told in advance if
43
+ output to SVG format will be required later, otherwise it will assume PNG output.
44
+ """
45
+ _figures: list[figure]
46
+ _layout: GridPlot
47
+ _palette: Palette
48
+ _want_svg: bool
49
+
50
+ def __init__(
51
+ self,
52
+ nrows: int = 1,
53
+ ncols: int = 1,
54
+ figsize: tuple[float, float] = (9, 9),
55
+ show_frame: bool = True,
56
+ want_svg: bool = False,
57
+ ) -> None:
58
+ self._want_svg = want_svg
59
+ self._palette = Category10[10]
60
+
61
+ total_size = 100*np.asarray(figsize, dtype=int) # Assuming 100 dpi.
62
+
63
+ nfigures = nrows*ncols
64
+ self._figures = []
65
+ backend: OutputBackendType = "svg" if self._want_svg else "canvas"
66
+ for _ in range(nfigures):
67
+ fig = figure(output_backend=backend)
68
+ fig.xgrid.visible = False # type: ignore[attr-defined]
69
+ fig.ygrid.visible = False # type: ignore[attr-defined]
70
+ self._figures.append(fig)
71
+ if not show_frame:
72
+ fig.outline_line_color = None
73
+ fig.axis.visible = False # type: ignore[attr-defined]
74
+
75
+ self._layout = gridplot(
76
+ self._figures, ncols=ncols, toolbar_location=None, # type: ignore[arg-type]
77
+ width=total_size[0] // ncols, height=total_size[1] // nrows)
78
+
79
+ def _convert_color(self, color: str) -> str:
80
+ if isinstance(color, str) and color[0] == "C":
81
+ index = int(color[1:])
82
+ color = self._palette[index]
83
+ return color
84
+
85
+ def _get_figure(self, ax: figure | int) -> figure:
86
+ if isinstance(ax, int):
87
+ ax = self._figures[ax]
88
+ return ax
89
+
90
+ def filled(
91
+ self,
92
+ filled: FillReturn,
93
+ fill_type: FillType | str,
94
+ ax: figure | int = 0,
95
+ color: str = "C0",
96
+ alpha: float = 0.7,
97
+ ) -> None:
98
+ """Plot filled contours on a single plot.
99
+
100
+ Args:
101
+ filled (sequence of arrays): Filled contour data as returned by
102
+ :meth:`~.ContourGenerator.filled`.
103
+ fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` data as returned
104
+ by :attr:`~.ContourGenerator.fill_type`, or a string equivalent.
105
+ ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
106
+ color (str, optional): Color to plot with. May be a string color or the letter ``"C"``
107
+ followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
108
+ ``Category10`` palette. Default ``"C0"``.
109
+ alpha (float, optional): Opacity to plot with, default ``0.7``.
110
+ """
111
+ fill_type = as_fill_type(fill_type)
112
+ fig = self._get_figure(ax)
113
+ color = self._convert_color(color)
114
+ xs, ys = filled_to_bokeh(filled, fill_type)
115
+ if len(xs) > 0:
116
+ fig.multi_polygons(xs=[xs], ys=[ys], color=color, fill_alpha=alpha, line_width=0) # type: ignore[arg-type]
117
+
118
+ def grid(
119
+ self,
120
+ x: ArrayLike,
121
+ y: ArrayLike,
122
+ ax: figure | int = 0,
123
+ color: str = "black",
124
+ alpha: float = 0.1,
125
+ point_color: str | None = None,
126
+ quad_as_tri_alpha: float = 0,
127
+ ) -> None:
128
+ """Plot quad grid lines on a single plot.
129
+
130
+ Args:
131
+ x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
132
+ y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
133
+ ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
134
+ color (str, optional): Color to plot grid lines, default ``"black"``.
135
+ alpha (float, optional): Opacity to plot lines with, default ``0.1``.
136
+ point_color (str, optional): Color to plot grid points or ``None`` if grid points
137
+ should not be plotted, default ``None``.
138
+ quad_as_tri_alpha (float, optional): Opacity to plot ``quad_as_tri`` grid, default
139
+ ``0``.
140
+
141
+ Colors may be a string color or the letter ``"C"`` followed by an integer in the range
142
+ ``"C0"`` to ``"C9"`` to use a color from the ``Category10`` palette.
143
+
144
+ Warning:
145
+ ``quad_as_tri_alpha > 0`` plots all quads as though they are unmasked.
146
+ """
147
+ fig = self._get_figure(ax)
148
+ x, y = self._grid_as_2d(x, y)
149
+ xs = list(x) + list(x.T)
150
+ ys = list(y) + list(y.T)
151
+ kwargs = {"line_color": color, "alpha": alpha}
152
+ fig.multi_line(xs, ys, **kwargs)
153
+ if quad_as_tri_alpha > 0:
154
+ # Assumes no quad mask.
155
+ xmid = (0.25*(x[:-1, :-1] + x[1:, :-1] + x[:-1, 1:] + x[1:, 1:])).ravel()
156
+ ymid = (0.25*(y[:-1, :-1] + y[1:, :-1] + y[:-1, 1:] + y[1:, 1:])).ravel()
157
+ fig.multi_line(
158
+ list(np.stack((x[:-1, :-1].ravel(), xmid, x[1:, 1:].ravel()), axis=1)),
159
+ list(np.stack((y[:-1, :-1].ravel(), ymid, y[1:, 1:].ravel()), axis=1)),
160
+ **kwargs)
161
+ fig.multi_line(
162
+ list(np.stack((x[:-1, 1:].ravel(), xmid, x[1:, :-1].ravel()), axis=1)),
163
+ list(np.stack((y[:-1, 1:].ravel(), ymid, y[1:, :-1].ravel()), axis=1)),
164
+ **kwargs)
165
+ if point_color is not None:
166
+ fig.scatter(
167
+ x=x.ravel(), y=y.ravel(), fill_color=color, line_color=None, alpha=alpha,
168
+ marker="circle", size=8)
169
+
170
+ def lines(
171
+ self,
172
+ lines: LineReturn,
173
+ line_type: LineType | str,
174
+ ax: figure | int = 0,
175
+ color: str = "C0",
176
+ alpha: float = 1.0,
177
+ linewidth: float = 1,
178
+ ) -> None:
179
+ """Plot contour lines on a single plot.
180
+
181
+ Args:
182
+ lines (sequence of arrays): Contour line data as returned by
183
+ :meth:`~.ContourGenerator.lines`.
184
+ line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` data as returned
185
+ by :attr:`~.ContourGenerator.line_type`, or a string equivalent.
186
+ ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
187
+ color (str, optional): Color to plot lines. May be a string color or the letter ``"C"``
188
+ followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
189
+ ``Category10`` palette. Default ``"C0"``.
190
+ alpha (float, optional): Opacity to plot lines with, default ``1.0``.
191
+ linewidth (float, optional): Width of lines, default ``1``.
192
+
193
+ Note:
194
+ Assumes all lines are open line strips not closed line loops.
195
+ """
196
+ line_type = as_line_type(line_type)
197
+ fig = self._get_figure(ax)
198
+ color = self._convert_color(color)
199
+ xs, ys = lines_to_bokeh(lines, line_type)
200
+ if xs is not None:
201
+ assert ys is not None
202
+ fig.line(xs, ys, line_color=color, line_alpha=alpha, line_width=linewidth)
203
+
204
+ def mask(
205
+ self,
206
+ x: ArrayLike,
207
+ y: ArrayLike,
208
+ z: ArrayLike | np.ma.MaskedArray[Any, Any],
209
+ ax: figure | int = 0,
210
+ color: str = "black",
211
+ ) -> None:
212
+ """Plot masked out grid points as circles on a single plot.
213
+
214
+ Args:
215
+ x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
216
+ y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
217
+ z (masked array of shape (ny, nx): z-values.
218
+ ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
219
+ color (str, optional): Circle color, default ``"black"``.
220
+ """
221
+ mask = np.ma.getmask(z) # type: ignore[no-untyped-call]
222
+ if mask is np.ma.nomask:
223
+ return
224
+ fig = self._get_figure(ax)
225
+ color = self._convert_color(color)
226
+ x, y = self._grid_as_2d(x, y)
227
+ fig.scatter(x[mask], y[mask], fill_color=color, marker="circle", size=10)
228
+
229
+ def save(
230
+ self,
231
+ filename: str,
232
+ transparent: bool = False,
233
+ *,
234
+ webdriver: WebDriver | None = None,
235
+ ) -> None:
236
+ """Save plots to SVG or PNG file.
237
+
238
+ Args:
239
+ filename (str): Filename to save to.
240
+ transparent (bool, optional): Whether background should be transparent, default
241
+ ``False``.
242
+ webdriver (WebDriver, optional): Selenium WebDriver instance to use to create the image.
243
+
244
+ .. versionadded:: 1.1.1
245
+
246
+ Warning:
247
+ To output to SVG file, ``want_svg=True`` must have been passed to the constructor.
248
+ """
249
+ if transparent:
250
+ for fig in self._figures:
251
+ fig.background_fill_color = None
252
+ fig.border_fill_color = None
253
+
254
+ if self._want_svg:
255
+ export_svg(self._layout, filename=filename, webdriver=webdriver)
256
+ else:
257
+ export_png(self._layout, filename=filename, webdriver=webdriver)
258
+
259
+ def save_to_buffer(self, *, webdriver: WebDriver | None = None) -> io.BytesIO:
260
+ """Save plots to an ``io.BytesIO`` buffer.
261
+
262
+ Args:
263
+ webdriver (WebDriver, optional): Selenium WebDriver instance to use to create the image.
264
+
265
+ .. versionadded:: 1.1.1
266
+
267
+ Return:
268
+ BytesIO: PNG image buffer.
269
+ """
270
+ image = get_screenshot_as_png(self._layout, driver=webdriver)
271
+ buffer = io.BytesIO()
272
+ image.save(buffer, "png")
273
+ return buffer
274
+
275
+ def show(self) -> None:
276
+ """Show plots in web browser, in usual Bokeh manner.
277
+ """
278
+ show(self._layout)
279
+
280
+ def title(self, title: str, ax: figure | int = 0, color: str | None = None) -> None:
281
+ """Set the title of a single plot.
282
+
283
+ Args:
284
+ title (str): Title text.
285
+ ax (int or Bokeh Figure, optional): Which plot to set the title of, default ``0``.
286
+ color (str, optional): Color to set title. May be a string color or the letter ``"C"``
287
+ followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
288
+ ``Category10`` palette. Default ``None`` which is ``black``.
289
+ """
290
+ fig = self._get_figure(ax)
291
+ fig.title = title
292
+ fig.title.align = "center" # type: ignore[attr-defined]
293
+ if color is not None:
294
+ fig.title.text_color = self._convert_color(color) # type: ignore[attr-defined]
295
+
296
+ def z_values(
297
+ self,
298
+ x: ArrayLike,
299
+ y: ArrayLike,
300
+ z: ArrayLike,
301
+ ax: figure | int = 0,
302
+ color: str = "green",
303
+ fmt: str = ".1f",
304
+ quad_as_tri: bool = False,
305
+ ) -> None:
306
+ """Show ``z`` values on a single plot.
307
+
308
+ Args:
309
+ x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
310
+ y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
311
+ z (array-like of shape (ny, nx): z-values.
312
+ ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
313
+ color (str, optional): Color of added text. May be a string color or the letter ``"C"``
314
+ followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
315
+ ``Category10`` palette. Default ``"green"``.
316
+ fmt (str, optional): Format to display z-values, default ``".1f"``.
317
+ quad_as_tri (bool, optional): Whether to show z-values at the ``quad_as_tri`` centres
318
+ of quads.
319
+
320
+ Warning:
321
+ ``quad_as_tri=True`` shows z-values for all quads, even if masked.
322
+ """
323
+ fig = self._get_figure(ax)
324
+ color = self._convert_color(color)
325
+ x, y = self._grid_as_2d(x, y)
326
+ z = np.asarray(z)
327
+ ny, nx = z.shape
328
+ kwargs = {"text_color": color, "text_align": "center", "text_baseline": "middle"}
329
+ for j in range(ny):
330
+ for i in range(nx):
331
+ label = Label(x=x[j, i], y=y[j, i], text=f"{z[j, i]:{fmt}}", **kwargs) # type: ignore[arg-type]
332
+ fig.add_layout(label)
333
+ if quad_as_tri:
334
+ for j in range(ny-1):
335
+ for i in range(nx-1):
336
+ xx = np.mean(x[j:j+2, i:i+2])
337
+ yy = np.mean(y[j:j+2, i:i+2])
338
+ zz = np.mean(z[j:j+2, i:i+2])
339
+ fig.add_layout(Label(x=xx, y=yy, text=f"{zz:{fmt}}", **kwargs)) # type: ignore[arg-type]
venv/lib/python3.10/site-packages/contourpy/util/bokeh_util.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, cast
4
+
5
+ from contourpy import FillType, LineType
6
+ from contourpy.array import offsets_from_codes
7
+ from contourpy.convert import convert_lines
8
+ from contourpy.dechunk import dechunk_lines
9
+
10
+ if TYPE_CHECKING:
11
+ from contourpy._contourpy import (
12
+ CoordinateArray,
13
+ FillReturn,
14
+ LineReturn,
15
+ LineReturn_ChunkCombinedNan,
16
+ )
17
+
18
+
19
+ def filled_to_bokeh(
20
+ filled: FillReturn,
21
+ fill_type: FillType,
22
+ ) -> tuple[list[list[CoordinateArray]], list[list[CoordinateArray]]]:
23
+ xs: list[list[CoordinateArray]] = []
24
+ ys: list[list[CoordinateArray]] = []
25
+ if fill_type in (FillType.OuterOffset, FillType.ChunkCombinedOffset,
26
+ FillType.OuterCode, FillType.ChunkCombinedCode):
27
+ have_codes = fill_type in (FillType.OuterCode, FillType.ChunkCombinedCode)
28
+
29
+ for points, offsets in zip(*filled):
30
+ if points is None:
31
+ continue
32
+ if have_codes:
33
+ offsets = offsets_from_codes(offsets)
34
+ xs.append([]) # New outer with zero or more holes.
35
+ ys.append([])
36
+ for i in range(len(offsets)-1):
37
+ xys = points[offsets[i]:offsets[i+1]]
38
+ xs[-1].append(xys[:, 0])
39
+ ys[-1].append(xys[:, 1])
40
+ elif fill_type in (FillType.ChunkCombinedCodeOffset, FillType.ChunkCombinedOffsetOffset):
41
+ for points, codes_or_offsets, outer_offsets in zip(*filled):
42
+ if points is None:
43
+ continue
44
+ for j in range(len(outer_offsets)-1):
45
+ if fill_type == FillType.ChunkCombinedCodeOffset:
46
+ codes = codes_or_offsets[outer_offsets[j]:outer_offsets[j+1]]
47
+ offsets = offsets_from_codes(codes) + outer_offsets[j]
48
+ else:
49
+ offsets = codes_or_offsets[outer_offsets[j]:outer_offsets[j+1]+1]
50
+ xs.append([]) # New outer with zero or more holes.
51
+ ys.append([])
52
+ for k in range(len(offsets)-1):
53
+ xys = points[offsets[k]:offsets[k+1]]
54
+ xs[-1].append(xys[:, 0])
55
+ ys[-1].append(xys[:, 1])
56
+ else:
57
+ raise RuntimeError(f"Conversion of FillType {fill_type} to Bokeh is not implemented")
58
+
59
+ return xs, ys
60
+
61
+
62
+ def lines_to_bokeh(
63
+ lines: LineReturn,
64
+ line_type: LineType,
65
+ ) -> tuple[CoordinateArray | None, CoordinateArray | None]:
66
+ lines = convert_lines(lines, line_type, LineType.ChunkCombinedNan)
67
+ lines = dechunk_lines(lines, LineType.ChunkCombinedNan)
68
+ if TYPE_CHECKING:
69
+ lines = cast(LineReturn_ChunkCombinedNan, lines)
70
+ points = lines[0][0]
71
+ if points is None:
72
+ return None, None
73
+ else:
74
+ return points[:, 0], points[:, 1]
venv/lib/python3.10/site-packages/contourpy/util/data.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ import numpy as np
6
+
7
+ if TYPE_CHECKING:
8
+ from contourpy._contourpy import CoordinateArray
9
+
10
+
11
+ def simple(
12
+ shape: tuple[int, int], want_mask: bool = False,
13
+ ) -> tuple[CoordinateArray, CoordinateArray, CoordinateArray | np.ma.MaskedArray[Any, Any]]:
14
+ """Return simple test data consisting of the sum of two gaussians.
15
+
16
+ Args:
17
+ shape (tuple(int, int)): 2D shape of data to return.
18
+ want_mask (bool, optional): Whether test data should be masked or not, default ``False``.
19
+
20
+ Return:
21
+ Tuple of 3 arrays: ``x``, ``y``, ``z`` test data, ``z`` will be masked if
22
+ ``want_mask=True``.
23
+ """
24
+ ny, nx = shape
25
+ x = np.arange(nx, dtype=np.float64)
26
+ y = np.arange(ny, dtype=np.float64)
27
+ x, y = np.meshgrid(x, y)
28
+
29
+ xscale = nx - 1.0
30
+ yscale = ny - 1.0
31
+
32
+ # z is sum of 2D gaussians.
33
+ amp = np.asarray([1.0, -1.0, 0.8, -0.9, 0.7])
34
+ mid = np.asarray([[0.4, 0.2], [0.3, 0.8], [0.9, 0.75], [0.7, 0.3], [0.05, 0.7]])
35
+ width = np.asarray([0.4, 0.2, 0.2, 0.2, 0.1])
36
+
37
+ z = np.zeros_like(x)
38
+ for i in range(len(amp)):
39
+ z += amp[i]*np.exp(-((x/xscale - mid[i, 0])**2 + (y/yscale - mid[i, 1])**2) / width[i]**2)
40
+
41
+ if want_mask:
42
+ mask = np.logical_or(
43
+ ((x/xscale - 1.0)**2 / 0.2 + (y/yscale - 0.0)**2 / 0.1) < 1.0,
44
+ ((x/xscale - 0.2)**2 / 0.02 + (y/yscale - 0.45)**2 / 0.08) < 1.0,
45
+ )
46
+ z = np.ma.array(z, mask=mask) # type: ignore[no-untyped-call]
47
+
48
+ return x, y, z
49
+
50
+
51
+ def random(
52
+ shape: tuple[int, int], seed: int = 2187, mask_fraction: float = 0.0,
53
+ ) -> tuple[CoordinateArray, CoordinateArray, CoordinateArray | np.ma.MaskedArray[Any, Any]]:
54
+ """Return random test data in the range 0 to 1.
55
+
56
+ Args:
57
+ shape (tuple(int, int)): 2D shape of data to return.
58
+ seed (int, optional): Seed for random number generator, default 2187.
59
+ mask_fraction (float, optional): Fraction of elements to mask, default 0.
60
+
61
+ Return:
62
+ Tuple of 3 arrays: ``x``, ``y``, ``z`` test data, ``z`` will be masked if
63
+ ``mask_fraction`` is greater than zero.
64
+ """
65
+ ny, nx = shape
66
+ x = np.arange(nx, dtype=np.float64)
67
+ y = np.arange(ny, dtype=np.float64)
68
+ x, y = np.meshgrid(x, y)
69
+
70
+ rng = np.random.default_rng(seed)
71
+ z = rng.uniform(size=shape)
72
+
73
+ if mask_fraction > 0.0:
74
+ mask_fraction = min(mask_fraction, 0.99)
75
+ mask = rng.uniform(size=shape) < mask_fraction
76
+ z = np.ma.array(z, mask=mask) # type: ignore[no-untyped-call]
77
+
78
+ return x, y, z
venv/lib/python3.10/site-packages/contourpy/util/mpl_renderer.py ADDED
@@ -0,0 +1,535 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import io
4
+ from itertools import pairwise
5
+ from typing import TYPE_CHECKING, Any, cast
6
+
7
+ import matplotlib.collections as mcollections
8
+ import matplotlib.pyplot as plt
9
+ import numpy as np
10
+
11
+ from contourpy import FillType, LineType
12
+ from contourpy.convert import convert_filled, convert_lines
13
+ from contourpy.enum_util import as_fill_type, as_line_type
14
+ from contourpy.util.mpl_util import filled_to_mpl_paths, lines_to_mpl_paths
15
+ from contourpy.util.renderer import Renderer
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Sequence
19
+
20
+ from matplotlib.axes import Axes
21
+ from matplotlib.figure import Figure
22
+ from numpy.typing import ArrayLike
23
+
24
+ import contourpy._contourpy as cpy
25
+
26
+
27
+ class MplRenderer(Renderer):
28
+ """Utility renderer using Matplotlib to render a grid of plots over the same (x, y) range.
29
+
30
+ Args:
31
+ nrows (int, optional): Number of rows of plots, default ``1``.
32
+ ncols (int, optional): Number of columns of plots, default ``1``.
33
+ figsize (tuple(float, float), optional): Figure size in inches, default ``(9, 9)``.
34
+ show_frame (bool, optional): Whether to show frame and axes ticks, default ``True``.
35
+ backend (str, optional): Matplotlib backend to use or ``None`` for default backend.
36
+ Default ``None``.
37
+ gridspec_kw (dict, optional): Gridspec keyword arguments to pass to ``plt.subplots``,
38
+ default None.
39
+ """
40
+ _axes: Sequence[Axes]
41
+ _fig: Figure
42
+ _want_tight: bool
43
+
44
+ def __init__(
45
+ self,
46
+ nrows: int = 1,
47
+ ncols: int = 1,
48
+ figsize: tuple[float, float] = (9, 9),
49
+ show_frame: bool = True,
50
+ backend: str | None = None,
51
+ gridspec_kw: dict[str, Any] | None = None,
52
+ ) -> None:
53
+ if backend is not None:
54
+ import matplotlib as mpl
55
+ mpl.use(backend)
56
+
57
+ kwargs: dict[str, Any] = {"figsize": figsize, "squeeze": False,
58
+ "sharex": True, "sharey": True}
59
+ if gridspec_kw is not None:
60
+ kwargs["gridspec_kw"] = gridspec_kw
61
+ else:
62
+ kwargs["subplot_kw"] = {"aspect": "equal"}
63
+
64
+ self._fig, axes = plt.subplots(nrows, ncols, **kwargs)
65
+ self._axes = axes.flatten()
66
+ if not show_frame:
67
+ for ax in self._axes:
68
+ ax.axis("off")
69
+
70
+ self._want_tight = True
71
+
72
+ def __del__(self) -> None:
73
+ if hasattr(self, "_fig"):
74
+ plt.close(self._fig)
75
+
76
+ def _autoscale(self) -> None:
77
+ # Using axes._need_autoscale attribute if need to autoscale before rendering after adding
78
+ # lines/filled. Only want to autoscale once per axes regardless of how many lines/filled
79
+ # added.
80
+ for ax in self._axes:
81
+ if getattr(ax, "_need_autoscale", False):
82
+ ax.autoscale_view(tight=True)
83
+ ax._need_autoscale = False # type: ignore[attr-defined]
84
+ if self._want_tight and len(self._axes) > 1:
85
+ self._fig.tight_layout()
86
+
87
+ def _get_ax(self, ax: Axes | int) -> Axes:
88
+ if isinstance(ax, int):
89
+ ax = self._axes[ax]
90
+ return ax
91
+
92
+ def filled(
93
+ self,
94
+ filled: cpy.FillReturn,
95
+ fill_type: FillType | str,
96
+ ax: Axes | int = 0,
97
+ color: str = "C0",
98
+ alpha: float = 0.7,
99
+ ) -> None:
100
+ """Plot filled contours on a single Axes.
101
+
102
+ Args:
103
+ filled (sequence of arrays): Filled contour data as returned by
104
+ :meth:`~.ContourGenerator.filled`.
105
+ fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` data as returned
106
+ by :attr:`~.ContourGenerator.fill_type`, or string equivalent
107
+ ax (int or Maplotlib Axes, optional): Which axes to plot on, default ``0``.
108
+ color (str, optional): Color to plot with. May be a string color or the letter ``"C"``
109
+ followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
110
+ ``tab10`` colormap. Default ``"C0"``.
111
+ alpha (float, optional): Opacity to plot with, default ``0.7``.
112
+ """
113
+ fill_type = as_fill_type(fill_type)
114
+ ax = self._get_ax(ax)
115
+ paths = filled_to_mpl_paths(filled, fill_type)
116
+ collection = mcollections.PathCollection(
117
+ paths, facecolors=color, edgecolors="none", lw=0, alpha=alpha)
118
+ ax.add_collection(collection)
119
+ ax._need_autoscale = True # type: ignore[attr-defined]
120
+
121
+ def grid(
122
+ self,
123
+ x: ArrayLike,
124
+ y: ArrayLike,
125
+ ax: Axes | int = 0,
126
+ color: str = "black",
127
+ alpha: float = 0.1,
128
+ point_color: str | None = None,
129
+ quad_as_tri_alpha: float = 0,
130
+ ) -> None:
131
+ """Plot quad grid lines on a single Axes.
132
+
133
+ Args:
134
+ x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
135
+ y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
136
+ ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``.
137
+ color (str, optional): Color to plot grid lines, default ``"black"``.
138
+ alpha (float, optional): Opacity to plot lines with, default ``0.1``.
139
+ point_color (str, optional): Color to plot grid points or ``None`` if grid points
140
+ should not be plotted, default ``None``.
141
+ quad_as_tri_alpha (float, optional): Opacity to plot ``quad_as_tri`` grid, default 0.
142
+
143
+ Colors may be a string color or the letter ``"C"`` followed by an integer in the range
144
+ ``"C0"`` to ``"C9"`` to use a color from the ``tab10`` colormap.
145
+
146
+ Warning:
147
+ ``quad_as_tri_alpha > 0`` plots all quads as though they are unmasked.
148
+ """
149
+ ax = self._get_ax(ax)
150
+ x, y = self._grid_as_2d(x, y)
151
+ kwargs: dict[str, Any] = {"color": color, "alpha": alpha}
152
+ ax.plot(x, y, x.T, y.T, **kwargs)
153
+ if quad_as_tri_alpha > 0:
154
+ # Assumes no quad mask.
155
+ xmid = 0.25*(x[:-1, :-1] + x[1:, :-1] + x[:-1, 1:] + x[1:, 1:])
156
+ ymid = 0.25*(y[:-1, :-1] + y[1:, :-1] + y[:-1, 1:] + y[1:, 1:])
157
+ kwargs["alpha"] = quad_as_tri_alpha
158
+ ax.plot(
159
+ np.stack((x[:-1, :-1], xmid, x[1:, 1:])).reshape((3, -1)),
160
+ np.stack((y[:-1, :-1], ymid, y[1:, 1:])).reshape((3, -1)),
161
+ np.stack((x[1:, :-1], xmid, x[:-1, 1:])).reshape((3, -1)),
162
+ np.stack((y[1:, :-1], ymid, y[:-1, 1:])).reshape((3, -1)),
163
+ **kwargs)
164
+ if point_color is not None:
165
+ ax.plot(x, y, color=point_color, alpha=alpha, marker="o", lw=0)
166
+ ax._need_autoscale = True # type: ignore[attr-defined]
167
+
168
+ def lines(
169
+ self,
170
+ lines: cpy.LineReturn,
171
+ line_type: LineType | str,
172
+ ax: Axes | int = 0,
173
+ color: str = "C0",
174
+ alpha: float = 1.0,
175
+ linewidth: float = 1,
176
+ ) -> None:
177
+ """Plot contour lines on a single Axes.
178
+
179
+ Args:
180
+ lines (sequence of arrays): Contour line data as returned by
181
+ :meth:`~.ContourGenerator.lines`.
182
+ line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` data as returned
183
+ by :attr:`~.ContourGenerator.line_type`, or string equivalent.
184
+ ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``.
185
+ color (str, optional): Color to plot lines. May be a string color or the letter ``"C"``
186
+ followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
187
+ ``tab10`` colormap. Default ``"C0"``.
188
+ alpha (float, optional): Opacity to plot lines with, default ``1.0``.
189
+ linewidth (float, optional): Width of lines, default ``1``.
190
+ """
191
+ line_type = as_line_type(line_type)
192
+ ax = self._get_ax(ax)
193
+ paths = lines_to_mpl_paths(lines, line_type)
194
+ collection = mcollections.PathCollection(
195
+ paths, facecolors="none", edgecolors=color, lw=linewidth, alpha=alpha)
196
+ ax.add_collection(collection)
197
+ ax._need_autoscale = True # type: ignore[attr-defined]
198
+
199
+ def mask(
200
+ self,
201
+ x: ArrayLike,
202
+ y: ArrayLike,
203
+ z: ArrayLike | np.ma.MaskedArray[Any, Any],
204
+ ax: Axes | int = 0,
205
+ color: str = "black",
206
+ ) -> None:
207
+ """Plot masked out grid points as circles on a single Axes.
208
+
209
+ Args:
210
+ x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
211
+ y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
212
+ z (masked array of shape (ny, nx): z-values.
213
+ ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``.
214
+ color (str, optional): Circle color, default ``"black"``.
215
+ """
216
+ mask = np.ma.getmask(z) # type: ignore[no-untyped-call]
217
+ if mask is np.ma.nomask:
218
+ return
219
+ ax = self._get_ax(ax)
220
+ x, y = self._grid_as_2d(x, y)
221
+ ax.plot(x[mask], y[mask], "o", c=color)
222
+
223
+ def save(self, filename: str, transparent: bool = False) -> None:
224
+ """Save plots to SVG or PNG file.
225
+
226
+ Args:
227
+ filename (str): Filename to save to.
228
+ transparent (bool, optional): Whether background should be transparent, default
229
+ ``False``.
230
+ """
231
+ self._autoscale()
232
+ self._fig.savefig(filename, transparent=transparent)
233
+
234
+ def save_to_buffer(self) -> io.BytesIO:
235
+ """Save plots to an ``io.BytesIO`` buffer.
236
+
237
+ Return:
238
+ BytesIO: PNG image buffer.
239
+ """
240
+ self._autoscale()
241
+ buf = io.BytesIO()
242
+ self._fig.savefig(buf, format="png")
243
+ buf.seek(0)
244
+ return buf
245
+
246
+ def show(self) -> None:
247
+ """Show plots in an interactive window, in the usual Matplotlib manner.
248
+ """
249
+ self._autoscale()
250
+ plt.show()
251
+
252
+ def title(self, title: str, ax: Axes | int = 0, color: str | None = None) -> None:
253
+ """Set the title of a single Axes.
254
+
255
+ Args:
256
+ title (str): Title text.
257
+ ax (int or Matplotlib Axes, optional): Which Axes to set the title of, default ``0``.
258
+ color (str, optional): Color to set title. May be a string color or the letter ``"C"``
259
+ followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
260
+ ``tab10`` colormap. Default is ``None`` which uses Matplotlib's default title color
261
+ that depends on the stylesheet in use.
262
+ """
263
+ if color:
264
+ self._get_ax(ax).set_title(title, color=color)
265
+ else:
266
+ self._get_ax(ax).set_title(title)
267
+
268
+ def z_values(
269
+ self,
270
+ x: ArrayLike,
271
+ y: ArrayLike,
272
+ z: ArrayLike,
273
+ ax: Axes | int = 0,
274
+ color: str = "green",
275
+ fmt: str = ".1f",
276
+ quad_as_tri: bool = False,
277
+ ) -> None:
278
+ """Show ``z`` values on a single Axes.
279
+
280
+ Args:
281
+ x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
282
+ y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
283
+ z (array-like of shape (ny, nx): z-values.
284
+ ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``.
285
+ color (str, optional): Color of added text. May be a string color or the letter ``"C"``
286
+ followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
287
+ ``tab10`` colormap. Default ``"green"``.
288
+ fmt (str, optional): Format to display z-values, default ``".1f"``.
289
+ quad_as_tri (bool, optional): Whether to show z-values at the ``quad_as_tri`` centers
290
+ of quads.
291
+
292
+ Warning:
293
+ ``quad_as_tri=True`` shows z-values for all quads, even if masked.
294
+ """
295
+ ax = self._get_ax(ax)
296
+ x, y = self._grid_as_2d(x, y)
297
+ z = np.asarray(z)
298
+ ny, nx = z.shape
299
+ for j in range(ny):
300
+ for i in range(nx):
301
+ ax.text(x[j, i], y[j, i], f"{z[j, i]:{fmt}}", ha="center", va="center",
302
+ color=color, clip_on=True)
303
+ if quad_as_tri:
304
+ for j in range(ny-1):
305
+ for i in range(nx-1):
306
+ xx = np.mean(x[j:j+2, i:i+2], dtype=np.float64)
307
+ yy = np.mean(y[j:j+2, i:i+2], dtype=np.float64)
308
+ zz = np.mean(z[j:j+2, i:i+2])
309
+ ax.text(xx, yy, f"{zz:{fmt}}", ha="center", va="center", color=color,
310
+ clip_on=True)
311
+
312
+
313
+ class MplTestRenderer(MplRenderer):
314
+ """Test renderer implemented using Matplotlib.
315
+
316
+ No whitespace around plots and no spines/ticks displayed.
317
+ Uses Agg backend, so can only save to file/buffer, cannot call ``show()``.
318
+ """
319
+ def __init__(
320
+ self,
321
+ nrows: int = 1,
322
+ ncols: int = 1,
323
+ figsize: tuple[float, float] = (9, 9),
324
+ ) -> None:
325
+ gridspec = {
326
+ "left": 0.01,
327
+ "right": 0.99,
328
+ "top": 0.99,
329
+ "bottom": 0.01,
330
+ "wspace": 0.01,
331
+ "hspace": 0.01,
332
+ }
333
+ super().__init__(
334
+ nrows, ncols, figsize, show_frame=True, backend="Agg", gridspec_kw=gridspec,
335
+ )
336
+
337
+ for ax in self._axes:
338
+ ax.set_xmargin(0.0)
339
+ ax.set_ymargin(0.0)
340
+ ax.set_xticks([])
341
+ ax.set_yticks([])
342
+
343
+ self._want_tight = False
344
+
345
+
346
+ class MplDebugRenderer(MplRenderer):
347
+ """Debug renderer implemented using Matplotlib.
348
+
349
+ Extends ``MplRenderer`` to add extra information to help in debugging such as markers, arrows,
350
+ text, etc.
351
+ """
352
+ def __init__(
353
+ self,
354
+ nrows: int = 1,
355
+ ncols: int = 1,
356
+ figsize: tuple[float, float] = (9, 9),
357
+ show_frame: bool = True,
358
+ ) -> None:
359
+ super().__init__(nrows, ncols, figsize, show_frame)
360
+
361
+ def _arrow(
362
+ self,
363
+ ax: Axes,
364
+ line_start: cpy.CoordinateArray,
365
+ line_end: cpy.CoordinateArray,
366
+ color: str,
367
+ alpha: float,
368
+ arrow_size: float,
369
+ ) -> None:
370
+ mid = 0.5*(line_start + line_end)
371
+ along = line_end - line_start
372
+ along /= np.sqrt(np.dot(along, along)) # Unit vector.
373
+ right = np.asarray((along[1], -along[0]))
374
+ arrow = np.stack((
375
+ mid - (along*0.5 - right)*arrow_size,
376
+ mid + along*0.5*arrow_size,
377
+ mid - (along*0.5 + right)*arrow_size,
378
+ ))
379
+ ax.plot(arrow[:, 0], arrow[:, 1], "-", c=color, alpha=alpha)
380
+
381
+ def filled(
382
+ self,
383
+ filled: cpy.FillReturn,
384
+ fill_type: FillType | str,
385
+ ax: Axes | int = 0,
386
+ color: str = "C1",
387
+ alpha: float = 0.7,
388
+ line_color: str = "C0",
389
+ line_alpha: float = 0.7,
390
+ point_color: str = "C0",
391
+ start_point_color: str = "red",
392
+ arrow_size: float = 0.1,
393
+ ) -> None:
394
+ fill_type = as_fill_type(fill_type)
395
+ super().filled(filled, fill_type, ax, color, alpha)
396
+
397
+ if line_color is None and point_color is None:
398
+ return
399
+
400
+ ax = self._get_ax(ax)
401
+ filled = convert_filled(filled, fill_type, FillType.ChunkCombinedOffset)
402
+
403
+ # Lines.
404
+ if line_color is not None:
405
+ for points, offsets in zip(*filled):
406
+ if points is None:
407
+ continue
408
+ for start, end in pairwise(offsets):
409
+ xys = points[start:end]
410
+ ax.plot(xys[:, 0], xys[:, 1], c=line_color, alpha=line_alpha)
411
+
412
+ if arrow_size > 0.0:
413
+ n = len(xys)
414
+ for i in range(n-1):
415
+ self._arrow(ax, xys[i], xys[i+1], line_color, line_alpha, arrow_size)
416
+
417
+ # Points.
418
+ if point_color is not None:
419
+ for points, offsets in zip(*filled):
420
+ if points is None:
421
+ continue
422
+ mask = np.ones(offsets[-1], dtype=bool)
423
+ mask[offsets[1:]-1] = False # Exclude end points.
424
+ if start_point_color is not None:
425
+ start_indices = offsets[:-1]
426
+ mask[start_indices] = False # Exclude start points.
427
+ ax.plot(
428
+ points[:, 0][mask], points[:, 1][mask], "o", c=point_color, alpha=line_alpha)
429
+
430
+ if start_point_color is not None:
431
+ ax.plot(points[:, 0][start_indices], points[:, 1][start_indices], "o",
432
+ c=start_point_color, alpha=line_alpha)
433
+
434
+ def lines(
435
+ self,
436
+ lines: cpy.LineReturn,
437
+ line_type: LineType | str,
438
+ ax: Axes | int = 0,
439
+ color: str = "C0",
440
+ alpha: float = 1.0,
441
+ linewidth: float = 1,
442
+ point_color: str = "C0",
443
+ start_point_color: str = "red",
444
+ arrow_size: float = 0.1,
445
+ ) -> None:
446
+ line_type = as_line_type(line_type)
447
+ super().lines(lines, line_type, ax, color, alpha, linewidth)
448
+
449
+ if arrow_size == 0.0 and point_color is None:
450
+ return
451
+
452
+ ax = self._get_ax(ax)
453
+ separate_lines = convert_lines(lines, line_type, LineType.Separate)
454
+ if TYPE_CHECKING:
455
+ separate_lines = cast(cpy.LineReturn_Separate, separate_lines)
456
+
457
+ if arrow_size > 0.0:
458
+ for line in separate_lines:
459
+ for i in range(len(line)-1):
460
+ self._arrow(ax, line[i], line[i+1], color, alpha, arrow_size)
461
+
462
+ if point_color is not None:
463
+ for line in separate_lines:
464
+ start_index = 0
465
+ end_index = len(line)
466
+ if start_point_color is not None:
467
+ ax.plot(line[0, 0], line[0, 1], "o", c=start_point_color, alpha=alpha)
468
+ start_index = 1
469
+ if line[0][0] == line[-1][0] and line[0][1] == line[-1][1]:
470
+ end_index -= 1
471
+ ax.plot(line[start_index:end_index, 0], line[start_index:end_index, 1], "o",
472
+ c=color, alpha=alpha)
473
+
474
+ def point_numbers(
475
+ self,
476
+ x: ArrayLike,
477
+ y: ArrayLike,
478
+ z: ArrayLike,
479
+ ax: Axes | int = 0,
480
+ color: str = "red",
481
+ ) -> None:
482
+ ax = self._get_ax(ax)
483
+ x, y = self._grid_as_2d(x, y)
484
+ z = np.asarray(z)
485
+ ny, nx = z.shape
486
+ for j in range(ny):
487
+ for i in range(nx):
488
+ quad = i + j*nx
489
+ ax.text(x[j, i], y[j, i], str(quad), ha="right", va="top", color=color,
490
+ clip_on=True)
491
+
492
+ def quad_numbers(
493
+ self,
494
+ x: ArrayLike,
495
+ y: ArrayLike,
496
+ z: ArrayLike,
497
+ ax: Axes | int = 0,
498
+ color: str = "blue",
499
+ ) -> None:
500
+ ax = self._get_ax(ax)
501
+ x, y = self._grid_as_2d(x, y)
502
+ z = np.asarray(z)
503
+ ny, nx = z.shape
504
+ for j in range(1, ny):
505
+ for i in range(1, nx):
506
+ quad = i + j*nx
507
+ xmid = x[j-1:j+1, i-1:i+1].mean()
508
+ ymid = y[j-1:j+1, i-1:i+1].mean()
509
+ ax.text(xmid, ymid, str(quad), ha="center", va="center", color=color, clip_on=True)
510
+
511
+ def z_levels(
512
+ self,
513
+ x: ArrayLike,
514
+ y: ArrayLike,
515
+ z: ArrayLike,
516
+ lower_level: float,
517
+ upper_level: float | None = None,
518
+ ax: Axes | int = 0,
519
+ color: str = "green",
520
+ ) -> None:
521
+ ax = self._get_ax(ax)
522
+ x, y = self._grid_as_2d(x, y)
523
+ z = np.asarray(z)
524
+ ny, nx = z.shape
525
+ for j in range(ny):
526
+ for i in range(nx):
527
+ zz = z[j, i]
528
+ if upper_level is not None and zz > upper_level:
529
+ z_level = 2
530
+ elif zz > lower_level:
531
+ z_level = 1
532
+ else:
533
+ z_level = 0
534
+ ax.text(x[j, i], y[j, i], str(z_level), ha="left", va="bottom", color=color,
535
+ clip_on=True)
venv/lib/python3.10/site-packages/contourpy/util/mpl_util.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from itertools import pairwise
4
+ from typing import TYPE_CHECKING, cast
5
+
6
+ import matplotlib.path as mpath
7
+ import numpy as np
8
+
9
+ from contourpy import FillType, LineType
10
+ from contourpy.array import codes_from_offsets
11
+
12
+ if TYPE_CHECKING:
13
+ from contourpy._contourpy import FillReturn, LineReturn, LineReturn_Separate
14
+
15
+
16
+ def filled_to_mpl_paths(filled: FillReturn, fill_type: FillType) -> list[mpath.Path]:
17
+ if fill_type in (FillType.OuterCode, FillType.ChunkCombinedCode):
18
+ paths = [mpath.Path(points, codes) for points, codes in zip(*filled) if points is not None]
19
+ elif fill_type in (FillType.OuterOffset, FillType.ChunkCombinedOffset):
20
+ paths = [mpath.Path(points, codes_from_offsets(offsets))
21
+ for points, offsets in zip(*filled) if points is not None]
22
+ elif fill_type == FillType.ChunkCombinedCodeOffset:
23
+ paths = []
24
+ for points, codes, outer_offsets in zip(*filled):
25
+ if points is None:
26
+ continue
27
+ points = np.split(points, outer_offsets[1:-1])
28
+ codes = np.split(codes, outer_offsets[1:-1])
29
+ paths += [mpath.Path(p, c) for p, c in zip(points, codes)]
30
+ elif fill_type == FillType.ChunkCombinedOffsetOffset:
31
+ paths = []
32
+ for points, offsets, outer_offsets in zip(*filled):
33
+ if points is None:
34
+ continue
35
+ for i in range(len(outer_offsets)-1):
36
+ offs = offsets[outer_offsets[i]:outer_offsets[i+1]+1]
37
+ pts = points[offs[0]:offs[-1]]
38
+ paths += [mpath.Path(pts, codes_from_offsets(offs - offs[0]))]
39
+ else:
40
+ raise RuntimeError(f"Conversion of FillType {fill_type} to MPL Paths is not implemented")
41
+ return paths
42
+
43
+
44
+ def lines_to_mpl_paths(lines: LineReturn, line_type: LineType) -> list[mpath.Path]:
45
+ if line_type == LineType.Separate:
46
+ if TYPE_CHECKING:
47
+ lines = cast(LineReturn_Separate, lines)
48
+ paths = []
49
+ for line in lines:
50
+ # Drawing as Paths so that they can be closed correctly.
51
+ closed = line[0, 0] == line[-1, 0] and line[0, 1] == line[-1, 1]
52
+ paths.append(mpath.Path(line, closed=closed))
53
+ elif line_type in (LineType.SeparateCode, LineType.ChunkCombinedCode):
54
+ paths = [mpath.Path(points, codes) for points, codes in zip(*lines) if points is not None]
55
+ elif line_type == LineType.ChunkCombinedOffset:
56
+ paths = []
57
+ for points, offsets in zip(*lines):
58
+ if points is None:
59
+ continue
60
+ for i in range(len(offsets)-1):
61
+ line = points[offsets[i]:offsets[i+1]]
62
+ closed = line[0, 0] == line[-1, 0] and line[0, 1] == line[-1, 1]
63
+ paths.append(mpath.Path(line, closed=closed))
64
+ elif line_type == LineType.ChunkCombinedNan:
65
+ paths = []
66
+ for points in lines[0]:
67
+ if points is None:
68
+ continue
69
+ nan_offsets = np.nonzero(np.isnan(points[:, 0]))[0]
70
+ nan_offsets = np.concatenate([[-1], nan_offsets, [len(points)]])
71
+ for s, e in pairwise(nan_offsets):
72
+ line = points[s+1:e]
73
+ closed = line[0, 0] == line[-1, 0] and line[0, 1] == line[-1, 1]
74
+ paths.append(mpath.Path(line, closed=closed))
75
+ else:
76
+ raise RuntimeError(f"Conversion of LineType {line_type} to MPL Paths is not implemented")
77
+ return paths
venv/lib/python3.10/site-packages/contourpy/util/renderer.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import numpy as np
7
+
8
+ if TYPE_CHECKING:
9
+ import io
10
+
11
+ from numpy.typing import ArrayLike
12
+
13
+ from contourpy._contourpy import CoordinateArray, FillReturn, FillType, LineReturn, LineType
14
+
15
+
16
+ class Renderer(ABC):
17
+ """Abstract base class for renderers."""
18
+
19
+ def _grid_as_2d(self, x: ArrayLike, y: ArrayLike) -> tuple[CoordinateArray, CoordinateArray]:
20
+ x = np.asarray(x)
21
+ y = np.asarray(y)
22
+ if x.ndim == 1:
23
+ x, y = np.meshgrid(x, y)
24
+ return x, y
25
+
26
+ @abstractmethod
27
+ def filled(
28
+ self,
29
+ filled: FillReturn,
30
+ fill_type: FillType | str,
31
+ ax: Any = 0,
32
+ color: str = "C0",
33
+ alpha: float = 0.7,
34
+ ) -> None:
35
+ pass
36
+
37
+ @abstractmethod
38
+ def grid(
39
+ self,
40
+ x: ArrayLike,
41
+ y: ArrayLike,
42
+ ax: Any = 0,
43
+ color: str = "black",
44
+ alpha: float = 0.1,
45
+ point_color: str | None = None,
46
+ quad_as_tri_alpha: float = 0,
47
+ ) -> None:
48
+ pass
49
+
50
+ @abstractmethod
51
+ def lines(
52
+ self,
53
+ lines: LineReturn,
54
+ line_type: LineType | str,
55
+ ax: Any = 0,
56
+ color: str = "C0",
57
+ alpha: float = 1.0,
58
+ linewidth: float = 1,
59
+ ) -> None:
60
+ pass
61
+
62
+ @abstractmethod
63
+ def mask(
64
+ self,
65
+ x: ArrayLike,
66
+ y: ArrayLike,
67
+ z: ArrayLike | np.ma.MaskedArray[Any, Any],
68
+ ax: Any = 0,
69
+ color: str = "black",
70
+ ) -> None:
71
+ pass
72
+
73
+ def multi_filled(
74
+ self,
75
+ multi_filled: list[FillReturn],
76
+ fill_type: FillType | str,
77
+ ax: Any = 0,
78
+ color: str | None = None,
79
+ **kwargs: Any,
80
+ ) -> None:
81
+ """Plot multiple sets of filled contours on a single axes.
82
+
83
+ Args:
84
+ multi_filled (list of filled contour arrays): Multiple filled contour sets as returned
85
+ by :meth:`.ContourGenerator.multi_filled`.
86
+ fill_type (FillType or str): Type of filled data as returned by
87
+ :attr:`~.ContourGenerator.fill_type`, or string equivalent.
88
+ ax (int or Renderer-specific axes or figure object, optional): Which axes to plot on,
89
+ default ``0``.
90
+ color (str or None, optional): If a string color then this same color is used for all
91
+ filled contours. If ``None``, the default, then the filled contour sets use colors
92
+ from the ``tab10`` colormap in order, wrapping around to the beginning if more than
93
+ 10 sets of filled contours are rendered.
94
+ kwargs: All other keyword argument are passed on to
95
+ :meth:`.Renderer.filled` unchanged.
96
+
97
+ .. versionadded:: 1.3.0
98
+ """
99
+ if color is not None:
100
+ kwargs["color"] = color
101
+ for i, filled in enumerate(multi_filled):
102
+ if color is None:
103
+ kwargs["color"] = f"C{i % 10}"
104
+ self.filled(filled, fill_type, ax, **kwargs)
105
+
106
+ def multi_lines(
107
+ self,
108
+ multi_lines: list[LineReturn],
109
+ line_type: LineType | str,
110
+ ax: Any = 0,
111
+ color: str | None = None,
112
+ **kwargs: Any,
113
+ ) -> None:
114
+ """Plot multiple sets of contour lines on a single axes.
115
+
116
+ Args:
117
+ multi_lines (list of contour line arrays): Multiple contour line sets as returned by
118
+ :meth:`.ContourGenerator.multi_lines`.
119
+ line_type (LineType or str): Type of line data as returned by
120
+ :attr:`~.ContourGenerator.line_type`, or string equivalent.
121
+ ax (int or Renderer-specific axes or figure object, optional): Which axes to plot on,
122
+ default ``0``.
123
+ color (str or None, optional): If a string color then this same color is used for all
124
+ lines. If ``None``, the default, then the line sets use colors from the ``tab10``
125
+ colormap in order, wrapping around to the beginning if more than 10 sets of lines
126
+ are rendered.
127
+ kwargs: All other keyword argument are passed on to
128
+ :meth:`Renderer.lines` unchanged.
129
+
130
+ .. versionadded:: 1.3.0
131
+ """
132
+ if color is not None:
133
+ kwargs["color"] = color
134
+ for i, lines in enumerate(multi_lines):
135
+ if color is None:
136
+ kwargs["color"] = f"C{i % 10}"
137
+ self.lines(lines, line_type, ax, **kwargs)
138
+
139
+ @abstractmethod
140
+ def save(self, filename: str, transparent: bool = False) -> None:
141
+ pass
142
+
143
+ @abstractmethod
144
+ def save_to_buffer(self) -> io.BytesIO:
145
+ pass
146
+
147
+ @abstractmethod
148
+ def show(self) -> None:
149
+ pass
150
+
151
+ @abstractmethod
152
+ def title(self, title: str, ax: Any = 0, color: str | None = None) -> None:
153
+ pass
154
+
155
+ @abstractmethod
156
+ def z_values(
157
+ self,
158
+ x: ArrayLike,
159
+ y: ArrayLike,
160
+ z: ArrayLike,
161
+ ax: Any = 0,
162
+ color: str = "green",
163
+ fmt: str = ".1f",
164
+ quad_as_tri: bool = False,
165
+ ) -> None:
166
+ pass
venv/lib/python3.10/site-packages/filelock-3.20.0.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
venv/lib/python3.10/site-packages/filelock-3.20.0.dist-info/METADATA ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: filelock
3
+ Version: 3.20.0
4
+ Summary: A platform independent file lock.
5
+ Project-URL: Documentation, https://py-filelock.readthedocs.io
6
+ Project-URL: Homepage, https://github.com/tox-dev/py-filelock
7
+ Project-URL: Source, https://github.com/tox-dev/py-filelock
8
+ Project-URL: Tracker, https://github.com/tox-dev/py-filelock/issues
9
+ Maintainer-email: Bernát Gábor <gaborjbernat@gmail.com>
10
+ License-Expression: Unlicense
11
+ License-File: LICENSE
12
+ Keywords: application,cache,directory,log,user
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: The Unlicense (Unlicense)
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Internet
25
+ Classifier: Topic :: Software Development :: Libraries
26
+ Classifier: Topic :: System
27
+ Requires-Python: >=3.10
28
+ Description-Content-Type: text/markdown
29
+
30
+ # filelock
31
+
32
+ [![PyPI](https://img.shields.io/pypi/v/filelock)](https://pypi.org/project/filelock/)
33
+ [![Supported Python
34
+ versions](https://img.shields.io/pypi/pyversions/filelock.svg)](https://pypi.org/project/filelock/)
35
+ [![Documentation
36
+ status](https://readthedocs.org/projects/py-filelock/badge/?version=latest)](https://py-filelock.readthedocs.io/en/latest/?badge=latest)
37
+ [![Code style:
38
+ black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
39
+ [![Downloads](https://static.pepy.tech/badge/filelock/month)](https://pepy.tech/project/filelock)
40
+ [![check](https://github.com/tox-dev/py-filelock/actions/workflows/check.yaml/badge.svg)](https://github.com/tox-dev/py-filelock/actions/workflows/check.yaml)
41
+
42
+ For more information checkout the [official documentation](https://py-filelock.readthedocs.io/en/latest/index.html).
venv/lib/python3.10/site-packages/filelock-3.20.0.dist-info/RECORD ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ filelock-3.20.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ filelock-3.20.0.dist-info/METADATA,sha256=gIghqdcbGNywxw52pN02_a9OxFqzhjA8v-9GsDWtNog,2110
3
+ filelock-3.20.0.dist-info/RECORD,,
4
+ filelock-3.20.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ filelock-3.20.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
+ filelock-3.20.0.dist-info/licenses/LICENSE,sha256=iNm062BXnBkew5HKBMFhMFctfu3EqG2qWL8oxuFMm80,1210
7
+ filelock/__init__.py,sha256=_t_-OAGXo_qyPa9lNQ1YnzVYEvSW3I0onPqzpomsVVg,1769
8
+ filelock/__pycache__/__init__.cpython-310.pyc,,
9
+ filelock/__pycache__/_api.cpython-310.pyc,,
10
+ filelock/__pycache__/_error.cpython-310.pyc,,
11
+ filelock/__pycache__/_soft.cpython-310.pyc,,
12
+ filelock/__pycache__/_unix.cpython-310.pyc,,
13
+ filelock/__pycache__/_util.cpython-310.pyc,,
14
+ filelock/__pycache__/_windows.cpython-310.pyc,,
15
+ filelock/__pycache__/asyncio.cpython-310.pyc,,
16
+ filelock/__pycache__/version.cpython-310.pyc,,
17
+ filelock/_api.py,sha256=2aATBeJ3-jtMj5OSm7EE539iNaTBsf13KXtcBMoi8oM,14545
18
+ filelock/_error.py,sha256=-5jMcjTu60YAvAO1UbqDD1GIEjVkwr8xCFwDBtMeYDg,787
19
+ filelock/_soft.py,sha256=haqtc_TB_KJbYv2a8iuEAclKuM4fMG1vTcp28sK919c,1711
20
+ filelock/_unix.py,sha256=eGOs4gDgZ-5fGnJUz-OkJDeZkAMzgvYcD8hVD6XH7e4,2351
21
+ filelock/_util.py,sha256=QHBoNFIYfbAThhotH3Q8E2acFc84wpG49-T-uu017ZE,1715
22
+ filelock/_windows.py,sha256=8k4XIBl_zZVfGC2gz0kEr8DZBvpNa8wdU9qeM1YrBb8,2179
23
+ filelock/asyncio.py,sha256=dSLe6XZSECFOgsVpcQUSh5Y5zAHxHGPu_tfpPX9I45k,12514
24
+ filelock/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ filelock/version.py,sha256=AW5MeEjK4TaQWWJrGb_AlBw8PlmFoIcn7GodG_AVSOM,706
venv/lib/python3.10/site-packages/filelock-3.20.0.dist-info/REQUESTED ADDED
File without changes
venv/lib/python3.10/site-packages/filelock-3.20.0.dist-info/WHEEL ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
venv/lib/python3.10/site-packages/filelock-3.20.0.dist-info/licenses/LICENSE ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org>
venv/lib/python3.10/site-packages/fontTools/__init__.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from fontTools.misc.loggingTools import configLogger
3
+
4
+ log = logging.getLogger(__name__)
5
+
6
+ version = __version__ = "4.60.1"
7
+
8
+ __all__ = ["version", "log", "configLogger"]
venv/lib/python3.10/site-packages/fontTools/__main__.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+
3
+
4
+ def main(args=None):
5
+ if args is None:
6
+ args = sys.argv[1:]
7
+
8
+ # TODO Handle library-wide options. Eg.:
9
+ # --unicodedata
10
+ # --verbose / other logging stuff
11
+
12
+ # TODO Allow a way to run arbitrary modules? Useful for setting
13
+ # library-wide options and calling another library. Eg.:
14
+ #
15
+ # $ fonttools --unicodedata=... fontmake ...
16
+ #
17
+ # This allows for a git-like command where thirdparty commands
18
+ # can be added. Should we just try importing the fonttools
19
+ # module first and try without if it fails?
20
+
21
+ if len(sys.argv) < 2:
22
+ sys.argv.append("help")
23
+ if sys.argv[1] == "-h" or sys.argv[1] == "--help":
24
+ sys.argv[1] = "help"
25
+ mod = "fontTools." + sys.argv[1]
26
+ sys.argv[1] = sys.argv[0] + " " + sys.argv[1]
27
+ del sys.argv[0]
28
+
29
+ import runpy
30
+
31
+ runpy.run_module(mod, run_name="__main__")
32
+
33
+
34
+ if __name__ == "__main__":
35
+ sys.exit(main())
venv/lib/python3.10/site-packages/fontTools/afmLib.py ADDED
@@ -0,0 +1,439 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module for reading and writing AFM (Adobe Font Metrics) files.
2
+
3
+ Note that this has been designed to read in AFM files generated by Fontographer
4
+ and has not been tested on many other files. In particular, it does not
5
+ implement the whole Adobe AFM specification [#f1]_ but, it should read most
6
+ "common" AFM files.
7
+
8
+ Here is an example of using `afmLib` to read, modify and write an AFM file:
9
+
10
+ >>> from fontTools.afmLib import AFM
11
+ >>> f = AFM("Tests/afmLib/data/TestAFM.afm")
12
+ >>>
13
+ >>> # Accessing a pair gets you the kern value
14
+ >>> f[("V","A")]
15
+ -60
16
+ >>>
17
+ >>> # Accessing a glyph name gets you metrics
18
+ >>> f["A"]
19
+ (65, 668, (8, -25, 660, 666))
20
+ >>> # (charnum, width, bounding box)
21
+ >>>
22
+ >>> # Accessing an attribute gets you metadata
23
+ >>> f.FontName
24
+ 'TestFont-Regular'
25
+ >>> f.FamilyName
26
+ 'TestFont'
27
+ >>> f.Weight
28
+ 'Regular'
29
+ >>> f.XHeight
30
+ 500
31
+ >>> f.Ascender
32
+ 750
33
+ >>>
34
+ >>> # Attributes and items can also be set
35
+ >>> f[("A","V")] = -150 # Tighten kerning
36
+ >>> f.FontName = "TestFont Squished"
37
+ >>>
38
+ >>> # And the font written out again (remove the # in front)
39
+ >>> #f.write("testfont-squished.afm")
40
+
41
+ .. rubric:: Footnotes
42
+
43
+ .. [#f1] `Adobe Technote 5004 <https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5004.AFM_Spec.pdf>`_,
44
+ Adobe Font Metrics File Format Specification.
45
+
46
+ """
47
+
48
+ import re
49
+
50
+ # every single line starts with a "word"
51
+ identifierRE = re.compile(r"^([A-Za-z]+).*")
52
+
53
+ # regular expression to parse char lines
54
+ charRE = re.compile(
55
+ r"(-?\d+)" # charnum
56
+ r"\s*;\s*WX\s+" # ; WX
57
+ r"(-?\d+)" # width
58
+ r"\s*;\s*N\s+" # ; N
59
+ r"([.A-Za-z0-9_]+)" # charname
60
+ r"\s*;\s*B\s+" # ; B
61
+ r"(-?\d+)" # left
62
+ r"\s+"
63
+ r"(-?\d+)" # bottom
64
+ r"\s+"
65
+ r"(-?\d+)" # right
66
+ r"\s+"
67
+ r"(-?\d+)" # top
68
+ r"\s*;\s*" # ;
69
+ )
70
+
71
+ # regular expression to parse kerning lines
72
+ kernRE = re.compile(
73
+ r"([.A-Za-z0-9_]+)" # leftchar
74
+ r"\s+"
75
+ r"([.A-Za-z0-9_]+)" # rightchar
76
+ r"\s+"
77
+ r"(-?\d+)" # value
78
+ r"\s*"
79
+ )
80
+
81
+ # regular expressions to parse composite info lines of the form:
82
+ # Aacute 2 ; PCC A 0 0 ; PCC acute 182 211 ;
83
+ compositeRE = re.compile(
84
+ r"([.A-Za-z0-9_]+)" # char name
85
+ r"\s+"
86
+ r"(\d+)" # number of parts
87
+ r"\s*;\s*"
88
+ )
89
+ componentRE = re.compile(
90
+ r"PCC\s+" # PPC
91
+ r"([.A-Za-z0-9_]+)" # base char name
92
+ r"\s+"
93
+ r"(-?\d+)" # x offset
94
+ r"\s+"
95
+ r"(-?\d+)" # y offset
96
+ r"\s*;\s*"
97
+ )
98
+
99
+ preferredAttributeOrder = [
100
+ "FontName",
101
+ "FullName",
102
+ "FamilyName",
103
+ "Weight",
104
+ "ItalicAngle",
105
+ "IsFixedPitch",
106
+ "FontBBox",
107
+ "UnderlinePosition",
108
+ "UnderlineThickness",
109
+ "Version",
110
+ "Notice",
111
+ "EncodingScheme",
112
+ "CapHeight",
113
+ "XHeight",
114
+ "Ascender",
115
+ "Descender",
116
+ ]
117
+
118
+
119
+ class error(Exception):
120
+ pass
121
+
122
+
123
+ class AFM(object):
124
+ _attrs = None
125
+
126
+ _keywords = [
127
+ "StartFontMetrics",
128
+ "EndFontMetrics",
129
+ "StartCharMetrics",
130
+ "EndCharMetrics",
131
+ "StartKernData",
132
+ "StartKernPairs",
133
+ "EndKernPairs",
134
+ "EndKernData",
135
+ "StartComposites",
136
+ "EndComposites",
137
+ ]
138
+
139
+ def __init__(self, path=None):
140
+ """AFM file reader.
141
+
142
+ Instantiating an object with a path name will cause the file to be opened,
143
+ read, and parsed. Alternatively the path can be left unspecified, and a
144
+ file can be parsed later with the :meth:`read` method."""
145
+ self._attrs = {}
146
+ self._chars = {}
147
+ self._kerning = {}
148
+ self._index = {}
149
+ self._comments = []
150
+ self._composites = {}
151
+ if path is not None:
152
+ self.read(path)
153
+
154
+ def read(self, path):
155
+ """Opens, reads and parses a file."""
156
+ lines = readlines(path)
157
+ for line in lines:
158
+ if not line.strip():
159
+ continue
160
+ m = identifierRE.match(line)
161
+ if m is None:
162
+ raise error("syntax error in AFM file: " + repr(line))
163
+
164
+ pos = m.regs[1][1]
165
+ word = line[:pos]
166
+ rest = line[pos:].strip()
167
+ if word in self._keywords:
168
+ continue
169
+ if word == "C":
170
+ self.parsechar(rest)
171
+ elif word == "KPX":
172
+ self.parsekernpair(rest)
173
+ elif word == "CC":
174
+ self.parsecomposite(rest)
175
+ else:
176
+ self.parseattr(word, rest)
177
+
178
+ def parsechar(self, rest):
179
+ m = charRE.match(rest)
180
+ if m is None:
181
+ raise error("syntax error in AFM file: " + repr(rest))
182
+ things = []
183
+ for fr, to in m.regs[1:]:
184
+ things.append(rest[fr:to])
185
+ charname = things[2]
186
+ del things[2]
187
+ charnum, width, l, b, r, t = (int(thing) for thing in things)
188
+ self._chars[charname] = charnum, width, (l, b, r, t)
189
+
190
+ def parsekernpair(self, rest):
191
+ m = kernRE.match(rest)
192
+ if m is None:
193
+ raise error("syntax error in AFM file: " + repr(rest))
194
+ things = []
195
+ for fr, to in m.regs[1:]:
196
+ things.append(rest[fr:to])
197
+ leftchar, rightchar, value = things
198
+ value = int(value)
199
+ self._kerning[(leftchar, rightchar)] = value
200
+
201
+ def parseattr(self, word, rest):
202
+ if word == "FontBBox":
203
+ l, b, r, t = [int(thing) for thing in rest.split()]
204
+ self._attrs[word] = l, b, r, t
205
+ elif word == "Comment":
206
+ self._comments.append(rest)
207
+ else:
208
+ try:
209
+ value = int(rest)
210
+ except (ValueError, OverflowError):
211
+ self._attrs[word] = rest
212
+ else:
213
+ self._attrs[word] = value
214
+
215
+ def parsecomposite(self, rest):
216
+ m = compositeRE.match(rest)
217
+ if m is None:
218
+ raise error("syntax error in AFM file: " + repr(rest))
219
+ charname = m.group(1)
220
+ ncomponents = int(m.group(2))
221
+ rest = rest[m.regs[0][1] :]
222
+ components = []
223
+ while True:
224
+ m = componentRE.match(rest)
225
+ if m is None:
226
+ raise error("syntax error in AFM file: " + repr(rest))
227
+ basechar = m.group(1)
228
+ xoffset = int(m.group(2))
229
+ yoffset = int(m.group(3))
230
+ components.append((basechar, xoffset, yoffset))
231
+ rest = rest[m.regs[0][1] :]
232
+ if not rest:
233
+ break
234
+ assert len(components) == ncomponents
235
+ self._composites[charname] = components
236
+
237
+ def write(self, path, sep="\r"):
238
+ """Writes out an AFM font to the given path."""
239
+ import time
240
+
241
+ lines = [
242
+ "StartFontMetrics 2.0",
243
+ "Comment Generated by afmLib; at %s"
244
+ % (time.strftime("%m/%d/%Y %H:%M:%S", time.localtime(time.time()))),
245
+ ]
246
+
247
+ # write comments, assuming (possibly wrongly!) they should
248
+ # all appear at the top
249
+ for comment in self._comments:
250
+ lines.append("Comment " + comment)
251
+
252
+ # write attributes, first the ones we know about, in
253
+ # a preferred order
254
+ attrs = self._attrs
255
+ for attr in preferredAttributeOrder:
256
+ if attr in attrs:
257
+ value = attrs[attr]
258
+ if attr == "FontBBox":
259
+ value = "%s %s %s %s" % value
260
+ lines.append(attr + " " + str(value))
261
+ # then write the attributes we don't know about,
262
+ # in alphabetical order
263
+ items = sorted(attrs.items())
264
+ for attr, value in items:
265
+ if attr in preferredAttributeOrder:
266
+ continue
267
+ lines.append(attr + " " + str(value))
268
+
269
+ # write char metrics
270
+ lines.append("StartCharMetrics " + repr(len(self._chars)))
271
+ items = [
272
+ (charnum, (charname, width, box))
273
+ for charname, (charnum, width, box) in self._chars.items()
274
+ ]
275
+
276
+ def myKey(a):
277
+ """Custom key function to make sure unencoded chars (-1)
278
+ end up at the end of the list after sorting."""
279
+ if a[0] == -1:
280
+ a = (0xFFFF,) + a[1:] # 0xffff is an arbitrary large number
281
+ return a
282
+
283
+ items.sort(key=myKey)
284
+
285
+ for charnum, (charname, width, (l, b, r, t)) in items:
286
+ lines.append(
287
+ "C %d ; WX %d ; N %s ; B %d %d %d %d ;"
288
+ % (charnum, width, charname, l, b, r, t)
289
+ )
290
+ lines.append("EndCharMetrics")
291
+
292
+ # write kerning info
293
+ lines.append("StartKernData")
294
+ lines.append("StartKernPairs " + repr(len(self._kerning)))
295
+ items = sorted(self._kerning.items())
296
+ for (leftchar, rightchar), value in items:
297
+ lines.append("KPX %s %s %d" % (leftchar, rightchar, value))
298
+ lines.append("EndKernPairs")
299
+ lines.append("EndKernData")
300
+
301
+ if self._composites:
302
+ composites = sorted(self._composites.items())
303
+ lines.append("StartComposites %s" % len(self._composites))
304
+ for charname, components in composites:
305
+ line = "CC %s %s ;" % (charname, len(components))
306
+ for basechar, xoffset, yoffset in components:
307
+ line = line + " PCC %s %s %s ;" % (basechar, xoffset, yoffset)
308
+ lines.append(line)
309
+ lines.append("EndComposites")
310
+
311
+ lines.append("EndFontMetrics")
312
+
313
+ writelines(path, lines, sep)
314
+
315
+ def has_kernpair(self, pair):
316
+ """Returns `True` if the given glyph pair (specified as a tuple) exists
317
+ in the kerning dictionary."""
318
+ return pair in self._kerning
319
+
320
+ def kernpairs(self):
321
+ """Returns a list of all kern pairs in the kerning dictionary."""
322
+ return list(self._kerning.keys())
323
+
324
+ def has_char(self, char):
325
+ """Returns `True` if the given glyph exists in the font."""
326
+ return char in self._chars
327
+
328
+ def chars(self):
329
+ """Returns a list of all glyph names in the font."""
330
+ return list(self._chars.keys())
331
+
332
+ def comments(self):
333
+ """Returns all comments from the file."""
334
+ return self._comments
335
+
336
+ def addComment(self, comment):
337
+ """Adds a new comment to the file."""
338
+ self._comments.append(comment)
339
+
340
+ def addComposite(self, glyphName, components):
341
+ """Specifies that the glyph `glyphName` is made up of the given components.
342
+ The components list should be of the following form::
343
+
344
+ [
345
+ (glyphname, xOffset, yOffset),
346
+ ...
347
+ ]
348
+
349
+ """
350
+ self._composites[glyphName] = components
351
+
352
+ def __getattr__(self, attr):
353
+ if attr in self._attrs:
354
+ return self._attrs[attr]
355
+ else:
356
+ raise AttributeError(attr)
357
+
358
+ def __setattr__(self, attr, value):
359
+ # all attrs *not* starting with "_" are consider to be AFM keywords
360
+ if attr[:1] == "_":
361
+ self.__dict__[attr] = value
362
+ else:
363
+ self._attrs[attr] = value
364
+
365
+ def __delattr__(self, attr):
366
+ # all attrs *not* starting with "_" are consider to be AFM keywords
367
+ if attr[:1] == "_":
368
+ try:
369
+ del self.__dict__[attr]
370
+ except KeyError:
371
+ raise AttributeError(attr)
372
+ else:
373
+ try:
374
+ del self._attrs[attr]
375
+ except KeyError:
376
+ raise AttributeError(attr)
377
+
378
+ def __getitem__(self, key):
379
+ if isinstance(key, tuple):
380
+ # key is a tuple, return the kernpair
381
+ return self._kerning[key]
382
+ else:
383
+ # return the metrics instead
384
+ return self._chars[key]
385
+
386
+ def __setitem__(self, key, value):
387
+ if isinstance(key, tuple):
388
+ # key is a tuple, set kernpair
389
+ self._kerning[key] = value
390
+ else:
391
+ # set char metrics
392
+ self._chars[key] = value
393
+
394
+ def __delitem__(self, key):
395
+ if isinstance(key, tuple):
396
+ # key is a tuple, del kernpair
397
+ del self._kerning[key]
398
+ else:
399
+ # del char metrics
400
+ del self._chars[key]
401
+
402
+ def __repr__(self):
403
+ if hasattr(self, "FullName"):
404
+ return "<AFM object for %s>" % self.FullName
405
+ else:
406
+ return "<AFM object at %x>" % id(self)
407
+
408
+
409
+ def readlines(path):
410
+ with open(path, "r", encoding="ascii") as f:
411
+ data = f.read()
412
+ return data.splitlines()
413
+
414
+
415
+ def writelines(path, lines, sep="\r"):
416
+ with open(path, "w", encoding="ascii", newline=sep) as f:
417
+ f.write("\n".join(lines) + "\n")
418
+
419
+
420
+ if __name__ == "__main__":
421
+ import EasyDialogs
422
+
423
+ path = EasyDialogs.AskFileForOpen()
424
+ if path:
425
+ afm = AFM(path)
426
+ char = "A"
427
+ if afm.has_char(char):
428
+ print(afm[char]) # print charnum, width and boundingbox
429
+ pair = ("A", "V")
430
+ if afm.has_kernpair(pair):
431
+ print(afm[pair]) # print kerning value for pair
432
+ print(afm.Version) # various other afm entries have become attributes
433
+ print(afm.Weight)
434
+ # afm.comments() returns a list of all Comment lines found in the AFM
435
+ print(afm.comments())
436
+ # print afm.chars()
437
+ # print afm.kernpairs()
438
+ print(afm)
439
+ afm.write(path + ".muck")
venv/lib/python3.10/site-packages/fontTools/agl.py ADDED
The diff for this file is too large to render. See raw diff
 
venv/lib/python3.10/site-packages/fontTools/annotations.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING, Iterable, Optional, TypeVar, Union
3
+ from collections.abc import Callable, Sequence
4
+ from fontTools.misc.filesystem._base import FS
5
+ from os import PathLike
6
+ from xml.etree.ElementTree import Element as ElementTreeElement
7
+
8
+ if TYPE_CHECKING:
9
+ from fontTools.ufoLib import UFOFormatVersion
10
+ from fontTools.ufoLib.glifLib import GLIFFormatVersion
11
+ from lxml.etree import _Element as LxmlElement
12
+
13
+
14
+ T = TypeVar("T") # Generic type
15
+ K = TypeVar("K") # Generic dict key type
16
+ V = TypeVar("V") # Generic dict value type
17
+
18
+ GlyphNameToFileNameFunc = Optional[Callable[[str, set[str]], str]]
19
+ ElementType = Union[ElementTreeElement, "LxmlElement"]
20
+ FormatVersion = Union[int, tuple[int, int]]
21
+ FormatVersions = Optional[Iterable[FormatVersion]]
22
+ GLIFFormatVersionInput = Optional[Union[int, tuple[int, int], "GLIFFormatVersion"]]
23
+ UFOFormatVersionInput = Optional[Union[int, tuple[int, int], "UFOFormatVersion"]]
24
+ IntFloat = Union[int, float]
25
+ KerningPair = tuple[str, str]
26
+ KerningDict = dict[KerningPair, IntFloat]
27
+ KerningGroups = dict[str, Sequence[str]]
28
+ KerningNested = dict[str, dict[str, IntFloat]]
29
+ PathStr = Union[str, PathLike[str]]
30
+ PathOrFS = Union[PathStr, FS]
venv/lib/python3.10/site-packages/fontTools/cffLib/CFF2ToCFF.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CFF2 to CFF converter."""
2
+
3
+ from fontTools.ttLib import TTFont, newTable
4
+ from fontTools.misc.cliTools import makeOutputFileName
5
+ from fontTools.misc.psCharStrings import T2StackUseExtractor
6
+ from fontTools.cffLib import (
7
+ TopDictIndex,
8
+ buildOrder,
9
+ buildDefaults,
10
+ topDictOperators,
11
+ privateDictOperators,
12
+ FDSelect,
13
+ )
14
+ from .transforms import desubroutinizeCharString
15
+ from .specializer import specializeProgram
16
+ from .width import optimizeWidths
17
+ from collections import defaultdict
18
+ import logging
19
+
20
+
21
+ __all__ = ["convertCFF2ToCFF", "main"]
22
+
23
+
24
+ log = logging.getLogger("fontTools.cffLib")
25
+
26
+
27
+ def _convertCFF2ToCFF(cff, otFont):
28
+ """Converts this object from CFF2 format to CFF format. This conversion
29
+ is done 'in-place'. The conversion cannot be reversed.
30
+
31
+ The CFF2 font cannot be variable. (TODO Accept those and convert to the
32
+ default instance?)
33
+
34
+ This assumes a decompiled CFF2 table. (i.e. that the object has been
35
+ filled via :meth:`decompile` and e.g. not loaded from XML.)"""
36
+
37
+ cff.major = 1
38
+
39
+ topDictData = TopDictIndex(None)
40
+ for item in cff.topDictIndex:
41
+ # Iterate over, such that all are decompiled
42
+ item.cff2GetGlyphOrder = None
43
+ topDictData.append(item)
44
+ cff.topDictIndex = topDictData
45
+ topDict = topDictData[0]
46
+
47
+ if hasattr(topDict, "VarStore"):
48
+ raise ValueError("Variable CFF2 font cannot be converted to CFF format.")
49
+
50
+ opOrder = buildOrder(topDictOperators)
51
+ topDict.order = opOrder
52
+ for key in topDict.rawDict.keys():
53
+ if key not in opOrder:
54
+ del topDict.rawDict[key]
55
+ if hasattr(topDict, key):
56
+ delattr(topDict, key)
57
+
58
+ charStrings = topDict.CharStrings
59
+
60
+ fdArray = topDict.FDArray
61
+ if not hasattr(topDict, "FDSelect"):
62
+ # FDSelect is optional in CFF2, but required in CFF.
63
+ fdSelect = topDict.FDSelect = FDSelect()
64
+ fdSelect.gidArray = [0] * len(charStrings.charStrings)
65
+
66
+ defaults = buildDefaults(privateDictOperators)
67
+ order = buildOrder(privateDictOperators)
68
+ for fd in fdArray:
69
+ fd.setCFF2(False)
70
+ privateDict = fd.Private
71
+ privateDict.order = order
72
+ for key in order:
73
+ if key not in privateDict.rawDict and key in defaults:
74
+ privateDict.rawDict[key] = defaults[key]
75
+ for key in privateDict.rawDict.keys():
76
+ if key not in order:
77
+ del privateDict.rawDict[key]
78
+ if hasattr(privateDict, key):
79
+ delattr(privateDict, key)
80
+
81
+ # Add ending operators
82
+ for cs in charStrings.values():
83
+ cs.decompile()
84
+ cs.program.append("endchar")
85
+ for subrSets in [cff.GlobalSubrs] + [
86
+ getattr(fd.Private, "Subrs", []) for fd in fdArray
87
+ ]:
88
+ for cs in subrSets:
89
+ cs.program.append("return")
90
+
91
+ # Add (optimal) width to CharStrings that need it.
92
+ widths = defaultdict(list)
93
+ metrics = otFont["hmtx"].metrics
94
+ for glyphName in charStrings.keys():
95
+ cs, fdIndex = charStrings.getItemAndSelector(glyphName)
96
+ if fdIndex == None:
97
+ fdIndex = 0
98
+ widths[fdIndex].append(metrics[glyphName][0])
99
+ for fdIndex, widthList in widths.items():
100
+ bestDefault, bestNominal = optimizeWidths(widthList)
101
+ private = fdArray[fdIndex].Private
102
+ private.defaultWidthX = bestDefault
103
+ private.nominalWidthX = bestNominal
104
+ for glyphName in charStrings.keys():
105
+ cs, fdIndex = charStrings.getItemAndSelector(glyphName)
106
+ if fdIndex == None:
107
+ fdIndex = 0
108
+ private = fdArray[fdIndex].Private
109
+ width = metrics[glyphName][0]
110
+ if width != private.defaultWidthX:
111
+ cs.program.insert(0, width - private.nominalWidthX)
112
+
113
+ # Handle stack use since stack-depth is lower in CFF than in CFF2.
114
+ for glyphName in charStrings.keys():
115
+ cs, fdIndex = charStrings.getItemAndSelector(glyphName)
116
+ if fdIndex is None:
117
+ fdIndex = 0
118
+ private = fdArray[fdIndex].Private
119
+ extractor = T2StackUseExtractor(
120
+ getattr(private, "Subrs", []), cff.GlobalSubrs, private=private
121
+ )
122
+ stackUse = extractor.execute(cs)
123
+ if stackUse > 48: # CFF stack depth is 48
124
+ desubroutinizeCharString(cs)
125
+ cs.program = specializeProgram(cs.program)
126
+
127
+ # Unused subroutines are still in CFF2 (ie. lacking 'return' operator)
128
+ # because they were not decompiled when we added the 'return'.
129
+ # Moreover, some used subroutines may have become unused after the
130
+ # stack-use fixup. So we remove all unused subroutines now.
131
+ cff.remove_unused_subroutines()
132
+
133
+ mapping = {
134
+ name: ("cid" + str(n).zfill(5) if n else ".notdef")
135
+ for n, name in enumerate(topDict.charset)
136
+ }
137
+ topDict.charset = [
138
+ "cid" + str(n).zfill(5) if n else ".notdef" for n in range(len(topDict.charset))
139
+ ]
140
+ charStrings.charStrings = {
141
+ mapping[name]: v for name, v in charStrings.charStrings.items()
142
+ }
143
+
144
+ topDict.ROS = ("Adobe", "Identity", 0)
145
+
146
+
147
+ def convertCFF2ToCFF(font, *, updatePostTable=True):
148
+ if "CFF2" not in font:
149
+ raise ValueError("Input font does not contain a CFF2 table.")
150
+ cff = font["CFF2"].cff
151
+ _convertCFF2ToCFF(cff, font)
152
+ del font["CFF2"]
153
+ table = font["CFF "] = newTable("CFF ")
154
+ table.cff = cff
155
+
156
+ if updatePostTable and "post" in font:
157
+ # Only version supported for fonts with CFF table is 0x00030000 not 0x20000
158
+ post = font["post"]
159
+ if post.formatType == 2.0:
160
+ post.formatType = 3.0
161
+
162
+
163
+ def main(args=None):
164
+ """Convert CFF2 OTF font to CFF OTF font"""
165
+ if args is None:
166
+ import sys
167
+
168
+ args = sys.argv[1:]
169
+
170
+ import argparse
171
+
172
+ parser = argparse.ArgumentParser(
173
+ "fonttools cffLib.CFF2ToCFF",
174
+ description="Convert a non-variable CFF2 font to CFF.",
175
+ )
176
+ parser.add_argument(
177
+ "input", metavar="INPUT.ttf", help="Input OTF file with CFF table."
178
+ )
179
+ parser.add_argument(
180
+ "-o",
181
+ "--output",
182
+ metavar="OUTPUT.ttf",
183
+ default=None,
184
+ help="Output instance OTF file (default: INPUT-CFF2.ttf).",
185
+ )
186
+ parser.add_argument(
187
+ "--no-recalc-timestamp",
188
+ dest="recalc_timestamp",
189
+ action="store_false",
190
+ help="Don't set the output font's timestamp to the current time.",
191
+ )
192
+ loggingGroup = parser.add_mutually_exclusive_group(required=False)
193
+ loggingGroup.add_argument(
194
+ "-v", "--verbose", action="store_true", help="Run more verbosely."
195
+ )
196
+ loggingGroup.add_argument(
197
+ "-q", "--quiet", action="store_true", help="Turn verbosity off."
198
+ )
199
+ options = parser.parse_args(args)
200
+
201
+ from fontTools import configLogger
202
+
203
+ configLogger(
204
+ level=("DEBUG" if options.verbose else "ERROR" if options.quiet else "INFO")
205
+ )
206
+
207
+ import os
208
+
209
+ infile = options.input
210
+ if not os.path.isfile(infile):
211
+ parser.error("No such file '{}'".format(infile))
212
+
213
+ outfile = (
214
+ makeOutputFileName(infile, overWrite=True, suffix="-CFF")
215
+ if not options.output
216
+ else options.output
217
+ )
218
+
219
+ font = TTFont(infile, recalcTimestamp=options.recalc_timestamp, recalcBBoxes=False)
220
+
221
+ convertCFF2ToCFF(font)
222
+
223
+ log.info(
224
+ "Saving %s",
225
+ outfile,
226
+ )
227
+ font.save(outfile)
228
+
229
+
230
+ if __name__ == "__main__":
231
+ import sys
232
+
233
+ sys.exit(main(sys.argv[1:]))
venv/lib/python3.10/site-packages/fontTools/cffLib/CFFToCFF2.py ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CFF to CFF2 converter."""
2
+
3
+ from fontTools.ttLib import TTFont, newTable
4
+ from fontTools.misc.cliTools import makeOutputFileName
5
+ from fontTools.misc.psCharStrings import T2WidthExtractor
6
+ from fontTools.cffLib import (
7
+ TopDictIndex,
8
+ FDArrayIndex,
9
+ FontDict,
10
+ buildOrder,
11
+ topDictOperators,
12
+ privateDictOperators,
13
+ topDictOperators2,
14
+ privateDictOperators2,
15
+ )
16
+ from io import BytesIO
17
+ import logging
18
+
19
+ __all__ = ["convertCFFToCFF2", "main"]
20
+
21
+
22
+ log = logging.getLogger("fontTools.cffLib")
23
+
24
+
25
+ class _NominalWidthUsedError(Exception):
26
+ def __add__(self, other):
27
+ raise self
28
+
29
+ def __radd__(self, other):
30
+ raise self
31
+
32
+
33
+ def _convertCFFToCFF2(cff, otFont):
34
+ """Converts this object from CFF format to CFF2 format. This conversion
35
+ is done 'in-place'. The conversion cannot be reversed.
36
+
37
+ This assumes a decompiled CFF table. (i.e. that the object has been
38
+ filled via :meth:`decompile` and e.g. not loaded from XML.)"""
39
+
40
+ # Clean up T2CharStrings
41
+
42
+ topDict = cff.topDictIndex[0]
43
+ fdArray = topDict.FDArray if hasattr(topDict, "FDArray") else None
44
+ charStrings = topDict.CharStrings
45
+ globalSubrs = cff.GlobalSubrs
46
+ localSubrs = (
47
+ [getattr(fd.Private, "Subrs", []) for fd in fdArray]
48
+ if fdArray
49
+ else (
50
+ [topDict.Private.Subrs]
51
+ if hasattr(topDict, "Private") and hasattr(topDict.Private, "Subrs")
52
+ else []
53
+ )
54
+ )
55
+
56
+ for glyphName in charStrings.keys():
57
+ cs, fdIndex = charStrings.getItemAndSelector(glyphName)
58
+ cs.decompile()
59
+
60
+ # Clean up subroutines first
61
+ for subrs in [globalSubrs] + localSubrs:
62
+ for subr in subrs:
63
+ program = subr.program
64
+ i = j = len(program)
65
+ try:
66
+ i = program.index("return")
67
+ except ValueError:
68
+ pass
69
+ try:
70
+ j = program.index("endchar")
71
+ except ValueError:
72
+ pass
73
+ program[min(i, j) :] = []
74
+
75
+ # Clean up glyph charstrings
76
+ removeUnusedSubrs = False
77
+ nominalWidthXError = _NominalWidthUsedError()
78
+ for glyphName in charStrings.keys():
79
+ cs, fdIndex = charStrings.getItemAndSelector(glyphName)
80
+ program = cs.program
81
+
82
+ thisLocalSubrs = (
83
+ localSubrs[fdIndex]
84
+ if fdIndex is not None
85
+ else (
86
+ getattr(topDict.Private, "Subrs", [])
87
+ if hasattr(topDict, "Private")
88
+ else []
89
+ )
90
+ )
91
+
92
+ # Intentionally use custom type for nominalWidthX, such that any
93
+ # CharString that has an explicit width encoded will throw back to us.
94
+ extractor = T2WidthExtractor(
95
+ thisLocalSubrs,
96
+ globalSubrs,
97
+ nominalWidthXError,
98
+ 0,
99
+ )
100
+ try:
101
+ extractor.execute(cs)
102
+ except _NominalWidthUsedError:
103
+ # Program has explicit width. We want to drop it, but can't
104
+ # just pop the first number since it may be a subroutine call.
105
+ # Instead, when seeing that, we embed the subroutine and recurse.
106
+ # If this ever happened, we later prune unused subroutines.
107
+ while len(program) >= 2 and program[1] in ["callsubr", "callgsubr"]:
108
+ removeUnusedSubrs = True
109
+ subrNumber = program.pop(0)
110
+ assert isinstance(subrNumber, int), subrNumber
111
+ op = program.pop(0)
112
+ bias = extractor.localBias if op == "callsubr" else extractor.globalBias
113
+ subrNumber += bias
114
+ subrSet = thisLocalSubrs if op == "callsubr" else globalSubrs
115
+ subrProgram = subrSet[subrNumber].program
116
+ program[:0] = subrProgram
117
+ # Now pop the actual width
118
+ assert len(program) >= 1, program
119
+ program.pop(0)
120
+
121
+ if program and program[-1] == "endchar":
122
+ program.pop()
123
+
124
+ if removeUnusedSubrs:
125
+ cff.remove_unused_subroutines()
126
+
127
+ # Upconvert TopDict
128
+
129
+ cff.major = 2
130
+ cff2GetGlyphOrder = cff.otFont.getGlyphOrder
131
+ topDictData = TopDictIndex(None, cff2GetGlyphOrder)
132
+ for item in cff.topDictIndex:
133
+ # Iterate over, such that all are decompiled
134
+ topDictData.append(item)
135
+ cff.topDictIndex = topDictData
136
+ topDict = topDictData[0]
137
+ if hasattr(topDict, "Private"):
138
+ privateDict = topDict.Private
139
+ else:
140
+ privateDict = None
141
+ opOrder = buildOrder(topDictOperators2)
142
+ topDict.order = opOrder
143
+ topDict.cff2GetGlyphOrder = cff2GetGlyphOrder
144
+
145
+ if not hasattr(topDict, "FDArray"):
146
+ fdArray = topDict.FDArray = FDArrayIndex()
147
+ fdArray.strings = None
148
+ fdArray.GlobalSubrs = topDict.GlobalSubrs
149
+ topDict.GlobalSubrs.fdArray = fdArray
150
+ charStrings = topDict.CharStrings
151
+ if charStrings.charStringsAreIndexed:
152
+ charStrings.charStringsIndex.fdArray = fdArray
153
+ else:
154
+ charStrings.fdArray = fdArray
155
+ fontDict = FontDict()
156
+ fontDict.setCFF2(True)
157
+ fdArray.append(fontDict)
158
+ fontDict.Private = privateDict
159
+ privateOpOrder = buildOrder(privateDictOperators2)
160
+ if privateDict is not None:
161
+ for entry in privateDictOperators:
162
+ key = entry[1]
163
+ if key not in privateOpOrder:
164
+ if key in privateDict.rawDict:
165
+ # print "Removing private dict", key
166
+ del privateDict.rawDict[key]
167
+ if hasattr(privateDict, key):
168
+ delattr(privateDict, key)
169
+ # print "Removing privateDict attr", key
170
+ else:
171
+ # clean up the PrivateDicts in the fdArray
172
+ fdArray = topDict.FDArray
173
+ privateOpOrder = buildOrder(privateDictOperators2)
174
+ for fontDict in fdArray:
175
+ fontDict.setCFF2(True)
176
+ for key in list(fontDict.rawDict.keys()):
177
+ if key not in fontDict.order:
178
+ del fontDict.rawDict[key]
179
+ if hasattr(fontDict, key):
180
+ delattr(fontDict, key)
181
+
182
+ privateDict = fontDict.Private
183
+ for entry in privateDictOperators:
184
+ key = entry[1]
185
+ if key not in privateOpOrder:
186
+ if key in list(privateDict.rawDict.keys()):
187
+ # print "Removing private dict", key
188
+ del privateDict.rawDict[key]
189
+ if hasattr(privateDict, key):
190
+ delattr(privateDict, key)
191
+ # print "Removing privateDict attr", key
192
+
193
+ # Now delete up the deprecated topDict operators from CFF 1.0
194
+ for entry in topDictOperators:
195
+ key = entry[1]
196
+ # We seem to need to keep the charset operator for now,
197
+ # or we fail to compile with some fonts, like AdditionFont.otf.
198
+ # I don't know which kind of CFF font those are. But keeping
199
+ # charset seems to work. It will be removed when we save and
200
+ # read the font again.
201
+ #
202
+ # AdditionFont.otf has <Encoding name="StandardEncoding"/>.
203
+ if key == "charset":
204
+ continue
205
+ if key not in opOrder:
206
+ if key in topDict.rawDict:
207
+ del topDict.rawDict[key]
208
+ if hasattr(topDict, key):
209
+ delattr(topDict, key)
210
+
211
+ # TODO(behdad): What does the following comment even mean? Both CFF and CFF2
212
+ # use the same T2Charstring class. I *think* what it means is that the CharStrings
213
+ # were loaded for CFF1, and we need to reload them for CFF2 to set varstore, etc
214
+ # on them. At least that's what I understand. It's probably safe to remove this
215
+ # and just set vstore where needed.
216
+ #
217
+ # See comment above about charset as well.
218
+
219
+ # At this point, the Subrs and Charstrings are all still T2Charstring class
220
+ # easiest to fix this by compiling, then decompiling again
221
+ file = BytesIO()
222
+ cff.compile(file, otFont, isCFF2=True)
223
+ file.seek(0)
224
+ cff.decompile(file, otFont, isCFF2=True)
225
+
226
+
227
+ def convertCFFToCFF2(font):
228
+ cff = font["CFF "].cff
229
+ del font["CFF "]
230
+ _convertCFFToCFF2(cff, font)
231
+ table = font["CFF2"] = newTable("CFF2")
232
+ table.cff = cff
233
+
234
+
235
+ def main(args=None):
236
+ """Convert CFF OTF font to CFF2 OTF font"""
237
+ if args is None:
238
+ import sys
239
+
240
+ args = sys.argv[1:]
241
+
242
+ import argparse
243
+
244
+ parser = argparse.ArgumentParser(
245
+ "fonttools cffLib.CFFToCFF2",
246
+ description="Upgrade a CFF font to CFF2.",
247
+ )
248
+ parser.add_argument(
249
+ "input", metavar="INPUT.ttf", help="Input OTF file with CFF table."
250
+ )
251
+ parser.add_argument(
252
+ "-o",
253
+ "--output",
254
+ metavar="OUTPUT.ttf",
255
+ default=None,
256
+ help="Output instance OTF file (default: INPUT-CFF2.ttf).",
257
+ )
258
+ parser.add_argument(
259
+ "--no-recalc-timestamp",
260
+ dest="recalc_timestamp",
261
+ action="store_false",
262
+ help="Don't set the output font's timestamp to the current time.",
263
+ )
264
+ loggingGroup = parser.add_mutually_exclusive_group(required=False)
265
+ loggingGroup.add_argument(
266
+ "-v", "--verbose", action="store_true", help="Run more verbosely."
267
+ )
268
+ loggingGroup.add_argument(
269
+ "-q", "--quiet", action="store_true", help="Turn verbosity off."
270
+ )
271
+ options = parser.parse_args(args)
272
+
273
+ from fontTools import configLogger
274
+
275
+ configLogger(
276
+ level=("DEBUG" if options.verbose else "ERROR" if options.quiet else "INFO")
277
+ )
278
+
279
+ import os
280
+
281
+ infile = options.input
282
+ if not os.path.isfile(infile):
283
+ parser.error("No such file '{}'".format(infile))
284
+
285
+ outfile = (
286
+ makeOutputFileName(infile, overWrite=True, suffix="-CFF2")
287
+ if not options.output
288
+ else options.output
289
+ )
290
+
291
+ font = TTFont(infile, recalcTimestamp=options.recalc_timestamp, recalcBBoxes=False)
292
+
293
+ convertCFFToCFF2(font)
294
+
295
+ log.info(
296
+ "Saving %s",
297
+ outfile,
298
+ )
299
+ font.save(outfile)
300
+
301
+
302
+ if __name__ == "__main__":
303
+ import sys
304
+
305
+ sys.exit(main(sys.argv[1:]))
venv/lib/python3.10/site-packages/fontTools/cffLib/__init__.py ADDED
The diff for this file is too large to render. See raw diff