epub-generator 0.1.2__tar.gz → 0.1.3__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.
- {epub_generator-0.1.2 → epub_generator-0.1.3}/PKG-INFO +74 -30
- {epub_generator-0.1.2 → epub_generator-0.1.3}/README.md +73 -29
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/__init__.py +4 -2
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/gen_chapter.py +21 -5
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/gen_epub.py +7 -6
- epub_generator-0.1.3/epub_generator/html_tag.py +11 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/types.py +17 -4
- {epub_generator-0.1.2 → epub_generator-0.1.3}/pyproject.toml +1 -1
- {epub_generator-0.1.2 → epub_generator-0.1.3}/LICENSE +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/context.py +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/container.xml.jinja +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/content.opf.jinja +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/cover.xhtml.jinja +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/mimetype.jinja +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/nav.xhtml.jinja +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/part.xhtml.jinja +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/style.css.jinja +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/__init__.py +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/gen_asset.py +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/gen_nav.py +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/gen_toc.py +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/xml_utils.py +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/i18n.py +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/options.py +0 -0
- {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/template.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: epub-generator
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: A simple Python EPUB 3.0 generator with a single API call
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: epub,epub3,ebook,generator,publishing
|
|
@@ -48,7 +48,7 @@ pip install epub-generator
|
|
|
48
48
|
### Generate Your First Book in 5 Minutes
|
|
49
49
|
|
|
50
50
|
```python
|
|
51
|
-
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter,
|
|
51
|
+
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
|
|
52
52
|
|
|
53
53
|
# Prepare book data
|
|
54
54
|
epub_data = EpubData(
|
|
@@ -61,9 +61,9 @@ epub_data = EpubData(
|
|
|
61
61
|
title="Chapter 1",
|
|
62
62
|
get_chapter=lambda: Chapter(
|
|
63
63
|
elements=[
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
TextBlock(kind=TextKind.HEADLINE, content=["Chapter 1"]),
|
|
65
|
+
TextBlock(kind=TextKind.BODY, content=["This is the first paragraph."]),
|
|
66
|
+
TextBlock(kind=TextKind.BODY, content=["This is the second paragraph."]),
|
|
67
67
|
]
|
|
68
68
|
),
|
|
69
69
|
),
|
|
@@ -80,7 +80,7 @@ That's it! You now have a valid EPUB 3.0 ebook file.
|
|
|
80
80
|
|
|
81
81
|
- **Minimal API**: Just one function call `generate_epub()`
|
|
82
82
|
- **EPUB 3.0**: Generates standards-compliant EPUB 3.0 format
|
|
83
|
-
- **Rich Content**: Supports text, images, tables, math formulas (block-level and inline), footnotes
|
|
83
|
+
- **Rich Content**: Supports text, images, tables, math formulas (block-level and inline), footnotes, custom HTML tags
|
|
84
84
|
- **Flexible Structure**: Nested chapters, prefaces, cover images
|
|
85
85
|
- **Math Support**: LaTeX to MathML/SVG conversion with inline formula support
|
|
86
86
|
- **Type Safe**: Full type annotations included
|
|
@@ -92,7 +92,7 @@ That's it! You now have a valid EPUB 3.0 ebook file.
|
|
|
92
92
|
```python
|
|
93
93
|
from datetime import datetime, timezone
|
|
94
94
|
from pathlib import Path
|
|
95
|
-
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter,
|
|
95
|
+
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
|
|
96
96
|
|
|
97
97
|
epub_data = EpubData(
|
|
98
98
|
meta=BookMeta(
|
|
@@ -111,8 +111,8 @@ epub_data = EpubData(
|
|
|
111
111
|
title="Chapter 1",
|
|
112
112
|
get_chapter=lambda: Chapter(
|
|
113
113
|
elements=[
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
TextBlock(kind=TextKind.HEADLINE, content=["Chapter 1"]),
|
|
115
|
+
TextBlock(kind=TextKind.BODY, content=["Main content..."]),
|
|
116
116
|
]
|
|
117
117
|
),
|
|
118
118
|
),
|
|
@@ -125,7 +125,7 @@ generate_epub(epub_data, "book_with_cover.epub")
|
|
|
125
125
|
### Nested Chapter Structure
|
|
126
126
|
|
|
127
127
|
```python
|
|
128
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
128
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind
|
|
129
129
|
|
|
130
130
|
epub_data = EpubData(
|
|
131
131
|
chapters=[
|
|
@@ -136,7 +136,7 @@ epub_data = EpubData(
|
|
|
136
136
|
title="Chapter 1.1",
|
|
137
137
|
get_chapter=lambda: Chapter(
|
|
138
138
|
elements=[
|
|
139
|
-
|
|
139
|
+
TextBlock(kind=TextKind.BODY, content=["Content 1.1..."]),
|
|
140
140
|
]
|
|
141
141
|
),
|
|
142
142
|
),
|
|
@@ -144,7 +144,7 @@ epub_data = EpubData(
|
|
|
144
144
|
title="Chapter 1.2",
|
|
145
145
|
get_chapter=lambda: Chapter(
|
|
146
146
|
elements=[
|
|
147
|
-
|
|
147
|
+
TextBlock(kind=TextKind.BODY, content=["Content 1.2..."]),
|
|
148
148
|
]
|
|
149
149
|
),
|
|
150
150
|
),
|
|
@@ -160,7 +160,7 @@ generate_epub(epub_data, "book_with_nested_chapters.epub")
|
|
|
160
160
|
|
|
161
161
|
```python
|
|
162
162
|
from pathlib import Path
|
|
163
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
163
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Image
|
|
164
164
|
|
|
165
165
|
epub_data = EpubData(
|
|
166
166
|
chapters=[
|
|
@@ -168,7 +168,7 @@ epub_data = EpubData(
|
|
|
168
168
|
title="Chapter 1",
|
|
169
169
|
get_chapter=lambda: Chapter(
|
|
170
170
|
elements=[
|
|
171
|
-
|
|
171
|
+
TextBlock(kind=TextKind.BODY, content=["Here's an image:"]),
|
|
172
172
|
Image(
|
|
173
173
|
path=Path("image.png"), # Image path
|
|
174
174
|
alt_text="Image description",
|
|
@@ -185,7 +185,7 @@ generate_epub(epub_data, "book_with_images.epub")
|
|
|
185
185
|
### Add Footnotes
|
|
186
186
|
|
|
187
187
|
```python
|
|
188
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
188
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Mark, Footnote
|
|
189
189
|
|
|
190
190
|
epub_data = EpubData(
|
|
191
191
|
chapters=[
|
|
@@ -193,7 +193,7 @@ epub_data = EpubData(
|
|
|
193
193
|
title="Chapter 1",
|
|
194
194
|
get_chapter=lambda: Chapter(
|
|
195
195
|
elements=[
|
|
196
|
-
|
|
196
|
+
TextBlock(
|
|
197
197
|
kind=TextKind.BODY,
|
|
198
198
|
content=[
|
|
199
199
|
"This is text with a footnote",
|
|
@@ -206,7 +206,7 @@ epub_data = EpubData(
|
|
|
206
206
|
Footnote(
|
|
207
207
|
id=1,
|
|
208
208
|
contents=[
|
|
209
|
-
|
|
209
|
+
TextBlock(kind=TextKind.BODY, content=["This is the footnote content."]),
|
|
210
210
|
],
|
|
211
211
|
),
|
|
212
212
|
],
|
|
@@ -221,7 +221,7 @@ generate_epub(epub_data, "book_with_footnotes.epub")
|
|
|
221
221
|
### Add Tables
|
|
222
222
|
|
|
223
223
|
```python
|
|
224
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
224
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Table
|
|
225
225
|
|
|
226
226
|
epub_data = EpubData(
|
|
227
227
|
chapters=[
|
|
@@ -229,7 +229,7 @@ epub_data = EpubData(
|
|
|
229
229
|
title="Chapter 1",
|
|
230
230
|
get_chapter=lambda: Chapter(
|
|
231
231
|
elements=[
|
|
232
|
-
|
|
232
|
+
TextBlock(kind=TextKind.BODY, content=["Here's a table:"]),
|
|
233
233
|
Table(
|
|
234
234
|
html_content="""
|
|
235
235
|
<table>
|
|
@@ -253,7 +253,7 @@ generate_epub(epub_data, "book_with_tables.epub")
|
|
|
253
253
|
Block-level formulas:
|
|
254
254
|
|
|
255
255
|
```python
|
|
256
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
256
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Formula, LaTeXRender
|
|
257
257
|
|
|
258
258
|
epub_data = EpubData(
|
|
259
259
|
chapters=[
|
|
@@ -261,7 +261,7 @@ epub_data = EpubData(
|
|
|
261
261
|
title="Chapter 1",
|
|
262
262
|
get_chapter=lambda: Chapter(
|
|
263
263
|
elements=[
|
|
264
|
-
|
|
264
|
+
TextBlock(kind=TextKind.BODY, content=["Pythagorean theorem:"]),
|
|
265
265
|
Formula(latex_expression="x^2 + y^2 = z^2"), # Block-level formula
|
|
266
266
|
]
|
|
267
267
|
),
|
|
@@ -282,7 +282,7 @@ epub_data = EpubData(
|
|
|
282
282
|
title="Chapter 1",
|
|
283
283
|
get_chapter=lambda: Chapter(
|
|
284
284
|
elements=[
|
|
285
|
-
|
|
285
|
+
TextBlock(
|
|
286
286
|
kind=TextKind.BODY,
|
|
287
287
|
content=[
|
|
288
288
|
"The Pythagorean theorem ",
|
|
@@ -301,10 +301,45 @@ epub_data = EpubData(
|
|
|
301
301
|
generate_epub(epub_data, "book_with_inline_math.epub", latex_render=LaTeXRender.MATHML)
|
|
302
302
|
```
|
|
303
303
|
|
|
304
|
+
### Add Custom HTML Tags
|
|
305
|
+
|
|
306
|
+
You can embed custom HTML tags within text content:
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, HTMLTag
|
|
310
|
+
|
|
311
|
+
epub_data = EpubData(
|
|
312
|
+
chapters=[
|
|
313
|
+
TocItem(
|
|
314
|
+
title="Chapter 1",
|
|
315
|
+
get_chapter=lambda: Chapter(elements=[TextBlock(
|
|
316
|
+
kind=TextKind.BODY,
|
|
317
|
+
content=[
|
|
318
|
+
"This is normal text with ",
|
|
319
|
+
HTMLTag(
|
|
320
|
+
name="span",
|
|
321
|
+
attributes=[("class", "highlight"), ("style", "color: red;")],
|
|
322
|
+
content=["highlighted content"],
|
|
323
|
+
),
|
|
324
|
+
" and more text with ",
|
|
325
|
+
HTMLTag(
|
|
326
|
+
name="strong",
|
|
327
|
+
content=["bold text"],
|
|
328
|
+
),
|
|
329
|
+
".",
|
|
330
|
+
],
|
|
331
|
+
)]),
|
|
332
|
+
),
|
|
333
|
+
],
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
generate_epub(epub_data, "book_with_html_tags.epub")
|
|
337
|
+
```
|
|
338
|
+
|
|
304
339
|
### Add Prefaces
|
|
305
340
|
|
|
306
341
|
```python
|
|
307
|
-
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter,
|
|
342
|
+
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
|
|
308
343
|
|
|
309
344
|
epub_data = EpubData(
|
|
310
345
|
meta=BookMeta(title="Book with Prefaces"),
|
|
@@ -313,8 +348,8 @@ epub_data = EpubData(
|
|
|
313
348
|
title="Preface",
|
|
314
349
|
get_chapter=lambda: Chapter(
|
|
315
350
|
elements=[
|
|
316
|
-
|
|
317
|
-
|
|
351
|
+
TextBlock(kind=TextKind.HEADLINE, content=["Preface"]),
|
|
352
|
+
TextBlock(kind=TextKind.BODY, content=["This is the preface content..."]),
|
|
318
353
|
]
|
|
319
354
|
),
|
|
320
355
|
),
|
|
@@ -324,7 +359,7 @@ epub_data = EpubData(
|
|
|
324
359
|
title="Chapter 1",
|
|
325
360
|
get_chapter=lambda: Chapter(
|
|
326
361
|
elements=[
|
|
327
|
-
|
|
362
|
+
TextBlock(kind=TextKind.BODY, content=["Main content..."]),
|
|
328
363
|
]
|
|
329
364
|
),
|
|
330
365
|
),
|
|
@@ -417,12 +452,12 @@ class Chapter:
|
|
|
417
452
|
|
|
418
453
|
`ContentBlock` is a union of:
|
|
419
454
|
|
|
420
|
-
- **`
|
|
455
|
+
- **`TextBlock`**: Text paragraph
|
|
421
456
|
```python
|
|
422
457
|
@dataclass
|
|
423
|
-
class
|
|
424
|
-
kind: TextKind
|
|
425
|
-
content: list[str | Mark | Formula]
|
|
458
|
+
class TextBlock:
|
|
459
|
+
kind: TextKind # BODY | HEADLINE | QUOTE
|
|
460
|
+
content: list[str | Mark | Formula | HTMLTag] # Text with optional marks, inline formulas, and HTML tags
|
|
426
461
|
```
|
|
427
462
|
|
|
428
463
|
- **`Image`**: Image reference
|
|
@@ -447,6 +482,15 @@ class Chapter:
|
|
|
447
482
|
latex_expression: str # LaTeX expression
|
|
448
483
|
```
|
|
449
484
|
|
|
485
|
+
- **`HTMLTag`**: HTML tag
|
|
486
|
+
```python
|
|
487
|
+
@dataclass
|
|
488
|
+
class HTMLTag:
|
|
489
|
+
name: str # Tag name (e.g., "span", "div")
|
|
490
|
+
attributes: list[tuple[str, str]] = [] # List of (attribute, value) pairs
|
|
491
|
+
content: list[str | Mark | Formula | HTMLTag] = [] # Inner HTML content
|
|
492
|
+
```
|
|
493
|
+
|
|
450
494
|
#### `Footnote`
|
|
451
495
|
|
|
452
496
|
Footnote/citation.
|
|
@@ -21,7 +21,7 @@ pip install epub-generator
|
|
|
21
21
|
### Generate Your First Book in 5 Minutes
|
|
22
22
|
|
|
23
23
|
```python
|
|
24
|
-
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter,
|
|
24
|
+
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
|
|
25
25
|
|
|
26
26
|
# Prepare book data
|
|
27
27
|
epub_data = EpubData(
|
|
@@ -34,9 +34,9 @@ epub_data = EpubData(
|
|
|
34
34
|
title="Chapter 1",
|
|
35
35
|
get_chapter=lambda: Chapter(
|
|
36
36
|
elements=[
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
TextBlock(kind=TextKind.HEADLINE, content=["Chapter 1"]),
|
|
38
|
+
TextBlock(kind=TextKind.BODY, content=["This is the first paragraph."]),
|
|
39
|
+
TextBlock(kind=TextKind.BODY, content=["This is the second paragraph."]),
|
|
40
40
|
]
|
|
41
41
|
),
|
|
42
42
|
),
|
|
@@ -53,7 +53,7 @@ That's it! You now have a valid EPUB 3.0 ebook file.
|
|
|
53
53
|
|
|
54
54
|
- **Minimal API**: Just one function call `generate_epub()`
|
|
55
55
|
- **EPUB 3.0**: Generates standards-compliant EPUB 3.0 format
|
|
56
|
-
- **Rich Content**: Supports text, images, tables, math formulas (block-level and inline), footnotes
|
|
56
|
+
- **Rich Content**: Supports text, images, tables, math formulas (block-level and inline), footnotes, custom HTML tags
|
|
57
57
|
- **Flexible Structure**: Nested chapters, prefaces, cover images
|
|
58
58
|
- **Math Support**: LaTeX to MathML/SVG conversion with inline formula support
|
|
59
59
|
- **Type Safe**: Full type annotations included
|
|
@@ -65,7 +65,7 @@ That's it! You now have a valid EPUB 3.0 ebook file.
|
|
|
65
65
|
```python
|
|
66
66
|
from datetime import datetime, timezone
|
|
67
67
|
from pathlib import Path
|
|
68
|
-
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter,
|
|
68
|
+
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
|
|
69
69
|
|
|
70
70
|
epub_data = EpubData(
|
|
71
71
|
meta=BookMeta(
|
|
@@ -84,8 +84,8 @@ epub_data = EpubData(
|
|
|
84
84
|
title="Chapter 1",
|
|
85
85
|
get_chapter=lambda: Chapter(
|
|
86
86
|
elements=[
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
TextBlock(kind=TextKind.HEADLINE, content=["Chapter 1"]),
|
|
88
|
+
TextBlock(kind=TextKind.BODY, content=["Main content..."]),
|
|
89
89
|
]
|
|
90
90
|
),
|
|
91
91
|
),
|
|
@@ -98,7 +98,7 @@ generate_epub(epub_data, "book_with_cover.epub")
|
|
|
98
98
|
### Nested Chapter Structure
|
|
99
99
|
|
|
100
100
|
```python
|
|
101
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
101
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind
|
|
102
102
|
|
|
103
103
|
epub_data = EpubData(
|
|
104
104
|
chapters=[
|
|
@@ -109,7 +109,7 @@ epub_data = EpubData(
|
|
|
109
109
|
title="Chapter 1.1",
|
|
110
110
|
get_chapter=lambda: Chapter(
|
|
111
111
|
elements=[
|
|
112
|
-
|
|
112
|
+
TextBlock(kind=TextKind.BODY, content=["Content 1.1..."]),
|
|
113
113
|
]
|
|
114
114
|
),
|
|
115
115
|
),
|
|
@@ -117,7 +117,7 @@ epub_data = EpubData(
|
|
|
117
117
|
title="Chapter 1.2",
|
|
118
118
|
get_chapter=lambda: Chapter(
|
|
119
119
|
elements=[
|
|
120
|
-
|
|
120
|
+
TextBlock(kind=TextKind.BODY, content=["Content 1.2..."]),
|
|
121
121
|
]
|
|
122
122
|
),
|
|
123
123
|
),
|
|
@@ -133,7 +133,7 @@ generate_epub(epub_data, "book_with_nested_chapters.epub")
|
|
|
133
133
|
|
|
134
134
|
```python
|
|
135
135
|
from pathlib import Path
|
|
136
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
136
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Image
|
|
137
137
|
|
|
138
138
|
epub_data = EpubData(
|
|
139
139
|
chapters=[
|
|
@@ -141,7 +141,7 @@ epub_data = EpubData(
|
|
|
141
141
|
title="Chapter 1",
|
|
142
142
|
get_chapter=lambda: Chapter(
|
|
143
143
|
elements=[
|
|
144
|
-
|
|
144
|
+
TextBlock(kind=TextKind.BODY, content=["Here's an image:"]),
|
|
145
145
|
Image(
|
|
146
146
|
path=Path("image.png"), # Image path
|
|
147
147
|
alt_text="Image description",
|
|
@@ -158,7 +158,7 @@ generate_epub(epub_data, "book_with_images.epub")
|
|
|
158
158
|
### Add Footnotes
|
|
159
159
|
|
|
160
160
|
```python
|
|
161
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
161
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Mark, Footnote
|
|
162
162
|
|
|
163
163
|
epub_data = EpubData(
|
|
164
164
|
chapters=[
|
|
@@ -166,7 +166,7 @@ epub_data = EpubData(
|
|
|
166
166
|
title="Chapter 1",
|
|
167
167
|
get_chapter=lambda: Chapter(
|
|
168
168
|
elements=[
|
|
169
|
-
|
|
169
|
+
TextBlock(
|
|
170
170
|
kind=TextKind.BODY,
|
|
171
171
|
content=[
|
|
172
172
|
"This is text with a footnote",
|
|
@@ -179,7 +179,7 @@ epub_data = EpubData(
|
|
|
179
179
|
Footnote(
|
|
180
180
|
id=1,
|
|
181
181
|
contents=[
|
|
182
|
-
|
|
182
|
+
TextBlock(kind=TextKind.BODY, content=["This is the footnote content."]),
|
|
183
183
|
],
|
|
184
184
|
),
|
|
185
185
|
],
|
|
@@ -194,7 +194,7 @@ generate_epub(epub_data, "book_with_footnotes.epub")
|
|
|
194
194
|
### Add Tables
|
|
195
195
|
|
|
196
196
|
```python
|
|
197
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
197
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Table
|
|
198
198
|
|
|
199
199
|
epub_data = EpubData(
|
|
200
200
|
chapters=[
|
|
@@ -202,7 +202,7 @@ epub_data = EpubData(
|
|
|
202
202
|
title="Chapter 1",
|
|
203
203
|
get_chapter=lambda: Chapter(
|
|
204
204
|
elements=[
|
|
205
|
-
|
|
205
|
+
TextBlock(kind=TextKind.BODY, content=["Here's a table:"]),
|
|
206
206
|
Table(
|
|
207
207
|
html_content="""
|
|
208
208
|
<table>
|
|
@@ -226,7 +226,7 @@ generate_epub(epub_data, "book_with_tables.epub")
|
|
|
226
226
|
Block-level formulas:
|
|
227
227
|
|
|
228
228
|
```python
|
|
229
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
229
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Formula, LaTeXRender
|
|
230
230
|
|
|
231
231
|
epub_data = EpubData(
|
|
232
232
|
chapters=[
|
|
@@ -234,7 +234,7 @@ epub_data = EpubData(
|
|
|
234
234
|
title="Chapter 1",
|
|
235
235
|
get_chapter=lambda: Chapter(
|
|
236
236
|
elements=[
|
|
237
|
-
|
|
237
|
+
TextBlock(kind=TextKind.BODY, content=["Pythagorean theorem:"]),
|
|
238
238
|
Formula(latex_expression="x^2 + y^2 = z^2"), # Block-level formula
|
|
239
239
|
]
|
|
240
240
|
),
|
|
@@ -255,7 +255,7 @@ epub_data = EpubData(
|
|
|
255
255
|
title="Chapter 1",
|
|
256
256
|
get_chapter=lambda: Chapter(
|
|
257
257
|
elements=[
|
|
258
|
-
|
|
258
|
+
TextBlock(
|
|
259
259
|
kind=TextKind.BODY,
|
|
260
260
|
content=[
|
|
261
261
|
"The Pythagorean theorem ",
|
|
@@ -274,10 +274,45 @@ epub_data = EpubData(
|
|
|
274
274
|
generate_epub(epub_data, "book_with_inline_math.epub", latex_render=LaTeXRender.MATHML)
|
|
275
275
|
```
|
|
276
276
|
|
|
277
|
+
### Add Custom HTML Tags
|
|
278
|
+
|
|
279
|
+
You can embed custom HTML tags within text content:
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, HTMLTag
|
|
283
|
+
|
|
284
|
+
epub_data = EpubData(
|
|
285
|
+
chapters=[
|
|
286
|
+
TocItem(
|
|
287
|
+
title="Chapter 1",
|
|
288
|
+
get_chapter=lambda: Chapter(elements=[TextBlock(
|
|
289
|
+
kind=TextKind.BODY,
|
|
290
|
+
content=[
|
|
291
|
+
"This is normal text with ",
|
|
292
|
+
HTMLTag(
|
|
293
|
+
name="span",
|
|
294
|
+
attributes=[("class", "highlight"), ("style", "color: red;")],
|
|
295
|
+
content=["highlighted content"],
|
|
296
|
+
),
|
|
297
|
+
" and more text with ",
|
|
298
|
+
HTMLTag(
|
|
299
|
+
name="strong",
|
|
300
|
+
content=["bold text"],
|
|
301
|
+
),
|
|
302
|
+
".",
|
|
303
|
+
],
|
|
304
|
+
)]),
|
|
305
|
+
),
|
|
306
|
+
],
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
generate_epub(epub_data, "book_with_html_tags.epub")
|
|
310
|
+
```
|
|
311
|
+
|
|
277
312
|
### Add Prefaces
|
|
278
313
|
|
|
279
314
|
```python
|
|
280
|
-
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter,
|
|
315
|
+
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
|
|
281
316
|
|
|
282
317
|
epub_data = EpubData(
|
|
283
318
|
meta=BookMeta(title="Book with Prefaces"),
|
|
@@ -286,8 +321,8 @@ epub_data = EpubData(
|
|
|
286
321
|
title="Preface",
|
|
287
322
|
get_chapter=lambda: Chapter(
|
|
288
323
|
elements=[
|
|
289
|
-
|
|
290
|
-
|
|
324
|
+
TextBlock(kind=TextKind.HEADLINE, content=["Preface"]),
|
|
325
|
+
TextBlock(kind=TextKind.BODY, content=["This is the preface content..."]),
|
|
291
326
|
]
|
|
292
327
|
),
|
|
293
328
|
),
|
|
@@ -297,7 +332,7 @@ epub_data = EpubData(
|
|
|
297
332
|
title="Chapter 1",
|
|
298
333
|
get_chapter=lambda: Chapter(
|
|
299
334
|
elements=[
|
|
300
|
-
|
|
335
|
+
TextBlock(kind=TextKind.BODY, content=["Main content..."]),
|
|
301
336
|
]
|
|
302
337
|
),
|
|
303
338
|
),
|
|
@@ -390,12 +425,12 @@ class Chapter:
|
|
|
390
425
|
|
|
391
426
|
`ContentBlock` is a union of:
|
|
392
427
|
|
|
393
|
-
- **`
|
|
428
|
+
- **`TextBlock`**: Text paragraph
|
|
394
429
|
```python
|
|
395
430
|
@dataclass
|
|
396
|
-
class
|
|
397
|
-
kind: TextKind
|
|
398
|
-
content: list[str | Mark | Formula]
|
|
431
|
+
class TextBlock:
|
|
432
|
+
kind: TextKind # BODY | HEADLINE | QUOTE
|
|
433
|
+
content: list[str | Mark | Formula | HTMLTag] # Text with optional marks, inline formulas, and HTML tags
|
|
399
434
|
```
|
|
400
435
|
|
|
401
436
|
- **`Image`**: Image reference
|
|
@@ -420,6 +455,15 @@ class Chapter:
|
|
|
420
455
|
latex_expression: str # LaTeX expression
|
|
421
456
|
```
|
|
422
457
|
|
|
458
|
+
- **`HTMLTag`**: HTML tag
|
|
459
|
+
```python
|
|
460
|
+
@dataclass
|
|
461
|
+
class HTMLTag:
|
|
462
|
+
name: str # Tag name (e.g., "span", "div")
|
|
463
|
+
attributes: list[tuple[str, str]] = [] # List of (attribute, value) pairs
|
|
464
|
+
content: list[str | Mark | Formula | HTMLTag] = [] # Inner HTML content
|
|
465
|
+
```
|
|
466
|
+
|
|
423
467
|
#### `Footnote`
|
|
424
468
|
|
|
425
469
|
Footnote/citation.
|
|
@@ -8,10 +8,11 @@ from .types import (
|
|
|
8
8
|
EpubData,
|
|
9
9
|
Footnote,
|
|
10
10
|
Formula,
|
|
11
|
+
HTMLTag,
|
|
11
12
|
Image,
|
|
12
13
|
Mark,
|
|
13
14
|
Table,
|
|
14
|
-
|
|
15
|
+
TextBlock,
|
|
15
16
|
TextKind,
|
|
16
17
|
TocItem,
|
|
17
18
|
)
|
|
@@ -29,10 +30,11 @@ __all__ = [
|
|
|
29
30
|
"Chapter",
|
|
30
31
|
"ChapterGetter",
|
|
31
32
|
"ContentBlock",
|
|
32
|
-
"
|
|
33
|
+
"TextBlock",
|
|
33
34
|
"TextKind",
|
|
34
35
|
"Table",
|
|
35
36
|
"Formula",
|
|
37
|
+
"HTMLTag",
|
|
36
38
|
"Image",
|
|
37
39
|
"Footnote",
|
|
38
40
|
"Mark",
|
|
@@ -7,10 +7,11 @@ from ..types import (
|
|
|
7
7
|
Chapter,
|
|
8
8
|
ContentBlock,
|
|
9
9
|
Formula,
|
|
10
|
+
HTMLTag,
|
|
10
11
|
Image,
|
|
11
12
|
Mark,
|
|
12
13
|
Table,
|
|
13
|
-
|
|
14
|
+
TextBlock,
|
|
14
15
|
TextKind,
|
|
15
16
|
)
|
|
16
17
|
from .gen_asset import process_formula, process_image, process_table
|
|
@@ -88,7 +89,7 @@ def _render_footnotes(
|
|
|
88
89
|
|
|
89
90
|
|
|
90
91
|
def _render_content_block(context: Context, block: ContentBlock) -> Element | None:
|
|
91
|
-
if isinstance(block,
|
|
92
|
+
if isinstance(block, TextBlock):
|
|
92
93
|
if block.kind == TextKind.HEADLINE:
|
|
93
94
|
container = Element("h1")
|
|
94
95
|
elif block.kind == TextKind.QUOTE:
|
|
@@ -98,8 +99,11 @@ def _render_content_block(context: Context, block: ContentBlock) -> Element | No
|
|
|
98
99
|
else:
|
|
99
100
|
raise ValueError(f"Unknown TextKind: {block.kind}")
|
|
100
101
|
|
|
101
|
-
_render_text_content(
|
|
102
|
-
|
|
102
|
+
_render_text_content(
|
|
103
|
+
context=context,
|
|
104
|
+
parent=container,
|
|
105
|
+
content=block.content,
|
|
106
|
+
)
|
|
103
107
|
if block.kind == TextKind.QUOTE:
|
|
104
108
|
blockquote = Element("blockquote")
|
|
105
109
|
blockquote.append(container)
|
|
@@ -120,7 +124,7 @@ def _render_content_block(context: Context, block: ContentBlock) -> Element | No
|
|
|
120
124
|
return None
|
|
121
125
|
|
|
122
126
|
|
|
123
|
-
def _render_text_content(context: Context, parent: Element, content: list[str | Mark | Formula]) -> None:
|
|
127
|
+
def _render_text_content(context: Context, parent: Element, content: list[str | Mark | Formula | HTMLTag]) -> None:
|
|
124
128
|
"""Render text content with inline citation marks."""
|
|
125
129
|
current_element = parent
|
|
126
130
|
for item in content:
|
|
@@ -136,6 +140,18 @@ def _render_text_content(context: Context, parent: Element, content: list[str |
|
|
|
136
140
|
else:
|
|
137
141
|
current_element.tail += item
|
|
138
142
|
|
|
143
|
+
elif isinstance(item, HTMLTag):
|
|
144
|
+
tag_element = Element(item.name)
|
|
145
|
+
for attr, value in item.attributes:
|
|
146
|
+
tag_element.set(attr, value)
|
|
147
|
+
_render_text_content(
|
|
148
|
+
context=context,
|
|
149
|
+
parent=tag_element,
|
|
150
|
+
content=item.content,
|
|
151
|
+
)
|
|
152
|
+
parent.append(tag_element)
|
|
153
|
+
current_element = tag_element
|
|
154
|
+
|
|
139
155
|
elif isinstance(item, Formula):
|
|
140
156
|
formula_element = process_formula(
|
|
141
157
|
context=context,
|
|
@@ -6,9 +6,10 @@ from uuid import uuid4
|
|
|
6
6
|
from zipfile import ZipFile
|
|
7
7
|
|
|
8
8
|
from ..context import Context, Template
|
|
9
|
+
from ..html_tag import search_content
|
|
9
10
|
from ..i18n import I18N
|
|
10
11
|
from ..options import LaTeXRender, TableRender
|
|
11
|
-
from ..types import EpubData, Formula,
|
|
12
|
+
from ..types import Chapter, EpubData, Formula, TextBlock
|
|
12
13
|
from .gen_chapter import generate_chapter
|
|
13
14
|
from .gen_nav import gen_nav
|
|
14
15
|
from .gen_toc import NavPoint, gen_toc
|
|
@@ -135,21 +136,21 @@ def _write_chapters_from_data(
|
|
|
135
136
|
assert_not_aborted()
|
|
136
137
|
|
|
137
138
|
|
|
138
|
-
def _chapter_has_formula(chapter) -> bool:
|
|
139
|
+
def _chapter_has_formula(chapter: Chapter) -> bool:
|
|
139
140
|
"""Check if chapter contains any formulas (block-level or inline)."""
|
|
140
141
|
for element in chapter.elements:
|
|
141
142
|
if isinstance(element, Formula):
|
|
142
143
|
return True
|
|
143
|
-
if isinstance(element,
|
|
144
|
-
for item in element.content:
|
|
144
|
+
if isinstance(element, TextBlock):
|
|
145
|
+
for item in search_content(element.content):
|
|
145
146
|
if isinstance(item, Formula):
|
|
146
147
|
return True
|
|
147
148
|
for footnote in chapter.footnotes:
|
|
148
149
|
for content_block in footnote.contents:
|
|
149
150
|
if isinstance(content_block, Formula):
|
|
150
151
|
return True
|
|
151
|
-
if isinstance(content_block,
|
|
152
|
-
for item in content_block.content:
|
|
152
|
+
if isinstance(content_block, TextBlock):
|
|
153
|
+
for item in search_content(content_block.content):
|
|
153
154
|
if isinstance(item, Formula):
|
|
154
155
|
return True
|
|
155
156
|
return False
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from typing import Generator
|
|
2
|
+
|
|
3
|
+
from .types import Formula, HTMLTag, Mark
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def search_content(content: list[str | Mark | Formula | HTMLTag]) -> Generator[str | Mark | Formula, None, None]:
|
|
7
|
+
for child in content:
|
|
8
|
+
if isinstance(child, HTMLTag):
|
|
9
|
+
yield from search_content(child.content)
|
|
10
|
+
else:
|
|
11
|
+
yield child
|
|
@@ -107,10 +107,10 @@ class Image:
|
|
|
107
107
|
"""Alt text (defaults to "image")"""
|
|
108
108
|
|
|
109
109
|
@dataclass
|
|
110
|
-
class
|
|
110
|
+
class TextBlock:
|
|
111
111
|
kind: TextKind
|
|
112
112
|
"""Kind of text block."""
|
|
113
|
-
content: list[str | Mark | Formula]
|
|
113
|
+
content: list["str | Mark | Formula | HTMLTag"]
|
|
114
114
|
"""Text content with optional citation marks."""
|
|
115
115
|
|
|
116
116
|
@dataclass
|
|
@@ -126,7 +126,7 @@ class Footnote:
|
|
|
126
126
|
"""Content blocks"""
|
|
127
127
|
|
|
128
128
|
|
|
129
|
-
ContentBlock =
|
|
129
|
+
ContentBlock = TextBlock | Table | Formula | Image
|
|
130
130
|
"""Union of all content blocks that appear in main chapter content."""
|
|
131
131
|
|
|
132
132
|
@dataclass
|
|
@@ -138,4 +138,17 @@ class Chapter:
|
|
|
138
138
|
footnotes: list[Footnote] = field(default_factory=list)
|
|
139
139
|
"""Footnotes"""
|
|
140
140
|
|
|
141
|
-
ChapterGetter = Callable[[], Chapter]
|
|
141
|
+
ChapterGetter = Callable[[], Chapter]
|
|
142
|
+
|
|
143
|
+
@dataclass
|
|
144
|
+
class HTMLTag:
|
|
145
|
+
"""Generic HTML tag representation."""
|
|
146
|
+
|
|
147
|
+
name: str
|
|
148
|
+
"""Tag name"""
|
|
149
|
+
|
|
150
|
+
attributes: list[tuple[str, str]] = field(default_factory=list)
|
|
151
|
+
"""List of (attribute, value) pairs"""
|
|
152
|
+
|
|
153
|
+
content: list["str | Mark | Formula | HTMLTag"] = field(default_factory=list)
|
|
154
|
+
"""Inner HTML content"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|