Skip to content

Commit bbb0de1

Browse files
committed
Figure.paragraph
1 parent 5a3a290 commit bbb0de1

File tree

4 files changed

+170
-0
lines changed

4 files changed

+170
-0
lines changed

pygmt/figure.py

+1
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ def _repr_html_(self):
414414
legend,
415415
logo,
416416
meca,
417+
paragraph,
417418
plot,
418419
plot3d,
419420
psconvert,

pygmt/src/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from pygmt.src.makecpt import makecpt
3838
from pygmt.src.meca import meca
3939
from pygmt.src.nearneighbor import nearneighbor
40+
from pygmt.src.paragraph import paragraph
4041
from pygmt.src.plot import plot
4142
from pygmt.src.plot3d import plot3d
4243
from pygmt.src.project import project

pygmt/src/paragraph.py

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""
2+
paragraph - Typeset one or multiple paragraphs.
3+
"""
4+
5+
import io
6+
from collections.abc import Sequence
7+
from typing import Literal
8+
9+
from pygmt._typing import AnchorCode
10+
from pygmt.clib import Session
11+
from pygmt.helpers import (
12+
_check_encoding,
13+
build_arg_list,
14+
is_nonstr_iter,
15+
non_ascii_to_octal,
16+
)
17+
18+
19+
def _parse_option_f_upper(
20+
font: float | str | None, angle: float | None, justify: AnchorCode | None
21+
) -> str | None:
22+
"""
23+
Parse the font, angle, and justification arguments and return the string to be
24+
appened to the module options.
25+
26+
Examples
27+
--------
28+
>>> _parse_font_angle_justify(None, None, None)
29+
>>> _parse_font_angle_justify("10p", None, None)
30+
'+f10p'
31+
>>> _parse_font_angle_justify(None, 45, None)
32+
'+a45'
33+
>>> _parse_font_angle_justify(None, None, "CM")
34+
'+jCM'
35+
>>> _parse_font_angle_justify("10p,Helvetica-Bold", 45, "CM")
36+
'+f10p,Helvetica-Bold+a45+jCM'
37+
"""
38+
args = ((font, "+f"), (angle, "+a"), (justify, "+j"))
39+
if all(arg is None for arg, _ in args):
40+
return None
41+
return "".join(f"{flag}{arg}" for arg, flag in args if arg is not None)
42+
43+
44+
def paragraph(
45+
self,
46+
x: float | str,
47+
y: float | str,
48+
text: str | Sequence[str],
49+
parwidth: float | str,
50+
linespacing: float | str,
51+
font: float | str | None = None,
52+
angle: float | None = None,
53+
justify: AnchorCode | None = None,
54+
alignment: Literal["left", "center", "right", "justified"] = "left",
55+
):
56+
"""
57+
Typeset one or multiple paragraphs.
58+
59+
Parameters
60+
----------
61+
x/y
62+
The x, y coordinates of the paragraph.
63+
text
64+
The paragraph text to typeset.
65+
parwidth
66+
The width of the paragraph.
67+
linespacing
68+
The spacing between lines.
69+
font
70+
The font of the text.
71+
angle
72+
The angle of the text.
73+
justify
74+
The justification of the block of text, relative to the given x, y position.
75+
alignment
76+
The alignment of the text. Valid values are ``"left"``, ``"center"``,
77+
``"right"``, and ``"justified"``.
78+
"""
79+
self._preprocess()
80+
81+
# Initialize a stringio object for storing the data input.
82+
stringio = io.StringIO()
83+
# The header line.
84+
stringio.write(f"> {x} {y} {linespacing} {parwidth} {alignment[0]}\n")
85+
# The text. Multiple paragraphs are separated by a blank line.
86+
text_in_stringio = "\n\n".join(text) if is_nonstr_iter(text) else text
87+
encoding = _check_encoding(text_in_stringio)
88+
stringio.write(non_ascii_to_octal(text_in_stringio, encoding=encoding))
89+
90+
confdict = {}
91+
if encoding not in {"ascii", "ISOLatin1+"}:
92+
confdict["PS_CHAR_ENCODING"] = encoding
93+
94+
# Prepare the keyword dictionary for the module options
95+
kwdict = {"M": True, "F": _parse_option_f_upper(font, angle, justify)}
96+
97+
with Session() as lib:
98+
with lib.virtualfile_from_stringio(stringio) as vfile:
99+
lib.call_module(
100+
"text", args=build_arg_list(kwdict, infile=vfile, confdict=confdict)
101+
)

pygmt/tests/test_paragraph.py

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""
2+
Tests for Figure.paragraph.
3+
"""
4+
5+
import pytest
6+
from pygmt import Figure
7+
8+
9+
@pytest.mark.mpl_image_compare
10+
def test_paragraph():
11+
"""
12+
Test typesetting a single paragraph.
13+
"""
14+
fig = Figure()
15+
fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
16+
fig.paragraph(
17+
x=4,
18+
y=4,
19+
text="This is a long paragraph. " * 10,
20+
parwidth="5c",
21+
linespacing="12p",
22+
)
23+
return fig
24+
25+
26+
@pytest.mark.mpl_image_compare
27+
def test_paragraph_multiple_paragraphs_list():
28+
"""
29+
Test typesetting a single paragraph.
30+
"""
31+
fig = Figure()
32+
fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
33+
fig.paragraph(
34+
x=4,
35+
y=4,
36+
text=[
37+
"This is the first paragraph. " * 5,
38+
"This is the second paragraph. " * 5,
39+
],
40+
parwidth="5c",
41+
linespacing="12p",
42+
)
43+
return fig
44+
45+
46+
@pytest.mark.mpl_image_compare
47+
def test_paragraph_multiple_paragraphs_blankline():
48+
"""
49+
Test typesetting a single paragraph.
50+
"""
51+
text = """
52+
This is the first paragraph.
53+
This is the first paragraph.
54+
This is the first paragraph.
55+
This is the first paragraph.
56+
This is the first paragraph.
57+
58+
This is the second paragraph.
59+
This is the second paragraph.
60+
This is the second paragraph.
61+
This is the second paragraph.
62+
This is the second paragraph.
63+
"""
64+
fig = Figure()
65+
fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
66+
fig.paragraph(x=4, y=4, text=text, parwidth="5c", linespacing="12p")
67+
return fig

0 commit comments

Comments
 (0)