Skip to content

Commit 2b303ab

Browse files
committed
Figure.paragraph: What you see is what you type for spaces and tabs.
1 parent 507009b commit 2b303ab

1 file changed

Lines changed: 42 additions & 4 deletions

File tree

pygmt/src/paragraph.py

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import io
6+
import re
67
from collections.abc import Sequence
78
from typing import Literal
89

@@ -35,6 +36,8 @@ def paragraph( # noqa: PLR0913
3536
fill: str | None = None,
3637
pen: str | None = None,
3738
alignment: Literal["left", "center", "right", "justified"] = "left",
39+
tab_width: int = 4,
40+
blankline_between_paragraphs: bool = False,
3841
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
3942
| bool = False,
4043
panel: int | Sequence[int] | bool = False,
@@ -79,6 +82,13 @@ def paragraph( # noqa: PLR0913
7982
alignment
8083
Set the alignment of the text. Valid values are ``"left"``, ``"center"``,
8184
``"right"``, and ``"justified"``.
85+
tab_width
86+
Number of spaces used to expand tab characters in ``text`` when typesetting.
87+
Must be a non-negative integer. Use ``0`` to remove tab characters instead of
88+
replacing them with spaces.
89+
blankline_between_paragraphs
90+
If ``True``, use a blank line between paragraphs. [Default is ``False``, i.e.,
91+
no blank line between paragraphs.]
8292
$verbose
8393
$panel
8494
$transparency
@@ -108,6 +118,12 @@ def paragraph( # noqa: PLR0913
108118
description="value for parameter 'alignment'",
109119
choices=_valid_alignments,
110120
)
121+
if tab_width < 0:
122+
raise GMTValueError(
123+
tab_width,
124+
description="value for parameter 'tab_width'",
125+
reason="Must be a non-negative integer.",
126+
)
111127

112128
aliasdict = AliasSystem(
113129
F=[
@@ -124,18 +140,40 @@ def paragraph( # noqa: PLR0913
124140
)
125141
aliasdict.merge({"M": True})
126142

127-
confdict = {}
128143
# Prepare the text string that will be passed to an io.StringIO object.
129-
# Multiple paragraphs are separated by a blank line "\n\n".
130-
_textstr: str = "\n\n".join(text) if is_nonstr_iter(text) else str(text)
131-
144+
#
145+
# The GMT's behavior:
146+
# - Leading and trailing spaces are ignored.
147+
# - Multiple spaces inside a paragraph are combined into one single space.
148+
# - Leading tabs are combined into one tab that results in a 4-space indentation.
149+
# - Trailing tabs are ignored.
150+
# - Multiple tabs inside a paragraph are converted to multiple spaces.
151+
# - Mixing tabs and spaces inside a paragraph has a complicated behavior.
152+
# - Newline characters are always converted into spaces.
153+
154+
# Separator for multiple paragraphs.
155+
# "\n\n": the default separator, which results in no blank line between paragraphs.
156+
# " \n\n": add a blank line between paragraphs.
157+
sep = " \n\n" if blankline_between_paragraphs else "\n\n"
158+
# Convert a single string into a list of paragraphs for consistent handling.
159+
# Split the single string on black lines, allowing for whitespaces in between.
160+
if not is_nonstr_iter(text):
161+
text = re.split(r"\n\s*\n", text)
162+
# Join multiple paragraphs with a blank line. Remove trailing whitespaces and
163+
# newlines in each paragraph, but keep leading whitespaces and tabs for now.
164+
_textstr = sep.join(t.rstrip().replace("\n", "") for t in text)
165+
# Replace two or more consecutive spaces with \040 (octal for space), and replace
166+
# tabs with the appropriate number of \040.
167+
_textstr = re.sub(r" {2,}", lambda m: r"\040" * len(m.group()), _textstr)
168+
_textstr = _textstr.replace("\t", r"\040" * tab_width)
132169
if _textstr == "":
133170
raise GMTValueError(
134171
text,
135172
description="text",
136173
reason="'text' must be a non-empty string or sequence of strings.",
137174
)
138175

176+
confdict = {}
139177
# Check the encoding of the text string and convert it to octal if necessary.
140178
if (encoding := _check_encoding(_textstr)) != "ascii":
141179
_textstr = non_ascii_to_octal(_textstr, encoding=encoding)

0 commit comments

Comments
 (0)