svg-ultralight 0.39.0__tar.gz → 0.40.0__tar.gz
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.
Potentially problematic release.
This version of svg-ultralight might be problematic. Click here for more details.
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/.pre-commit-config.yaml +3 -1
- {svg_ultralight-0.39.0/src/svg_ultralight.egg-info → svg_ultralight-0.40.0}/PKG-INFO +4 -2
- svg_ultralight-0.40.0/dev-requirements.txt +110 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/pyproject.toml +11 -4
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/__init__.py +7 -1
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/animate.py +3 -3
- svg_ultralight-0.40.0/src/svg_ultralight/bounding_boxes/padded_text_initializers.py +186 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/bounding_boxes/supports_bounds.py +1 -1
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/bounding_boxes/type_bound_collection.py +1 -1
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/bounding_boxes/type_bound_element.py +1 -1
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/bounding_boxes/type_bounding_box.py +14 -12
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/bounding_boxes/type_padded_text.py +81 -4
- svg_ultralight-0.40.0/src/svg_ultralight/font_tools/__init__.py +5 -0
- svg_ultralight-0.40.0/src/svg_ultralight/font_tools/comp_results.py +284 -0
- svg_ultralight-0.40.0/src/svg_ultralight/font_tools/font_css.py +82 -0
- svg_ultralight-0.40.0/src/svg_ultralight/font_tools/font_info.py +567 -0
- svg_ultralight-0.40.0/src/svg_ultralight/font_tools/globs.py +7 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/image_ops.py +4 -2
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/inkscape.py +29 -18
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/main.py +5 -3
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/query.py +8 -37
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/string_conversion.py +69 -1
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/transformations.py +10 -3
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0/src/svg_ultralight.egg-info}/PKG-INFO +4 -2
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight.egg-info/SOURCES.txt +9 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight.egg-info/requires.txt +3 -1
- svg_ultralight-0.40.0/tests/conftest.py +32 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/tests/test_bounding.py +3 -2
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/tests/test_inkscape.py +5 -12
- svg_ultralight-0.40.0/tests/test_padded_text_initializers.py +77 -0
- svg_ultralight-0.40.0/tests/test_padding.py +56 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/tests/test_queries.py +15 -8
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/tests/test_string_conversion.py +40 -3
- svg_ultralight-0.39.0/tests/conftest.py +0 -16
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/.gitignore +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/README.md +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/setup.cfg +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/bounding_boxes/__init__.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/bounding_boxes/bound_helpers.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/constructors/__init__.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/constructors/new_element.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/layout.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/metadata.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/nsmap.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/py.typed +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/root_elements.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/strings/__init__.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/strings/svg_strings.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight/unit_conversion.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight.egg-info/dependency_links.txt +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/src/svg_ultralight.egg-info/top_level.txt +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/tests/__init__.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/tests/resources/arrow.svg +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/tests/test_layout.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/tests/test_matrices.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/tests/test_metadata.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/tests/test_new_element.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/tests/test_root_elements.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/tests/test_svg_ultralight.py +0 -0
- {svg_ultralight-0.39.0 → svg_ultralight-0.40.0}/tox.ini +0 -0
|
@@ -92,6 +92,8 @@ repos:
|
|
|
92
92
|
# S301 don't use pickle
|
|
93
93
|
# B028 wants explicit stacklevel on warn
|
|
94
94
|
# BLE001 Use of `except Exception:` detected
|
|
95
|
+
# ANN401 Any type disallowed
|
|
96
|
+
# FLY002 Consider f-string instead of string join
|
|
95
97
|
rev: 'v0.11.11'
|
|
96
98
|
hooks:
|
|
97
99
|
- id: ruff
|
|
@@ -100,7 +102,7 @@ repos:
|
|
|
100
102
|
args:
|
|
101
103
|
- --target-version=py39
|
|
102
104
|
- --select=ALL
|
|
103
|
-
- --ignore=ANN201,ANN202,B905,COM812,D203,D213,I001,ISC003,N802,N806,PGH003,PLR0913,PTH108,S101,S603,PLR2004,S320,S301,B028,BLE001
|
|
105
|
+
- --ignore=ANN201,ANN202,B905,COM812,D203,D213,I001,ISC003,N802,N806,PGH003,PLR0913,PTH108,S101,S603,PLR2004,S320,S301,B028,BLE001,ANN401,FLY002
|
|
104
106
|
- --fix
|
|
105
107
|
- --fixable=RUF022
|
|
106
108
|
- id: ruff
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: svg-ultralight
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.40.0
|
|
4
4
|
Summary: a sensible way to create svg files with Python
|
|
5
5
|
Author-email: Shay Hill <shay_public@hotmail.com>
|
|
6
6
|
License: MIT
|
|
7
7
|
Requires-Python: >=3.9
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: cssutils
|
|
10
|
+
Requires-Dist: fonttools
|
|
9
11
|
Requires-Dist: lxml
|
|
10
|
-
Requires-Dist: pillow
|
|
11
12
|
Requires-Dist: paragraphs
|
|
13
|
+
Requires-Dist: pillow
|
|
12
14
|
Requires-Dist: types-lxml
|
|
13
15
|
Requires-Dist: typing-extensions
|
|
14
16
|
Provides-Extra: dev
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.11
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile --extra=dev --output-file=dev-requirements.txt --strip-extras pyproject.toml
|
|
6
|
+
#
|
|
7
|
+
argcomplete==3.6.2
|
|
8
|
+
# via commitizen
|
|
9
|
+
beautifulsoup4==4.13.4
|
|
10
|
+
# via types-lxml
|
|
11
|
+
cachetools==6.0.0
|
|
12
|
+
# via tox
|
|
13
|
+
cfgv==3.4.0
|
|
14
|
+
# via pre-commit
|
|
15
|
+
chardet==5.2.0
|
|
16
|
+
# via tox
|
|
17
|
+
charset-normalizer==3.4.2
|
|
18
|
+
# via commitizen
|
|
19
|
+
colorama==0.4.6
|
|
20
|
+
# via
|
|
21
|
+
# commitizen
|
|
22
|
+
# pytest
|
|
23
|
+
# tox
|
|
24
|
+
commitizen==4.8.2
|
|
25
|
+
# via svg-ultralight (pyproject.toml)
|
|
26
|
+
cssselect==1.3.0
|
|
27
|
+
# via types-lxml
|
|
28
|
+
cssutils==2.11.1
|
|
29
|
+
# via svg-ultralight (pyproject.toml)
|
|
30
|
+
decli==0.6.3
|
|
31
|
+
# via commitizen
|
|
32
|
+
distlib==0.3.9
|
|
33
|
+
# via virtualenv
|
|
34
|
+
filelock==3.18.0
|
|
35
|
+
# via
|
|
36
|
+
# tox
|
|
37
|
+
# virtualenv
|
|
38
|
+
fonttools==4.58.1
|
|
39
|
+
# via svg-ultralight (pyproject.toml)
|
|
40
|
+
identify==2.6.12
|
|
41
|
+
# via pre-commit
|
|
42
|
+
iniconfig==2.1.0
|
|
43
|
+
# via pytest
|
|
44
|
+
jinja2==3.1.6
|
|
45
|
+
# via commitizen
|
|
46
|
+
lxml==5.4.0
|
|
47
|
+
# via svg-ultralight (pyproject.toml)
|
|
48
|
+
markupsafe==3.0.2
|
|
49
|
+
# via jinja2
|
|
50
|
+
more-itertools==10.7.0
|
|
51
|
+
# via cssutils
|
|
52
|
+
nodeenv==1.9.1
|
|
53
|
+
# via pre-commit
|
|
54
|
+
packaging==25.0
|
|
55
|
+
# via
|
|
56
|
+
# commitizen
|
|
57
|
+
# pyproject-api
|
|
58
|
+
# pytest
|
|
59
|
+
# tox
|
|
60
|
+
paragraphs==1.0.1
|
|
61
|
+
# via svg-ultralight (pyproject.toml)
|
|
62
|
+
pillow==11.2.1
|
|
63
|
+
# via svg-ultralight (pyproject.toml)
|
|
64
|
+
platformdirs==4.3.8
|
|
65
|
+
# via
|
|
66
|
+
# tox
|
|
67
|
+
# virtualenv
|
|
68
|
+
pluggy==1.6.0
|
|
69
|
+
# via
|
|
70
|
+
# pytest
|
|
71
|
+
# tox
|
|
72
|
+
pre-commit==4.2.0
|
|
73
|
+
# via svg-ultralight (pyproject.toml)
|
|
74
|
+
prompt-toolkit==3.0.51
|
|
75
|
+
# via questionary
|
|
76
|
+
pygments==2.19.1
|
|
77
|
+
# via pytest
|
|
78
|
+
pyproject-api==1.9.1
|
|
79
|
+
# via tox
|
|
80
|
+
pytest==8.4.0
|
|
81
|
+
# via svg-ultralight (pyproject.toml)
|
|
82
|
+
pyyaml==6.0.2
|
|
83
|
+
# via
|
|
84
|
+
# commitizen
|
|
85
|
+
# pre-commit
|
|
86
|
+
questionary==2.1.0
|
|
87
|
+
# via commitizen
|
|
88
|
+
soupsieve==2.7
|
|
89
|
+
# via beautifulsoup4
|
|
90
|
+
termcolor==2.5.0
|
|
91
|
+
# via commitizen
|
|
92
|
+
tomlkit==0.13.2
|
|
93
|
+
# via commitizen
|
|
94
|
+
tox==4.26.0
|
|
95
|
+
# via svg-ultralight (pyproject.toml)
|
|
96
|
+
types-html5lib==1.1.11.20250516
|
|
97
|
+
# via types-lxml
|
|
98
|
+
types-lxml==2025.3.30
|
|
99
|
+
# via svg-ultralight (pyproject.toml)
|
|
100
|
+
typing-extensions==4.14.0
|
|
101
|
+
# via
|
|
102
|
+
# beautifulsoup4
|
|
103
|
+
# svg-ultralight (pyproject.toml)
|
|
104
|
+
# types-lxml
|
|
105
|
+
virtualenv==20.31.2
|
|
106
|
+
# via
|
|
107
|
+
# pre-commit
|
|
108
|
+
# tox
|
|
109
|
+
wcwidth==0.2.13
|
|
110
|
+
# via prompt-toolkit
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "svg-ultralight"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.40.0"
|
|
4
4
|
description = "a sensible way to create svg files with Python"
|
|
5
5
|
authors = [{ name = "Shay Hill", email = "shay_public@hotmail.com" }]
|
|
6
6
|
license = { text = "MIT" }
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
requires-python = ">=3.9"
|
|
9
|
-
dependencies = [
|
|
10
|
-
|
|
9
|
+
dependencies = [
|
|
10
|
+
"cssutils",
|
|
11
|
+
"fonttools",
|
|
12
|
+
"lxml",
|
|
13
|
+
"paragraphs",
|
|
14
|
+
"pillow",
|
|
15
|
+
"types-lxml",
|
|
16
|
+
"typing-extensions",
|
|
17
|
+
]
|
|
11
18
|
[project.optional-dependencies]
|
|
12
19
|
dev = ["pytest", "commitizen", "pre-commit", "tox"]
|
|
13
20
|
images = ["pillow"]
|
|
@@ -42,7 +49,7 @@ convention = "pep257"
|
|
|
42
49
|
|
|
43
50
|
[tool.commitizen]
|
|
44
51
|
name = "cz_conventional_commits"
|
|
45
|
-
version = "0.
|
|
52
|
+
version = "0.40.0"
|
|
46
53
|
tag_format = "$version"
|
|
47
54
|
version_files = ["pyproject.toml:^version"]
|
|
48
55
|
annotated_tag = true
|
|
@@ -14,6 +14,11 @@ from svg_ultralight.bounding_boxes.bound_helpers import (
|
|
|
14
14
|
pad_bbox,
|
|
15
15
|
parse_bound_element,
|
|
16
16
|
)
|
|
17
|
+
from svg_ultralight.bounding_boxes.padded_text_initializers import (
|
|
18
|
+
pad_text,
|
|
19
|
+
pad_text_ft,
|
|
20
|
+
pad_text_mix,
|
|
21
|
+
)
|
|
17
22
|
from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
|
|
18
23
|
from svg_ultralight.bounding_boxes.type_bound_collection import BoundCollection
|
|
19
24
|
from svg_ultralight.bounding_boxes.type_bound_element import BoundElement
|
|
@@ -39,7 +44,6 @@ from svg_ultralight.query import (
|
|
|
39
44
|
clear_svg_ultralight_cache,
|
|
40
45
|
get_bounding_box,
|
|
41
46
|
get_bounding_boxes,
|
|
42
|
-
pad_text,
|
|
43
47
|
)
|
|
44
48
|
from svg_ultralight.root_elements import new_svg_root_around_bounds
|
|
45
49
|
from svg_ultralight.string_conversion import (
|
|
@@ -87,6 +91,8 @@ __all__ = [
|
|
|
87
91
|
"new_svg_root_around_bounds",
|
|
88
92
|
"pad_bbox",
|
|
89
93
|
"pad_text",
|
|
94
|
+
"pad_text_ft",
|
|
95
|
+
"pad_text_mix",
|
|
90
96
|
"parse_bound_element",
|
|
91
97
|
"transform_element",
|
|
92
98
|
"update_element",
|
|
@@ -16,13 +16,13 @@ except ModuleNotFoundError as exc:
|
|
|
16
16
|
from typing import TYPE_CHECKING
|
|
17
17
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
|
+
import os
|
|
19
20
|
from collections.abc import Iterable
|
|
20
|
-
from pathlib import Path
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def write_gif(
|
|
24
|
-
gif: str |
|
|
25
|
-
pngs: Iterable[str] | Iterable[
|
|
24
|
+
gif: str | os.PathLike[str],
|
|
25
|
+
pngs: Iterable[str] | Iterable[os.PathLike[str]] | Iterable[str | os.PathLike[str]],
|
|
26
26
|
duration: float = 100,
|
|
27
27
|
loop: int = 0,
|
|
28
28
|
) -> None:
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Functions that create PaddedText instances.
|
|
2
|
+
|
|
3
|
+
Three variants:
|
|
4
|
+
|
|
5
|
+
- `pad_text`: uses Inkscape to measure text bounds
|
|
6
|
+
|
|
7
|
+
- `pad_text_ft`: uses fontTools to measure text bounds (faster, and you get line_gap)
|
|
8
|
+
|
|
9
|
+
- `pad_text_mix`: uses Inkscape and fontTools to give true ascent, descent, and
|
|
10
|
+
line_gap while correcting some of the layout differences between fontTools and
|
|
11
|
+
Inkscape.
|
|
12
|
+
|
|
13
|
+
:author: Shay Hill
|
|
14
|
+
:created: 2025-06-09
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from copy import deepcopy
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import TYPE_CHECKING
|
|
22
|
+
|
|
23
|
+
from svg_ultralight.bounding_boxes.type_padded_text import PaddedText
|
|
24
|
+
from svg_ultralight.constructors import new_element, update_element
|
|
25
|
+
from svg_ultralight.font_tools.font_info import (
|
|
26
|
+
get_padded_text_info,
|
|
27
|
+
get_svg_font_attributes,
|
|
28
|
+
)
|
|
29
|
+
from svg_ultralight.font_tools.globs import DEFAULT_FONT_SIZE
|
|
30
|
+
from svg_ultralight.query import get_bounding_boxes
|
|
31
|
+
from svg_ultralight.string_conversion import (
|
|
32
|
+
encode_to_css_class_name,
|
|
33
|
+
format_attr_dict,
|
|
34
|
+
format_number,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
import os
|
|
39
|
+
|
|
40
|
+
from lxml.etree import (
|
|
41
|
+
_Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
DEFAULT_Y_BOUNDS_REFERENCE = "{[|gjpqyf"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def pad_text(
|
|
48
|
+
inkscape: str | os.PathLike[str],
|
|
49
|
+
text_elem: EtreeElement,
|
|
50
|
+
y_bounds_reference: str | None = None,
|
|
51
|
+
*,
|
|
52
|
+
font: str | os.PathLike[str] | None = None,
|
|
53
|
+
) -> PaddedText:
|
|
54
|
+
r"""Create a PaddedText instance from a text element.
|
|
55
|
+
|
|
56
|
+
:param inkscape: path to an inkscape executable on your local file system
|
|
57
|
+
IMPORTANT: path cannot end with ``.exe``.
|
|
58
|
+
Use something like ``"C:\\Program Files\\Inkscape\\inkscape"``
|
|
59
|
+
:param text_elem: an etree element with a text tag
|
|
60
|
+
:param y_bounds_reference: an optional string to use to determine the ascent and
|
|
61
|
+
capline of the font. The default is a good choice, which approaches or even
|
|
62
|
+
meets the ascent of descent of most fonts without using utf-8 characters. You
|
|
63
|
+
might want to use a letter like "M" or even "x" if you are using an all-caps
|
|
64
|
+
string and want to center between the capline and baseline or if you'd like
|
|
65
|
+
to center between the baseline and x-line.
|
|
66
|
+
:param font: optionally add a path to a font file to use for the text element.
|
|
67
|
+
This is going to conflict with any font-family, font-style, or other
|
|
68
|
+
font-related attributes *except* font-size. You likely want to use
|
|
69
|
+
`font_tools.new_padded_text` if you're going to pass a font path, but you can
|
|
70
|
+
use it here to compare results between `pad_text` and `new_padded_text`.
|
|
71
|
+
:return: a PaddedText instance
|
|
72
|
+
"""
|
|
73
|
+
if y_bounds_reference is None:
|
|
74
|
+
y_bounds_reference = DEFAULT_Y_BOUNDS_REFERENCE
|
|
75
|
+
if font is not None:
|
|
76
|
+
_ = update_element(text_elem, **get_svg_font_attributes(font))
|
|
77
|
+
if "font-size" not in text_elem.attrib:
|
|
78
|
+
text_elem.attrib["font-size"] = format_number(DEFAULT_FONT_SIZE)
|
|
79
|
+
rmargin_ref = deepcopy(text_elem)
|
|
80
|
+
capline_ref = deepcopy(text_elem)
|
|
81
|
+
_ = rmargin_ref.attrib.pop("id", None)
|
|
82
|
+
_ = capline_ref.attrib.pop("id", None)
|
|
83
|
+
rmargin_ref.attrib["text-anchor"] = "end"
|
|
84
|
+
capline_ref.text = y_bounds_reference
|
|
85
|
+
|
|
86
|
+
bboxes = get_bounding_boxes(inkscape, text_elem, rmargin_ref, capline_ref)
|
|
87
|
+
bbox, rmargin_bbox, capline_bbox = bboxes
|
|
88
|
+
|
|
89
|
+
tpad = bbox.y - capline_bbox.y
|
|
90
|
+
rpad = -rmargin_bbox.x2
|
|
91
|
+
bpad = capline_bbox.y2 - bbox.y2
|
|
92
|
+
lpad = bbox.x
|
|
93
|
+
return PaddedText(text_elem, bbox, tpad, rpad, bpad, lpad)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def pad_text_ft(
|
|
97
|
+
font: str | os.PathLike[str],
|
|
98
|
+
text: str,
|
|
99
|
+
font_size: float = DEFAULT_FONT_SIZE,
|
|
100
|
+
ascent: float | None = None,
|
|
101
|
+
descent: float | None = None,
|
|
102
|
+
*,
|
|
103
|
+
y_bounds_reference: str | None = None,
|
|
104
|
+
**attributes: str | float,
|
|
105
|
+
) -> PaddedText:
|
|
106
|
+
"""Create a new PaddedText instance using fontTools.
|
|
107
|
+
|
|
108
|
+
:param font: path to a font file.
|
|
109
|
+
:param text: the text of the text element.
|
|
110
|
+
:param font_size: the font size to use.
|
|
111
|
+
:param ascent: the ascent of the font. If not provided, it will be calculated
|
|
112
|
+
from the font file.
|
|
113
|
+
:param descent: the descent of the font. If not provided, it will be calculated
|
|
114
|
+
from the font file.
|
|
115
|
+
:param y_bounds_reference: optional character or string to use as a reference
|
|
116
|
+
for the ascent and descent. If provided, the ascent and descent will be the y
|
|
117
|
+
extents of the capline reference. This argument is provided to mimic the
|
|
118
|
+
behavior of the query module's `pad_text` function. `pad_text` does no
|
|
119
|
+
inspect font files and relies on Inkscape to measure reference characters.
|
|
120
|
+
:param attributes: additional attributes to set on the text element. There is a
|
|
121
|
+
chance these will cause the font element to exceed the BoundingBox of the
|
|
122
|
+
PaddedText instance.
|
|
123
|
+
:return: a PaddedText instance with a line_gap defined.
|
|
124
|
+
"""
|
|
125
|
+
attributes_ = format_attr_dict(**attributes)
|
|
126
|
+
attributes_["font-size"] = attributes_.get("font-size", format_number(font_size))
|
|
127
|
+
attributes_["class"] = encode_to_css_class_name(Path(font).name)
|
|
128
|
+
|
|
129
|
+
elem = new_element("text", text=text, **attributes_)
|
|
130
|
+
info = get_padded_text_info(
|
|
131
|
+
font, text, font_size, ascent, descent, y_bounds_reference=y_bounds_reference
|
|
132
|
+
)
|
|
133
|
+
return PaddedText(elem, info.bbox, *info.padding, info.line_gap)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def pad_text_mix(
|
|
137
|
+
inkscape: str | os.PathLike[str],
|
|
138
|
+
font: str | os.PathLike[str],
|
|
139
|
+
text: str,
|
|
140
|
+
font_size: float = DEFAULT_FONT_SIZE,
|
|
141
|
+
ascent: float | None = None,
|
|
142
|
+
descent: float | None = None,
|
|
143
|
+
*,
|
|
144
|
+
y_bounds_reference: str | None = None,
|
|
145
|
+
**attributes: str | float,
|
|
146
|
+
) -> PaddedText:
|
|
147
|
+
"""Use Inkscape text bounds and fill missing with fontTools.
|
|
148
|
+
|
|
149
|
+
:param font: path to a font file.
|
|
150
|
+
:param text: the text of the text element.
|
|
151
|
+
:param font_size: the font size to use.
|
|
152
|
+
:param ascent: the ascent of the font. If not provided, it will be calculated
|
|
153
|
+
from the font file.
|
|
154
|
+
:param descent: the descent of the font. If not provided, it will be calculated
|
|
155
|
+
from the font file.
|
|
156
|
+
:param y_bounds_reference: optional character or string to use as a reference
|
|
157
|
+
for the ascent and descent. If provided, the ascent and descent will be the y
|
|
158
|
+
extents of the capline reference. This argument is provided to mimic the
|
|
159
|
+
behavior of the query module's `pad_text` function. `pad_text` does no
|
|
160
|
+
inspect font files and relies on Inkscape to measure reference characters.
|
|
161
|
+
:param attributes: additional attributes to set on the text element. There is a
|
|
162
|
+
chance these will cause the font element to exceed the BoundingBox of the
|
|
163
|
+
PaddedText instance.
|
|
164
|
+
:return: a PaddedText instance with a line_gap defined.
|
|
165
|
+
"""
|
|
166
|
+
elem = new_element("text", text=text, **attributes)
|
|
167
|
+
padded_inkscape = pad_text(inkscape, elem, y_bounds_reference, font=font)
|
|
168
|
+
padded_fonttools = pad_text_ft(
|
|
169
|
+
font,
|
|
170
|
+
text,
|
|
171
|
+
font_size,
|
|
172
|
+
ascent,
|
|
173
|
+
descent,
|
|
174
|
+
y_bounds_reference=y_bounds_reference,
|
|
175
|
+
**attributes,
|
|
176
|
+
)
|
|
177
|
+
bbox = padded_inkscape.unpadded_bbox
|
|
178
|
+
rpad = padded_inkscape.rpad
|
|
179
|
+
lpad = padded_inkscape.lpad
|
|
180
|
+
if y_bounds_reference is None:
|
|
181
|
+
tpad = padded_fonttools.tpad
|
|
182
|
+
bpad = padded_fonttools.bpad
|
|
183
|
+
else:
|
|
184
|
+
tpad = padded_inkscape.tpad
|
|
185
|
+
bpad = padded_inkscape.bpad
|
|
186
|
+
return PaddedText(elem, bbox, tpad, rpad, bpad, lpad, padded_fonttools.line_gap)
|
|
@@ -41,6 +41,18 @@ class HasBoundingBox(SupportsBounds):
|
|
|
41
41
|
y2 = y + self.bbox.base_height
|
|
42
42
|
return (x, y), (x2, y), (x2, y2), (x, y2)
|
|
43
43
|
|
|
44
|
+
def values(self) -> tuple[float, float, float, float]:
|
|
45
|
+
"""Get the values of the bounding box.
|
|
46
|
+
|
|
47
|
+
:return: x, y, width, height of the bounding box
|
|
48
|
+
"""
|
|
49
|
+
return (
|
|
50
|
+
self.bbox.x,
|
|
51
|
+
self.bbox.y,
|
|
52
|
+
self.bbox.width,
|
|
53
|
+
self.bbox.height,
|
|
54
|
+
)
|
|
55
|
+
|
|
44
56
|
def _get_transformed_corners(
|
|
45
57
|
self,
|
|
46
58
|
) -> tuple[
|
|
@@ -59,21 +71,11 @@ class HasBoundingBox(SupportsBounds):
|
|
|
59
71
|
)
|
|
60
72
|
return c0, c1, c2, c3
|
|
61
73
|
|
|
62
|
-
def _scale_scale_by_uniform_scalar(self, scalar: float) -> None:
|
|
63
|
-
"""Scale the bounding box uniformly by a factor.
|
|
64
|
-
|
|
65
|
-
:param scale: scale factor
|
|
66
|
-
Unlike self.scale, this does not set the scale, but scales the scale. So if
|
|
67
|
-
the current scale is (2, 6), and you call this with a scalar of 2, the new
|
|
68
|
-
scale will be (4, 12).
|
|
69
|
-
"""
|
|
70
|
-
self.transform(scale=(scalar, scalar))
|
|
71
|
-
|
|
72
74
|
def transform(
|
|
73
75
|
self,
|
|
74
76
|
transformation: _Matrix | None = None,
|
|
75
77
|
*,
|
|
76
|
-
scale: tuple[float, float] | None = None,
|
|
78
|
+
scale: tuple[float, float] | float | None = None,
|
|
77
79
|
dx: float | None = None,
|
|
78
80
|
dy: float | None = None,
|
|
79
81
|
):
|
|
@@ -241,7 +243,7 @@ class HasBoundingBox(SupportsBounds):
|
|
|
241
243
|
"""
|
|
242
244
|
current_x = self.x
|
|
243
245
|
current_y = self.y
|
|
244
|
-
self.
|
|
246
|
+
self.transform(scale=value / self.width)
|
|
245
247
|
self.x = current_x
|
|
246
248
|
self.y = current_y
|
|
247
249
|
|
|
@@ -66,6 +66,8 @@ from __future__ import annotations
|
|
|
66
66
|
|
|
67
67
|
from typing import TYPE_CHECKING
|
|
68
68
|
|
|
69
|
+
from paragraphs import par
|
|
70
|
+
|
|
69
71
|
from svg_ultralight.bounding_boxes.type_bound_element import BoundElement
|
|
70
72
|
from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
71
73
|
from svg_ultralight.transformations import new_transformation_matrix, transform_element
|
|
@@ -77,6 +79,14 @@ if TYPE_CHECKING:
|
|
|
77
79
|
|
|
78
80
|
_Matrix = tuple[float, float, float, float, float, float]
|
|
79
81
|
|
|
82
|
+
_no_line_gap_msg = par(
|
|
83
|
+
"""No line_gap defined. Line gap is an inherent font attribute defined within a
|
|
84
|
+
font file. If this PaddedText instance was created with `pad_text` from reference
|
|
85
|
+
elements, a line_gap was not defined. Reading line_gap from the font file
|
|
86
|
+
requires creating a PaddedText instance with `pad_text_ft` or `pad_text_mixed`.
|
|
87
|
+
You can set an arbitrary line_gap after init with `instance.line_gap = value`."""
|
|
88
|
+
)
|
|
89
|
+
|
|
80
90
|
|
|
81
91
|
class PaddedText(BoundElement):
|
|
82
92
|
"""A line of text with a bounding box and padding."""
|
|
@@ -89,6 +99,7 @@ class PaddedText(BoundElement):
|
|
|
89
99
|
rpad: float,
|
|
90
100
|
bpad: float,
|
|
91
101
|
lpad: float,
|
|
102
|
+
line_gap: float | None = None,
|
|
92
103
|
) -> None:
|
|
93
104
|
"""Initialize a PaddedText instance.
|
|
94
105
|
|
|
@@ -105,6 +116,7 @@ class PaddedText(BoundElement):
|
|
|
105
116
|
self.rpad = rpad
|
|
106
117
|
self.base_bpad = bpad
|
|
107
118
|
self.lpad = lpad
|
|
119
|
+
self._line_gap = line_gap
|
|
108
120
|
|
|
109
121
|
@property
|
|
110
122
|
def bbox(self) -> BoundingBox:
|
|
@@ -122,7 +134,6 @@ class PaddedText(BoundElement):
|
|
|
122
134
|
self.y,
|
|
123
135
|
self.width,
|
|
124
136
|
self.height,
|
|
125
|
-
self.unpadded_bbox.transformation,
|
|
126
137
|
)
|
|
127
138
|
|
|
128
139
|
@bbox.setter
|
|
@@ -139,7 +150,7 @@ class PaddedText(BoundElement):
|
|
|
139
150
|
self,
|
|
140
151
|
transformation: _Matrix | None = None,
|
|
141
152
|
*,
|
|
142
|
-
scale: tuple[float, float] | None = None,
|
|
153
|
+
scale: tuple[float, float] | float | None = None,
|
|
143
154
|
dx: float | None = None,
|
|
144
155
|
dy: float | None = None,
|
|
145
156
|
):
|
|
@@ -154,6 +165,32 @@ class PaddedText(BoundElement):
|
|
|
154
165
|
self.unpadded_bbox.transform(tmat)
|
|
155
166
|
_ = transform_element(self.elem, tmat)
|
|
156
167
|
|
|
168
|
+
@property
|
|
169
|
+
def line_gap(self) -> float:
|
|
170
|
+
"""The line gap between this line of text and the next.
|
|
171
|
+
|
|
172
|
+
:return: The line gap between this line of text and the next.
|
|
173
|
+
"""
|
|
174
|
+
if self._line_gap is None:
|
|
175
|
+
raise AttributeError(_no_line_gap_msg)
|
|
176
|
+
return self._line_gap
|
|
177
|
+
|
|
178
|
+
@line_gap.setter
|
|
179
|
+
def line_gap(self, value: float) -> None:
|
|
180
|
+
"""Set the line gap between this line of text and the next.
|
|
181
|
+
|
|
182
|
+
:param value: The new line gap.
|
|
183
|
+
"""
|
|
184
|
+
self._line_gap = value
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def leading(self) -> float:
|
|
188
|
+
"""The leading of this line of text.
|
|
189
|
+
|
|
190
|
+
:return: The line gap plus the height of this line of text.
|
|
191
|
+
"""
|
|
192
|
+
return self.height + self.line_gap
|
|
193
|
+
|
|
157
194
|
@property
|
|
158
195
|
def tpad(self) -> float:
|
|
159
196
|
"""The top padding of this line of text.
|
|
@@ -208,7 +245,12 @@ class PaddedText(BoundElement):
|
|
|
208
245
|
*and* y2) when scaling.
|
|
209
246
|
"""
|
|
210
247
|
y2 = self.y2
|
|
211
|
-
|
|
248
|
+
|
|
249
|
+
no_margins_old = self.unpadded_bbox.width
|
|
250
|
+
no_margins_new = value - self.lpad - self.rpad
|
|
251
|
+
scale = no_margins_new / no_margins_old
|
|
252
|
+
self.transform(scale=(scale, scale))
|
|
253
|
+
|
|
212
254
|
self.y2 = y2
|
|
213
255
|
|
|
214
256
|
@property
|
|
@@ -226,7 +268,10 @@ class PaddedText(BoundElement):
|
|
|
226
268
|
:param height: The new height of this line of text.
|
|
227
269
|
:effects: the text_element bounding box is scaled to height - tpad - bpad.
|
|
228
270
|
"""
|
|
229
|
-
|
|
271
|
+
y2 = self.y2
|
|
272
|
+
scale = value / self.height
|
|
273
|
+
self.transform(scale=(scale, scale))
|
|
274
|
+
self.y2 = y2
|
|
230
275
|
|
|
231
276
|
@property
|
|
232
277
|
def x(self) -> float:
|
|
@@ -244,6 +289,22 @@ class PaddedText(BoundElement):
|
|
|
244
289
|
"""
|
|
245
290
|
self.transform(dx=value + self.lpad - self.unpadded_bbox.x)
|
|
246
291
|
|
|
292
|
+
@property
|
|
293
|
+
def cx(self) -> float:
|
|
294
|
+
"""The horizontal center of this line of text.
|
|
295
|
+
|
|
296
|
+
:return: The horizontal center of this line of text.
|
|
297
|
+
"""
|
|
298
|
+
return self.x + self.width / 2
|
|
299
|
+
|
|
300
|
+
@cx.setter
|
|
301
|
+
def cx(self, value: float) -> None:
|
|
302
|
+
"""Set the horizontal center of this line of text.
|
|
303
|
+
|
|
304
|
+
:param value: The horizontal center of this line of text.
|
|
305
|
+
"""
|
|
306
|
+
self.x += value - self.cx
|
|
307
|
+
|
|
247
308
|
@property
|
|
248
309
|
def x2(self) -> float:
|
|
249
310
|
"""The right margin of this line of text.
|
|
@@ -276,6 +337,22 @@ class PaddedText(BoundElement):
|
|
|
276
337
|
"""
|
|
277
338
|
self.transform(dy=value + self.tpad - self.unpadded_bbox.y)
|
|
278
339
|
|
|
340
|
+
@property
|
|
341
|
+
def cy(self) -> float:
|
|
342
|
+
"""The horizontal center of this line of text.
|
|
343
|
+
|
|
344
|
+
:return: The horizontal center of this line of text.
|
|
345
|
+
"""
|
|
346
|
+
return self.y + self.height / 2
|
|
347
|
+
|
|
348
|
+
@cy.setter
|
|
349
|
+
def cy(self, value: float) -> None:
|
|
350
|
+
"""Set the horizontal center of this line of text.
|
|
351
|
+
|
|
352
|
+
:param value: The horizontal center of this line of text.
|
|
353
|
+
"""
|
|
354
|
+
self.y += value - self.cy
|
|
355
|
+
|
|
279
356
|
@property
|
|
280
357
|
def y2(self) -> float:
|
|
281
358
|
"""The bottom of this line of text.
|