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/_typeset.py ADDED
@@ -0,0 +1,384 @@
1
+ """Glyph/typesetting support for grid_py (port of R's grid ``typeset.R``).
2
+
3
+ This module provides grob constructors for rendering pre-typeset glyph
4
+ information, mirroring R's ``glyphGrob()`` and ``grid.glyph()`` functions.
5
+ A *glyph grob* wraps a ``GlyphInfo`` object together with position,
6
+ justification, and graphical parameters, allowing the grid drawing
7
+ pipeline to render individual glyphs at specified locations.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import warnings
13
+ from typing import Any, Dict, List, Optional, Sequence, Union
14
+
15
+ from ._gpar import Gpar
16
+ from ._grob import Grob, grob_name
17
+ from ._units import Unit, is_unit
18
+
19
+ __all__ = [
20
+ "GlyphJust",
21
+ "glyph_just",
22
+ "GlyphInfo",
23
+ "glyph_grob",
24
+ "grid_glyph",
25
+ ]
26
+
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Glyph justification
30
+ # ---------------------------------------------------------------------------
31
+
32
+
33
+ class GlyphJust:
34
+ """Wrapper for glyph justification values.
35
+
36
+ In R, glyph justification can be a numeric proportion (0 = left/bottom,
37
+ 1 = right/top) or a named anchor string (e.g. ``"left"``, ``"centre"``).
38
+ This class tags the value so that downstream code can distinguish between
39
+ numeric proportional justification and named-anchor justification.
40
+
41
+ Parameters
42
+ ----------
43
+ value : float, int, or str
44
+ The justification value. Strings such as ``"left"``, ``"centre"``,
45
+ ``"center"``, ``"right"``, ``"top"``, ``"bottom"`` are accepted as
46
+ named anchors. Numeric values are interpreted as proportional
47
+ offsets (0.0 to 1.0).
48
+ name : str or None, optional
49
+ An optional width/height name to associate with a numeric
50
+ justification (mirrors R's ``names(hjust)``).
51
+
52
+ Attributes
53
+ ----------
54
+ value : float or str
55
+ The justification value.
56
+ name : str or None
57
+ Optional name qualifier.
58
+ """
59
+
60
+ __slots__ = ("value", "name")
61
+
62
+ def __init__(
63
+ self,
64
+ value: Union[float, int, str],
65
+ name: Optional[str] = None,
66
+ ) -> None:
67
+ self.value: Union[float, str] = value
68
+ self.name: Optional[str] = name
69
+
70
+ def __repr__(self) -> str:
71
+ if self.name is not None:
72
+ return f"GlyphJust(value={self.value!r}, name={self.name!r})"
73
+ return f"GlyphJust(value={self.value!r})"
74
+
75
+ @property
76
+ def is_numeric(self) -> bool:
77
+ """Return ``True`` if this justification is a numeric proportion."""
78
+ return isinstance(self.value, (int, float))
79
+
80
+
81
+ def glyph_just(value: Union[float, int, str, "GlyphJust"]) -> GlyphJust:
82
+ """Normalise a justification value into a :class:`GlyphJust`.
83
+
84
+ Parameters
85
+ ----------
86
+ value : float, int, str, or GlyphJust
87
+ If already a ``GlyphJust``, return as-is. Strings are mapped to
88
+ canonical anchor names; numeric values are wrapped directly.
89
+
90
+ Returns
91
+ -------
92
+ GlyphJust
93
+ A validated glyph-justification object.
94
+
95
+ Raises
96
+ ------
97
+ TypeError
98
+ If *value* is not a recognised type.
99
+ """
100
+ if isinstance(value, GlyphJust):
101
+ return value
102
+
103
+ if isinstance(value, str):
104
+ canonical = _JUST_ALIASES.get(value.lower(), value.lower())
105
+ return GlyphJust(canonical)
106
+
107
+ if isinstance(value, (int, float)):
108
+ return GlyphJust(float(value))
109
+
110
+ raise TypeError(
111
+ f"'hjust'/'vjust' must be numeric or a string, got {type(value).__name__}"
112
+ )
113
+
114
+
115
+ _JUST_ALIASES: Dict[str, str] = {
116
+ "center": "centre",
117
+ "left": "left",
118
+ "right": "right",
119
+ "top": "top",
120
+ "bottom": "bottom",
121
+ "centre": "centre",
122
+ }
123
+
124
+
125
+ # ---------------------------------------------------------------------------
126
+ # GlyphInfo (minimal container)
127
+ # ---------------------------------------------------------------------------
128
+
129
+
130
+ class GlyphInfo:
131
+ """Container for pre-typeset glyph information.
132
+
133
+ This is a lightweight Python analogue of R's ``RGlyphInfo`` objects
134
+ produced by typesetting engines (e.g. ``systemfonts::shape_string``).
135
+ It carries per-glyph positions, font information, and overall bounding
136
+ metrics.
137
+
138
+ Parameters
139
+ ----------
140
+ glyphs : dict
141
+ Per-glyph data. Expected keys include ``"x"``, ``"y"``, and
142
+ optionally ``"font"``, ``"size"``, ``"rot"``, ``"colour"``.
143
+ width : dict or float
144
+ Overall width(s) of the typeset block in big points. May be a
145
+ dict mapping names to widths.
146
+ height : dict or float
147
+ Overall height(s) of the typeset block in big points.
148
+ h_anchor : dict or None, optional
149
+ Named horizontal anchors (e.g. ``{"left": 0, "right": 100}``).
150
+ v_anchor : dict or None, optional
151
+ Named vertical anchors (e.g. ``{"bottom": 0, "top": 80}``).
152
+
153
+ Attributes
154
+ ----------
155
+ glyphs : dict
156
+ width : dict or float
157
+ height : dict or float
158
+ h_anchor : dict
159
+ v_anchor : dict
160
+ """
161
+
162
+ def __init__(
163
+ self,
164
+ glyphs: Dict[str, Any],
165
+ width: Union[Dict[str, float], float],
166
+ height: Union[Dict[str, float], float],
167
+ h_anchor: Optional[Dict[str, float]] = None,
168
+ v_anchor: Optional[Dict[str, float]] = None,
169
+ ) -> None:
170
+ self.glyphs = glyphs
171
+ self.width = width
172
+ self.height = height
173
+ self.h_anchor = h_anchor if h_anchor is not None else {"left": 0.0}
174
+ self.v_anchor = v_anchor if v_anchor is not None else {"bottom": 0.0}
175
+
176
+ def __repr__(self) -> str:
177
+ n = len(self.glyphs.get("x", []))
178
+ return f"GlyphInfo(n_glyphs={n})"
179
+
180
+
181
+ # ---------------------------------------------------------------------------
182
+ # Validation helper
183
+ # ---------------------------------------------------------------------------
184
+
185
+
186
+ def _valid_glyph_grob(x: Grob) -> Grob:
187
+ """Validate a glyph grob (mirrors R's ``validDetails.glyphgrob``).
188
+
189
+ Parameters
190
+ ----------
191
+ x : Grob
192
+ The grob to validate.
193
+
194
+ Returns
195
+ -------
196
+ Grob
197
+ The validated grob (unchanged if valid).
198
+
199
+ Raises
200
+ ------
201
+ TypeError
202
+ If *glyphInfo* is not a :class:`GlyphInfo`, or if *x*/*y* are not
203
+ units, or if justification values are invalid.
204
+ ValueError
205
+ If *x*/*y* have length < 1, or justification values have length != 1.
206
+ """
207
+ glyph_info = getattr(x, "glyphInfo", None)
208
+ if not isinstance(glyph_info, GlyphInfo):
209
+ raise TypeError("Invalid glyph info; expected a GlyphInfo instance")
210
+
211
+ grob_x = getattr(x, "x", None)
212
+ grob_y = getattr(x, "y", None)
213
+ if not is_unit(grob_x) or not is_unit(grob_y):
214
+ raise TypeError("'x' and 'y' must be Unit objects")
215
+
216
+ if len(grob_x) < 1 or len(grob_y) < 1:
217
+ raise ValueError("'x' and 'y' must have length > 0")
218
+
219
+ hjust = getattr(x, "hjust", None)
220
+ vjust = getattr(x, "vjust", None)
221
+ if not isinstance(hjust, GlyphJust) or not isinstance(vjust, GlyphJust):
222
+ raise TypeError("'hjust' and 'vjust' must be GlyphJust values")
223
+
224
+ return x
225
+
226
+
227
+ # ---------------------------------------------------------------------------
228
+ # Glyph grob constructor
229
+ # ---------------------------------------------------------------------------
230
+
231
+
232
+ def glyph_grob(
233
+ glyphInfo: GlyphInfo,
234
+ x: Union[float, Unit] = 0.5,
235
+ y: Union[float, Unit] = 0.5,
236
+ default_units: str = "npc",
237
+ hjust: Union[float, str, GlyphJust] = "centre",
238
+ vjust: Union[float, str, GlyphJust] = "centre",
239
+ gp: Optional[Gpar] = None,
240
+ vp: Optional[Any] = None,
241
+ name: Optional[str] = None,
242
+ ) -> Grob:
243
+ """Create a glyph grob for rendering pre-typeset glyph information.
244
+
245
+ This mirrors R's ``glyphGrob()`` function. The resulting :class:`Grob`
246
+ has ``_grid_class="glyphgrob"`` and carries the glyph data, position,
247
+ and justification as attributes.
248
+
249
+ Parameters
250
+ ----------
251
+ glyphInfo : GlyphInfo
252
+ The pre-typeset glyph information to render.
253
+ x : float or Unit, optional
254
+ Horizontal position of the glyph block (default ``0.5``).
255
+ y : float or Unit, optional
256
+ Vertical position of the glyph block (default ``0.5``).
257
+ default_units : str, optional
258
+ Unit type for *x* and *y* when they are plain numbers
259
+ (default ``"npc"``).
260
+ hjust : float, str, or GlyphJust, optional
261
+ Horizontal justification (default ``"centre"``).
262
+ vjust : float, str, or GlyphJust, optional
263
+ Vertical justification (default ``"centre"``).
264
+ gp : Gpar or None, optional
265
+ Graphical parameters.
266
+ vp : object or None, optional
267
+ Viewport.
268
+ name : str or None, optional
269
+ Grob name. Auto-generated when ``None``.
270
+
271
+ Returns
272
+ -------
273
+ Grob
274
+ A grob with ``_grid_class="glyphgrob"`` ready for drawing.
275
+
276
+ Raises
277
+ ------
278
+ TypeError
279
+ If *glyphInfo* is not a :class:`GlyphInfo`.
280
+
281
+ Examples
282
+ --------
283
+ >>> info = GlyphInfo({"x": [0], "y": [0]}, width=10.0, height=12.0)
284
+ >>> g = glyph_grob(info)
285
+ >>> g._grid_class
286
+ 'glyphgrob'
287
+ """
288
+ # Coerce x/y to Unit if needed
289
+ if not is_unit(x):
290
+ x = Unit(x, default_units)
291
+ if not is_unit(y):
292
+ y = Unit(y, default_units)
293
+
294
+ # Normalise justification
295
+ hjust_val = glyph_just(hjust)
296
+ vjust_val = glyph_just(vjust)
297
+
298
+ grob_obj = Grob(
299
+ name=name,
300
+ gp=gp if gp is not None else Gpar(),
301
+ vp=vp,
302
+ _grid_class="glyphgrob",
303
+ glyphInfo=glyphInfo,
304
+ x=x,
305
+ y=y,
306
+ hjust=hjust_val,
307
+ vjust=vjust_val,
308
+ )
309
+
310
+ # Validate
311
+ _valid_glyph_grob(grob_obj)
312
+
313
+ return grob_obj
314
+
315
+
316
+ # ---------------------------------------------------------------------------
317
+ # grid.glyph equivalent
318
+ # ---------------------------------------------------------------------------
319
+
320
+
321
+ def grid_glyph(
322
+ glyphInfo: GlyphInfo,
323
+ x: Union[float, Unit] = 0.5,
324
+ y: Union[float, Unit] = 0.5,
325
+ default_units: str = "npc",
326
+ hjust: Union[float, str, GlyphJust] = "centre",
327
+ vjust: Union[float, str, GlyphJust] = "centre",
328
+ gp: Optional[Gpar] = None,
329
+ vp: Optional[Any] = None,
330
+ name: Optional[str] = None,
331
+ draw: bool = True,
332
+ ) -> Grob:
333
+ """Create and optionally draw a glyph grob.
334
+
335
+ This is the high-level interface mirroring R's ``grid.glyph()``. It
336
+ constructs a glyph grob via :func:`glyph_grob` and, when *draw* is
337
+ ``True``, immediately renders it by calling ``grid_draw``.
338
+
339
+ Parameters
340
+ ----------
341
+ glyphInfo : GlyphInfo
342
+ The pre-typeset glyph information.
343
+ x : float or Unit, optional
344
+ Horizontal position (default ``0.5``).
345
+ y : float or Unit, optional
346
+ Vertical position (default ``0.5``).
347
+ default_units : str, optional
348
+ Unit type when *x*/*y* are plain numbers (default ``"npc"``).
349
+ hjust : float, str, or GlyphJust, optional
350
+ Horizontal justification (default ``"centre"``).
351
+ vjust : float, str, or GlyphJust, optional
352
+ Vertical justification (default ``"centre"``).
353
+ gp : Gpar or None, optional
354
+ Graphical parameters.
355
+ vp : object or None, optional
356
+ Viewport.
357
+ name : str or None, optional
358
+ Grob name.
359
+ draw : bool, optional
360
+ If ``True`` (default), the grob is drawn immediately.
361
+
362
+ Returns
363
+ -------
364
+ Grob
365
+ The glyph grob (returned invisibly in R; here simply returned).
366
+ """
367
+ g = glyph_grob(
368
+ glyphInfo=glyphInfo,
369
+ x=x,
370
+ y=y,
371
+ default_units=default_units,
372
+ hjust=hjust,
373
+ vjust=vjust,
374
+ gp=gp,
375
+ vp=vp,
376
+ name=name,
377
+ )
378
+
379
+ if draw:
380
+ from ._draw import grid_draw
381
+
382
+ grid_draw(g)
383
+
384
+ return g