Support LaTeX Theming; a set of document class settings (refs: #6672)

This commit is contained in:
Takeshi KOMIYA 2019-12-21 17:49:49 +09:00
parent 4ab4100cc5
commit a13ec4f41c
5 changed files with 139 additions and 21 deletions

View File

@ -42,6 +42,7 @@ Deprecated
* ``sphinx.testing.path.Path.text()`` * ``sphinx.testing.path.Path.text()``
* ``sphinx.testing.path.Path.bytes()`` * ``sphinx.testing.path.Path.bytes()``
* ``sphinx.util.inspect.getargspec()`` * ``sphinx.util.inspect.getargspec()``
* ``sphinx.writers.latex.LaTeXWriter.format_docclass()``
Features added Features added
-------------- --------------
@ -68,6 +69,7 @@ Features added
``no-scaled-link`` class ``no-scaled-link`` class
* #7144: Add CSS class indicating its domain for each desc node * #7144: Add CSS class indicating its domain for each desc node
* #7211: latex: Use babel for Chinese document when using XeLaTeX * #7211: latex: Use babel for Chinese document when using XeLaTeX
* #6672: LaTeX: Support LaTeX Theming (experimental)
* #7005: LaTeX: Add LaTeX styling macro for :rst:role:`kbd` role * #7005: LaTeX: Add LaTeX styling macro for :rst:role:`kbd` role
* #7220: genindex: Show "main" index entries at first * #7220: genindex: Show "main" index entries at first
* #7103: linkcheck: writes all links to ``output.json`` * #7103: linkcheck: writes all links to ``output.json``

View File

@ -61,6 +61,11 @@ The following is a list of deprecated interfaces.
- 5.0 - 5.0
- ``inspect.getargspec()`` - ``inspect.getargspec()``
* - ``sphinx.writers.latex.LaTeXWriter.format_docclass()``
- 3.0
- 5.0
- LaTeX Themes
* - ``decode`` argument of ``sphinx.pycode.ModuleAnalyzer()`` * - ``decode`` argument of ``sphinx.pycode.ModuleAnalyzer()``
- 2.4 - 2.4
- 4.0 - 4.0

View File

@ -21,6 +21,7 @@ from sphinx import package_dir, addnodes, highlighting
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.builders import Builder from sphinx.builders import Builder
from sphinx.builders.latex.constants import ADDITIONAL_SETTINGS, DEFAULT_SETTINGS from sphinx.builders.latex.constants import ADDITIONAL_SETTINGS, DEFAULT_SETTINGS
from sphinx.builders.latex.theming import Theme, ThemeFactory
from sphinx.builders.latex.util import ExtBabel from sphinx.builders.latex.util import ExtBabel
from sphinx.config import Config, ENUM from sphinx.config import Config, ENUM
from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.deprecation import RemovedInSphinx40Warning
@ -126,6 +127,7 @@ class LaTeXBuilder(Builder):
self.context = {} # type: Dict[str, Any] self.context = {} # type: Dict[str, Any]
self.docnames = [] # type: Iterable[str] self.docnames = [] # type: Iterable[str]
self.document_data = [] # type: List[Tuple[str, str, str, str, str, bool]] self.document_data = [] # type: List[Tuple[str, str, str, str, str, bool]]
self.themes = ThemeFactory(self.app)
self.usepackages = self.app.registry.latex_packages self.usepackages = self.app.registry.latex_packages
texescape.init() texescape.init()
@ -227,7 +229,8 @@ class LaTeXBuilder(Builder):
self.write_stylesheet() self.write_stylesheet()
for entry in self.document_data: for entry in self.document_data:
docname, targetname, title, author, docclass = entry[:5] docname, targetname, title, author, themename = entry[:5]
theme = self.themes.get(themename)
toctree_only = False toctree_only = False
if len(entry) > 5: if len(entry) > 5:
toctree_only = entry[5] toctree_only = entry[5]
@ -243,21 +246,22 @@ class LaTeXBuilder(Builder):
doctree = self.assemble_doctree( doctree = self.assemble_doctree(
docname, toctree_only, docname, toctree_only,
appendices=(self.config.latex_appendices if docclass != 'howto' else [])) appendices=(self.config.latex_appendices if theme.name != 'howto' else []))
doctree['docclass'] = docclass doctree['docclass'] = theme.docclass
doctree['contentsname'] = self.get_contentsname(docname) doctree['contentsname'] = self.get_contentsname(docname)
doctree['tocdepth'] = tocdepth doctree['tocdepth'] = tocdepth
self.post_process_images(doctree) self.post_process_images(doctree)
self.update_doc_context(title, author) self.update_doc_context(title, author, theme)
with progress_message(__("writing")): with progress_message(__("writing")):
docsettings._author = author docsettings._author = author
docsettings._title = title docsettings._title = title
docsettings._contentsname = doctree['contentsname'] docsettings._contentsname = doctree['contentsname']
docsettings._docname = docname docsettings._docname = docname
docsettings._docclass = docclass docsettings._docclass = theme.name
doctree.settings = docsettings doctree.settings = docsettings
docwriter.theme = theme
docwriter.write(doctree, destination) docwriter.write(doctree, destination)
def get_contentsname(self, indexfile: str) -> str: def get_contentsname(self, indexfile: str) -> str:
@ -270,9 +274,11 @@ class LaTeXBuilder(Builder):
return contentsname return contentsname
def update_doc_context(self, title: str, author: str) -> None: def update_doc_context(self, title: str, author: str, theme: Theme) -> None:
self.context['title'] = title self.context['title'] = title
self.context['author'] = author self.context['author'] = author
self.context['docclass'] = theme.docclass
self.context['wrapperclass'] = theme.wrapperclass
def assemble_doctree(self, indexfile: str, toctree_only: bool, appendices: List[str]) -> nodes.document: # NOQA def assemble_doctree(self, indexfile: str, toctree_only: bool, appendices: List[str]) -> nodes.document: # NOQA
self.docnames = set([indexfile] + appendices) self.docnames = set([indexfile] + appendices)

View File

@ -0,0 +1,76 @@
"""
sphinx.builders.latex.theming
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Theming support for LaTeX builder.
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from typing import Dict
from sphinx.application import Sphinx
from sphinx.config import Config
class Theme:
"""A set of LaTeX configurations."""
def __init__(self, name: str) -> None:
self.name = name
self.docclass = name
self.wrapperclass = name
self.toplevel_sectioning = 'chapter'
class BuiltInTheme(Theme):
"""A built-in LaTeX theme."""
def __init__(self, name: str, config: Config) -> None:
# Note: Don't call supermethod here.
self.name = name
self.latex_docclass = config.latex_docclass # type: Dict[str, str]
@property
def docclass(self) -> str: # type: ignore
if self.name == 'howto':
return self.latex_docclass.get('howto', 'article')
else:
return self.latex_docclass.get('manual', 'report')
@property
def wrapperclass(self) -> str: # type: ignore
if self.name in ('manual', 'howto'):
return 'sphinx' + self.name
else:
return self.name
@property
def toplevel_sectioning(self) -> str: # type: ignore
# we assume LaTeX class provides \chapter command except in case
# of non-Japanese 'howto' case
if self.name == 'howto' and not self.docclass.startswith('j'):
return 'section'
else:
return 'chapter'
class ThemeFactory:
"""A factory class for LaTeX Themes."""
def __init__(self, app: Sphinx) -> None:
self.themes = {} # type: Dict[str, Theme]
self.load_builtin_themes(app.config)
def load_builtin_themes(self, config: Config) -> None:
"""Load built-in themes."""
self.themes['manual'] = BuiltInTheme('manual', config)
self.themes['howto'] = BuiltInTheme('howto', config)
def get(self, name: str) -> Theme:
"""Get a theme for given *name*."""
if name not in self.themes:
return Theme(name)
else:
return self.themes[name]

View File

@ -23,7 +23,9 @@ from docutils.nodes import Element, Node, Text
from sphinx import addnodes from sphinx import addnodes
from sphinx import highlighting from sphinx import highlighting
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.deprecation import (
RemovedInSphinx40Warning, RemovedInSphinx50Warning, deprecated_alias
)
from sphinx.domains import IndexEntry from sphinx.domains import IndexEntry
from sphinx.domains.std import StandardDomain from sphinx.domains.std import StandardDomain
from sphinx.errors import SphinxError from sphinx.errors import SphinxError
@ -43,6 +45,7 @@ except ImportError:
if False: if False:
# For type annotation # For type annotation
from sphinx.builders.latex import LaTeXBuilder from sphinx.builders.latex import LaTeXBuilder
from sphinx.builders.latex.theming import Theme
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -93,9 +96,16 @@ class LaTeXWriter(writers.Writer):
def __init__(self, builder: "LaTeXBuilder") -> None: def __init__(self, builder: "LaTeXBuilder") -> None:
super().__init__() super().__init__()
self.builder = builder self.builder = builder
self.theme = None # type: Theme
def translate(self) -> None: def translate(self) -> None:
visitor = self.builder.create_translator(self.document, self.builder) try:
visitor = self.builder.create_translator(self.document, self.builder, self.theme)
except TypeError:
warnings.warn('LaTeXTranslator now takes 3rd argument; "theme".',
RemovedInSphinx50Warning)
visitor = self.builder.create_translator(self.document, self.builder)
self.document.walkabout(visitor) self.document.walkabout(visitor)
self.output = cast(LaTeXTranslator, visitor).astext() self.output = cast(LaTeXTranslator, visitor).astext()
@ -281,9 +291,15 @@ class LaTeXTranslator(SphinxTranslator):
# sphinx specific document classes # sphinx specific document classes
docclasses = ('howto', 'manual') docclasses = ('howto', 'manual')
def __init__(self, document: nodes.document, builder: "LaTeXBuilder") -> None: def __init__(self, document: nodes.document, builder: "LaTeXBuilder",
theme: "Theme" = None) -> None:
super().__init__(document, builder) super().__init__(document, builder)
self.body = [] # type: List[str] self.body = [] # type: List[str]
self.theme = theme
if theme is None:
warnings.warn('LaTeXTranslator now takes 3rd argument; "theme".',
RemovedInSphinx50Warning)
# flags # flags
self.in_title = 0 self.in_title = 0
@ -306,21 +322,32 @@ class LaTeXTranslator(SphinxTranslator):
# sort out some elements # sort out some elements
self.elements = self.builder.context.copy() self.elements = self.builder.context.copy()
# but some have other interface in config file # initial section names
self.elements['wrapperclass'] = self.format_docclass(document.get('docclass'))
# we assume LaTeX class provides \chapter command except in case
# of non-Japanese 'howto' case
self.sectionnames = LATEXSECTIONNAMES[:] self.sectionnames = LATEXSECTIONNAMES[:]
if document.get('docclass') == 'howto':
docclass = self.config.latex_docclass.get('howto', 'article') if self.theme:
if docclass[0] == 'j': # Japanese class... # new style: control sectioning via theme's setting
pass #
else: # .. note:: template variables(elements) are already assigned in builder
docclass = self.theme.docclass
if self.theme.toplevel_sectioning == 'section':
self.sectionnames.remove('chapter') self.sectionnames.remove('chapter')
else: else:
docclass = self.config.latex_docclass.get('manual', 'report') # old style: sectioning control is hard-coded
self.elements['docclass'] = docclass # but some have other interface in config file
self.elements['wrapperclass'] = self.format_docclass(self.settings.docclass)
# we assume LaTeX class provides \chapter command except in case
# of non-Japanese 'howto' case
if document.get('docclass') == 'howto':
docclass = self.config.latex_docclass.get('howto', 'article')
if docclass[0] == 'j': # Japanese class...
pass
else:
self.sectionnames.remove('chapter')
else:
docclass = self.config.latex_docclass.get('manual', 'report')
self.elements['docclass'] = docclass
# determine top section level # determine top section level
self.top_sectionlevel = 1 self.top_sectionlevel = 1
@ -472,6 +499,8 @@ class LaTeXTranslator(SphinxTranslator):
def format_docclass(self, docclass: str) -> str: def format_docclass(self, docclass: str) -> str:
""" prepends prefix to sphinx document classes """ prepends prefix to sphinx document classes
""" """
warnings.warn('LaTeXWriter.format_docclass() is deprecated.',
RemovedInSphinx50Warning, stacklevel=2)
if docclass in self.docclasses: if docclass in self.docclasses:
docclass = 'sphinx' + docclass docclass = 'sphinx' + docclass
return docclass return docclass