Skip to content

Commit 6c9e320

Browse files
authored
LaTeX: support CSS3 length units (#13657)
1 parent bda34f9 commit 6c9e320

File tree

9 files changed

+96
-12
lines changed

9 files changed

+96
-12
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,8 @@ jobs:
312312
enable-cache: false
313313
- name: Install dependencies
314314
run: uv pip install . --group test
315+
- name: Install Docutils' HEAD
316+
run: uv pip install "docutils @ git+https://repo.or.cz/docutils.git#subdirectory=docutils"
315317
- name: Test with pytest
316318
run: python -m pytest -vv --durations 25
317319
env:

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ Features added
5858
Patch by Adam Turner.
5959
* #13647: LaTeX: allow more cases of table nesting.
6060
Patch by Jean-François B.
61+
* #13657: LaTeX: support CSS3 length units.
62+
Patch by Jean-François B.
6163
* #13684: intersphinx: Add a file-based cache for remote inventories.
6264
The location of the cache directory must not be relied upon externally,
6365
as it may change without notice or warning in future releases.

sphinx/templates/latex/latex.tex.jinja

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
\ifdefined\pdfimageresolution
1717
\pdfimageresolution= \numexpr \dimexpr1in\relax/\sphinxpxdimen\relax
1818
\fi
19+
\newdimen\sphinxremdimen\sphinxremdimen = <%= pointsize%>
1920
%% let collapsible pdf bookmarks panel have high depth per default
2021
\PassOptionsToPackage{bookmarksdepth=5}{hyperref}
2122
<% if use_xindy -%>

sphinx/texinputs/sphinx.sty

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1241,5 +1241,25 @@
12411241
% FIXME: this line should be dropped, as "9" is default anyhow.
12421242
\ifdefined\pdfcompresslevel\pdfcompresslevel = 9 \fi
12431243

1244-
1244+
%%% SUPPORT FOR CSS3 EXTRA LENGTH UNITS
1245+
% cf rstdim_to_latexdim in latex.py
1246+
%
1247+
\def\sphinxchdimen{\dimexpr\fontcharwd\font`0\relax}
1248+
% TODO: decide if we want rather \textwidth/\textheight.
1249+
\newdimen\sphinxvwdimen
1250+
\sphinxvwdimen=\dimexpr0.01\paperwidth\relax
1251+
\newdimen\sphinxvhdimen
1252+
\sphinxvhdimen=\dimexpr0.01\paperheight\relax
1253+
\newdimen\sphinxvmindimen
1254+
\sphinxvmindimen=\dimexpr
1255+
\ifdim\paperwidth<\paperheight\sphinxvwdimen\else\sphinxvhdimen\fi
1256+
\relax
1257+
\newdimen\sphinxvmaxdimen
1258+
\sphinxvmaxdimen=\dimexpr
1259+
\ifdim\paperwidth<\paperheight\sphinxvhdimen\else\sphinxvwdimen\fi
1260+
\relax
1261+
\newdimen\sphinxQdimen
1262+
\sphinxQdimen=0.25mm
1263+
% MEMO: \sphinxremdimen is defined in the template as it needs
1264+
% the config variable pointsize.
12451265
\endinput

sphinx/writers/latex.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ def escape_abbr(text: str) -> str:
297297

298298
def rstdim_to_latexdim(width_str: str, scale: int = 100) -> str:
299299
"""Convert `width_str` with rst length to LaTeX length."""
300+
# MEMO: the percent unit is interpreted here as a percentage
301+
# of \linewidth. Let's keep in mind though that \linewidth
302+
# is dynamic in LaTeX, e.g. it is smaller in lists.
300303
match = re.match(r'^(\d*\.?\d*)\s*(\S*)$', width_str)
301304
if not match:
302305
raise ValueError
@@ -310,6 +313,8 @@ def rstdim_to_latexdim(width_str: str, scale: int = 100) -> str:
310313
res = '%sbp' % amount # convert to 'bp'
311314
elif unit == '%':
312315
res = r'%.3f\linewidth' % (float(amount) / 100.0)
316+
elif unit in {'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax', 'Q'}:
317+
res = rf'{amount}\sphinx{unit}dimen'
313318
else:
314319
amount_float = float(amount) * scale / 100.0
315320
if unit in {'', 'px'}:
@@ -318,8 +323,13 @@ def rstdim_to_latexdim(width_str: str, scale: int = 100) -> str:
318323
res = '%.5fbp' % amount_float
319324
elif unit == '%':
320325
res = r'%.5f\linewidth' % (amount_float / 100.0)
326+
elif unit in {'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax', 'Q'}:
327+
res = rf'{amount_float:.5f}\sphinx{unit}dimen'
321328
else:
322329
res = f'{amount_float:.5f}{unit}'
330+
# Those further units are passed through and accepted "as is" by TeX:
331+
# em and ex (both font dependent), bp, cm, mm, in, and pc.
332+
# Non-CSS units (TeX only presumably) are cc, nc, dd, nd, and sp.
323333
return res
324334

325335

tests/roots/test-latex-images-css3-lengths/conf.py

Whitespace-only changes.
64.7 KB
Loading
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
=============
2+
TEST IMAGES
3+
=============
4+
5+
test-latex-images-css3-lengths
6+
==============================
7+
8+
.. image:: img.png
9+
:width: 10.03ch
10+
:height: 9.97rem
11+
12+
.. image:: img.png
13+
:width: 60vw
14+
:height: 10vh
15+
16+
.. image:: img.png
17+
:width: 10.5vmin
18+
:height: 10.5vmax
19+
20+
.. image:: img.png
21+
:width: 195.345Q
22+
23+
.. image:: img.png
24+
:width: 195.345Q
25+
:scale: 50%

tests/test_builders/test_build_latex.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ def kpsetest(*filenames):
5555

5656

5757
# compile latex document with app.config.latex_engine
58-
def compile_latex_document(app, filename='projectnamenotset.tex', docclass='manual'):
58+
def compile_latex_document(
59+
app, filename='projectnamenotset.tex', docclass='manual', runtwice=False
60+
):
5961
# now, try to run latex over it
6062
try:
6163
with chdir(app.outdir):
@@ -82,7 +84,7 @@ def compile_latex_document(app, filename='projectnamenotset.tex', docclass='manu
8284
# as configured in the Makefile and in presence of latexmkrc
8385
# or latexmkjarc and also sphinx.xdy and other xindy support.
8486
# And two passes are not enough except for simplest documents.
85-
if app.config.latex_engine == 'pdflatex':
87+
if runtwice:
8688
subprocess.run(args, capture_output=True, check=True)
8789
except OSError as exc: # most likely the latex executable was not found
8890
raise pytest.skip.Exception from exc
@@ -101,6 +103,10 @@ def compile_latex_document(app, filename='projectnamenotset.tex', docclass='manu
101103
not kpsetest(*STYLEFILES),
102104
reason='not running latex, the required styles do not seem to be installed',
103105
)
106+
skip_if_docutils_not_at_least_at_0_22 = pytest.mark.skipif(
107+
docutils.__version_info__[:2] < (0, 22),
108+
reason='this test requires Docutils at least at 0.22',
109+
)
104110

105111

106112
class RemoteImageHandler(http.server.BaseHTTPRequestHandler):
@@ -128,25 +134,27 @@ def do_GET(self):
128134
@skip_if_requested
129135
@skip_if_stylefiles_notfound
130136
@pytest.mark.parametrize(
131-
('engine', 'docclass', 'python_maximum_signature_line_length'),
137+
('engine', 'docclass', 'python_maximum_signature_line_length', 'runtwice'),
132138
# Only running test with `python_maximum_signature_line_length` not None with last
133139
# LaTeX engine to reduce testing time, as if this configuration does not fail with
134140
# one engine, it's almost impossible it would fail with another.
135141
[
136-
('pdflatex', 'manual', None),
137-
('pdflatex', 'howto', None),
138-
('lualatex', 'manual', None),
139-
('lualatex', 'howto', None),
140-
('xelatex', 'manual', 1),
141-
('xelatex', 'howto', 1),
142+
('pdflatex', 'manual', None, True),
143+
('pdflatex', 'howto', None, True),
144+
('lualatex', 'manual', None, False),
145+
('lualatex', 'howto', None, False),
146+
('xelatex', 'manual', 1, False),
147+
('xelatex', 'howto', 1, False),
142148
],
143149
)
144150
@pytest.mark.sphinx(
145151
'latex',
146152
testroot='root',
147153
freshenv=True,
148154
)
149-
def test_build_latex_doc(app, engine, docclass, python_maximum_signature_line_length):
155+
def test_build_latex_doc(
156+
app, engine, docclass, python_maximum_signature_line_length, runtwice
157+
):
150158
app.config.python_maximum_signature_line_length = (
151159
python_maximum_signature_line_length
152160
)
@@ -170,7 +178,23 @@ def test_build_latex_doc(app, engine, docclass, python_maximum_signature_line_le
170178
# file from latex_additional_files
171179
assert (app.outdir / 'svgimg.svg').is_file()
172180

173-
compile_latex_document(app, 'sphinxtests.tex', docclass)
181+
compile_latex_document(app, 'sphinxtests.tex', docclass, runtwice)
182+
183+
184+
@skip_if_requested
185+
@skip_if_stylefiles_notfound
186+
@skip_if_docutils_not_at_least_at_0_22
187+
@pytest.mark.parametrize('engine', ['pdflatex', 'lualatex', 'xelatex'])
188+
@pytest.mark.sphinx(
189+
'latex',
190+
testroot='latex-images-css3-lengths',
191+
)
192+
def test_build_latex_with_css3_lengths(app, engine):
193+
app.config.latex_engine = engine
194+
app.config.latex_documents = [(*app.config.latex_documents[0][:4], 'howto')]
195+
app.builder.init()
196+
app.build(force_all=True)
197+
compile_latex_document(app, docclass='howto')
174198

175199

176200
@pytest.mark.sphinx('latex', testroot='root')

0 commit comments

Comments
 (0)