LaTeX: support elliptical corners in rounded boxes

This commit is contained in:
Jean-François B 2023-03-19 20:17:51 +01:00
parent f78d86554c
commit de60cd8618
2 changed files with 225 additions and 82 deletions

View File

@ -1178,12 +1178,15 @@ inserted via the :dudir:`raw` directive) and the CSS syntax is only imitated.
* A colon in place of the equal sign will break LaTeX.
* As a rule, avoid inserting unneeded spaces in the key values.
* ``...border-width`` or ``...padding`` expect a *single* dimension: they can not
be used so far with space separated dimensions.
* Dimensional parameters such as ``border-width`` or ``padding`` expect a
*unique* dimension: they can not be used so far with space separated
dimensions. The sole property which handles space separated input is
``box-shadow``.
* ``...top-right-radius`` et al. values may be either a single or *two* space
separated dimensions.
.. versionchanged:: 6.2.0
Formerly only circular corners were supported.
* Dimension specifications must use TeX units such as ``pt`` or ``cm`` or
``in``. The ``px`` unit is recognized by ``pdflatex`` and ``lualatex``
@ -1196,6 +1199,12 @@ inserted via the :dudir:`raw` directive) and the CSS syntax is only imitated.
double the backslash or to employ a raw Python string for the value of
the :ref:`'sphinxsetup' <latexsphinxsetup>` key.
* As a rule, avoid inserting unneeded spaces in the key values: especially
for the radii an input such ``2 pt 3pt`` will break LaTeX. Beware also
that ``\fboxsep \fboxsep`` will not be seen as space separated in LaTeX.
You must use something such as ``{\fboxsep} \fboxsep``. Or use
directly ``3pt 3pt`` which is a priori equivalent and simpler.
The options are all named in a similar pattern which depends on a ``prefix``,
which is then followed by an underscore, then the property name.
@ -1219,15 +1228,16 @@ forget the underscore separating the prefix from the property names.
| ``<prefix>_border-left-width``,
| ``<prefix>_border-width``. The latter can (currently) be only a *single*
dimension which then sets all four others.
The default is that all those dimensions are equal. They are set to:
* ``\fboxrule`` (i.e. a priori ``0.4pt``) for :rst:dir:`code-block`,
* ``\fboxrule`` for :dudir:`topic` or contents_ directive,
* ``1pt`` for :dudir:`warning` and other "strong" admonitions,
* ``0.5pt`` for :dudir:`note` and other "light" admonitions. The framing
style of the "lighbox" used for them in absence of usage of CSS-named
options will be emulated by the richer "heavybox" if setting
``border-left-width`` and ``border-right-width`` both to ``0pt``.
The default is that all those dimensions are equal. They are set to:
* ``\fboxrule`` (i.e. a priori ``0.4pt``) for :rst:dir:`code-block`,
* ``\fboxrule`` for :dudir:`topic` or contents_ directive,
* ``1pt`` for :dudir:`warning` and other "strong" admonitions,
* ``0.5pt`` for :dudir:`note` and other "light" admonitions. The framing
style of the "lighbox" used for them in absence of usage of CSS-named
options will be emulated by the richer "heavybox" if setting
``border-left-width`` and ``border-right-width`` both to ``0pt``.
- ``<prefix>_box-decoration-break`` can be set to either ``clone`` or
``slice`` and configures the behavior at page breaks.
@ -1239,20 +1249,21 @@ forget the underscore separating the prefix from the property names.
| ``<prefix>_padding-left``,
| ``<prefix>_padding``. The latter can (currently) be only a *single*
dimension which then sets all four others.
The default is that all those dimensions are equal. They are set to:
* ``\fboxsep`` (i.e. a priori ``3pt``) for :rst:dir:`code-block`,
* ``5pt`` for :dudir:`topic` or contents_ directive,
* a special value for :dudir:`warning` and other "strong" admonitions,
which ensures a backward compatible behavior.
The default is that all those dimensions are equal. They are set to:
.. important:: Prior to 5.1.0 there was no separate customizability of
padding for warning-type boxes in PDF via LaTeX output. The sum of
padding and border-width (as set for example for :dudir:`warning` by
``warningborder``, now also named ``div.warning_border-width``) was
kept to a certain constant value. This limited the border-width
to small values else the border could overlap the text contents.
This behavior is kept as default.
* ``\fboxsep`` (i.e. a priori ``3pt``) for :rst:dir:`code-block`,
* ``5pt`` for :dudir:`topic` or contents_ directive,
* a special value for :dudir:`warning` and other "strong" admonitions,
which ensures a backward compatible behavior.
.. important:: Prior to 5.1.0 there was no separate customizability of
padding for warning-type boxes in PDF via LaTeX output. The sum of
padding and border-width (as set for example for :dudir:`warning` by
``warningborder``, now also named ``div.warning_border-width``) was
kept to a certain constant value. This limited the border-width
to small values else the border could overlap the text contents.
This behavior is kept as default.
* the same padding behavior is obeyed per default for :dudir:`note` or
other "light" admonitions when using ``sphinxheavybox``.
@ -1260,13 +1271,17 @@ forget the underscore separating the prefix from the property names.
| ``<prefix>_border-top-right-radius``,
| ``<prefix>_border-bottom-right-radius``,
| ``<prefix>_border-bottom-left-radius``,
| ``<prefix>_border-radius``. The latter can (currently) be only a *single*
dimension which then sets all four others (rounded corners are
circular arcs only, no ellipses).
The default is that all those dimensions are equal. They are set to:
| ``<prefix>_border-radius``. Each can be either a single, or *two*,
space separated, dimensions. The last one sets the first four to be
identical to itself.
* ``\fboxsep`` (i.e. a priori ``3pt``) for :rst:dir:`code-block` (since 6.0.0).
* ``0pt`` for all other directives; this means to use straight corners.
The default is that all four corners are either circular or straight,
with common radii:
* ``\fboxsep`` (i.e. a priori ``3pt``) for :rst:dir:`code-block` (since 6.0.0).
* ``0pt`` for all other directives; this means to use straight corners.
See a remark above about traps with spaces in LaTeX.
- ``<prefix>_box-shadow`` is special in so far as it may be:
* the ``none`` keyword,
@ -1316,10 +1331,9 @@ forget the underscore separating the prefix from the property names.
``clone`` for all other directives, but this will probably change at
7.0.0.
- Prior to 6.2.0, rounded corners forced a constant border width: the
separate settings were ignored in favor of the sole
``<prefix>_border-width``. Now (up to) 4 distinct radii happily cohabit
with (up to) 4 distinct border widths.
- Prior to 6.2.0, and since 5.1.0, the situation was that only circular
rounded corners were supported and that a rounded corner forced the whole
frame to use ``<prefix>_border-width`` for all four sides.
- Inset shadows are incompatible with rounded corners. In case
both are specified the inset shadow will simply be ignored.
@ -1624,7 +1638,7 @@ satisfactory.
Refer to :ref:`additionalcss` for important syntax information regarding the
other keys. The default
configuration uses no shadow, a border-width of ``\fboxrule``, a padding of
``\fboxsep``, rounded corners (with radius ``\fboxsep``) and background and
``\fboxsep``, circular corners with radii ``\fboxsep`` and background and
border colors as for the default rendering of code-blocks.
When a ``\sphinxbox`` usage is nested within another one, it will ignore the

View File

@ -9,6 +9,16 @@
% Or we could use extra package "picture". We opt for custom wrappers
% \spx@moveto, \spx@lineto, ..., working with old versions.
%
% - ellipse. This package extends pict2e with elliptical arcs. Its author
% Daan Leijen also has contributed package longfbox which is part of
% TeXLive. Had I known about it, I would perhaps have based Sphinx CSS on
% top of longfbox at least partly. But this would not have spared me all
% the work in sphinx.sty, which was a long walk until 6.2.0 version.
% Besides I don't need the breakable boxes from longfbox, as Sphinx has
% its own rather advanced layer on top of framed. I would need to check if
% some thorny color issues solved by Sphinx (and not by tcolorbox) at page
% breaks are solved by longfbox as well. (I have not tested)
% At 6.2.0 refactoring, we do not wait for at begin document to try to load
% pict2e. Actually since 6.0.0 the default is for code-blocks to use
% rounded boxes, and the only reason since then to wait "at begin document"
@ -42,6 +52,28 @@
options setting radii have all been ignored}}%
}%
\IfFileExists{ellipse.sty}
{\RequirePackage{ellipse}}
{\PackageWarningNoLine{sphinx}{%
The package ellipse is required for elliptical corners.\MessageBreak
It does not seem to be available on your system.\MessageBreak
All non-straight corners will use circle arcs.%
}%
\AtEndDocument{\PackageWarningNoLine{sphinx}{%
The package ellipse is required for elliptical corners.\MessageBreak
As it does not seem to be available on your system,\MessageBreak
all non-straight corners have used circle arcs.}}%
}%
% Surprisingly it seems ellipse.sty uses a \@tempdimd which does not exist in
% LaTeX. Was it removed at some point? Perhaps ellipse.sty was always tested
% with some other package? I don't see it being defined in packages longfbox,
% longbox, or options either. Or pict2e stopped defining it at some point? I
% was very surprised that my first very test after completing the coding would
% fail with an error about \@tempdimd, but simply defining it, everything
% does seem to work fine! Not thoroughly tested, though.
\@ifpackageloaded{ellipse}{\ifdefined\@tempdimd\else\newdimen\@tempdimd\fi}{}
% Provides box registers \spx@tempboxa, \spx@tempboxb usable in other places
\newbox\spx@tempboxa
\newbox\spx@tempboxb
@ -71,10 +103,14 @@
\newdimen\spx@boxes@shadow@xoffset
\newdimen\spx@boxes@shadow@yoffset
%
\newdimen\spx@boxes@radius@topleft % only circular arcs, x-radius same as y-radius
\newdimen\spx@boxes@radius@topright
\newdimen\spx@boxes@radius@bottomright
\newdimen\spx@boxes@radius@bottomleft
\newdimen\spx@boxes@radius@topleft@x
\newdimen\spx@boxes@radius@topright@x
\newdimen\spx@boxes@radius@bottomright@x
\newdimen\spx@boxes@radius@bottomleft@x
\newdimen\spx@boxes@radius@topleft@y
\newdimen\spx@boxes@radius@topright@y
\newdimen\spx@boxes@radius@bottomright@y
\newdimen\spx@boxes@radius@bottomleft@y
%
% These colors will be set to colors defined appropriately by caller of
% \spx@boxes@fcolorbox@setup macro
@ -95,6 +131,22 @@
%
% The #1 is one of: pre, topic, warning, danger, etc....
%
% We delay until here the parsing of radii options to extract x and y
% components.
\def\spx@boxes@setradii#1 #2 #3\@nnil#4#5{%
#4\dimexpr#1\relax
#5\dimexpr#2\relax
\ifdim#5=-\maxdimen#5#4\fi
% if one of them is zero or negative set both to zero
\ifdim#4>\z@\else#4\z@#5\z@\fi
\ifdim#5>\z@\else#4\z@#5\z@\fi
}%
% if ellipse.sty is not available ignore the second component of all radii
% specifications, use circle arcs with radius the x component
\@ifpackageloaded{ellipse}
{}
{\def\spx@boxes@setradii#1 #2 #3\@nnil#4#5{#4\dimexpr#1\relax #5#4}}
% Using \dimexpr for maximal user input flexibility.
\def\spx@boxes@fcolorbox@setup#1{%
\spx@boxes@border@top \dimexpr\@nameuse{spx@#1@border@top}\relax
@ -108,10 +160,34 @@
\spx@boxes@padding@bottom\dimexpr\@nameuse{spx@#1@padding@bottom}\relax
\spx@boxes@padding@left \dimexpr\@nameuse{spx@#1@padding@left}\relax
%
\spx@boxes@radius@topleft \dimexpr\@nameuse{spx@#1@radius@topleft}\relax
\spx@boxes@radius@topright \dimexpr\@nameuse{spx@#1@radius@topright}\relax
\spx@boxes@radius@bottomright \dimexpr\@nameuse{spx@#1@radius@bottomright}\relax
\spx@boxes@radius@bottomleft \dimexpr\@nameuse{spx@#1@radius@bottomleft}\relax
\edef\spx@temp{\csname spx@#1@radius@topleft\endcsname\space}%
\expandafter
\spx@boxes@setradii
\spx@temp
{-\maxdimen}
\@nnil
\spx@boxes@radius@topleft@x\spx@boxes@radius@topleft@y
\edef\spx@temp{\csname spx@#1@radius@topright\endcsname\space}%
\expandafter
\spx@boxes@setradii
\spx@temp
{-\maxdimen}
\@nnil
\spx@boxes@radius@topright@x\spx@boxes@radius@topright@y
\edef\spx@temp{\csname spx@#1@radius@bottomright\endcsname\space}%
\expandafter
\spx@boxes@setradii
\spx@temp
{-\maxdimen}
\@nnil
\spx@boxes@radius@bottomright@x\spx@boxes@radius@bottomright@y
\edef\spx@temp{\csname spx@#1@radius@bottomleft\endcsname\space}%
\expandafter
\spx@boxes@setradii
\spx@temp
{-\maxdimen}
\@nnil
\spx@boxes@radius@bottomleft@x\spx@boxes@radius@bottomleft@y
%
\@nameuse{ifspx@#1@withshadow}%
\spx@boxes@withshadowtrue
@ -160,10 +236,14 @@
{% pict2e is available and loaded
\def\spx@boxes@fcolorbox@setup@fcolorbox{%
\if1% use rounded boxes only if needed
\ifdim\spx@boxes@radius@topleft >\z@0\fi
\ifdim\spx@boxes@radius@topright >\z@0\fi
\ifdim\spx@boxes@radius@bottomright>\z@0\fi
\ifdim\spx@boxes@radius@bottomleft >\z@0\fi
\ifdim\spx@boxes@radius@topleft@x >\z@0\fi
\ifdim\spx@boxes@radius@topright@x >\z@0\fi
\ifdim\spx@boxes@radius@bottomright@x>\z@0\fi
\ifdim\spx@boxes@radius@bottomleft@x >\z@0\fi
\ifdim\spx@boxes@radius@topleft@y >\z@0\fi
\ifdim\spx@boxes@radius@topright@y >\z@0\fi
\ifdim\spx@boxes@radius@bottomright@y>\z@0\fi
\ifdim\spx@boxes@radius@bottomleft@y >\z@0\fi
1\def\spx@boxes@fcolorbox{\spx@boxes@fcolorbox@rectangle}%
\else
\def\spx@boxes@fcolorbox{\spx@boxes@fcolorbox@rounded}%
@ -189,23 +269,23 @@
% optimization.
\def\spx@boxes@fcolorbox@setup@openbottom{%
\spx@boxes@border@bottom \z@
\spx@boxes@radius@bottomright\z@
\spx@boxes@radius@bottomleft \z@
\spx@boxes@radius@bottomright@x\z@ \spx@boxes@radius@bottomright@y\z@
\spx@boxes@radius@bottomleft@x \z@ \spx@boxes@radius@bottomleft@y \z@
\spx@boxes@fcolorbox@setup@fcolorbox
}%
\def\spx@boxes@fcolorbox@setup@opentop{%
\spx@boxes@border@top \z@
\spx@boxes@radius@topright\z@
\spx@boxes@radius@topleft \z@
\spx@boxes@radius@topright@x\z@ \spx@boxes@radius@topright@y\z@
\spx@boxes@radius@topleft@x \z@ \spx@boxes@radius@topleft@y \z@
\spx@boxes@fcolorbox@setup@fcolorbox
}%
\def\spx@boxes@fcolorbox@setup@openboth{%
\spx@boxes@border@top \z@
\spx@boxes@border@bottom \z@
\spx@boxes@radius@topright\z@
\spx@boxes@radius@topleft \z@
\spx@boxes@radius@bottomright\z@
\spx@boxes@radius@bottomleft \z@
\spx@boxes@radius@bottomright@x\z@ \spx@boxes@radius@bottomright@y\z@
\spx@boxes@radius@bottomleft@x \z@ \spx@boxes@radius@bottomleft@y \z@
\spx@boxes@radius@topright@x\z@ \spx@boxes@radius@topright@y\z@
\spx@boxes@radius@topleft@x \z@ \spx@boxes@radius@topleft@y \z@
\def\spx@boxes@fcolorbox{\spx@boxes@fcolorbox@rectangle}%
}%
@ -567,9 +647,12 @@
% one filling up to external border, the second one actually filling for the
% background paradoxically on top of it, up to internal border path.
%
% This 6.2.0 abandonment of \strokepath allowed great simplifcation in
% This 6.2.0 abandonment of \strokepath allowed great simplification in
% supporting opentop, openbottom and openboth situations, and it can
% allow automatic support of openleft and openright analogs.
%
% And 6.2.0 also implements elliptical arcs thanks to ellipse package,
% which extends pict2e.
% Currently, inset shadow is not supported.
%
@ -588,43 +671,89 @@
\def\spx@moveto(#1,#2){\moveto({\strip@pt\dimexpr#1\relax},{\strip@pt\dimexpr#2\relax})}
\def\spx@lineto(#1,#2){\lineto({\strip@pt\dimexpr#1\relax},{\strip@pt\dimexpr#2\relax})}
% attention here the [N] becomes mandatory
% \circlearc[<N>]{<X>}{<Y>}{<RAD>}{<ANGLE1>}{<ANGLE2>}
\def\spx@circlearc[#1]#2#3#4%#5#6
{\circlearc[#1]{\strip@pt\dimexpr#2\relax}%
{\strip@pt\dimexpr#3\relax}%
{\strip@pt\dimexpr#4\relax}}
{\strip@pt\dimexpr#4\relax}%
}
% attention here too the [N] becomes mandatory
% the core path macro of ellipse.sty. Thanks to Daan Leijen, author of this
% package.
% \elliparc [<initial>]{<center-x>}{<center-y>}{<x-rad>}{<y-rad>}{<start-angle>}{<end-angle>}
% maybe this wrapper is unneeded but I don't have real time to check
\def\spx@elliparc[#1]#2#3#4#5%#6#7
{\elliparc[#1]{\strip@pt\dimexpr#2\relax}%
{\strip@pt\dimexpr#3\relax}%
{\strip@pt\dimexpr#4\relax}%
{\strip@pt\dimexpr#5\relax}%
}
% Macro whose execution prepares a path to be either stroked or filled
% Only fill operation is used at 6.2.0. The radii are given by the set box
% parameters, but the width and height are in \spx@width and \spx@height. A
% \put command will be used for appropriate shifts.
% 6.2.0 adds elliptical corners!
% But I feel perhaps I need to think about how x-radius and y-radius should
% interact with border-width. So consider output WIP for time being.
\def\spx@boxes@border@defpath{%
\spx@moveto(\spx@boxes@radius@bottomleft,\z@)% our \spx@moveto is a bit rigid
% and we must use (\z@, \z@) not (0, 0) here
\spx@lineto(\spx@width-\spx@boxes@radius@bottomright,\z@)%
\ifdim\spx@boxes@radius@bottomright>\z@
% probably not needed to guard against zero or negative radius
% but let's nevertheless
\spx@circlearc[2]{\spx@width-\spx@boxes@radius@bottomright}%
{\spx@boxes@radius@bottomright}%
{\spx@boxes@radius@bottomright}{-90}{0}%
\spx@moveto(\spx@boxes@radius@bottomleft@x,\z@)% our \spx@moveto is a bit rigid
% and we must use \z@ not 0 here
\spx@lineto(\spx@width-\spx@boxes@radius@bottomright@x,\z@)%
% x and y radii are either both positive or both zero
% probably not needed to actually guard against the latter case,
% let's do it nevertheless
\ifdim\spx@boxes@radius@bottomright@x>\z@
\ifdim\spx@boxes@radius@bottomright@x=\spx@boxes@radius@bottomright@y
\spx@circlearc[2]{\spx@width-\spx@boxes@radius@bottomright@x}%
{\spx@boxes@radius@bottomright@y}%
{\spx@boxes@radius@bottomright@x}{-90}{0}%
\else
\spx@elliparc[2]{\spx@width-\spx@boxes@radius@bottomright@x}%
{\spx@boxes@radius@bottomright@y}%
{\spx@boxes@radius@bottomright@x}
{\spx@boxes@radius@bottomright@y}{-90}{0}%
\fi
\fi
\spx@lineto(\spx@width,%
\spx@height-\spx@boxes@radius@topright)%
\ifdim\spx@boxes@radius@topright>\z@
\spx@circlearc[2]{\spx@width-\spx@boxes@radius@topright}
{\spx@height-\spx@boxes@radius@topright}%
{\spx@boxes@radius@topright}{0}{90}%
\spx@height-\spx@boxes@radius@topright@y)%
\ifdim\spx@boxes@radius@topright@x>\z@
\ifdim\spx@boxes@radius@topright@x=\spx@boxes@radius@topright@y
\spx@circlearc[2]{\spx@width-\spx@boxes@radius@topright@x}
{\spx@height-\spx@boxes@radius@topright@y}%
{\spx@boxes@radius@topright@x}{0}{90}%
\else
\spx@elliparc[2]{\spx@width-\spx@boxes@radius@topright@x}
{\spx@height-\spx@boxes@radius@topright@y}%
{\spx@boxes@radius@topright@x}%
{\spx@boxes@radius@topright@y}{0}{90}%
\fi
\fi
\spx@lineto(\spx@boxes@radius@topleft,\spx@height)%
\ifdim\spx@boxes@radius@topleft>\z@
\spx@circlearc[2]{\spx@boxes@radius@topleft}%
{\spx@height-\spx@boxes@radius@topleft}%
{\spx@boxes@radius@topleft}{90}{180}%
\spx@lineto(\spx@boxes@radius@topleft@x,\spx@height)%
\ifdim\spx@boxes@radius@topleft@x>\z@
\ifdim\spx@boxes@radius@topleft@x=\spx@boxes@radius@topleft@y
\spx@circlearc[2]{\spx@boxes@radius@topleft@x}%
{\spx@height-\spx@boxes@radius@topleft@y}%
{\spx@boxes@radius@topleft@x}{90}{180}%
\else
\spx@elliparc[2]{\spx@boxes@radius@topleft@x}%
{\spx@height-\spx@boxes@radius@topleft@y}%
{\spx@boxes@radius@topleft@x}%
{\spx@boxes@radius@topleft@y}{90}{180}%
\fi
\fi
\spx@lineto(\z@,\spx@boxes@radius@bottomleft)%
\ifdim\spx@boxes@radius@bottomleft>\z@
\spx@circlearc[2]{\spx@boxes@radius@bottomleft}%
{\spx@boxes@radius@bottomleft}%
{\spx@boxes@radius@bottomleft}{180}{270}%
\spx@lineto(\z@,\spx@boxes@radius@bottomleft@y)%
\ifdim\spx@boxes@radius@bottomleft@x>\z@
\ifdim\spx@boxes@radius@bottomleft@x=\spx@boxes@radius@bottomleft@y
\spx@circlearc[2]{\spx@boxes@radius@bottomleft@x}%
{\spx@boxes@radius@bottomleft@y}%
{\spx@boxes@radius@bottomleft@x}{180}{270}%
\else
\spx@elliparc[2]{\spx@boxes@radius@bottomleft@x}%
{\spx@boxes@radius@bottomleft@y}%
{\spx@boxes@radius@bottomleft@x}%
{\spx@boxes@radius@bottomleft@y}{180}{270}%
\fi
\fi
}% end of definition of \spx@boxes@border@defpath