summaryrefslogtreecommitdiffstats
path: root/sphinx/builders/latex/theming.py
blob: 21b49e8ab0ddb4c93d9035603a759eff9db6c566 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
"""Theming support for LaTeX builder."""

from __future__ import annotations

import configparser
from os import path
from typing import TYPE_CHECKING

from sphinx.errors import ThemeError
from sphinx.locale import __
from sphinx.util import logging

if TYPE_CHECKING:
    from sphinx.application import Sphinx
    from sphinx.config import Config

logger = logging.getLogger(__name__)


class Theme:
    """A set of LaTeX configurations."""

    LATEX_ELEMENTS_KEYS = ['papersize', 'pointsize']
    UPDATABLE_KEYS = ['papersize', 'pointsize']

    def __init__(self, name: str) -> None:
        self.name = name
        self.docclass = name
        self.wrapperclass = name
        self.papersize = 'letterpaper'
        self.pointsize = '10pt'
        self.toplevel_sectioning = 'chapter'

    def update(self, config: Config) -> None:
        """Override theme settings by user's configuration."""
        for key in self.LATEX_ELEMENTS_KEYS:
            if config.latex_elements.get(key):
                value = config.latex_elements[key]
                setattr(self, key, value)

        for key in self.UPDATABLE_KEYS:
            if key in config.latex_theme_options:
                value = config.latex_theme_options[key]
                setattr(self, key, value)


class BuiltInTheme(Theme):
    """A built-in LaTeX theme."""

    def __init__(self, name: str, config: Config) -> None:
        super().__init__(name)

        if name == 'howto':
            self.docclass = config.latex_docclass.get('howto', 'article')
        else:
            self.docclass = config.latex_docclass.get('manual', 'report')

        if name in ('manual', 'howto'):
            self.wrapperclass = 'sphinx' + name
        else:
            self.wrapperclass = name

        # we assume LaTeX class provides \chapter command except in case
        # of non-Japanese 'howto' case
        if name == 'howto' and not self.docclass.startswith('j'):
            self.toplevel_sectioning = 'section'
        else:
            self.toplevel_sectioning = 'chapter'


class UserTheme(Theme):
    """A user defined LaTeX theme."""

    REQUIRED_CONFIG_KEYS = ['docclass', 'wrapperclass']
    OPTIONAL_CONFIG_KEYS = ['papersize', 'pointsize', 'toplevel_sectioning']

    def __init__(self, name: str, filename: str) -> None:
        super().__init__(name)
        self.config = configparser.RawConfigParser()
        self.config.read(path.join(filename), encoding='utf-8')

        for key in self.REQUIRED_CONFIG_KEYS:
            try:
                value = self.config.get('theme', key)
                setattr(self, key, value)
            except configparser.NoSectionError as exc:
                raise ThemeError(__('%r doesn\'t have "theme" setting') %
                                 filename) from exc
            except configparser.NoOptionError as exc:
                raise ThemeError(__('%r doesn\'t have "%s" setting') %
                                 (filename, exc.args[0])) from exc

        for key in self.OPTIONAL_CONFIG_KEYS:
            try:
                value = self.config.get('theme', key)
                setattr(self, key, value)
            except configparser.NoOptionError:
                pass


class ThemeFactory:
    """A factory class for LaTeX Themes."""

    def __init__(self, app: Sphinx) -> None:
        self.themes: dict[str, Theme] = {}
        self.theme_paths = [path.join(app.srcdir, p) for p in app.config.latex_theme_path]
        self.config = app.config
        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 in self.themes:
            theme = self.themes[name]
        else:
            theme = self.find_user_theme(name) or Theme(name)

        theme.update(self.config)
        return theme

    def find_user_theme(self, name: str) -> Theme | None:
        """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