rgrid-python 4.5.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- grid_py/__init__.py +340 -0
- grid_py/_arrow.py +331 -0
- grid_py/_clippath.py +170 -0
- grid_py/_colour.py +815 -0
- grid_py/_coords.py +1534 -0
- grid_py/_curve.py +1668 -0
- grid_py/_display_list.py +507 -0
- grid_py/_draw.py +1397 -0
- grid_py/_edit.py +756 -0
- grid_py/_font_metrics.py +319 -0
- grid_py/_gpar.py +572 -0
- grid_py/_grab.py +501 -0
- grid_py/_grob.py +1377 -0
- grid_py/_group.py +798 -0
- grid_py/_highlevel.py +2176 -0
- grid_py/_just.py +361 -0
- grid_py/_layout.py +593 -0
- grid_py/_ls.py +895 -0
- grid_py/_mask.py +196 -0
- grid_py/_path.py +414 -0
- grid_py/_patterns.py +1049 -0
- grid_py/_primitives.py +2198 -0
- grid_py/_renderer_base.py +1184 -0
- grid_py/_scene_graph.py +248 -0
- grid_py/_size.py +1352 -0
- grid_py/_state.py +683 -0
- grid_py/_transforms.py +448 -0
- grid_py/_typeset.py +384 -0
- grid_py/_units.py +1924 -0
- grid_py/_utils.py +310 -0
- grid_py/_viewport.py +1649 -0
- grid_py/_vp_calc.py +970 -0
- grid_py/py.typed +0 -0
- grid_py/renderer.py +1762 -0
- grid_py/renderer_web.py +764 -0
- grid_py/resources/d3.v7.min.js +2 -0
- grid_py/resources/gridpy.css +80 -0
- grid_py/resources/gridpy.js +813 -0
- rgrid_python-4.5.3.dist-info/METADATA +489 -0
- rgrid_python-4.5.3.dist-info/RECORD +42 -0
- rgrid_python-4.5.3.dist-info/WHEEL +4 -0
- rgrid_python-4.5.3.dist-info/licenses/LICENSE +3 -0
grid_py/_size.py
ADDED
|
@@ -0,0 +1,1352 @@
|
|
|
1
|
+
"""Size and metric computation for grid_py (port of R's grid ``size.R``).
|
|
2
|
+
|
|
3
|
+
This module provides functions for computing grob dimensions (width, height,
|
|
4
|
+
ascent, descent) and text string metrics using Cairo's font engine.
|
|
5
|
+
These mirror the ``widthDetails``, ``heightDetails``, ``xDetails``,
|
|
6
|
+
``yDetails``, ``ascentDetails``, and ``descentDetails`` generics in R's
|
|
7
|
+
grid package.
|
|
8
|
+
|
|
9
|
+
The ``calc_string_metric`` function measures text using Cairo's FreeType-backed
|
|
10
|
+
font engine and returns ascent, descent, and width in inches. The ``grob_*``
|
|
11
|
+
helpers create :class:`Unit` objects whose unit type references a grob,
|
|
12
|
+
paralleling R's ``"grobwidth"``, ``"grobheight"``, etc. unit family.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any, Dict, Optional, Union
|
|
18
|
+
|
|
19
|
+
import cairo
|
|
20
|
+
|
|
21
|
+
from ._gpar import Gpar
|
|
22
|
+
from ._units import Unit
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"calc_string_metric",
|
|
26
|
+
"grob_width",
|
|
27
|
+
"grob_height",
|
|
28
|
+
"grob_x",
|
|
29
|
+
"grob_y",
|
|
30
|
+
"grob_ascent",
|
|
31
|
+
"grob_descent",
|
|
32
|
+
"width_details",
|
|
33
|
+
"height_details",
|
|
34
|
+
"ascent_details",
|
|
35
|
+
"descent_details",
|
|
36
|
+
"x_details",
|
|
37
|
+
"y_details",
|
|
38
|
+
"absolute_size",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# Cairo font helpers
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
# Shared measurement surface (tiny ImageSurface; never written to file).
|
|
47
|
+
_MEASURE_SURFACE: Optional[cairo.ImageSurface] = None
|
|
48
|
+
_MEASURE_CTX: Optional[cairo.Context] = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _get_measure_ctx() -> cairo.Context:
|
|
52
|
+
"""Return a Cairo context used solely for text measurement."""
|
|
53
|
+
global _MEASURE_SURFACE, _MEASURE_CTX
|
|
54
|
+
if _MEASURE_CTX is None:
|
|
55
|
+
_MEASURE_SURFACE = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1)
|
|
56
|
+
_MEASURE_CTX = cairo.Context(_MEASURE_SURFACE)
|
|
57
|
+
return _MEASURE_CTX
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _apply_font_from_gpar(
|
|
61
|
+
ctx: cairo.Context,
|
|
62
|
+
gp: Optional[Gpar] = None,
|
|
63
|
+
) -> float:
|
|
64
|
+
"""Configure *ctx*'s font from a :class:`Gpar` and return the font size
|
|
65
|
+
in **points**.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
ctx : cairo.Context
|
|
70
|
+
The Cairo context to configure.
|
|
71
|
+
gp : Gpar or None
|
|
72
|
+
Graphical parameters. If ``None``, defaults (sans-serif, 12 pt)
|
|
73
|
+
are used.
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
float
|
|
78
|
+
The resolved font size in points.
|
|
79
|
+
"""
|
|
80
|
+
family = "sans-serif"
|
|
81
|
+
slant = cairo.FONT_SLANT_NORMAL
|
|
82
|
+
weight = cairo.FONT_WEIGHT_NORMAL
|
|
83
|
+
fontsize = 12.0 # points
|
|
84
|
+
|
|
85
|
+
if gp is not None:
|
|
86
|
+
ff = gp.get("fontfamily", None)
|
|
87
|
+
if ff is not None:
|
|
88
|
+
family = str(ff[0] if isinstance(ff, (list, tuple)) else ff)
|
|
89
|
+
|
|
90
|
+
fs = gp.get("fontsize", None)
|
|
91
|
+
if fs is not None:
|
|
92
|
+
fontsize = float(fs[0] if isinstance(fs, (list, tuple)) else fs)
|
|
93
|
+
|
|
94
|
+
cex = gp.get("cex", None)
|
|
95
|
+
if cex is not None:
|
|
96
|
+
fontsize *= float(cex[0] if isinstance(cex, (list, tuple)) else cex)
|
|
97
|
+
|
|
98
|
+
face = gp.get("fontface", None)
|
|
99
|
+
if face is not None:
|
|
100
|
+
val = face[0] if isinstance(face, (list, tuple)) else face
|
|
101
|
+
if isinstance(val, str):
|
|
102
|
+
val = val.lower()
|
|
103
|
+
if val in (2, "bold"):
|
|
104
|
+
weight = cairo.FONT_WEIGHT_BOLD
|
|
105
|
+
elif val in (3, "italic", "oblique"):
|
|
106
|
+
slant = cairo.FONT_SLANT_ITALIC
|
|
107
|
+
elif val in (4, "bold.italic"):
|
|
108
|
+
weight = cairo.FONT_WEIGHT_BOLD
|
|
109
|
+
slant = cairo.FONT_SLANT_ITALIC
|
|
110
|
+
|
|
111
|
+
ctx.select_font_face(family, slant, weight)
|
|
112
|
+
ctx.set_font_size(fontsize) # in points (measurement context has no scaling)
|
|
113
|
+
return fontsize
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
# String metrics
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def calc_string_metric(
|
|
122
|
+
text: str,
|
|
123
|
+
gp: Optional[Gpar] = None,
|
|
124
|
+
) -> Dict[str, float]:
|
|
125
|
+
"""Compute text metrics (ascent, descent, width) in inches.
|
|
126
|
+
|
|
127
|
+
Uses Cairo's FreeType-backed font engine to measure the given *text*
|
|
128
|
+
string with the font described by *gp*.
|
|
129
|
+
|
|
130
|
+
Returns **text-specific** ascent and descent (like R's ``GEStrMetric``),
|
|
131
|
+
not font-level values. For example, ``"H"`` has descent ≈ 0 because
|
|
132
|
+
it has no descender strokes, while ``"g"`` has a positive descent.
|
|
133
|
+
|
|
134
|
+
To avoid integer-quantisation artifacts from Cairo's toy font API at
|
|
135
|
+
small point sizes, measurements are taken at a scaled-up font size
|
|
136
|
+
and normalised back.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
text : str
|
|
141
|
+
The string to measure.
|
|
142
|
+
gp : Gpar or None, optional
|
|
143
|
+
Graphical parameters controlling the font family, size, and style.
|
|
144
|
+
When ``None``, Cairo defaults (sans-serif, 12 pt) are used.
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
dict
|
|
149
|
+
A dictionary with keys ``"ascent"``, ``"descent"``, and ``"width"``,
|
|
150
|
+
each a ``float`` value in inches.
|
|
151
|
+
|
|
152
|
+
Examples
|
|
153
|
+
--------
|
|
154
|
+
>>> m = calc_string_metric("Hello")
|
|
155
|
+
>>> sorted(m.keys())
|
|
156
|
+
['ascent', 'descent', 'width']
|
|
157
|
+
"""
|
|
158
|
+
ctx = _get_measure_ctx()
|
|
159
|
+
fontsize = _apply_font_from_gpar(ctx, gp)
|
|
160
|
+
|
|
161
|
+
# --- Scaled measurement to defeat integer quantisation -------
|
|
162
|
+
# Cairo's toy font API quantises font_extents and text_extents
|
|
163
|
+
# to integer user-units. At small point sizes (e.g. 8.8 pt)
|
|
164
|
+
# this introduces large relative errors. By measuring at
|
|
165
|
+
# SCALE× the requested size and dividing back, we recover
|
|
166
|
+
# sub-pixel precision.
|
|
167
|
+
_SCALE = 100
|
|
168
|
+
ctx.set_font_size(fontsize * _SCALE)
|
|
169
|
+
|
|
170
|
+
# text_extents: (x_bearing, y_bearing, width, height, x_advance, y_advance)
|
|
171
|
+
te = ctx.text_extents(text)
|
|
172
|
+
|
|
173
|
+
pts_per_inch = 72.0
|
|
174
|
+
|
|
175
|
+
# Text-specific ascent and descent from text_extents
|
|
176
|
+
# (matches R's GEStrMetric which returns per-string metrics):
|
|
177
|
+
# ascent = -y_bearing (baseline to top of ink)
|
|
178
|
+
# descent = height + y_bearing (baseline to bottom of ink; ≥0)
|
|
179
|
+
# width = x_advance (total advance width)
|
|
180
|
+
ascent = (-te[1]) / _SCALE / pts_per_inch
|
|
181
|
+
descent = max(0.0, (te[3] + te[1])) / _SCALE / pts_per_inch
|
|
182
|
+
width = te[4] / _SCALE / pts_per_inch
|
|
183
|
+
|
|
184
|
+
# Restore the original font size on the shared context.
|
|
185
|
+
ctx.set_font_size(fontsize)
|
|
186
|
+
|
|
187
|
+
return {"ascent": ascent, "descent": descent, "width": width}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
# _grid_class-specific metric implementations
|
|
192
|
+
# (mirrors R's S3 methods in primitives.R)
|
|
193
|
+
# ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _normalise_labels(grob: Any) -> list:
|
|
197
|
+
"""Extract *label* from a grob as a plain Python list of strings."""
|
|
198
|
+
labels = getattr(grob, "label", "")
|
|
199
|
+
if isinstance(labels, str):
|
|
200
|
+
return [labels]
|
|
201
|
+
if isinstance(labels, (list, tuple)):
|
|
202
|
+
return [str(l) for l in labels]
|
|
203
|
+
# numpy array or other iterable
|
|
204
|
+
try:
|
|
205
|
+
return [str(l) for l in labels]
|
|
206
|
+
except TypeError:
|
|
207
|
+
return [str(labels)]
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# -- text grob (R: primitives.R:1430-1470) ---------------------------------
|
|
211
|
+
|
|
212
|
+
def _resolve_grob_gp(grob: Any) -> "Optional[Gpar]":
|
|
213
|
+
"""Resolve a grob's gpar by merging with the current viewport stack.
|
|
214
|
+
|
|
215
|
+
Port of R's ``resolveGPar`` — the grob's own gp overrides inherited
|
|
216
|
+
values from the viewport gpar stack, matching R's ``C_textBounds``
|
|
217
|
+
which always uses the fully-resolved gpar.
|
|
218
|
+
"""
|
|
219
|
+
grob_gp = getattr(grob, "gp", None)
|
|
220
|
+
try:
|
|
221
|
+
from ._gpar import get_gpar
|
|
222
|
+
vp_gp = get_gpar()
|
|
223
|
+
except Exception:
|
|
224
|
+
return grob_gp
|
|
225
|
+
if grob_gp is None:
|
|
226
|
+
return vp_gp
|
|
227
|
+
if vp_gp is None:
|
|
228
|
+
return grob_gp
|
|
229
|
+
return grob_gp._merge(vp_gp)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _text_bbox(grob: Any) -> tuple:
|
|
233
|
+
"""Compute (width, height) of the text bounding box in inches.
|
|
234
|
+
|
|
235
|
+
Mirrors what R's ``heightDetails.text`` / ``widthDetails.text`` return
|
|
236
|
+
(empirically verified against R 4.4 ``cairo_png`` at 150 dpi):
|
|
237
|
+
|
|
238
|
+
width = max(per-line ink widths) — R's ``GEStrWidth``
|
|
239
|
+
height = ink_height(first line)
|
|
240
|
+
+ (n - 1) × cex × lineheight × fontsize × 1.2 / 72
|
|
241
|
+
— R's ``GEStrHeight``
|
|
242
|
+
|
|
243
|
+
The per-extra-line gap ``1.2 × fontsize / 72`` is R's device-level
|
|
244
|
+
``cra[1] × ipr[1] / default_ps`` collapsed for the standard cairo /
|
|
245
|
+
PostScript setups (default_ps = 12 pt, cin[1] = 1.2 × 12 / 72 in).
|
|
246
|
+
|
|
247
|
+
Single-line text is therefore independent of ``lineheight`` (matches
|
|
248
|
+
R exactly), while each extra newline adds the lineheight-scaled gap.
|
|
249
|
+
|
|
250
|
+
The grob's gp is merged with the current viewport stack gpar, so
|
|
251
|
+
``grobHeight`` inherits fontsize / lineheight from the viewport.
|
|
252
|
+
"""
|
|
253
|
+
import math
|
|
254
|
+
labels = _normalise_labels(grob)
|
|
255
|
+
gp = _resolve_grob_gp(grob)
|
|
256
|
+
rot = float(getattr(grob, "rot", 0.0))
|
|
257
|
+
|
|
258
|
+
if not labels:
|
|
259
|
+
return (0.0, 0.0)
|
|
260
|
+
|
|
261
|
+
# Resolve cex and lineheight; fontsize is read per-line by calc_string_metric.
|
|
262
|
+
cex = 1.0
|
|
263
|
+
lineheight = 1.2
|
|
264
|
+
fontsize = 12.0
|
|
265
|
+
if gp is not None:
|
|
266
|
+
fs = gp.get("fontsize", None)
|
|
267
|
+
if fs is not None:
|
|
268
|
+
fontsize = float(fs[0] if isinstance(fs, (list, tuple)) else fs)
|
|
269
|
+
cx = gp.get("cex", None)
|
|
270
|
+
if cx is not None:
|
|
271
|
+
cex = float(cx[0] if isinstance(cx, (list, tuple)) else cx)
|
|
272
|
+
lh = gp.get("lineheight", None)
|
|
273
|
+
if lh is not None:
|
|
274
|
+
lineheight = float(lh[0] if isinstance(lh, (list, tuple)) else lh)
|
|
275
|
+
|
|
276
|
+
# Per-extra-line gap in inches — matches R's ``cra[1] × ipr[1] / default_ps``
|
|
277
|
+
# collapsed for the standard device (= fontsize × 1.2 / 72).
|
|
278
|
+
inter_line_gap = cex * lineheight * fontsize * 1.2 / 72.0
|
|
279
|
+
|
|
280
|
+
xmin = float("inf")
|
|
281
|
+
xmax = float("-inf")
|
|
282
|
+
ymin = float("inf")
|
|
283
|
+
ymax = float("-inf")
|
|
284
|
+
|
|
285
|
+
for lab in labels:
|
|
286
|
+
lines = lab.split("\n") if lab else [""]
|
|
287
|
+
n_lines = len(lines)
|
|
288
|
+
# Width: max per-line width (R's ``GEStrWidth`` walks each line).
|
|
289
|
+
w = max(calc_string_metric(ln, gp=gp)["width"] for ln in lines)
|
|
290
|
+
# Height: ink of first line + gap × (n - 1). R uses the first
|
|
291
|
+
# line's ink bounds (``ascent + descent``) and extends by per-line
|
|
292
|
+
# gaps for additional lines.
|
|
293
|
+
m0 = calc_string_metric(lines[0], gp=gp)
|
|
294
|
+
h = m0["ascent"] + m0["descent"] + (n_lines - 1) * inter_line_gap
|
|
295
|
+
|
|
296
|
+
if rot == 0.0:
|
|
297
|
+
# No rotation: bbox is just the text extent
|
|
298
|
+
corners_x = [0, w, w, 0]
|
|
299
|
+
corners_y = [0, 0, h, h]
|
|
300
|
+
else:
|
|
301
|
+
rad = math.radians(rot)
|
|
302
|
+
cos_r = math.cos(rad)
|
|
303
|
+
sin_r = math.sin(rad)
|
|
304
|
+
# Four corners of the unrotated text rectangle
|
|
305
|
+
corners = [(0, 0), (w, 0), (w, h), (0, h)]
|
|
306
|
+
corners_x = [cx_ * cos_r - cy_ * sin_r for cx_, cy_ in corners]
|
|
307
|
+
corners_y = [cx_ * sin_r + cy_ * cos_r for cx_, cy_ in corners]
|
|
308
|
+
|
|
309
|
+
for cx_ in corners_x:
|
|
310
|
+
if cx_ < xmin:
|
|
311
|
+
xmin = cx_
|
|
312
|
+
if cx_ > xmax:
|
|
313
|
+
xmax = cx_
|
|
314
|
+
for cy_ in corners_y:
|
|
315
|
+
if cy_ < ymin:
|
|
316
|
+
ymin = cy_
|
|
317
|
+
if cy_ > ymax:
|
|
318
|
+
ymax = cy_
|
|
319
|
+
|
|
320
|
+
if xmin == float("inf"):
|
|
321
|
+
return (0.0, 0.0)
|
|
322
|
+
return (xmax - xmin, ymax - ymin)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _text_width_details(grob: Any) -> Unit:
|
|
326
|
+
"""Width of a text grob: rotated bounding box width.
|
|
327
|
+
|
|
328
|
+
Port of R ``widthDetails.text`` (primitives.R:1430) which calls
|
|
329
|
+
``C_textBounds`` with rotation to compute the axis-aligned bbox.
|
|
330
|
+
"""
|
|
331
|
+
w, _ = _text_bbox(grob)
|
|
332
|
+
return Unit(w, "inches")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _text_height_details(grob: Any) -> Unit:
|
|
336
|
+
"""Height of a text grob: rotated bounding box height.
|
|
337
|
+
|
|
338
|
+
Port of R ``heightDetails.text`` (primitives.R:1442) which calls
|
|
339
|
+
``C_textBounds`` with rotation to compute the axis-aligned bbox.
|
|
340
|
+
"""
|
|
341
|
+
_, h = _text_bbox(grob)
|
|
342
|
+
return Unit(h, "inches")
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _text_ascent_details(grob: Any) -> Unit:
|
|
346
|
+
"""Ascent of a text grob.
|
|
347
|
+
|
|
348
|
+
For a single label, returns the font ascent. For multiple labels,
|
|
349
|
+
falls back to ``_text_height_details``.
|
|
350
|
+
|
|
351
|
+
Mirrors ``ascentDetails.text`` (R ``primitives.R:1454``).
|
|
352
|
+
"""
|
|
353
|
+
labels = _normalise_labels(grob)
|
|
354
|
+
gp = _resolve_grob_gp(grob)
|
|
355
|
+
if len(labels) == 1:
|
|
356
|
+
m = calc_string_metric(labels[0], gp=gp)
|
|
357
|
+
return Unit(m["ascent"], "inches")
|
|
358
|
+
return _text_height_details(grob)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _text_descent_details(grob: Any) -> Unit:
|
|
362
|
+
"""Descent of a text grob.
|
|
363
|
+
|
|
364
|
+
For a single label, returns the font descent. For multiple labels,
|
|
365
|
+
returns ``Unit(0, "inches")``.
|
|
366
|
+
|
|
367
|
+
Mirrors ``descentDetails.text`` (R ``primitives.R:1463``).
|
|
368
|
+
"""
|
|
369
|
+
labels = _normalise_labels(grob)
|
|
370
|
+
gp = _resolve_grob_gp(grob)
|
|
371
|
+
if len(labels) == 1:
|
|
372
|
+
m = calc_string_metric(labels[0], gp=gp)
|
|
373
|
+
return Unit(m["descent"], "inches")
|
|
374
|
+
return Unit(0, "inches")
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# -- null grob (R: primitives.R:1676-1682) ---------------------------------
|
|
378
|
+
|
|
379
|
+
def _null_width_details(grob: Any) -> Unit:
|
|
380
|
+
"""Width of a null grob: always zero.
|
|
381
|
+
|
|
382
|
+
Mirrors ``widthDetails.null`` (R ``primitives.R:1676``).
|
|
383
|
+
"""
|
|
384
|
+
return Unit(0, "inches")
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _null_height_details(grob: Any) -> Unit:
|
|
388
|
+
"""Height of a null grob: always zero.
|
|
389
|
+
|
|
390
|
+
Mirrors ``heightDetails.null`` (R ``primitives.R:1680``).
|
|
391
|
+
"""
|
|
392
|
+
return Unit(0, "inches")
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
# -- rect grob (R: primitives.R:1146-1166) ---------------------------------
|
|
396
|
+
|
|
397
|
+
def _rect_width_details(grob: Any) -> Unit:
|
|
398
|
+
"""Width of a rect grob: actual bounding box width in inches.
|
|
399
|
+
|
|
400
|
+
Port of R ``widthDetails.rect`` (primitives.R:1146) which calls
|
|
401
|
+
``C_rectBounds``. Resolves x, width, hjust to inches, computes
|
|
402
|
+
the bounding box ``xmax - xmin`` across all rectangles.
|
|
403
|
+
"""
|
|
404
|
+
from ._just import resolve_hjust, resolve_vjust
|
|
405
|
+
|
|
406
|
+
renderer = _get_renderer()
|
|
407
|
+
if renderer is None:
|
|
408
|
+
# Fallback: return the raw width attribute
|
|
409
|
+
w = getattr(grob, "width", None)
|
|
410
|
+
if w is not None and isinstance(w, Unit):
|
|
411
|
+
return w
|
|
412
|
+
return Unit(1, "npc")
|
|
413
|
+
|
|
414
|
+
gp = getattr(grob, "gp", None)
|
|
415
|
+
x_unit = getattr(grob, "x", None)
|
|
416
|
+
w_unit = getattr(grob, "width", None)
|
|
417
|
+
if x_unit is None or w_unit is None:
|
|
418
|
+
return Unit(0, "inches")
|
|
419
|
+
|
|
420
|
+
just = getattr(grob, "just", None) or "centre"
|
|
421
|
+
hjust_val = getattr(grob, "hjust", None)
|
|
422
|
+
hjust = resolve_hjust(just, hjust_val)
|
|
423
|
+
|
|
424
|
+
n = max(len(x_unit), len(w_unit))
|
|
425
|
+
xmin = float("inf")
|
|
426
|
+
xmax = float("-inf")
|
|
427
|
+
for i in range(n):
|
|
428
|
+
cx = renderer._resolve_to_inches_idx(x_unit, i % len(x_unit), "x", False, gp)
|
|
429
|
+
w = renderer._resolve_to_inches_idx(w_unit, i % len(w_unit), "x", True, gp)
|
|
430
|
+
left = cx - hjust * w
|
|
431
|
+
right = left + w
|
|
432
|
+
if left < xmin:
|
|
433
|
+
xmin = left
|
|
434
|
+
if right > xmax:
|
|
435
|
+
xmax = right
|
|
436
|
+
|
|
437
|
+
if xmin == float("inf"):
|
|
438
|
+
return Unit(0, "inches")
|
|
439
|
+
return Unit(xmax - xmin, "inches")
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _rect_height_details(grob: Any) -> Unit:
|
|
443
|
+
"""Height of a rect grob: actual bounding box height in inches.
|
|
444
|
+
|
|
445
|
+
Port of R ``heightDetails.rect`` (primitives.R:1157) which calls
|
|
446
|
+
``C_rectBounds``.
|
|
447
|
+
"""
|
|
448
|
+
from ._just import resolve_hjust, resolve_vjust
|
|
449
|
+
|
|
450
|
+
renderer = _get_renderer()
|
|
451
|
+
if renderer is None:
|
|
452
|
+
h = getattr(grob, "height", None)
|
|
453
|
+
if h is not None and isinstance(h, Unit):
|
|
454
|
+
return h
|
|
455
|
+
return Unit(1, "npc")
|
|
456
|
+
|
|
457
|
+
gp = getattr(grob, "gp", None)
|
|
458
|
+
y_unit = getattr(grob, "y", None)
|
|
459
|
+
h_unit = getattr(grob, "height", None)
|
|
460
|
+
if y_unit is None or h_unit is None:
|
|
461
|
+
return Unit(0, "inches")
|
|
462
|
+
|
|
463
|
+
just = getattr(grob, "just", None) or "centre"
|
|
464
|
+
vjust_val = getattr(grob, "vjust", None)
|
|
465
|
+
vjust = resolve_vjust(just, vjust_val)
|
|
466
|
+
|
|
467
|
+
n = max(len(y_unit), len(h_unit))
|
|
468
|
+
ymin = float("inf")
|
|
469
|
+
ymax = float("-inf")
|
|
470
|
+
for i in range(n):
|
|
471
|
+
cy = renderer._resolve_to_inches_idx(y_unit, i % len(y_unit), "y", False, gp)
|
|
472
|
+
h = renderer._resolve_to_inches_idx(h_unit, i % len(h_unit), "y", True, gp)
|
|
473
|
+
bottom = cy - vjust * h
|
|
474
|
+
top = bottom + h
|
|
475
|
+
if bottom < ymin:
|
|
476
|
+
ymin = bottom
|
|
477
|
+
if top > ymax:
|
|
478
|
+
ymax = top
|
|
479
|
+
|
|
480
|
+
if ymin == float("inf"):
|
|
481
|
+
return Unit(0, "inches")
|
|
482
|
+
return Unit(ymax - ymin, "inches")
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
# -- coordinate-based bounding box helpers ----------------------------------
|
|
486
|
+
#
|
|
487
|
+
# R uses C_locnBounds (for lines, points, polygon, polyline, segments)
|
|
488
|
+
# and C_circleBounds (for circles). Both resolve all coordinates to
|
|
489
|
+
# inches in the current viewport context, then compute min/max.
|
|
490
|
+
#
|
|
491
|
+
# In grid_py we achieve the same by obtaining the active renderer
|
|
492
|
+
# and calling ``renderer.resolve_to_npc()`` on each coordinate.
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _get_renderer() -> Any:
|
|
496
|
+
"""Return the active renderer, or ``None`` if none is bound."""
|
|
497
|
+
from ._state import get_state
|
|
498
|
+
state = get_state()
|
|
499
|
+
return state.get_renderer()
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def _locn_bounds_inches(
|
|
503
|
+
unit_obj: Any, renderer: Any, axis: str, gp: Any = None,
|
|
504
|
+
) -> tuple:
|
|
505
|
+
"""Resolve all elements of a Unit to inches and return (min, max).
|
|
506
|
+
|
|
507
|
+
Port of R ``C_locnBounds`` (grid.c:5296-5376): resolves each coordinate
|
|
508
|
+
to inches via ``transformXtoINCHES``/``transformYtoINCHES``, then computes
|
|
509
|
+
the bounding box.
|
|
510
|
+
|
|
511
|
+
Parameters
|
|
512
|
+
----------
|
|
513
|
+
unit_obj : Unit
|
|
514
|
+
Coordinate unit.
|
|
515
|
+
renderer : object
|
|
516
|
+
Active renderer with ``_resolve_to_inches_idx``.
|
|
517
|
+
axis : str
|
|
518
|
+
``"x"`` or ``"y"``.
|
|
519
|
+
gp : object, optional
|
|
520
|
+
Graphical parameters.
|
|
521
|
+
|
|
522
|
+
Returns
|
|
523
|
+
-------
|
|
524
|
+
tuple
|
|
525
|
+
``(min_inches, max_inches)`` or ``(0.0, 0.0)`` if empty.
|
|
526
|
+
"""
|
|
527
|
+
from ._units import Unit
|
|
528
|
+
if not isinstance(unit_obj, Unit) or len(unit_obj) == 0:
|
|
529
|
+
return (0.0, 0.0)
|
|
530
|
+
|
|
531
|
+
xmin = float("inf")
|
|
532
|
+
xmax = float("-inf")
|
|
533
|
+
n = len(unit_obj)
|
|
534
|
+
for i in range(n):
|
|
535
|
+
val = renderer._resolve_to_inches_idx(unit_obj, i, axis, False, gp)
|
|
536
|
+
if val < xmin:
|
|
537
|
+
xmin = val
|
|
538
|
+
if val > xmax:
|
|
539
|
+
xmax = val
|
|
540
|
+
|
|
541
|
+
if xmin == float("inf"):
|
|
542
|
+
return (0.0, 0.0)
|
|
543
|
+
return (xmin, xmax)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _locn_bounds_width(x_unit: Any, renderer: Any, gp: Any = None) -> float:
|
|
547
|
+
"""Compute the width (in inches) of a set of x-coordinates.
|
|
548
|
+
|
|
549
|
+
Port of R ``C_locnBounds`` returning ``bounds[3]`` (width = xmax - xmin).
|
|
550
|
+
Uses the inches-based pipeline (not NPC).
|
|
551
|
+
"""
|
|
552
|
+
lo, hi = _locn_bounds_inches(x_unit, renderer, "x", gp)
|
|
553
|
+
return hi - lo
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def _locn_bounds_height(y_unit: Any, renderer: Any, gp: Any = None) -> float:
|
|
557
|
+
"""Compute the height (in inches) of a set of y-coordinates.
|
|
558
|
+
|
|
559
|
+
Port of R ``C_locnBounds`` returning ``bounds[4]`` (height = ymax - ymin).
|
|
560
|
+
Uses the inches-based pipeline (not NPC).
|
|
561
|
+
"""
|
|
562
|
+
lo, hi = _locn_bounds_inches(y_unit, renderer, "y", gp)
|
|
563
|
+
return hi - lo
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
# -- lines grob (R: primitives.R:186-200, uses C_locnBounds) ---------------
|
|
567
|
+
|
|
568
|
+
def _lines_width_details(grob: Any) -> Unit:
|
|
569
|
+
"""Width of a lines/polyline grob: bounding box of x-coordinates.
|
|
570
|
+
|
|
571
|
+
Mirrors ``widthDetails.lines`` (R ``primitives.R:186``).
|
|
572
|
+
"""
|
|
573
|
+
renderer = _get_renderer()
|
|
574
|
+
if renderer is None:
|
|
575
|
+
return Unit(0, "inches")
|
|
576
|
+
x_unit = getattr(grob, "x", None)
|
|
577
|
+
gp = getattr(grob, "gp", None)
|
|
578
|
+
return Unit(_locn_bounds_width(x_unit, renderer, gp), "inches")
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def _lines_height_details(grob: Any) -> Unit:
|
|
582
|
+
"""Height of a lines/polyline grob.
|
|
583
|
+
|
|
584
|
+
Mirrors ``heightDetails.lines`` (R ``primitives.R:194``).
|
|
585
|
+
"""
|
|
586
|
+
renderer = _get_renderer()
|
|
587
|
+
if renderer is None:
|
|
588
|
+
return Unit(0, "inches")
|
|
589
|
+
y_unit = getattr(grob, "y", None)
|
|
590
|
+
gp = getattr(grob, "gp", None)
|
|
591
|
+
return Unit(_locn_bounds_height(y_unit, renderer, gp), "inches")
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
# -- points grob (R: primitives.R:1546-1560, uses C_locnBounds) ------------
|
|
595
|
+
|
|
596
|
+
def _points_width_details(grob: Any) -> Unit:
|
|
597
|
+
"""Width of a points grob: bounding box of x-coordinates.
|
|
598
|
+
|
|
599
|
+
Mirrors ``widthDetails.points`` (R ``primitives.R:1546``).
|
|
600
|
+
"""
|
|
601
|
+
renderer = _get_renderer()
|
|
602
|
+
if renderer is None:
|
|
603
|
+
return Unit(0, "inches")
|
|
604
|
+
x_unit = getattr(grob, "x", None)
|
|
605
|
+
gp = getattr(grob, "gp", None)
|
|
606
|
+
return Unit(_locn_bounds_width(x_unit, renderer, gp), "inches")
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def _points_height_details(grob: Any) -> Unit:
|
|
610
|
+
"""Height of a points grob.
|
|
611
|
+
|
|
612
|
+
Mirrors ``heightDetails.points`` (R ``primitives.R:1554``).
|
|
613
|
+
"""
|
|
614
|
+
renderer = _get_renderer()
|
|
615
|
+
if renderer is None:
|
|
616
|
+
return Unit(0, "inches")
|
|
617
|
+
y_unit = getattr(grob, "y", None)
|
|
618
|
+
gp = getattr(grob, "gp", None)
|
|
619
|
+
return Unit(_locn_bounds_height(y_unit, renderer, gp), "inches")
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
# -- polygon grob (R: primitives.R:607-621, uses C_locnBounds) -------------
|
|
623
|
+
|
|
624
|
+
def _polygon_width_details(grob: Any) -> Unit:
|
|
625
|
+
"""Width of a polygon grob.
|
|
626
|
+
|
|
627
|
+
Mirrors ``widthDetails.polygon`` (R ``primitives.R:607``).
|
|
628
|
+
"""
|
|
629
|
+
renderer = _get_renderer()
|
|
630
|
+
if renderer is None:
|
|
631
|
+
return Unit(0, "inches")
|
|
632
|
+
x_unit = getattr(grob, "x", None)
|
|
633
|
+
gp = getattr(grob, "gp", None)
|
|
634
|
+
return Unit(_locn_bounds_width(x_unit, renderer, gp), "inches")
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def _polygon_height_details(grob: Any) -> Unit:
|
|
638
|
+
"""Height of a polygon grob.
|
|
639
|
+
|
|
640
|
+
Mirrors ``heightDetails.polygon`` (R ``primitives.R:615``).
|
|
641
|
+
"""
|
|
642
|
+
renderer = _get_renderer()
|
|
643
|
+
if renderer is None:
|
|
644
|
+
return Unit(0, "inches")
|
|
645
|
+
y_unit = getattr(grob, "y", None)
|
|
646
|
+
gp = getattr(grob, "gp", None)
|
|
647
|
+
return Unit(_locn_bounds_height(y_unit, renderer, gp), "inches")
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
# -- segments grob (R: primitives.R:367-381, uses segmentBounds helper) -----
|
|
651
|
+
|
|
652
|
+
def _segments_width_details(grob: Any) -> Unit:
|
|
653
|
+
"""Width of a segments grob: bounding box of all endpoints.
|
|
654
|
+
|
|
655
|
+
Mirrors ``widthDetails.segments`` (R ``primitives.R:367``).
|
|
656
|
+
R's ``segmentBounds`` concatenates x0,x1 and y0,y1 into single
|
|
657
|
+
vectors, then calls ``C_locnBounds``.
|
|
658
|
+
"""
|
|
659
|
+
from ._units import Unit as _Unit, unit_c
|
|
660
|
+
renderer = _get_renderer()
|
|
661
|
+
if renderer is None:
|
|
662
|
+
return Unit(0, "inches")
|
|
663
|
+
x0 = getattr(grob, "x0", None)
|
|
664
|
+
x1 = getattr(grob, "x1", None)
|
|
665
|
+
gp = getattr(grob, "gp", None)
|
|
666
|
+
if x0 is not None and x1 is not None:
|
|
667
|
+
if isinstance(x0, _Unit) and isinstance(x1, _Unit):
|
|
668
|
+
combined_x = unit_c(x0, x1)
|
|
669
|
+
else:
|
|
670
|
+
combined_x = x0
|
|
671
|
+
elif x0 is not None:
|
|
672
|
+
combined_x = x0
|
|
673
|
+
elif x1 is not None:
|
|
674
|
+
combined_x = x1
|
|
675
|
+
else:
|
|
676
|
+
return Unit(0, "inches")
|
|
677
|
+
return Unit(_locn_bounds_width(combined_x, renderer, gp), "inches")
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def _segments_height_details(grob: Any) -> Unit:
|
|
681
|
+
"""Height of a segments grob.
|
|
682
|
+
|
|
683
|
+
Mirrors ``heightDetails.segments`` (R ``primitives.R:375``).
|
|
684
|
+
"""
|
|
685
|
+
from ._units import Unit as _Unit, unit_c
|
|
686
|
+
renderer = _get_renderer()
|
|
687
|
+
if renderer is None:
|
|
688
|
+
return Unit(0, "inches")
|
|
689
|
+
y0 = getattr(grob, "y0", None)
|
|
690
|
+
y1 = getattr(grob, "y1", None)
|
|
691
|
+
gp = getattr(grob, "gp", None)
|
|
692
|
+
if y0 is not None and y1 is not None:
|
|
693
|
+
if isinstance(y0, _Unit) and isinstance(y1, _Unit):
|
|
694
|
+
combined_y = unit_c(y0, y1)
|
|
695
|
+
else:
|
|
696
|
+
combined_y = y0
|
|
697
|
+
elif y0 is not None:
|
|
698
|
+
combined_y = y0
|
|
699
|
+
elif y1 is not None:
|
|
700
|
+
combined_y = y1
|
|
701
|
+
else:
|
|
702
|
+
return Unit(0, "inches")
|
|
703
|
+
return Unit(_locn_bounds_height(combined_y, renderer, gp), "inches")
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
# -- circle grob (R: primitives.R:1062-1076, uses C_circleBounds) ----------
|
|
707
|
+
|
|
708
|
+
def _circle_width_details(grob: Any) -> Unit:
|
|
709
|
+
"""Width of a circle grob: bounding box considering radius.
|
|
710
|
+
|
|
711
|
+
Port of R ``widthDetails.circle`` (primitives.R:1062).
|
|
712
|
+
R's ``C_circleBounds`` computes ``max(cx+r) - min(cx-r)`` in inches.
|
|
713
|
+
"""
|
|
714
|
+
from ._units import Unit as _Unit
|
|
715
|
+
renderer = _get_renderer()
|
|
716
|
+
if renderer is None:
|
|
717
|
+
return Unit(0, "inches")
|
|
718
|
+
|
|
719
|
+
x_unit = getattr(grob, "x", None)
|
|
720
|
+
r_unit = getattr(grob, "r", None)
|
|
721
|
+
gp = getattr(grob, "gp", None)
|
|
722
|
+
|
|
723
|
+
if x_unit is None or not isinstance(x_unit, _Unit):
|
|
724
|
+
return Unit(0, "inches")
|
|
725
|
+
|
|
726
|
+
n = len(x_unit)
|
|
727
|
+
nr = len(r_unit) if r_unit is not None and isinstance(r_unit, _Unit) else 0
|
|
728
|
+
|
|
729
|
+
xmin = float("inf")
|
|
730
|
+
xmax = float("-inf")
|
|
731
|
+
for i in range(n):
|
|
732
|
+
cx = renderer._resolve_to_inches_idx(x_unit, i, "x", False, gp)
|
|
733
|
+
if nr > 0:
|
|
734
|
+
# R: r = pmin(convertWidth(r), convertHeight(r))
|
|
735
|
+
rw = renderer._resolve_to_inches_idx(r_unit, i % nr, "x", True, gp)
|
|
736
|
+
rh = renderer._resolve_to_inches_idx(r_unit, i % nr, "y", True, gp)
|
|
737
|
+
r = min(rw, rh)
|
|
738
|
+
else:
|
|
739
|
+
r = 0.0
|
|
740
|
+
left = cx - r
|
|
741
|
+
right = cx + r
|
|
742
|
+
if left < xmin:
|
|
743
|
+
xmin = left
|
|
744
|
+
if right > xmax:
|
|
745
|
+
xmax = right
|
|
746
|
+
|
|
747
|
+
if xmin == float("inf"):
|
|
748
|
+
return Unit(0, "inches")
|
|
749
|
+
return Unit(xmax - xmin, "inches")
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
def _circle_height_details(grob: Any) -> Unit:
|
|
753
|
+
"""Height of a circle grob.
|
|
754
|
+
|
|
755
|
+
Port of R ``heightDetails.circle`` (primitives.R:1070).
|
|
756
|
+
"""
|
|
757
|
+
from ._units import Unit as _Unit
|
|
758
|
+
renderer = _get_renderer()
|
|
759
|
+
if renderer is None:
|
|
760
|
+
return Unit(0, "inches")
|
|
761
|
+
|
|
762
|
+
y_unit = getattr(grob, "y", None)
|
|
763
|
+
r_unit = getattr(grob, "r", None)
|
|
764
|
+
gp = getattr(grob, "gp", None)
|
|
765
|
+
|
|
766
|
+
if y_unit is None or not isinstance(y_unit, _Unit):
|
|
767
|
+
return Unit(0, "inches")
|
|
768
|
+
|
|
769
|
+
n = len(y_unit)
|
|
770
|
+
nr = len(r_unit) if r_unit is not None and isinstance(r_unit, _Unit) else 0
|
|
771
|
+
|
|
772
|
+
ymin = float("inf")
|
|
773
|
+
ymax = float("-inf")
|
|
774
|
+
for i in range(n):
|
|
775
|
+
cy = renderer._resolve_to_inches_idx(y_unit, i, "y", False, gp)
|
|
776
|
+
if nr > 0:
|
|
777
|
+
rw = renderer._resolve_to_inches_idx(r_unit, i % nr, "x", True, gp)
|
|
778
|
+
rh = renderer._resolve_to_inches_idx(r_unit, i % nr, "y", True, gp)
|
|
779
|
+
r = min(rw, rh)
|
|
780
|
+
else:
|
|
781
|
+
r = 0.0
|
|
782
|
+
bottom = cy - r
|
|
783
|
+
top = cy + r
|
|
784
|
+
if bottom < ymin:
|
|
785
|
+
ymin = bottom
|
|
786
|
+
if top > ymax:
|
|
787
|
+
ymax = top
|
|
788
|
+
|
|
789
|
+
if ymin == float("inf"):
|
|
790
|
+
return Unit(0, "inches")
|
|
791
|
+
return Unit(ymax - ymin, "inches")
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
# -- roundrect grob (same as rect: returns own width/height) ----------------
|
|
795
|
+
|
|
796
|
+
def _roundrect_width_details(grob: Any) -> Unit:
|
|
797
|
+
"""Width of a roundrect grob: its own *width* attribute.
|
|
798
|
+
|
|
799
|
+
Mirrors ``widthDetails.roundrect`` — same as rect.
|
|
800
|
+
"""
|
|
801
|
+
return _rect_width_details(grob)
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
def _roundrect_height_details(grob: Any) -> Unit:
|
|
805
|
+
"""Height of a roundrect grob: its own *height* attribute."""
|
|
806
|
+
return _rect_height_details(grob)
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
# -- pathgrob (coordinate bounding box, same pattern as polygon) ------------
|
|
810
|
+
|
|
811
|
+
def _path_width_details(grob: Any) -> Unit:
|
|
812
|
+
"""Width of a path grob: bounding box of x-coordinates.
|
|
813
|
+
|
|
814
|
+
Mirrors ``widthDetails.path`` (uses ``C_locnBounds``).
|
|
815
|
+
"""
|
|
816
|
+
renderer = _get_renderer()
|
|
817
|
+
if renderer is None:
|
|
818
|
+
return Unit(0, "inches")
|
|
819
|
+
x_unit = getattr(grob, "x", None)
|
|
820
|
+
gp = getattr(grob, "gp", None)
|
|
821
|
+
return Unit(_locn_bounds_width(x_unit, renderer, gp), "inches")
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def _path_height_details(grob: Any) -> Unit:
|
|
825
|
+
"""Height of a path grob."""
|
|
826
|
+
renderer = _get_renderer()
|
|
827
|
+
if renderer is None:
|
|
828
|
+
return Unit(0, "inches")
|
|
829
|
+
y_unit = getattr(grob, "y", None)
|
|
830
|
+
gp = getattr(grob, "gp", None)
|
|
831
|
+
return Unit(_locn_bounds_height(y_unit, renderer, gp), "inches")
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
# -- rastergrob (returns own width/height, same as rect) --------------------
|
|
835
|
+
|
|
836
|
+
def _raster_width_details(grob: Any) -> Unit:
|
|
837
|
+
"""Width of a raster grob.
|
|
838
|
+
|
|
839
|
+
Port of R ``widthDetails.rastergrob`` (primitives.R:1313) — uses
|
|
840
|
+
``C_rectBounds`` after resolving raster size. Same logic as rect.
|
|
841
|
+
"""
|
|
842
|
+
return _rect_width_details(grob)
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def _raster_height_details(grob: Any) -> Unit:
|
|
846
|
+
"""Height of a raster grob.
|
|
847
|
+
|
|
848
|
+
Port of R ``heightDetails.rastergrob`` (primitives.R:1325).
|
|
849
|
+
"""
|
|
850
|
+
return _rect_height_details(grob)
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
# -- xspline grob (R: primitives.R:845-861, uses C_xsplineBounds) ----------
|
|
854
|
+
|
|
855
|
+
def _xspline_width_details(grob: Any) -> Unit:
|
|
856
|
+
"""Width of an xspline grob: bounding box of control points.
|
|
857
|
+
|
|
858
|
+
Port of R ``widthDetails.xspline`` (primitives.R:845) which uses
|
|
859
|
+
``C_xsplineBounds``. We approximate by using the control point
|
|
860
|
+
bounding box (same as C_locnBounds on the control points).
|
|
861
|
+
"""
|
|
862
|
+
renderer = _get_renderer()
|
|
863
|
+
if renderer is None:
|
|
864
|
+
return Unit(0, "inches")
|
|
865
|
+
x_unit = getattr(grob, "x", None)
|
|
866
|
+
gp = getattr(grob, "gp", None)
|
|
867
|
+
return Unit(_locn_bounds_width(x_unit, renderer, gp), "inches")
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
def _xspline_height_details(grob: Any) -> Unit:
|
|
871
|
+
"""Height of an xspline grob.
|
|
872
|
+
|
|
873
|
+
Port of R ``heightDetails.xspline`` (primitives.R:854).
|
|
874
|
+
"""
|
|
875
|
+
renderer = _get_renderer()
|
|
876
|
+
if renderer is None:
|
|
877
|
+
return Unit(0, "inches")
|
|
878
|
+
y_unit = getattr(grob, "y", None)
|
|
879
|
+
gp = getattr(grob, "gp", None)
|
|
880
|
+
return Unit(_locn_bounds_height(y_unit, renderer, gp), "inches")
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
# -- bezier grob (R: primitives.R:997-1003, expands via splinegrob()) ------
|
|
884
|
+
|
|
885
|
+
def _bezier_width_details(grob: Any) -> Unit:
|
|
886
|
+
"""Width of a bezier grob: bounding box of control points.
|
|
887
|
+
|
|
888
|
+
Port of R ``widthDetails.beziergrob`` (primitives.R:997).
|
|
889
|
+
"""
|
|
890
|
+
renderer = _get_renderer()
|
|
891
|
+
if renderer is None:
|
|
892
|
+
return Unit(0, "inches")
|
|
893
|
+
x_unit = getattr(grob, "x", None)
|
|
894
|
+
gp = getattr(grob, "gp", None)
|
|
895
|
+
return Unit(_locn_bounds_width(x_unit, renderer, gp), "inches")
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
def _bezier_height_details(grob: Any) -> Unit:
|
|
899
|
+
"""Height of a bezier grob.
|
|
900
|
+
|
|
901
|
+
Port of R ``heightDetails.beziergrob`` (primitives.R:1001).
|
|
902
|
+
"""
|
|
903
|
+
renderer = _get_renderer()
|
|
904
|
+
if renderer is None:
|
|
905
|
+
return Unit(0, "inches")
|
|
906
|
+
y_unit = getattr(grob, "y", None)
|
|
907
|
+
gp = getattr(grob, "gp", None)
|
|
908
|
+
return Unit(_locn_bounds_height(y_unit, renderer, gp), "inches")
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
# -- curve grob (R: curve.R:481-495, expands via calcCurveGrob()) ----------
|
|
912
|
+
|
|
913
|
+
def _curve_width_details(grob: Any) -> Unit:
|
|
914
|
+
"""Width of a curve grob.
|
|
915
|
+
|
|
916
|
+
Port of R ``widthDetails.curve`` (curve.R:481). R expands to a
|
|
917
|
+
child grob then delegates. We use the endpoint bounding box.
|
|
918
|
+
"""
|
|
919
|
+
renderer = _get_renderer()
|
|
920
|
+
if renderer is None:
|
|
921
|
+
return Unit(0, "inches")
|
|
922
|
+
from ._units import Unit as _Unit, unit_c
|
|
923
|
+
x1 = getattr(grob, "x1", None)
|
|
924
|
+
x2 = getattr(grob, "x2", None)
|
|
925
|
+
gp = getattr(grob, "gp", None)
|
|
926
|
+
if x1 is not None and x2 is not None and isinstance(x1, _Unit) and isinstance(x2, _Unit):
|
|
927
|
+
combined = unit_c(x1, x2)
|
|
928
|
+
return Unit(_locn_bounds_width(combined, renderer, gp), "inches")
|
|
929
|
+
return Unit(0, "inches")
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
def _curve_height_details(grob: Any) -> Unit:
|
|
933
|
+
"""Height of a curve grob.
|
|
934
|
+
|
|
935
|
+
Port of R ``heightDetails.curve`` (curve.R:489).
|
|
936
|
+
"""
|
|
937
|
+
renderer = _get_renderer()
|
|
938
|
+
if renderer is None:
|
|
939
|
+
return Unit(0, "inches")
|
|
940
|
+
from ._units import Unit as _Unit, unit_c
|
|
941
|
+
y1 = getattr(grob, "y1", None)
|
|
942
|
+
y2 = getattr(grob, "y2", None)
|
|
943
|
+
gp = getattr(grob, "gp", None)
|
|
944
|
+
if y1 is not None and y2 is not None and isinstance(y1, _Unit) and isinstance(y2, _Unit):
|
|
945
|
+
combined = unit_c(y1, y2)
|
|
946
|
+
return Unit(_locn_bounds_height(combined, renderer, gp), "inches")
|
|
947
|
+
return Unit(0, "inches")
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
# ---------------------------------------------------------------------------
|
|
951
|
+
# _grid_class dispatch tables
|
|
952
|
+
# ---------------------------------------------------------------------------
|
|
953
|
+
|
|
954
|
+
_WIDTH_DISPATCH: Dict[str, Any] = {
|
|
955
|
+
"text": _text_width_details,
|
|
956
|
+
"null": _null_width_details,
|
|
957
|
+
"rect": _rect_width_details,
|
|
958
|
+
"roundrect": _roundrect_width_details,
|
|
959
|
+
"lines": _lines_width_details,
|
|
960
|
+
"polyline": _lines_width_details,
|
|
961
|
+
"points": _points_width_details,
|
|
962
|
+
"polygon": _polygon_width_details,
|
|
963
|
+
"segments": _segments_width_details,
|
|
964
|
+
"circle": _circle_width_details,
|
|
965
|
+
"pathgrob": _path_width_details,
|
|
966
|
+
"rastergrob": _raster_width_details,
|
|
967
|
+
"xspline": _xspline_width_details,
|
|
968
|
+
"beziergrob": _bezier_width_details,
|
|
969
|
+
"curve": _curve_width_details,
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
_HEIGHT_DISPATCH: Dict[str, Any] = {
|
|
973
|
+
"text": _text_height_details,
|
|
974
|
+
"null": _null_height_details,
|
|
975
|
+
"rect": _rect_height_details,
|
|
976
|
+
"roundrect": _roundrect_height_details,
|
|
977
|
+
"lines": _lines_height_details,
|
|
978
|
+
"polyline": _lines_height_details,
|
|
979
|
+
"points": _points_height_details,
|
|
980
|
+
"polygon": _polygon_height_details,
|
|
981
|
+
"segments": _segments_height_details,
|
|
982
|
+
"circle": _circle_height_details,
|
|
983
|
+
"pathgrob": _path_height_details,
|
|
984
|
+
"rastergrob": _raster_height_details,
|
|
985
|
+
"xspline": _xspline_height_details,
|
|
986
|
+
"beziergrob": _bezier_height_details,
|
|
987
|
+
"curve": _curve_height_details,
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
_ASCENT_DISPATCH: Dict[str, Any] = {
|
|
991
|
+
"text": _text_ascent_details,
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
_DESCENT_DISPATCH: Dict[str, Any] = {
|
|
995
|
+
"text": _text_descent_details,
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
# ---------------------------------------------------------------------------
|
|
1000
|
+
# Generic detail dispatchers (mirroring R's S3 method dispatch)
|
|
1001
|
+
# ---------------------------------------------------------------------------
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
def width_details(x: Any) -> Unit:
|
|
1005
|
+
"""Return the width of grob *x*.
|
|
1006
|
+
|
|
1007
|
+
Dispatches first by ``_grid_class`` attribute (text, null, rect),
|
|
1008
|
+
then by ``width_details`` method on the object, and finally falls
|
|
1009
|
+
back to ``Unit(1, "null")``.
|
|
1010
|
+
|
|
1011
|
+
Parameters
|
|
1012
|
+
----------
|
|
1013
|
+
x : Grob
|
|
1014
|
+
A graphical object.
|
|
1015
|
+
|
|
1016
|
+
Returns
|
|
1017
|
+
-------
|
|
1018
|
+
Unit
|
|
1019
|
+
The width as a grid unit.
|
|
1020
|
+
"""
|
|
1021
|
+
cls = getattr(x, "_grid_class", None)
|
|
1022
|
+
handler = _WIDTH_DISPATCH.get(cls)
|
|
1023
|
+
if handler is not None:
|
|
1024
|
+
return handler(x)
|
|
1025
|
+
if hasattr(x, "width_details") and callable(x.width_details):
|
|
1026
|
+
result = x.width_details()
|
|
1027
|
+
if result is not None:
|
|
1028
|
+
return result
|
|
1029
|
+
return Unit(1, "null")
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
def height_details(x: Any) -> Unit:
|
|
1033
|
+
"""Return the height of grob *x*.
|
|
1034
|
+
|
|
1035
|
+
Parameters
|
|
1036
|
+
----------
|
|
1037
|
+
x : Grob
|
|
1038
|
+
A graphical object.
|
|
1039
|
+
|
|
1040
|
+
Returns
|
|
1041
|
+
-------
|
|
1042
|
+
Unit
|
|
1043
|
+
The height as a grid unit.
|
|
1044
|
+
"""
|
|
1045
|
+
cls = getattr(x, "_grid_class", None)
|
|
1046
|
+
handler = _HEIGHT_DISPATCH.get(cls)
|
|
1047
|
+
if handler is not None:
|
|
1048
|
+
return handler(x)
|
|
1049
|
+
if hasattr(x, "height_details") and callable(x.height_details):
|
|
1050
|
+
result = x.height_details()
|
|
1051
|
+
if result is not None:
|
|
1052
|
+
return result
|
|
1053
|
+
return Unit(1, "null")
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
def ascent_details(x: Any) -> Unit:
|
|
1057
|
+
"""Return the text ascent of grob *x*.
|
|
1058
|
+
|
|
1059
|
+
Parameters
|
|
1060
|
+
----------
|
|
1061
|
+
x : Grob
|
|
1062
|
+
A graphical object.
|
|
1063
|
+
|
|
1064
|
+
Returns
|
|
1065
|
+
-------
|
|
1066
|
+
Unit
|
|
1067
|
+
The ascent as a grid unit. Falls back to ``height_details`` for
|
|
1068
|
+
grobs that do not define ``ascent_details``.
|
|
1069
|
+
"""
|
|
1070
|
+
cls = getattr(x, "_grid_class", None)
|
|
1071
|
+
handler = _ASCENT_DISPATCH.get(cls)
|
|
1072
|
+
if handler is not None:
|
|
1073
|
+
return handler(x)
|
|
1074
|
+
if hasattr(x, "ascent_details") and callable(x.ascent_details):
|
|
1075
|
+
result = x.ascent_details()
|
|
1076
|
+
if result is not None:
|
|
1077
|
+
return result
|
|
1078
|
+
return height_details(x)
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
def descent_details(x: Any) -> Unit:
|
|
1082
|
+
"""Return the text descent of grob *x*.
|
|
1083
|
+
|
|
1084
|
+
Parameters
|
|
1085
|
+
----------
|
|
1086
|
+
x : Grob
|
|
1087
|
+
A graphical object.
|
|
1088
|
+
|
|
1089
|
+
Returns
|
|
1090
|
+
-------
|
|
1091
|
+
Unit
|
|
1092
|
+
The descent as a grid unit. Default is ``Unit(0, "inches")``.
|
|
1093
|
+
"""
|
|
1094
|
+
cls = getattr(x, "_grid_class", None)
|
|
1095
|
+
handler = _DESCENT_DISPATCH.get(cls)
|
|
1096
|
+
if handler is not None:
|
|
1097
|
+
return handler(x)
|
|
1098
|
+
if hasattr(x, "descent_details") and callable(x.descent_details):
|
|
1099
|
+
result = x.descent_details()
|
|
1100
|
+
if result is not None:
|
|
1101
|
+
return result
|
|
1102
|
+
return Unit(0, "inches")
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
def x_details(x: Any, theta: float = 0) -> Unit:
|
|
1106
|
+
"""Return the x position on the edge of grob *x* at angle *theta*.
|
|
1107
|
+
|
|
1108
|
+
Parameters
|
|
1109
|
+
----------
|
|
1110
|
+
x : Grob
|
|
1111
|
+
A graphical object.
|
|
1112
|
+
theta : float, optional
|
|
1113
|
+
Angle in degrees (default ``0``).
|
|
1114
|
+
|
|
1115
|
+
Returns
|
|
1116
|
+
-------
|
|
1117
|
+
Unit
|
|
1118
|
+
The x position as a grid unit. Default is ``Unit(0.5, "npc")``.
|
|
1119
|
+
"""
|
|
1120
|
+
if hasattr(x, "x_details") and callable(x.x_details):
|
|
1121
|
+
return x.x_details(theta)
|
|
1122
|
+
return Unit(0.5, "npc")
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
def y_details(x: Any, theta: float = 0) -> Unit:
|
|
1126
|
+
"""Return the y position on the edge of grob *x* at angle *theta*.
|
|
1127
|
+
|
|
1128
|
+
Parameters
|
|
1129
|
+
----------
|
|
1130
|
+
x : Grob
|
|
1131
|
+
A graphical object.
|
|
1132
|
+
theta : float, optional
|
|
1133
|
+
Angle in degrees (default ``0``).
|
|
1134
|
+
|
|
1135
|
+
Returns
|
|
1136
|
+
-------
|
|
1137
|
+
Unit
|
|
1138
|
+
The y position as a grid unit. Default is ``Unit(0.5, "npc")``.
|
|
1139
|
+
"""
|
|
1140
|
+
if hasattr(x, "y_details") and callable(x.y_details):
|
|
1141
|
+
return x.y_details(theta)
|
|
1142
|
+
return Unit(0.5, "npc")
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
# ---------------------------------------------------------------------------
|
|
1146
|
+
# grob_* convenience constructors
|
|
1147
|
+
# ---------------------------------------------------------------------------
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
def grob_width(x: Any) -> Unit:
|
|
1151
|
+
"""Create a ``"grobwidth"`` unit referencing *x*.
|
|
1152
|
+
|
|
1153
|
+
Port of R ``grobWidth()`` (unit.R:674-692).
|
|
1154
|
+
Accepts Grob, GList, GPath, or string (auto-wrapped in GPath).
|
|
1155
|
+
|
|
1156
|
+
Parameters
|
|
1157
|
+
----------
|
|
1158
|
+
x : Grob, GList, GPath, or str
|
|
1159
|
+
The graphical object whose width is referenced.
|
|
1160
|
+
|
|
1161
|
+
Returns
|
|
1162
|
+
-------
|
|
1163
|
+
Unit
|
|
1164
|
+
A unit of type ``"grobwidth"`` with *x* stored as auxiliary data.
|
|
1165
|
+
|
|
1166
|
+
Examples
|
|
1167
|
+
--------
|
|
1168
|
+
>>> from grid_py._grob import Grob
|
|
1169
|
+
>>> g = Grob(name="test")
|
|
1170
|
+
>>> u = grob_width(g)
|
|
1171
|
+
>>> u._units[0]
|
|
1172
|
+
'grobwidth'
|
|
1173
|
+
"""
|
|
1174
|
+
from ._grob import Grob, GList
|
|
1175
|
+
from ._path import GPath
|
|
1176
|
+
|
|
1177
|
+
if isinstance(x, GList):
|
|
1178
|
+
# R unit.R:682-684: rep_len(1, length(x)) with data=x
|
|
1179
|
+
return Unit([1.0] * len(x), ["grobwidth"] * len(x),
|
|
1180
|
+
data=[x[i] for i in range(len(x))])
|
|
1181
|
+
if isinstance(x, (Grob, GPath)):
|
|
1182
|
+
return Unit(1, "grobwidth", data=x)
|
|
1183
|
+
# Default: wrap string in GPath (R unit.R:690-692)
|
|
1184
|
+
return Unit(1, "grobwidth", data=GPath(str(x)))
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
def grob_height(x: Any) -> Unit:
|
|
1188
|
+
"""Create a ``"grobheight"`` unit referencing *x*.
|
|
1189
|
+
|
|
1190
|
+
Port of R ``grobHeight()`` (unit.R:695-713).
|
|
1191
|
+
|
|
1192
|
+
Parameters
|
|
1193
|
+
----------
|
|
1194
|
+
x : Grob, GList, GPath, or str
|
|
1195
|
+
The graphical object whose height is referenced.
|
|
1196
|
+
|
|
1197
|
+
Returns
|
|
1198
|
+
-------
|
|
1199
|
+
Unit
|
|
1200
|
+
A unit of type ``"grobheight"`` with *x* stored as auxiliary data.
|
|
1201
|
+
"""
|
|
1202
|
+
from ._grob import Grob, GList
|
|
1203
|
+
from ._path import GPath
|
|
1204
|
+
|
|
1205
|
+
if isinstance(x, GList):
|
|
1206
|
+
return Unit([1.0] * len(x), ["grobheight"] * len(x),
|
|
1207
|
+
data=[x[i] for i in range(len(x))])
|
|
1208
|
+
if isinstance(x, (Grob, GPath)):
|
|
1209
|
+
return Unit(1, "grobheight", data=x)
|
|
1210
|
+
return Unit(1, "grobheight", data=GPath(str(x)))
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
def grob_x(x: Any, theta: Any = 0) -> Unit:
|
|
1214
|
+
"""Create a ``"grobx"`` unit referencing *x* at angle *theta*.
|
|
1215
|
+
|
|
1216
|
+
Port of R ``grobX()`` (unit.R:632-650).
|
|
1217
|
+
|
|
1218
|
+
Parameters
|
|
1219
|
+
----------
|
|
1220
|
+
x : Grob, GList, GPath, or str
|
|
1221
|
+
The graphical object.
|
|
1222
|
+
theta : str or float
|
|
1223
|
+
Angle in degrees or one of ``"east"``, ``"north"``, ``"west"``,
|
|
1224
|
+
``"south"``. Normalised to [0, 360).
|
|
1225
|
+
|
|
1226
|
+
Returns
|
|
1227
|
+
-------
|
|
1228
|
+
Unit
|
|
1229
|
+
A unit of type ``"grobx"``.
|
|
1230
|
+
"""
|
|
1231
|
+
from ._grob import Grob, GList
|
|
1232
|
+
from ._path import GPath
|
|
1233
|
+
from ._units import convert_theta
|
|
1234
|
+
|
|
1235
|
+
t = convert_theta(theta)
|
|
1236
|
+
if isinstance(x, GList):
|
|
1237
|
+
return Unit([t] * len(x), ["grobx"] * len(x),
|
|
1238
|
+
data=[x[i] for i in range(len(x))])
|
|
1239
|
+
if isinstance(x, (Grob, GPath)):
|
|
1240
|
+
return Unit(t, "grobx", data=x)
|
|
1241
|
+
return Unit(t, "grobx", data=GPath(str(x)))
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
def grob_y(x: Any, theta: Any = 0) -> Unit:
|
|
1245
|
+
"""Create a ``"groby"`` unit referencing *x* at angle *theta*.
|
|
1246
|
+
|
|
1247
|
+
Port of R ``grobY()`` (unit.R:653-671).
|
|
1248
|
+
|
|
1249
|
+
Parameters
|
|
1250
|
+
----------
|
|
1251
|
+
x : Grob, GList, GPath, or str
|
|
1252
|
+
The graphical object.
|
|
1253
|
+
theta : str or float
|
|
1254
|
+
Angle in degrees or one of ``"east"``, ``"north"``, ``"west"``,
|
|
1255
|
+
``"south"``. Normalised to [0, 360).
|
|
1256
|
+
|
|
1257
|
+
Returns
|
|
1258
|
+
-------
|
|
1259
|
+
Unit
|
|
1260
|
+
A unit of type ``"groby"``.
|
|
1261
|
+
"""
|
|
1262
|
+
from ._grob import Grob, GList
|
|
1263
|
+
from ._path import GPath
|
|
1264
|
+
from ._units import convert_theta
|
|
1265
|
+
|
|
1266
|
+
t = convert_theta(theta)
|
|
1267
|
+
if isinstance(x, GList):
|
|
1268
|
+
return Unit([t] * len(x), ["groby"] * len(x),
|
|
1269
|
+
data=[x[i] for i in range(len(x))])
|
|
1270
|
+
if isinstance(x, (Grob, GPath)):
|
|
1271
|
+
return Unit(t, "groby", data=x)
|
|
1272
|
+
return Unit(t, "groby", data=GPath(str(x)))
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
def grob_ascent(x: Any) -> Unit:
|
|
1276
|
+
"""Create a ``"grobascent"`` unit referencing *x*.
|
|
1277
|
+
|
|
1278
|
+
Port of R ``grobAscent()`` (unit.R:716+).
|
|
1279
|
+
|
|
1280
|
+
Parameters
|
|
1281
|
+
----------
|
|
1282
|
+
x : Grob, GList, GPath, or str
|
|
1283
|
+
The graphical object whose text ascent is referenced.
|
|
1284
|
+
|
|
1285
|
+
Returns
|
|
1286
|
+
-------
|
|
1287
|
+
Unit
|
|
1288
|
+
A unit of type ``"grobascent"`` with *x* stored as auxiliary data.
|
|
1289
|
+
"""
|
|
1290
|
+
from ._grob import Grob, GList
|
|
1291
|
+
from ._path import GPath
|
|
1292
|
+
|
|
1293
|
+
if isinstance(x, GList):
|
|
1294
|
+
return Unit([1.0] * len(x), ["grobascent"] * len(x),
|
|
1295
|
+
data=[x[i] for i in range(len(x))])
|
|
1296
|
+
if isinstance(x, (Grob, GPath)):
|
|
1297
|
+
return Unit(1, "grobascent", data=x)
|
|
1298
|
+
return Unit(1, "grobascent", data=GPath(str(x)))
|
|
1299
|
+
|
|
1300
|
+
|
|
1301
|
+
def grob_descent(x: Any) -> Unit:
|
|
1302
|
+
"""Create a ``"grobdescent"`` unit referencing *x*.
|
|
1303
|
+
|
|
1304
|
+
Port of R ``grobDescent()`` (unit.R:737+).
|
|
1305
|
+
|
|
1306
|
+
Parameters
|
|
1307
|
+
----------
|
|
1308
|
+
x : Grob, GList, GPath, or str
|
|
1309
|
+
The graphical object whose text descent is referenced.
|
|
1310
|
+
|
|
1311
|
+
Returns
|
|
1312
|
+
-------
|
|
1313
|
+
Unit
|
|
1314
|
+
A unit of type ``"grobdescent"`` with *x* stored as auxiliary data.
|
|
1315
|
+
"""
|
|
1316
|
+
from ._grob import Grob, GList
|
|
1317
|
+
from ._path import GPath
|
|
1318
|
+
|
|
1319
|
+
if isinstance(x, GList):
|
|
1320
|
+
return Unit([1.0] * len(x), ["grobdescent"] * len(x),
|
|
1321
|
+
data=[x[i] for i in range(len(x))])
|
|
1322
|
+
if isinstance(x, (Grob, GPath)):
|
|
1323
|
+
return Unit(1, "grobdescent", data=x)
|
|
1324
|
+
return Unit(1, "grobdescent", data=GPath(str(x)))
|
|
1325
|
+
|
|
1326
|
+
|
|
1327
|
+
# ---------------------------------------------------------------------------
|
|
1328
|
+
# absolute_size
|
|
1329
|
+
# ---------------------------------------------------------------------------
|
|
1330
|
+
|
|
1331
|
+
|
|
1332
|
+
def absolute_size(u: Unit) -> Unit:
|
|
1333
|
+
"""Return absolute components of *u*; replace relative ones with null.
|
|
1334
|
+
|
|
1335
|
+
For units that do not depend on the parent drawing context (e.g.
|
|
1336
|
+
``"inches"``, ``"cm"``, ``"mm"``), the value is returned unchanged.
|
|
1337
|
+
Context-dependent units (e.g. ``"npc"``, ``"native"``) are replaced
|
|
1338
|
+
with ``Unit(1, "null")``. This mirrors R's ``absolute.size()``.
|
|
1339
|
+
|
|
1340
|
+
Parameters
|
|
1341
|
+
----------
|
|
1342
|
+
u : Unit
|
|
1343
|
+
The unit to filter.
|
|
1344
|
+
|
|
1345
|
+
Returns
|
|
1346
|
+
-------
|
|
1347
|
+
Unit
|
|
1348
|
+
A new unit with only absolute components retained.
|
|
1349
|
+
"""
|
|
1350
|
+
from ._units import absolute_size as _absolute_size # avoid shadowing
|
|
1351
|
+
|
|
1352
|
+
return _absolute_size(u)
|