bookcraft 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. bookcraft-0.1.0/PKG-INFO +66 -0
  2. bookcraft-0.1.0/README.md +49 -0
  3. bookcraft-0.1.0/pyproject.toml +20 -0
  4. bookcraft-0.1.0/src/bookcraft/__init__.py +3 -0
  5. bookcraft-0.1.0/src/bookcraft/_generate.py +48 -0
  6. bookcraft-0.1.0/src/bookcraft/book.py +188 -0
  7. bookcraft-0.1.0/src/bookcraft/cell/CellFactory.py +56 -0
  8. bookcraft-0.1.0/src/bookcraft/cell/__init__.py +0 -0
  9. bookcraft-0.1.0/src/bookcraft/cell/rules/BreakLineRule.py +14 -0
  10. bookcraft-0.1.0/src/bookcraft/cell/rules/CharRule.py +17 -0
  11. bookcraft-0.1.0/src/bookcraft/cell/rules/HeaderSpacingRule.py +14 -0
  12. bookcraft-0.1.0/src/bookcraft/cell/rules/Rule.py +23 -0
  13. bookcraft-0.1.0/src/bookcraft/cell/rules/__init__.py +0 -0
  14. bookcraft-0.1.0/src/bookcraft/cli.py +23 -0
  15. bookcraft-0.1.0/src/bookcraft/config.py +134 -0
  16. bookcraft-0.1.0/src/bookcraft/cursor/CursorModifierFactory.py +99 -0
  17. bookcraft-0.1.0/src/bookcraft/cursor/CursorModifierProcessor.py +40 -0
  18. bookcraft-0.1.0/src/bookcraft/cursor/CursorModifierReducer.py +28 -0
  19. bookcraft-0.1.0/src/bookcraft/cursor/__init__.py +0 -0
  20. bookcraft-0.1.0/src/bookcraft/cursor/rules/BlankSpaceRule.py +20 -0
  21. bookcraft-0.1.0/src/bookcraft/cursor/rules/ContentsItemRule.py +15 -0
  22. bookcraft-0.1.0/src/bookcraft/cursor/rules/DarkenRoleRule.py +23 -0
  23. bookcraft-0.1.0/src/bookcraft/cursor/rules/DefaultRule.py +11 -0
  24. bookcraft-0.1.0/src/bookcraft/cursor/rules/HeaderRule.py +12 -0
  25. bookcraft-0.1.0/src/bookcraft/cursor/rules/HyphenRule.py +11 -0
  26. bookcraft-0.1.0/src/bookcraft/cursor/rules/ItalicRule.py +25 -0
  27. bookcraft-0.1.0/src/bookcraft/cursor/rules/KeywordRule.py +25 -0
  28. bookcraft-0.1.0/src/bookcraft/cursor/rules/ParenthesisRule.py +17 -0
  29. bookcraft-0.1.0/src/bookcraft/cursor/rules/RoleRule.py +23 -0
  30. bookcraft-0.1.0/src/bookcraft/cursor/rules/Rule.py +23 -0
  31. bookcraft-0.1.0/src/bookcraft/cursor/rules/__init__.py +0 -0
  32. bookcraft-0.1.0/src/bookcraft/helpers.py +110 -0
  33. bookcraft-0.1.0/src/bookcraft/models.py +83 -0
  34. bookcraft-0.1.0/src/bookcraft/specs/AfterRoleSpecification.py +16 -0
  35. bookcraft-0.1.0/src/bookcraft/specs/FirstLineSpecification.py +7 -0
  36. bookcraft-0.1.0/src/bookcraft/specs/IsBoldSpecification.py +7 -0
  37. bookcraft-0.1.0/src/bookcraft/specs/IsCharEqualSpecification.py +13 -0
  38. bookcraft-0.1.0/src/bookcraft/specs/IsEndOfLineSpecification.py +11 -0
  39. bookcraft-0.1.0/src/bookcraft/specs/IsItalicSpecification.py +7 -0
  40. bookcraft-0.1.0/src/bookcraft/specs/LineHasCharSpecification.py +17 -0
  41. bookcraft-0.1.0/src/bookcraft/specs/NextCharEquals.py +19 -0
  42. bookcraft-0.1.0/src/bookcraft/specs/PreviousCharEquals.py +16 -0
  43. bookcraft-0.1.0/src/bookcraft/specs/Specification.py +50 -0
  44. bookcraft-0.1.0/src/bookcraft/specs/__init__.py +0 -0
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.3
2
+ Name: bookcraft
3
+ Version: 0.1.0
4
+ Summary: A PDF book generator for structured ritual and ceremonial texts
5
+ Author: Albert Kolozsvari
6
+ Author-email: albertartk@gmail.com
7
+ Requires-Python: >=3.11.0,<4.0.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Requires-Dist: PyYAML (>=6.0.2)
13
+ Requires-Dist: fpdf2 (>=2.8.5,<3.0.0)
14
+ Requires-Dist: typing-extensions (>=4.15.0,<5.0.0)
15
+ Description-Content-Type: text/markdown
16
+
17
+ # bookcraft
18
+
19
+ A Python library for generating PDF books from structured text content.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install bookcraft
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ from bookcraft import generate
31
+
32
+ generate(
33
+ books_path="./books/",
34
+ settings_path="./c-settings.yaml",
35
+ fonts_path="./fonts.yaml",
36
+ keywords_path="./c-keywords.yaml",
37
+ output_path="./output/craft.pdf",
38
+ mode="craft",
39
+ )
40
+ ```
41
+
42
+ ### Modes
43
+
44
+ | Mode | Description |
45
+ |------|-------------|
46
+ | `craft` | Craft, light theme |
47
+ | `craft-dark` | Craft, dark theme |
48
+ | `ra` | Royal Arch, light theme |
49
+ | `ra-dark` | Royal Arch, dark theme |
50
+
51
+ ## Content layout
52
+
53
+ Your content project should provide:
54
+
55
+ ```
56
+ your-project/
57
+ ├── books/ # directories of page-N.txt files
58
+ ├── fonts/ # font files referenced in fonts.yaml
59
+ ├── fonts.yaml
60
+ ├── c-settings.yaml
61
+ ├── c-keywords.yaml
62
+ ├── ra-settings.yaml
63
+ ├── ra-keywords.yaml
64
+ └── output/ # generated PDFs written here
65
+ ```
66
+
@@ -0,0 +1,49 @@
1
+ # bookcraft
2
+
3
+ A Python library for generating PDF books from structured text content.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install bookcraft
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from bookcraft import generate
15
+
16
+ generate(
17
+ books_path="./books/",
18
+ settings_path="./c-settings.yaml",
19
+ fonts_path="./fonts.yaml",
20
+ keywords_path="./c-keywords.yaml",
21
+ output_path="./output/craft.pdf",
22
+ mode="craft",
23
+ )
24
+ ```
25
+
26
+ ### Modes
27
+
28
+ | Mode | Description |
29
+ |------|-------------|
30
+ | `craft` | Craft, light theme |
31
+ | `craft-dark` | Craft, dark theme |
32
+ | `ra` | Royal Arch, light theme |
33
+ | `ra-dark` | Royal Arch, dark theme |
34
+
35
+ ## Content layout
36
+
37
+ Your content project should provide:
38
+
39
+ ```
40
+ your-project/
41
+ ├── books/ # directories of page-N.txt files
42
+ ├── fonts/ # font files referenced in fonts.yaml
43
+ ├── fonts.yaml
44
+ ├── c-settings.yaml
45
+ ├── c-keywords.yaml
46
+ ├── ra-settings.yaml
47
+ ├── ra-keywords.yaml
48
+ └── output/ # generated PDFs written here
49
+ ```
@@ -0,0 +1,20 @@
1
+ [tool.poetry]
2
+ name = "bookcraft"
3
+ version = "0.1.0"
4
+ description = "A PDF book generator for structured ritual and ceremonial texts"
5
+ authors = ["Albert Kolozsvari <albertartk@gmail.com>"]
6
+ readme = "README.md"
7
+ packages = [{include = "bookcraft", from = "src"}]
8
+
9
+ [tool.poetry.dependencies]
10
+ python = "^3.11.0"
11
+ PyYAML = ">=6.0.2"
12
+ fpdf2 = "^2.8.5"
13
+ typing-extensions = "^4.15.0"
14
+
15
+ [tool.poetry.scripts]
16
+ bookcraft = "bookcraft.cli:main"
17
+
18
+ [build-system]
19
+ requires = ["poetry-core"]
20
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,3 @@
1
+ from bookcraft._generate import generate
2
+
3
+ __all__ = ["generate"]
@@ -0,0 +1,48 @@
1
+ from bookcraft.book import Book
2
+ from bookcraft.cell.CellFactory import CellFactory
3
+ from bookcraft.config import load_config
4
+ from bookcraft.cursor.CursorModifierFactory import CursorModifierFactory
5
+ from bookcraft.cursor.CursorModifierProcessor import CursorModifierProcessor
6
+ from bookcraft.cursor.CursorModifierReducer import CursorModifierReducer
7
+ from bookcraft.helpers import get_files
8
+
9
+ _SWITCH_MAP = {
10
+ "craft": "c-",
11
+ "craft-dark": "c-dark-",
12
+ "ra": "ra-",
13
+ "ra-dark": "ra-dark-",
14
+ }
15
+
16
+
17
+ def generate(
18
+ books_path: str,
19
+ settings_path: str,
20
+ fonts_path: str,
21
+ keywords_path: str,
22
+ output_path: str,
23
+ mode: str = "craft",
24
+ ) -> None:
25
+ if mode not in _SWITCH_MAP:
26
+ raise ValueError(f"Unknown mode '{mode}'. Choose from: {', '.join(_SWITCH_MAP)}")
27
+
28
+ switch = _SWITCH_MAP[mode]
29
+ config = load_config(books_path, settings_path, fonts_path, keywords_path, switch)
30
+
31
+ book = Book(config)
32
+ book.set_title(config.SETTINGS["title"]["text"])
33
+ book.set_book_font(config.FONTS)
34
+
35
+ for book_title in config.SETTINGS["books"]:
36
+ if config.is_dark:
37
+ book.page_background = (18, 18, 18)
38
+
39
+ book.set_path(books_path + book_title)
40
+ book.set_margin(config.PAGE)
41
+ book.set_subject(book_title)
42
+ book.set_cm_factory(CursorModifierFactory())
43
+ book.set_cell_factory(CellFactory())
44
+ book.set_cm_processor(CursorModifierProcessor())
45
+ book.set_cm_reducer(CursorModifierReducer())
46
+ book.set_pages(get_files(books_path + book_title))
47
+
48
+ book.build(output_path)
@@ -0,0 +1,188 @@
1
+ from __future__ import annotations
2
+
3
+ from fpdf import FPDF
4
+
5
+ from bookcraft.cell.CellFactory import CellFactory
6
+ from bookcraft.config import Config
7
+ from bookcraft.cursor.CursorModifierFactory import CursorModifierFactory
8
+ from bookcraft.cursor.CursorModifierProcessor import CursorModifierProcessor
9
+ from bookcraft.cursor.CursorModifierReducer import CursorModifierReducer
10
+ from bookcraft.helpers import get_pages
11
+ from bookcraft.models import Cell, Context, CursorModifier, Page
12
+
13
+
14
+ class Book(FPDF):
15
+ def __init__(self, config: Config) -> Book:
16
+ self.config = config
17
+ format = (535, 785) if config.is_ra else (475, 785)
18
+ super().__init__(unit="pt", format=format)
19
+ self._page_subjects = {}
20
+
21
+ def header(self) -> None:
22
+ subject = self._page_subjects.get(self.page_no(), self.subject)
23
+ if "Cover" in subject and self.config.is_ra:
24
+ return
25
+
26
+ page_no = self.page_no() + 22 if self.config.is_ra else self.page_no() - 5
27
+ self.set_font(**self.config.TEMPLATE_FONT)
28
+ self.set_text_color(*self.config.TEMPLATE_COLOR)
29
+ self.set_draw_color(*self.config.TEMPLATE_COLOR)
30
+ width = self.w - self.l_margin - self.r_margin
31
+ height = self.config.TEMPLATE_HEIGHT
32
+ page_no_width = self.get_string_width(f"{page_no}")
33
+
34
+ # Use per-page subject if available
35
+ subject = self._page_subjects.get(self.page_no(), self.subject)
36
+ subject_width = self.get_string_width(subject)
37
+
38
+ line_start = self.r_margin
39
+ line_end = self.w - self.r_margin
40
+
41
+ if page_no > 0 and page_no < 150:
42
+ self.cell(subject_width, height, subject)
43
+ self.cell(width - page_no_width - subject_width, height, "", 0)
44
+ self.cell(page_no_width, height, f"{page_no}", 0, 1)
45
+ self.cell(width, height, "", 0, 1)
46
+ self.dashed_line(line_start, self.y, line_end, self.y, 3, 3)
47
+ self.cell(width, height, "", 0, 1)
48
+ self.cell(width, height / 2, "", 0, 1)
49
+
50
+ def set_path(self, book_path: str) -> Book:
51
+ self.book_path = book_path
52
+
53
+ return self
54
+
55
+ def set_book_font(self, fonts: dict) -> Book:
56
+ [self.add_font(**font, uni=True) for font in fonts]
57
+
58
+ return self
59
+
60
+ def set_margin(self, page: dict) -> Book:
61
+ self.set_top_margin(page["top_margin"])
62
+ self.set_left_margin(page["margin_size"])
63
+ self.set_right_margin(page["margin_size"])
64
+ self.set_auto_page_break(True, page["bottom_margin"])
65
+ self.c_margin = 0
66
+
67
+ return self
68
+
69
+ def set_title(self, title: str) -> Book:
70
+ title = title.split("/")[-1]
71
+ super().set_title(title)
72
+
73
+ return self
74
+
75
+ def set_subject(self, subject: str) -> Book:
76
+ subject = subject.split("/")[-1]
77
+ super().set_subject(subject)
78
+ # Mark all future pages with this subject until changed
79
+ self._current_subject = subject
80
+ return self
81
+
82
+ def set_cm_reducer(self, cm_reducer: CursorModifierReducer) -> Book:
83
+ self.cm_reducer = cm_reducer
84
+
85
+ return self
86
+
87
+ def set_cm_processor(self, cm_processor: CursorModifierProcessor) -> Book:
88
+ self.cm_processor = cm_processor
89
+
90
+ return self
91
+
92
+ def set_cm_factory(self, cm_factory: CursorModifierFactory) -> Book:
93
+ self.cm_factory = cm_factory
94
+
95
+ return self
96
+
97
+ def set_cell_factory(self, cell_factory: CellFactory) -> Book:
98
+ self.cell_factory = cell_factory
99
+
100
+ return self
101
+
102
+ def set_pages(self, pages: list[Page]) -> Book:
103
+ self.modifiers: list[CursorModifier] = []
104
+
105
+ for page_index in range(1, len(pages) + 1):
106
+ memory = get_pages(self.book_path, page_index, 2)
107
+ self._print_page(memory)
108
+ # After adding a page, record the subject for that page number
109
+ self._page_subjects[self.page_no()] = getattr(self, "_current_subject", getattr(self, "subject", ""))
110
+
111
+ return self
112
+
113
+ def build(self, output_path: str) -> None:
114
+ self.output(output_path)
115
+
116
+ def _print_page(self, memory: list[Page]) -> list[CursorModifier]:
117
+ self.add_page()
118
+
119
+ page = memory[0]
120
+ for i in range(len(page)):
121
+ line = page[i]
122
+ body = []
123
+
124
+ for j in range(len(line)):
125
+ # get previous cell cursor
126
+ cursor = self.cm_reducer.reduce(self.modifiers)
127
+ context = Context(i, j, memory, self.config, cursor)
128
+
129
+ new_cms = self.cm_factory.resolve(context)
130
+ self.modifiers.extend(new_cms)
131
+ self.modifiers = self.cm_processor.process(self.modifiers)
132
+
133
+ # get current cell cursor
134
+ cursor = self.cm_reducer.reduce(self.modifiers)
135
+ context = Context(i, j, memory, self.config, cursor)
136
+
137
+ # get new cells
138
+ cells = self.cell_factory.create_cells(context)
139
+
140
+ if not cells:
141
+ continue
142
+
143
+ body.extend(cells)
144
+
145
+ self._print_line(body, line)
146
+
147
+ def _print_line(self, cells: list[Cell], memory_line: list[str]) -> None:
148
+ cells = self._justify_line(cells, memory_line)
149
+ for cell in cells:
150
+ cursor = cell.cursor
151
+ self.set_font(cursor.family, cursor.style, cursor.size)
152
+ self.set_text_color(*cursor.colour)
153
+ if cursor.fill is not None:
154
+ self.set_fill_color(*cursor.fill)
155
+ self.set_draw_color(*cursor.fill)
156
+ else:
157
+ cell.has_fill = False
158
+
159
+ self.cell(
160
+ w=cell.width,
161
+ h=cell.height,
162
+ txt=cell.text,
163
+ ln=cell.has_break,
164
+ fill=cell.has_fill,
165
+ )
166
+
167
+ def _justify_line(self, cells: list[Cell], memory_line: list[str]) -> list[Cell]:
168
+ width = self.w - self.l_margin - self.r_margin
169
+ clean_line_w = 0
170
+ for cell in cells:
171
+ cursor = cell.cursor
172
+ self.set_font(cursor.family, cursor.style, cursor.size)
173
+ cell.width = self.get_string_width(cell.text)
174
+ if cell.text != " ":
175
+ clean_line_w += cell.width
176
+
177
+ char_check = any([char in memory_line for char in ["=", "#"]])
178
+ if memory_line.count(" ") and not char_check:
179
+ space_width = (width - clean_line_w) / memory_line.count(" ")
180
+ for cell in cells:
181
+ if cell.text == " ":
182
+ cell.width = space_width
183
+
184
+ for cell in cells:
185
+ if cell.text == "^":
186
+ cell.text = " "
187
+
188
+ return cells
@@ -0,0 +1,56 @@
1
+ from dataclasses import dataclass
2
+
3
+ from bookcraft.cell.rules.BreakLineRule import BreakLineRule
4
+ from bookcraft.cell.rules.CharRule import CharRule
5
+ from bookcraft.cell.rules.HeaderSpacingRule import HeaderSpacingRule
6
+ from bookcraft.cell.rules.Rule import CellRule, CellSpecificationRule
7
+ from bookcraft.models import Cell, Context
8
+ from bookcraft.specs.FirstLineSpecification import FirstLineSpecification
9
+ from bookcraft.specs.IsCharEqualSpecification import IsCharEqualSpecification
10
+ from bookcraft.specs.IsEndOfLineSpecification import IsEndOfLineSpecification
11
+ from bookcraft.specs.LineHasCharSpecification import LineHasCharSpecification
12
+ from bookcraft.specs.Specification import AndSpecification, NotSpecification
13
+
14
+
15
+ @dataclass
16
+ class CellFactory:
17
+ rules: tuple[CellRule, ...] = (
18
+ CellSpecificationRule(
19
+ AndSpecification(
20
+ NotSpecification(LineHasCharSpecification(["#"], -1)),
21
+ IsCharEqualSpecification(["#"]),
22
+ NotSpecification(FirstLineSpecification()),
23
+ ),
24
+ HeaderSpacingRule(),
25
+ ),
26
+ CellSpecificationRule(
27
+ NotSpecification(
28
+ IsCharEqualSpecification(["#", ">", "$", "<", "=", "%", "&"])
29
+ ),
30
+ CharRule(),
31
+ ),
32
+ CellSpecificationRule(
33
+ IsEndOfLineSpecification(),
34
+ BreakLineRule(),
35
+ ),
36
+ CellSpecificationRule(
37
+ AndSpecification(
38
+ IsEndOfLineSpecification(),
39
+ LineHasCharSpecification(["#"], 0),
40
+ NotSpecification(LineHasCharSpecification(["#"], 1)),
41
+ ),
42
+ HeaderSpacingRule(),
43
+ ),
44
+ )
45
+
46
+ def create_cells(self, context: Context) -> list[Cell]:
47
+ cells = []
48
+ for rule in self.rules:
49
+ cell = rule.apply(context)
50
+
51
+ if cell is None:
52
+ continue
53
+
54
+ cells.append(cell)
55
+
56
+ return cells
File without changes
@@ -0,0 +1,14 @@
1
+ from bookcraft.cell.rules.Rule import CellRule
2
+ from bookcraft.models import Cell, Context
3
+
4
+
5
+ class BreakLineRule(CellRule):
6
+ def apply(self, context: Context) -> Cell:
7
+ CONFIG = context.config
8
+ return Cell(
9
+ width=0,
10
+ height=CONFIG.DEFAULT_HEIGHT,
11
+ text="",
12
+ has_break=True,
13
+ cursor=context.cursor,
14
+ )
@@ -0,0 +1,17 @@
1
+ from bookcraft.cell.rules.Rule import CellRule
2
+ from bookcraft.models import Cell, Context
3
+
4
+
5
+ class CharRule(CellRule):
6
+ def apply(self, context: Context) -> Cell:
7
+ CONFIG = context.config
8
+ page = context.memory[0]
9
+ char = page[context.i][context.j]
10
+
11
+ return Cell(
12
+ width=0,
13
+ height=CONFIG.DEFAULT_HEIGHT,
14
+ text=char,
15
+ cursor=context.cursor,
16
+ has_fill=True,
17
+ )
@@ -0,0 +1,14 @@
1
+ from bookcraft.cell.rules.Rule import CellRule
2
+ from bookcraft.models import Cell, Context
3
+
4
+
5
+ class HeaderSpacingRule(CellRule):
6
+ def apply(self, context: Context) -> Cell:
7
+ CONFIG = context.config
8
+ return Cell(
9
+ width=0,
10
+ height=CONFIG.HEADING_SPACING_HEIGHT,
11
+ text="",
12
+ has_break=True,
13
+ cursor=context.cursor,
14
+ )
@@ -0,0 +1,23 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional
3
+
4
+ from bookcraft.models import Cell, Context
5
+ from bookcraft.specs.Specification import Specification
6
+
7
+
8
+ class CellRule(ABC):
9
+ @abstractmethod
10
+ def apply(self, context: Context) -> Cell:
11
+ pass
12
+
13
+
14
+ class CellSpecificationRule(CellRule):
15
+ def __init__(self, specification: Specification, rule: CellRule) -> None:
16
+ self.specification = specification
17
+ self.rule = rule
18
+
19
+ def apply(self, context: Context) -> Optional[Cell]:
20
+ if self.specification.is_satisfied(context):
21
+ return self.rule.apply(context)
22
+
23
+ return None
File without changes
@@ -0,0 +1,23 @@
1
+ import argparse
2
+
3
+ from bookcraft._generate import generate, _SWITCH_MAP
4
+
5
+
6
+ def main() -> None:
7
+ parser = argparse.ArgumentParser(description="Generate a PDF book with bookcraft.")
8
+ parser.add_argument("mode", choices=list(_SWITCH_MAP), help="Rendering mode")
9
+ parser.add_argument("--books", required=True, help="Path to books directory")
10
+ parser.add_argument("--settings", required=True, help="Path to settings YAML")
11
+ parser.add_argument("--fonts", required=True, help="Path to fonts YAML")
12
+ parser.add_argument("--keywords", required=True, help="Path to keywords YAML")
13
+ parser.add_argument("--output", required=True, help="Output PDF path")
14
+ args = parser.parse_args()
15
+
16
+ generate(
17
+ books_path=args.books,
18
+ settings_path=args.settings,
19
+ fonts_path=args.fonts,
20
+ keywords_path=args.keywords,
21
+ output_path=args.output,
22
+ mode=args.mode,
23
+ )
@@ -0,0 +1,134 @@
1
+ from dataclasses import dataclass
2
+
3
+ import yaml
4
+
5
+
6
+ @dataclass
7
+ class Config:
8
+ BOOKS_PATH: str
9
+ FONTS: dict
10
+ SETTINGS: dict
11
+ KEYWORDS: list
12
+ switch: str # "c-", "c-dark-", "ra-", "ra-dark-"
13
+
14
+ @property
15
+ def is_dark(self) -> bool:
16
+ return "dark" in self.switch
17
+
18
+ @property
19
+ def is_ra(self) -> bool:
20
+ return "ra" in self.switch
21
+
22
+ @property
23
+ def PAGE(self) -> dict:
24
+ return self.SETTINGS.get("page")
25
+
26
+ @property
27
+ def COLOUR(self) -> dict:
28
+ return self.SETTINGS.get("colour")
29
+
30
+ @property
31
+ def ROLES(self) -> dict:
32
+ return self.SETTINGS.get("roles")
33
+
34
+ @property
35
+ def TEXT(self) -> dict:
36
+ return self.SETTINGS.get("text")
37
+
38
+ @property
39
+ def DEFAULT_CURSOR(self) -> dict:
40
+ return {
41
+ "family": self.TEXT["default"]["cursor"]["family"],
42
+ "style": self.TEXT["default"]["cursor"]["style"],
43
+ "size": self.TEXT["default"]["cursor"]["size"],
44
+ "colour": self.TEXT["default"]["cursor"]["colour"],
45
+ "fill": self.TEXT["default"]["cursor"]["fill"],
46
+ }
47
+
48
+ @property
49
+ def DEFAULT_FONT(self) -> dict:
50
+ return {
51
+ "family": self.TEXT["default"]["cursor"]["family"],
52
+ "style": self.TEXT["default"]["cursor"]["style"],
53
+ "size": self.TEXT["default"]["cursor"]["size"],
54
+ }
55
+
56
+ @property
57
+ def DEFAULT_HEIGHT(self) -> dict:
58
+ return self.TEXT["default"]["height"]
59
+
60
+ @property
61
+ def DEFAULT_FILL(self) -> dict:
62
+ return self.TEXT["default"]["cursor"]["fill"]
63
+
64
+ @property
65
+ def TEMPLATE_FONT(self) -> dict:
66
+ return {
67
+ "family": self.TEXT["template"]["cursor"]["family"],
68
+ "style": self.TEXT["template"]["cursor"]["style"],
69
+ "size": self.TEXT["template"]["cursor"]["size"],
70
+ }
71
+
72
+ @property
73
+ def TEMPLATE_COLOR(self) -> dict:
74
+ return self.TEXT["template"]["cursor"]["colour"]
75
+
76
+ @property
77
+ def TEMPLATE_HEIGHT(self) -> int:
78
+ return self.TEXT["template"]["height"]
79
+
80
+ @property
81
+ def BOLD_CURSOR(self) -> dict:
82
+ return {
83
+ "family": self.TEXT["bold"]["cursor"]["family"],
84
+ "style": self.TEXT["bold"]["cursor"]["style"],
85
+ "size": self.TEXT["bold"]["cursor"]["size"],
86
+ "colour": self.TEXT["bold"]["cursor"]["colour"],
87
+ "fill": self.TEXT["bold"]["cursor"]["fill"],
88
+ }
89
+
90
+ @property
91
+ def ITALIC_CURSOR(self) -> dict:
92
+ return {
93
+ "family": self.TEXT["italic"]["cursor"]["family"],
94
+ "style": self.TEXT["italic"]["cursor"]["style"],
95
+ "size": self.TEXT["italic"]["cursor"]["size"],
96
+ "colour": self.TEXT["italic"]["cursor"]["colour"],
97
+ "fill": self.TEXT["italic"]["cursor"]["fill"],
98
+ }
99
+
100
+ @property
101
+ def HEADING_CURSOR(self) -> dict:
102
+ return {
103
+ "family": self.TEXT["heading"]["cursor"]["family"],
104
+ "style": self.TEXT["heading"]["cursor"]["style"],
105
+ "size": self.TEXT["heading"]["cursor"]["size"],
106
+ "colour": self.TEXT["heading"]["cursor"]["colour"],
107
+ "fill": self.TEXT["heading"]["cursor"]["fill"],
108
+ }
109
+
110
+ @property
111
+ def HEADING_SPACING_HEIGHT(self) -> int:
112
+ return self.TEXT["heading_spacing"]["height"]
113
+
114
+
115
+ def load_config(
116
+ books_path: str,
117
+ settings_path: str,
118
+ fonts_path: str,
119
+ keywords_path: str,
120
+ switch: str,
121
+ ) -> Config:
122
+ with open(fonts_path, "r") as f:
123
+ fonts = yaml.safe_load(f).get("fonts")
124
+ with open(settings_path, "r") as f:
125
+ settings = yaml.safe_load(f)
126
+ with open(keywords_path, "r") as f:
127
+ keywords = yaml.safe_load(f).get("keywords")
128
+ return Config(
129
+ BOOKS_PATH=books_path,
130
+ FONTS=fonts,
131
+ SETTINGS=settings,
132
+ KEYWORDS=keywords,
133
+ switch=switch,
134
+ )