Support user defined LaTeX themes

This commit is contained in:
Takeshi KOMIYA 2019-12-28 19:36:55 +09:00
parent a13ec4f41c
commit cdbefb600f
7 changed files with 97 additions and 13 deletions

View File

@ -1904,7 +1904,7 @@ These options influence LaTeX output.
This value determines how to group the document tree into LaTeX source files. This value determines how to group the document tree into LaTeX source files.
It must be a list of tuples ``(startdocname, targetname, title, author, It must be a list of tuples ``(startdocname, targetname, title, author,
documentclass, toctree_only)``, where the items are: theme, toctree_only)``, where the items are:
*startdocname* *startdocname*
String that specifies the :term:`document name` of the LaTeX file's master String that specifies the :term:`document name` of the LaTeX file's master
@ -1926,13 +1926,8 @@ These options influence LaTeX output.
applies. Use ``\\and`` to separate multiple authors, as in: applies. Use ``\\and`` to separate multiple authors, as in:
``'John \\and Sarah'`` (backslashes must be Python-escaped to reach LaTeX). ``'John \\and Sarah'`` (backslashes must be Python-escaped to reach LaTeX).
*documentclass* *theme*
Normally, one of ``'manual'`` or ``'howto'`` (provided by Sphinx and based LaTeX theme. See :confval:`latex_theme`.
on ``'report'``, resp. ``'article'``; Japanese documents use ``'jsbook'``,
resp. ``'jreport'``.) "howto" (non-Japanese) documents will not get
appendices. Also they have a simpler title page. Other document classes
can be given. Independently of the document class, the "sphinx" package is
always loaded in order to define Sphinx's custom LaTeX commands.
*toctree_only* *toctree_only*
Must be ``True`` or ``False``. If true, the *startdoc* document itself is Must be ``True`` or ``False``. If true, the *startdoc* document itself is
@ -2087,6 +2082,33 @@ These options influence LaTeX output.
This overrides the files which is provided from Sphinx such as This overrides the files which is provided from Sphinx such as
``sphinx.sty``. ``sphinx.sty``.
.. confval:: latex_theme
The "theme" that the LaTeX output should use. It is a collection of settings
for LaTeX output (ex. document class, top level sectioning unit and so on).
As a built-in LaTeX themes, ``manual`` and ``howto`` are bundled.
``manual``
A LaTeX theme for writing a manual. It imports the ``report`` document
class (Japanese documents use ``jsbook``).
``howto``
A LaTeX theme for writing an article. It imports the ``article`` document
class (Japanese documents use ``jreport`` rather). :confval:`latex_appendices`
is available only for this theme.
It defaults to ``'manual'``.
.. versionadded:: 3.0
.. confval:: latex_theme_path
A list of paths that contain custom LaTeX themes as subdirectories. Relative
paths are taken as relative to the configuration directory.
.. versionadded:: 3.0
.. _text-options: .. _text-options:

View File

@ -493,7 +493,7 @@ def default_latex_documents(config: Config) -> List[Tuple[str, str, str, str, st
make_filename_from_project(config.project) + '.tex', make_filename_from_project(config.project) + '.tex',
texescape.escape_abbr(project), texescape.escape_abbr(project),
texescape.escape_abbr(author), texescape.escape_abbr(author),
'manual')] config.latex_theme)]
def setup(app: Sphinx) -> Dict[str, Any]: def setup(app: Sphinx) -> Dict[str, Any]:
@ -516,6 +516,8 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('latex_show_pagerefs', False, None) app.add_config_value('latex_show_pagerefs', False, None)
app.add_config_value('latex_elements', {}, None) app.add_config_value('latex_elements', {}, None)
app.add_config_value('latex_additional_files', [], None) app.add_config_value('latex_additional_files', [], None)
app.add_config_value('latex_theme', 'manual', None, [str])
app.add_config_value('latex_theme_path', [], None, [list])
app.add_config_value('latex_docclass', default_latex_docclass, None) app.add_config_value('latex_docclass', default_latex_docclass, None)

View File

@ -8,10 +8,17 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
import configparser
from os import path
from typing import Dict from typing import Dict
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.config import Config from sphinx.config import Config
from sphinx.errors import ThemeError
from sphinx.locale import __
from sphinx.util import logging
logger = logging.getLogger(__name__)
class Theme: class Theme:
@ -56,11 +63,30 @@ class BuiltInTheme(Theme):
return 'chapter' return 'chapter'
class UserTheme(Theme):
"""A user defined LaTeX theme."""
def __init__(self, name: str, filename: str) -> None:
self.name = name
self.config = configparser.RawConfigParser()
self.config.read(path.join(filename))
try:
self.docclass = self.config.get('theme', 'docclass')
self.wrapperclass = self.config.get('theme', 'wrapperclass')
self.toplevel_sectioning = self.config.get('theme', 'toplevel_sectioning')
except configparser.NoSectionError:
raise ThemeError(__('%r doesn\'t have "theme" setting') % filename)
except configparser.NoOptionError as exc:
raise ThemeError(__('%r doesn\'t have "%s" setting') % (filename, exc.args[0]))
class ThemeFactory: class ThemeFactory:
"""A factory class for LaTeX Themes.""" """A factory class for LaTeX Themes."""
def __init__(self, app: Sphinx) -> None: def __init__(self, app: Sphinx) -> None:
self.themes = {} # type: Dict[str, Theme] self.themes = {} # type: Dict[str, Theme]
self.theme_paths = [path.join(app.srcdir, p) for p in app.config.latex_theme_path]
self.load_builtin_themes(app.config) self.load_builtin_themes(app.config)
def load_builtin_themes(self, config: Config) -> None: def load_builtin_themes(self, config: Config) -> None:
@ -70,7 +96,23 @@ class ThemeFactory:
def get(self, name: str) -> Theme: def get(self, name: str) -> Theme:
"""Get a theme for given *name*.""" """Get a theme for given *name*."""
if name not in self.themes: if name in self.themes:
return Theme(name)
else:
return self.themes[name] return self.themes[name]
else:
theme = self.find_user_theme(name)
if theme:
return theme
else:
return Theme(name)
def find_user_theme(self, name: str) -> Theme:
"""Find a theme named as *name* from latex_theme_path."""
for theme_path in self.theme_paths:
config_path = path.join(theme_path, name, 'theme.conf')
if path.isfile(config_path):
try:
return UserTheme(name, config_path)
except ThemeError as exc:
logger.warning(exc)
return None

View File

@ -0,0 +1,2 @@
latex_theme = 'custom'
latex_theme_path = ['theme']

View File

@ -0,0 +1,2 @@
latex_theme
===========

View File

@ -0,0 +1,4 @@
[theme]
docclass = book
wrapperclass = sphinxbook
toplevel_sectioning = chapter

View File

@ -20,7 +20,7 @@ from test_build_html import ENV_WARNINGS
from sphinx.builders.latex import default_latex_documents from sphinx.builders.latex import default_latex_documents
from sphinx.config import Config from sphinx.config import Config
from sphinx.errors import SphinxError from sphinx.errors import SphinxError, ThemeError
from sphinx.testing.util import strip_escseq from sphinx.testing.util import strip_escseq
from sphinx.util import docutils from sphinx.util import docutils
from sphinx.util.osutil import cd, ensuredir from sphinx.util.osutil import cd, ensuredir
@ -215,6 +215,15 @@ def test_latex_basic_howto_ja(app, status, warning):
assert r'\documentclass[letterpaper,10pt,dvipdfmx]{sphinxhowto}' in result assert r'\documentclass[letterpaper,10pt,dvipdfmx]{sphinxhowto}' in result
@pytest.mark.sphinx('latex', testroot='latex-theme')
def test_latex_theme(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'python.tex').read_text(encoding='utf8')
print(result)
assert r'\def\sphinxdocclass{book}' in result
assert r'\documentclass[letterpaper,10pt,english]{sphinxbook}' in result
@pytest.mark.sphinx('latex', testroot='basic', confoverrides={'language': 'zh'}) @pytest.mark.sphinx('latex', testroot='basic', confoverrides={'language': 'zh'})
def test_latex_additional_settings_for_language_code(app, status, warning): def test_latex_additional_settings_for_language_code(app, status, warning):
app.builder.build_all() app.builder.build_all()
@ -1465,6 +1474,7 @@ def test_default_latex_documents():
'author': "Wolfgang Schäuble & G'Beckstein."}) 'author': "Wolfgang Schäuble & G'Beckstein."})
config.init_values() config.init_values()
config.add('latex_engine', None, True, None) config.add('latex_engine', None, True, None)
config.add('latex_theme', 'manual', True, None)
expected = [('index', 'stasi.tex', 'STASI™ Documentation', expected = [('index', 'stasi.tex', 'STASI™ Documentation',
r"Wolfgang Schäuble \& G\textquotesingle{}Beckstein.\@{}", 'manual')] r"Wolfgang Schäuble \& G\textquotesingle{}Beckstein.\@{}", 'manual')]
assert default_latex_documents(config) == expected assert default_latex_documents(config) == expected