mpl-richtext 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ """
2
+ mpl-richtext: Rich text rendering for Matplotlib
3
+ """
4
+
5
+ from .core import richtext
6
+ from .version import __version__
7
+
8
+ __all__ = ['richtext', '__version__']
9
+
10
+ __author__ = 'Rabin Katel'
11
+ __email__ = 'kattelrabinraja13@gmail.com'
12
+ __license__ = 'MIT'
mpl_richtext/core.py ADDED
@@ -0,0 +1,460 @@
1
+ import matplotlib.pyplot as plt
2
+ from matplotlib.axes import Axes
3
+ from matplotlib.text import Text
4
+ from matplotlib.lines import Line2D
5
+ from typing import List, Optional, Tuple, Union, Dict, Any
6
+
7
+ def richtext(
8
+ x: float,
9
+ y: float,
10
+ strings: List[str],
11
+ colors: Optional[Union[str, List[Any], Dict[Any, Any]]] = None,
12
+ ax: Optional[Axes] = None,
13
+ **kwargs
14
+ ) -> List[Text]:
15
+ """
16
+ Display text with different colors and properties for each string, supporting word wrapping and alignment.
17
+
18
+ Parameters
19
+ ----------
20
+ x, y : float
21
+ Starting position of the text block.
22
+ strings : List[str]
23
+ List of text strings to display.
24
+ colors : Union[str, List[str], Dict[int, str]]
25
+ Color for the text. Can be:
26
+ - A single string: Applied to all strings.
27
+ - A list of colors: Corresponding to each string (dynamic extension supported).
28
+ - A dictionary: Mapping indices to colors (e.g., {1: 'red'}). Unspecified indices use default/black.
29
+ ax : matplotlib.axes.Axes, optional
30
+ The axes to draw on. If None, uses the current axes.
31
+ **kwargs : dict
32
+ Additional arguments passed to `ax.text`.
33
+
34
+ Global arguments (applied to the whole block):
35
+ - box_width (float): Maximum width of the text block for wrapping. If None, no wrapping occurs.
36
+ - linespacing (float): Line spacing multiplier (default: 1.5).
37
+ - ha (str): Horizontal alignment ('left', 'center', 'right').
38
+ - va (str): Vertical alignment ('top', 'center', 'bottom').
39
+ - zorder (int): Z-order for the text.
40
+ - transform: Transform for the text.
41
+
42
+ Per-segment arguments:
43
+ - Any other text property (e.g., fontsize, fontweight, fontproperties) can be:
44
+ - A single value: Applied to all strings.
45
+ - A list of values: Corresponding to each string (dynamic extension supported).
46
+ - A dictionary: Mapping indices to values (e.g., {1: 20}).
47
+ - underline (bool): If True, draws a line below the text.
48
+
49
+ Returns
50
+ -------
51
+ List[matplotlib.text.Text]
52
+ A list of the created Text objects.
53
+
54
+ Raises
55
+ ------
56
+ ValueError
57
+ If inputs are invalid.
58
+ """
59
+ if ax is None:
60
+ ax = plt.gca()
61
+
62
+ # Extract global special kwargs
63
+ box_width: Optional[float] = kwargs.pop('box_width', None)
64
+ linespacing: float = kwargs.pop('linespacing', 1.5)
65
+ if 'spacing' in kwargs:
66
+ linespacing = kwargs.pop('spacing')
67
+
68
+ ha = kwargs.pop('ha', kwargs.pop('horizontalalignment', 'left'))
69
+ va = kwargs.pop('va', kwargs.pop('verticalalignment', 'center'))
70
+ transform = kwargs.pop('transform', ax.transData)
71
+ zorder = kwargs.pop('zorder', 1)
72
+
73
+ # Normalize properties for each string segment
74
+ segment_properties = _normalize_properties(strings, colors, **kwargs)
75
+
76
+ # Get renderer for measuring text
77
+ fig = ax.get_figure()
78
+ if fig == None:
79
+ raise ValueError("The axes must be associated with a figure.")
80
+
81
+ try:
82
+ renderer = fig.canvas.get_renderer()
83
+ except Exception:
84
+ fig.canvas.draw()
85
+ renderer = fig.canvas.get_renderer()
86
+
87
+ # Logic separation: Wrapping vs Non-Wrapping
88
+ if box_width is not None:
89
+ # 1. Tokenize into words with properties
90
+ words = _tokenize_strings(strings, segment_properties)
91
+ # 2. Build lines with wrapping
92
+ lines = _build_lines_wrapped(words, ax, renderer, box_width)
93
+ else:
94
+ # 1. Treat strings as segments
95
+ # 2. Build a single line
96
+ lines = [_build_line_seamless(strings, segment_properties, ax, renderer)]
97
+
98
+ # 3. Draw lines
99
+ text_objects = _draw_lines(
100
+ lines, x, y, ax, renderer,
101
+ linespacing=linespacing, ha=ha, va=va,
102
+ transform=transform, zorder=zorder
103
+ )
104
+
105
+ return text_objects
106
+
107
+
108
+ def _normalize_properties(
109
+ strings: List[str],
110
+ colors: Union[str, List[Any], Dict[Any, Any]],
111
+ **kwargs
112
+ ) -> List[Dict[str, Any]]:
113
+ """
114
+ Normalize colors and kwargs into a list of property dictionaries, one for each string.
115
+ Supports:
116
+ - Scalar values (applied globally).
117
+ - Lists of values (dynamic extension).
118
+ - Dictionaries with int/tuple keys (targeted overrides).
119
+ - Lists of pairs [(indices, value)] (targeted overrides).
120
+ - Plural arguments (e.g., fontsizes) overriding singular ones (e.g., fontsize).
121
+ """
122
+ n = len(strings)
123
+ props_list = []
124
+
125
+ # Helper to extend list to length n
126
+ def extend_list(lst: List[Any], target_len: int) -> List[Any]:
127
+ if not lst:
128
+ return [None] * target_len
129
+ if len(lst) >= target_len:
130
+ return lst[:target_len]
131
+ last_val = lst[-1]
132
+ extension = [last_val] * (target_len - len(lst))
133
+ return lst + extension
134
+
135
+ # Helper to parse mapping from dict or list-of-pairs
136
+ def parse_mapping(val: Any) -> Optional[Dict[int, Any]]:
137
+ flat_d = {}
138
+
139
+ if isinstance(val, dict):
140
+ for k, v in val.items():
141
+ if isinstance(k, tuple):
142
+ for idx in k:
143
+ flat_d[idx] = v
144
+ else:
145
+ flat_d[k] = v
146
+ return flat_d
147
+
148
+ if isinstance(val, list):
149
+ if not val:
150
+ return None
151
+ first = val[0]
152
+ if isinstance(first, (list, tuple)) and len(first) == 2:
153
+ k, v = first
154
+ if isinstance(k, int) or (isinstance(k, (list, tuple)) and all(isinstance(x, int) for x in k)):
155
+ for item in val:
156
+ if not (isinstance(item, (list, tuple)) and len(item) == 2):
157
+ return None
158
+ k, v = item
159
+ if isinstance(k, (list, tuple)):
160
+ for idx in k:
161
+ flat_d[idx] = v
162
+ else:
163
+ flat_d[k] = v
164
+ return flat_d
165
+ return None
166
+ return None
167
+
168
+ # Handle colors
169
+ color_mapping = parse_mapping(colors)
170
+ color_list = [None] * n
171
+
172
+ if color_mapping is not None:
173
+ pass
174
+ elif isinstance(colors, str):
175
+ color_list = [colors] * n
176
+ elif isinstance(colors, list):
177
+ color_list = extend_list(colors, n)
178
+ else:
179
+ if colors is not None:
180
+ raise ValueError("colors must be a string, a list, or a mapping.")
181
+
182
+ # Handle other kwargs
183
+ list_kwargs = {}
184
+ mapping_kwargs = {}
185
+ scalar_kwargs = {}
186
+
187
+ # Map plural keys to singular keys
188
+ plural_map = {
189
+ 'fontsizes': 'fontsize',
190
+ 'fontweights': 'fontweight',
191
+ 'fontfamilies': 'fontfamily',
192
+ 'fontstyles': 'fontstyle',
193
+ 'alphas': 'alpha',
194
+ 'backgroundcolors': 'backgroundcolor',
195
+ 'colors': 'color', # Special case if passed in kwargs
196
+ 'underlines': 'underline'
197
+ }
198
+
199
+ # Separate kwargs into types
200
+ for k, v in kwargs.items():
201
+ # Check for plural override first
202
+ if k in plural_map:
203
+ singular_k = plural_map[k]
204
+ mapping = parse_mapping(v)
205
+ if mapping is not None:
206
+ if singular_k not in mapping_kwargs:
207
+ mapping_kwargs[singular_k] = {}
208
+ mapping_kwargs[singular_k].update(mapping)
209
+ elif isinstance(v, list):
210
+ # If plural is a list, treat as list_kwargs for the singular key
211
+ list_kwargs[singular_k] = extend_list(v, n)
212
+ continue
213
+
214
+ mapping = parse_mapping(v)
215
+ if mapping is not None:
216
+ mapping_kwargs[k] = mapping
217
+ elif isinstance(v, list):
218
+ list_kwargs[k] = extend_list(v, n)
219
+ else:
220
+ scalar_kwargs[k] = v
221
+
222
+ for i in range(n):
223
+ # 1. Start with scalar (global) properties
224
+ props = scalar_kwargs.copy()
225
+
226
+ # 2. Apply list-based properties (if any)
227
+ for k, v_list in list_kwargs.items():
228
+ props[k] = v_list[i]
229
+
230
+ # 3. Apply color (specific overrides global)
231
+ if color_list[i] is not None:
232
+ props['color'] = color_list[i]
233
+
234
+ if color_mapping and i in color_mapping:
235
+ props['color'] = color_mapping[i]
236
+
237
+ # 4. Apply mapping-based properties (specific overrides global & list)
238
+ for k, v_map in mapping_kwargs.items():
239
+ if i in v_map:
240
+ props[k] = v_map[i]
241
+
242
+ props_list.append(props)
243
+
244
+ return props_list
245
+
246
+
247
+ def _tokenize_strings(
248
+ strings: List[str],
249
+ properties: List[Dict[str, Any]]
250
+ ) -> List[Tuple[str, Dict[str, Any]]]:
251
+ """
252
+ Split strings into words while preserving spaces and associating properties.
253
+ Used ONLY when wrapping is enabled.
254
+ """
255
+ words: List[Tuple[str, Dict[str, Any]]] = []
256
+ for string, props in zip(strings, properties):
257
+ parts = string.split(' ')
258
+ for i, part in enumerate(parts):
259
+ if i < len(parts) - 1:
260
+ words.append((part + ' ', props))
261
+ else:
262
+ if part:
263
+ words.append((part, props))
264
+ elif not part and i > 0:
265
+ pass
266
+ return words
267
+
268
+
269
+ def _get_text_width(text: str, ax: Axes, renderer, **text_kwargs) -> float:
270
+ """Measure the width of a text string."""
271
+ # Remove custom properties that ax.text doesn't understand
272
+ kwargs = text_kwargs.copy()
273
+ kwargs.pop('underline', None)
274
+
275
+ t = ax.text(0, 0, text, **kwargs)
276
+ bbox = t.get_window_extent(renderer=renderer)
277
+ bbox_data = bbox.transformed(ax.transData.inverted())
278
+ w = bbox_data.width
279
+ t.remove()
280
+ return w
281
+
282
+
283
+ def _get_text_height(text: str, ax: Axes, renderer, **text_kwargs) -> float:
284
+ """Measure the height of a text string."""
285
+ # Remove custom properties that ax.text doesn't understand
286
+ kwargs = text_kwargs.copy()
287
+ kwargs.pop('underline', None)
288
+
289
+ # Use a representative character for height if text is empty or space
290
+ # But we need the height of THIS specific font configuration.
291
+ measure_text = text if text.strip() else "Hg"
292
+ t = ax.text(0, 0, measure_text, **kwargs)
293
+ bbox = t.get_window_extent(renderer=renderer)
294
+ bbox_data = bbox.transformed(ax.transData.inverted())
295
+ h = bbox_data.height
296
+ t.remove()
297
+ return h
298
+
299
+
300
+ def _build_lines_wrapped(
301
+ words: List[Tuple[str, Dict[str, Any]]],
302
+ ax: Axes,
303
+ renderer,
304
+ box_width: float
305
+ ) -> List[List[Tuple[str, Dict[str, Any], float]]]:
306
+ """
307
+ Group words into lines based on box_width.
308
+ """
309
+ lines: List[List[Tuple[str, Dict[str, Any], float]]] = []
310
+ current_line: List[Tuple[str, Dict[str, Any], float]] = []
311
+ current_line_width = 0.0
312
+
313
+ for word, props in words:
314
+ w = _get_text_width(word, ax, renderer, **props)
315
+
316
+ if current_line_width + w > box_width and current_line:
317
+ # Wrap to new line
318
+ lines.append(current_line)
319
+ current_line = [(word, props, w)]
320
+ current_line_width = w
321
+ else:
322
+ current_line.append((word, props, w))
323
+ current_line_width += w
324
+
325
+ if current_line:
326
+ lines.append(current_line)
327
+
328
+ return lines
329
+
330
+
331
+ def _build_line_seamless(
332
+ strings: List[str],
333
+ properties: List[Dict[str, Any]],
334
+ ax: Axes,
335
+ renderer
336
+ ) -> List[Tuple[str, Dict[str, Any], float]]:
337
+ """
338
+ Build a single line from strings without splitting by spaces.
339
+ """
340
+ line: List[Tuple[str, Dict[str, Any], float]] = []
341
+ for string, props in zip(strings, properties):
342
+ w = _get_text_width(string, ax, renderer, **props)
343
+ line.append((string, props, w))
344
+ return line
345
+
346
+
347
+ def _draw_lines(
348
+ lines: List[List[Tuple[str, Dict[str, Any], float]]],
349
+ x: float,
350
+ y: float,
351
+ ax: Axes,
352
+ renderer,
353
+ linespacing: float,
354
+ ha: str,
355
+ va: str,
356
+ transform,
357
+ zorder: int
358
+ ) -> List[Text]:
359
+ """
360
+ Draw the lines of text onto the axes.
361
+ """
362
+ text_objects: List[Text] = []
363
+
364
+ # Calculate height for each line
365
+ line_heights = []
366
+ for line in lines:
367
+ # Find max height in this line
368
+ max_h = 0.0
369
+ for word, props, _ in line:
370
+ h = _get_text_height(word, ax, renderer, **props)
371
+ if h > max_h:
372
+ max_h = h
373
+ line_heights.append(max_h * linespacing)
374
+
375
+ total_block_height = sum(line_heights)
376
+
377
+ # Calculate top Y position based on vertical alignment
378
+ if va == 'center':
379
+ top_y = y + (total_block_height / 2)
380
+ elif va == 'top':
381
+ top_y = y
382
+ elif va == 'bottom':
383
+ top_y = y + total_block_height
384
+ else:
385
+ top_y = y + (total_block_height / 2)
386
+
387
+ current_y = top_y
388
+
389
+ for i, line in enumerate(lines):
390
+ line_height = line_heights[i]
391
+
392
+ # Position line center
393
+ line_center_y = current_y - (line_height / 2)
394
+
395
+ # Calculate line width for horizontal alignment
396
+ line_width = sum(item[2] for item in line)
397
+
398
+ if ha == 'center':
399
+ line_start_x = x - (line_width / 2)
400
+ elif ha == 'right':
401
+ line_start_x = x - line_width
402
+ else: # left
403
+ line_start_x = x
404
+
405
+ current_x = line_start_x
406
+
407
+ for word, props, w in line:
408
+ text_kwargs = props.copy()
409
+
410
+ # Extract underline property
411
+ underline = text_kwargs.pop('underline', False)
412
+
413
+ text_kwargs.update({
414
+ 'va': 'center',
415
+ 'ha': 'left',
416
+ 'transform': transform,
417
+ 'zorder': zorder
418
+ })
419
+
420
+ t = ax.text(current_x, line_center_y, word, **text_kwargs)
421
+ text_objects.append(t)
422
+
423
+ # Draw underline if requested
424
+ if underline:
425
+ # Get the bounding box of the text we just drew
426
+ # We can use the width w we already calculated
427
+ # But for Y position, we want it slightly below the text
428
+
429
+ # A simple approximation is to draw it at the bottom of the line band?
430
+ # Or relative to the text baseline?
431
+ # Since we used va='center', the text is centered at line_center_y.
432
+ # The height is roughly line_height / linespacing.
433
+ # Let's put the underline at line_center_y - (h/2) - padding?
434
+
435
+ # Let's use the bbox of the text object to find the bottom
436
+ bbox = t.get_window_extent(renderer=renderer)
437
+ bbox_data = bbox.transformed(ax.transData.inverted())
438
+
439
+ # y0 is the bottom of the text bbox
440
+ y_bottom = bbox_data.y0
441
+
442
+ # Draw line from current_x to current_x + w
443
+ # Use the same color as the text
444
+ color = t.get_color()
445
+
446
+ line = Line2D(
447
+ [current_x, current_x + w],
448
+ [y_bottom, y_bottom],
449
+ color=color,
450
+ linewidth=1, # Maybe configurable?
451
+ transform=transform,
452
+ zorder=zorder
453
+ )
454
+ ax.add_line(line)
455
+
456
+ current_x += w
457
+
458
+ current_y -= line_height
459
+
460
+ return text_objects
@@ -0,0 +1 @@
1
+ __version__ = '0.1.0'
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: mpl-richtext
3
+ Version: 0.1.0
4
+ Summary: Rich text rendering for Matplotlib with multi-color and multi-style support
5
+ Home-page: https://github.com/ra8in/mpl-richtext
6
+ Author: Rabin Katel
7
+ Author-email: Rabin Katel <kattelrabinraja13@gmail.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/ra8in/mpl-richtext
10
+ Project-URL: Documentation, https://github.com/ra8in/mpl-richtext#readme
11
+ Project-URL: Repository, https://github.com/ra8in/mpl-richtext
12
+ Project-URL: Bug Tracker, https://github.com/ra8in/mpl-richtext/issues
13
+ Keywords: matplotlib,text,color,rich-text,multi-color,visualization,plotting
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Topic :: Scientific/Engineering :: Visualization
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Operating System :: OS Independent
26
+ Requires-Python: >=3.8
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: matplotlib>=3.5.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.0; extra == "dev"
32
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
33
+ Requires-Dist: black>=22.0; extra == "dev"
34
+ Requires-Dist: flake8>=5.0; extra == "dev"
35
+ Requires-Dist: mypy>=0.990; extra == "dev"
36
+ Dynamic: author
37
+ Dynamic: home-page
38
+ Dynamic: license-file
39
+ Dynamic: requires-python
40
+
41
+ # mpl-richtext
42
+
43
+ **Rich text rendering for Matplotlib** - Create beautiful multi-color, multi-style text in a single line.
44
+
45
+ [![PyPI version](https://badge.fury.io/py/mpl-richtext.svg)](https://pypi.org/project/mpl-richtext/)
46
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
47
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
48
+
49
+ ## Why mpl-richtext?
50
+
51
+ Standard Matplotlib only supports single-color text. To create multi-colored text, you need to manually position each piece and calculate spacing - tedious and error-prone!
52
+
53
+ **mpl-richtext** solves this by letting you specify colors and styles for each text segment in one simple function call.
54
+
55
+ ![mpl-richtext Showcase](examples/mpl_richtext_examples.png)
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ pip install mpl-richtext
61
+ ```
62
+
63
+ ## Quick Start
64
+
65
+ ```python
66
+ import matplotlib.pyplot as plt
67
+ from mpl_richtext import richtext
68
+
69
+ fig, ax = plt.subplots()
70
+
71
+ # Create multi-colored text in one line!
72
+ richtext(0.5, 0.5,
73
+ strings=["hello", ", ", "world"],
74
+ colors=["red", "blue", "green"],
75
+ ax=ax, fontsize=20, transform=ax.transAxes)
76
+
77
+ plt.show()
78
+ ```
79
+
80
+
81
+
82
+ ## Features
83
+
84
+
85
+
86
+ ✨ **Multi-color text** - Different colors for each word or character
87
+ 🎨 **Multi-style text** - Mix font sizes, weights, families, and styles (italic/oblique)
88
+ 📦 **Flexible input** - Lists, dicts, or tuples for colors and properties
89
+ 📏 **Auto word-wrapping** - Specify `box_width` for automatic text wrapping
90
+ 🎯 **Full alignment** - Left, center, right horizontal and vertical alignment
91
+ ✨ **Decorations** - Support for **underlines** and **background colors**
92
+ 🔄 **Transformations** - Support for text **rotation**
93
+ 👻 **Transparency** - Support for **alpha** values per segment
94
+ ⚡ **Easy to use** - Simple API, works with any Matplotlib axes
95
+
96
+
97
+
98
+ ## API Reference
99
+
100
+ ### `richtext(x, y, strings, colors=None, ax=None, **kwargs)`
101
+
102
+ **Parameters:**
103
+
104
+ - **x, y** : `float`
105
+ Starting position of the text
106
+
107
+ - **strings** : `list of str`
108
+ List of text segments, e.g., `["hello", ", ", "world"]`
109
+
110
+ - **colors** : `str`, `list`, or `dict`, optional
111
+ Colors for each segment. Can be:
112
+ - Single string: `"red"` (applies to all)
113
+ - List: `["red", "blue", "green"]` (one per segment)
114
+ - Dict: `{0: "red", 2: "green"}` (specific indices)
115
+ - Tuple keys: `{(0, 2): "red"}` (multiple indices same color)
116
+
117
+ - **ax** : `matplotlib.axes.Axes`, optional
118
+ Axes to draw on. If `None`, uses current axes.
119
+
120
+ - **kwargs** : Additional properties
121
+ - **Global:** `box_width`, `linespacing`, `ha`, `va`, `transform`, `zorder`
122
+ - **Per-segment:** `fontsize`/`fontsizes`, `fontweight`/`fontweights`, `fontfamily`/`fontfamilies`, etc.
123
+ - Any property can be:
124
+ - Single value (applies to all)
125
+ - List (one per segment, auto-extends)
126
+ - Dict (specific indices)
127
+
128
+ **Returns:**
129
+ - `list of Text` - List of created matplotlib Text objects
130
+
131
+ ## Contributing
132
+
133
+ Contributions are welcome! Please feel free to submit a Pull Request.
134
+
135
+ 1. Fork the repository
136
+ 2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
137
+ 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
138
+ 4. Push to the branch (`git push origin feature/AmazingFeature`)
139
+ 5. Open a Pull Request
140
+
141
+ ## License
142
+
143
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
144
+
145
+ ## Links
146
+
147
+ - **PyPI:** https://pypi.org/project/mpl-richtext/
148
+ - **GitHub:** https://github.com/ra8in/mpl-richtext
149
+ - **Issues:** https://github.com/ra8in/mpl-richtext/issues
150
+
151
+ ---
152
+
153
+ Made with ❤️ for the Matplotlib community
@@ -0,0 +1,8 @@
1
+ mpl_richtext/__init__.py,sha256=430UvdXDNTa9xCoQ48OT_lzHrFCW_xCuVaTr1apEVq4,246
2
+ mpl_richtext/core.py,sha256=OZ1NCJQOHpIsdcM6c-7NKXdZxeTKBGoAPWkPkhpTfr4,15504
3
+ mpl_richtext/version.py,sha256=L6zbQIZKsAP-Knhm6fBcQFPoVdIDuejxze60qX23jiw,21
4
+ mpl_richtext-0.1.0.dist-info/licenses/LICENSE,sha256=3ldFhzXg_DuFYRbdYJaN661E5PAzUpVjWxFCxVm7kG8,1067
5
+ mpl_richtext-0.1.0.dist-info/METADATA,sha256=4NdhCgy6coUWZo6JOex4dZQgABBY17ReCKnwwtWkLC8,5311
6
+ mpl_richtext-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ mpl_richtext-0.1.0.dist-info/top_level.txt,sha256=3K_jSX3xxD2Dhaqm4jtejlO0HI_5tl7F7AMq4lGk6xw,13
8
+ mpl_richtext-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Rabin Katel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ mpl_richtext