epub-generator 0.1.2__tar.gz → 0.1.4__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 (25) hide show
  1. {epub_generator-0.1.2 → epub_generator-0.1.4}/PKG-INFO +78 -30
  2. {epub_generator-0.1.2 → epub_generator-0.1.4}/README.md +77 -29
  3. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/__init__.py +4 -2
  4. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/generation/gen_chapter.py +25 -6
  5. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/generation/gen_epub.py +7 -6
  6. epub_generator-0.1.4/epub_generator/html_tag.py +11 -0
  7. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/types.py +19 -4
  8. {epub_generator-0.1.2 → epub_generator-0.1.4}/pyproject.toml +1 -1
  9. {epub_generator-0.1.2 → epub_generator-0.1.4}/LICENSE +0 -0
  10. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/context.py +0 -0
  11. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/data/container.xml.jinja +0 -0
  12. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/data/content.opf.jinja +0 -0
  13. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/data/cover.xhtml.jinja +0 -0
  14. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/data/mimetype.jinja +0 -0
  15. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/data/nav.xhtml.jinja +0 -0
  16. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/data/part.xhtml.jinja +0 -0
  17. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/data/style.css.jinja +0 -0
  18. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/generation/__init__.py +0 -0
  19. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/generation/gen_asset.py +0 -0
  20. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/generation/gen_nav.py +0 -0
  21. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/generation/gen_toc.py +0 -0
  22. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/generation/xml_utils.py +0 -0
  23. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/i18n.py +0 -0
  24. {epub_generator-0.1.2 → epub_generator-0.1.4}/epub_generator/options.py +0 -0
  25. {epub_generator-0.1.2 → epub_generator-0.1.4}/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.2
3
+ Version: 0.1.4
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, Text, TextKind
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
- Text(kind=TextKind.HEADLINE, content=["Chapter 1"]),
65
- Text(kind=TextKind.BODY, content=["This is the first paragraph."]),
66
- Text(kind=TextKind.BODY, content=["This is the second paragraph."]),
64
+ TextBlock(kind=TextKind.HEADLINE, level=0, content=["Chapter 1"]),
65
+ TextBlock(kind=TextKind.BODY, level=0, content=["This is the first paragraph."]),
66
+ TextBlock(kind=TextKind.BODY, level=0, 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, Text, TextKind
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
- Text(kind=TextKind.HEADLINE, content=["Chapter 1"]),
115
- Text(kind=TextKind.BODY, content=["Main content..."]),
114
+ TextBlock(kind=TextKind.HEADLINE, level=0, content=["Chapter 1"]),
115
+ TextBlock(kind=TextKind.BODY, level=0, 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, Text, TextKind
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
- Text(kind=TextKind.BODY, content=["Content 1.1..."]),
139
+ TextBlock(kind=TextKind.BODY, level=0, 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
- Text(kind=TextKind.BODY, content=["Content 1.2..."]),
147
+ TextBlock(kind=TextKind.BODY, level=0, 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, Text, TextKind, Image
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
- Text(kind=TextKind.BODY, content=["Here's an image:"]),
171
+ TextBlock(kind=TextKind.BODY, level=0, 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, Text, TextKind, Mark, Footnote
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,8 +193,9 @@ epub_data = EpubData(
193
193
  title="Chapter 1",
194
194
  get_chapter=lambda: Chapter(
195
195
  elements=[
196
- Text(
196
+ TextBlock(
197
197
  kind=TextKind.BODY,
198
+ level=0,
198
199
  content=[
199
200
  "This is text with a footnote",
200
201
  Mark(id=1), # Footnote marker
@@ -206,7 +207,7 @@ epub_data = EpubData(
206
207
  Footnote(
207
208
  id=1,
208
209
  contents=[
209
- Text(kind=TextKind.BODY, content=["This is the footnote content."]),
210
+ TextBlock(kind=TextKind.BODY, level=0, content=["This is the footnote content."]),
210
211
  ],
211
212
  ),
212
213
  ],
@@ -221,7 +222,7 @@ generate_epub(epub_data, "book_with_footnotes.epub")
221
222
  ### Add Tables
222
223
 
223
224
  ```python
224
- from epub_generator import generate_epub, EpubData, TocItem, Chapter, Text, TextKind, Table
225
+ from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Table
225
226
 
226
227
  epub_data = EpubData(
227
228
  chapters=[
@@ -229,7 +230,7 @@ epub_data = EpubData(
229
230
  title="Chapter 1",
230
231
  get_chapter=lambda: Chapter(
231
232
  elements=[
232
- Text(kind=TextKind.BODY, content=["Here's a table:"]),
233
+ TextBlock(kind=TextKind.BODY, level=0, content=["Here's a table:"]),
233
234
  Table(
234
235
  html_content="""
235
236
  <table>
@@ -253,7 +254,7 @@ generate_epub(epub_data, "book_with_tables.epub")
253
254
  Block-level formulas:
254
255
 
255
256
  ```python
256
- from epub_generator import generate_epub, EpubData, TocItem, Chapter, Text, TextKind, Formula, LaTeXRender
257
+ from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Formula, LaTeXRender
257
258
 
258
259
  epub_data = EpubData(
259
260
  chapters=[
@@ -261,7 +262,7 @@ epub_data = EpubData(
261
262
  title="Chapter 1",
262
263
  get_chapter=lambda: Chapter(
263
264
  elements=[
264
- Text(kind=TextKind.BODY, content=["Pythagorean theorem:"]),
265
+ TextBlock(kind=TextKind.BODY, level=0, content=["Pythagorean theorem:"]),
265
266
  Formula(latex_expression="x^2 + y^2 = z^2"), # Block-level formula
266
267
  ]
267
268
  ),
@@ -282,8 +283,9 @@ epub_data = EpubData(
282
283
  title="Chapter 1",
283
284
  get_chapter=lambda: Chapter(
284
285
  elements=[
285
- Text(
286
+ TextBlock(
286
287
  kind=TextKind.BODY,
288
+ level=0,
287
289
  content=[
288
290
  "The Pythagorean theorem ",
289
291
  Formula(latex_expression="a^2 + b^2 = c^2"), # Inline formula
@@ -301,10 +303,46 @@ epub_data = EpubData(
301
303
  generate_epub(epub_data, "book_with_inline_math.epub", latex_render=LaTeXRender.MATHML)
302
304
  ```
303
305
 
306
+ ### Add Custom HTML Tags
307
+
308
+ You can embed custom HTML tags within text content:
309
+
310
+ ```python
311
+ from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, HTMLTag
312
+
313
+ epub_data = EpubData(
314
+ chapters=[
315
+ TocItem(
316
+ title="Chapter 1",
317
+ get_chapter=lambda: Chapter(elements=[TextBlock(
318
+ kind=TextKind.BODY,
319
+ level=0,
320
+ content=[
321
+ "This is normal text with ",
322
+ HTMLTag(
323
+ name="span",
324
+ attributes=[("class", "highlight"), ("style", "color: red;")],
325
+ content=["highlighted content"],
326
+ ),
327
+ " and more text with ",
328
+ HTMLTag(
329
+ name="strong",
330
+ content=["bold text"],
331
+ ),
332
+ ".",
333
+ ],
334
+ )]),
335
+ ),
336
+ ],
337
+ )
338
+
339
+ generate_epub(epub_data, "book_with_html_tags.epub")
340
+ ```
341
+
304
342
  ### Add Prefaces
305
343
 
306
344
  ```python
307
- from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, Text, TextKind
345
+ from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
308
346
 
309
347
  epub_data = EpubData(
310
348
  meta=BookMeta(title="Book with Prefaces"),
@@ -313,8 +351,8 @@ epub_data = EpubData(
313
351
  title="Preface",
314
352
  get_chapter=lambda: Chapter(
315
353
  elements=[
316
- Text(kind=TextKind.HEADLINE, content=["Preface"]),
317
- Text(kind=TextKind.BODY, content=["This is the preface content..."]),
354
+ TextBlock(kind=TextKind.HEADLINE, level=0, content=["Preface"]),
355
+ TextBlock(kind=TextKind.BODY, level=0, content=["This is the preface content..."]),
318
356
  ]
319
357
  ),
320
358
  ),
@@ -324,7 +362,7 @@ epub_data = EpubData(
324
362
  title="Chapter 1",
325
363
  get_chapter=lambda: Chapter(
326
364
  elements=[
327
- Text(kind=TextKind.BODY, content=["Main content..."]),
365
+ TextBlock(kind=TextKind.BODY, level=0, content=["Main content..."]),
328
366
  ]
329
367
  ),
330
368
  ),
@@ -417,12 +455,13 @@ class Chapter:
417
455
 
418
456
  `ContentBlock` is a union of:
419
457
 
420
- - **`Text`**: Text paragraph
458
+ - **`TextBlock`**: Text paragraph
421
459
  ```python
422
460
  @dataclass
423
- class Text:
424
- kind: TextKind # BODY | HEADLINE | QUOTE
425
- content: list[str | Mark | Formula] # Text with optional marks and inline formulas
461
+ class TextBlock:
462
+ kind: TextKind # BODY | HEADLINE | QUOTE
463
+ level: int # Heading level (0→h1, 1→h2, max h6; only for HEADLINE)
464
+ content: list[str | Mark | Formula | HTMLTag] # Text with optional marks, inline formulas, and HTML tags
426
465
  ```
427
466
 
428
467
  - **`Image`**: Image reference
@@ -447,6 +486,15 @@ class Chapter:
447
486
  latex_expression: str # LaTeX expression
448
487
  ```
449
488
 
489
+ - **`HTMLTag`**: HTML tag
490
+ ```python
491
+ @dataclass
492
+ class HTMLTag:
493
+ name: str # Tag name (e.g., "span", "div")
494
+ attributes: list[tuple[str, str]] = [] # List of (attribute, value) pairs
495
+ content: list[str | Mark | Formula | HTMLTag] = [] # Inner HTML content
496
+ ```
497
+
450
498
  #### `Footnote`
451
499
 
452
500
  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, Text, TextKind
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
- Text(kind=TextKind.HEADLINE, content=["Chapter 1"]),
38
- Text(kind=TextKind.BODY, content=["This is the first paragraph."]),
39
- Text(kind=TextKind.BODY, content=["This is the second paragraph."]),
37
+ TextBlock(kind=TextKind.HEADLINE, level=0, content=["Chapter 1"]),
38
+ TextBlock(kind=TextKind.BODY, level=0, content=["This is the first paragraph."]),
39
+ TextBlock(kind=TextKind.BODY, level=0, 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, Text, TextKind
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
- Text(kind=TextKind.HEADLINE, content=["Chapter 1"]),
88
- Text(kind=TextKind.BODY, content=["Main content..."]),
87
+ TextBlock(kind=TextKind.HEADLINE, level=0, content=["Chapter 1"]),
88
+ TextBlock(kind=TextKind.BODY, level=0, 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, Text, TextKind
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
- Text(kind=TextKind.BODY, content=["Content 1.1..."]),
112
+ TextBlock(kind=TextKind.BODY, level=0, 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
- Text(kind=TextKind.BODY, content=["Content 1.2..."]),
120
+ TextBlock(kind=TextKind.BODY, level=0, 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, Text, TextKind, Image
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
- Text(kind=TextKind.BODY, content=["Here's an image:"]),
144
+ TextBlock(kind=TextKind.BODY, level=0, 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, Text, TextKind, Mark, Footnote
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,8 +166,9 @@ epub_data = EpubData(
166
166
  title="Chapter 1",
167
167
  get_chapter=lambda: Chapter(
168
168
  elements=[
169
- Text(
169
+ TextBlock(
170
170
  kind=TextKind.BODY,
171
+ level=0,
171
172
  content=[
172
173
  "This is text with a footnote",
173
174
  Mark(id=1), # Footnote marker
@@ -179,7 +180,7 @@ epub_data = EpubData(
179
180
  Footnote(
180
181
  id=1,
181
182
  contents=[
182
- Text(kind=TextKind.BODY, content=["This is the footnote content."]),
183
+ TextBlock(kind=TextKind.BODY, level=0, content=["This is the footnote content."]),
183
184
  ],
184
185
  ),
185
186
  ],
@@ -194,7 +195,7 @@ generate_epub(epub_data, "book_with_footnotes.epub")
194
195
  ### Add Tables
195
196
 
196
197
  ```python
197
- from epub_generator import generate_epub, EpubData, TocItem, Chapter, Text, TextKind, Table
198
+ from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Table
198
199
 
199
200
  epub_data = EpubData(
200
201
  chapters=[
@@ -202,7 +203,7 @@ epub_data = EpubData(
202
203
  title="Chapter 1",
203
204
  get_chapter=lambda: Chapter(
204
205
  elements=[
205
- Text(kind=TextKind.BODY, content=["Here's a table:"]),
206
+ TextBlock(kind=TextKind.BODY, level=0, content=["Here's a table:"]),
206
207
  Table(
207
208
  html_content="""
208
209
  <table>
@@ -226,7 +227,7 @@ generate_epub(epub_data, "book_with_tables.epub")
226
227
  Block-level formulas:
227
228
 
228
229
  ```python
229
- from epub_generator import generate_epub, EpubData, TocItem, Chapter, Text, TextKind, Formula, LaTeXRender
230
+ from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Formula, LaTeXRender
230
231
 
231
232
  epub_data = EpubData(
232
233
  chapters=[
@@ -234,7 +235,7 @@ epub_data = EpubData(
234
235
  title="Chapter 1",
235
236
  get_chapter=lambda: Chapter(
236
237
  elements=[
237
- Text(kind=TextKind.BODY, content=["Pythagorean theorem:"]),
238
+ TextBlock(kind=TextKind.BODY, level=0, content=["Pythagorean theorem:"]),
238
239
  Formula(latex_expression="x^2 + y^2 = z^2"), # Block-level formula
239
240
  ]
240
241
  ),
@@ -255,8 +256,9 @@ epub_data = EpubData(
255
256
  title="Chapter 1",
256
257
  get_chapter=lambda: Chapter(
257
258
  elements=[
258
- Text(
259
+ TextBlock(
259
260
  kind=TextKind.BODY,
261
+ level=0,
260
262
  content=[
261
263
  "The Pythagorean theorem ",
262
264
  Formula(latex_expression="a^2 + b^2 = c^2"), # Inline formula
@@ -274,10 +276,46 @@ epub_data = EpubData(
274
276
  generate_epub(epub_data, "book_with_inline_math.epub", latex_render=LaTeXRender.MATHML)
275
277
  ```
276
278
 
279
+ ### Add Custom HTML Tags
280
+
281
+ You can embed custom HTML tags within text content:
282
+
283
+ ```python
284
+ from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, HTMLTag
285
+
286
+ epub_data = EpubData(
287
+ chapters=[
288
+ TocItem(
289
+ title="Chapter 1",
290
+ get_chapter=lambda: Chapter(elements=[TextBlock(
291
+ kind=TextKind.BODY,
292
+ level=0,
293
+ content=[
294
+ "This is normal text with ",
295
+ HTMLTag(
296
+ name="span",
297
+ attributes=[("class", "highlight"), ("style", "color: red;")],
298
+ content=["highlighted content"],
299
+ ),
300
+ " and more text with ",
301
+ HTMLTag(
302
+ name="strong",
303
+ content=["bold text"],
304
+ ),
305
+ ".",
306
+ ],
307
+ )]),
308
+ ),
309
+ ],
310
+ )
311
+
312
+ generate_epub(epub_data, "book_with_html_tags.epub")
313
+ ```
314
+
277
315
  ### Add Prefaces
278
316
 
279
317
  ```python
280
- from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, Text, TextKind
318
+ from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
281
319
 
282
320
  epub_data = EpubData(
283
321
  meta=BookMeta(title="Book with Prefaces"),
@@ -286,8 +324,8 @@ epub_data = EpubData(
286
324
  title="Preface",
287
325
  get_chapter=lambda: Chapter(
288
326
  elements=[
289
- Text(kind=TextKind.HEADLINE, content=["Preface"]),
290
- Text(kind=TextKind.BODY, content=["This is the preface content..."]),
327
+ TextBlock(kind=TextKind.HEADLINE, level=0, content=["Preface"]),
328
+ TextBlock(kind=TextKind.BODY, level=0, content=["This is the preface content..."]),
291
329
  ]
292
330
  ),
293
331
  ),
@@ -297,7 +335,7 @@ epub_data = EpubData(
297
335
  title="Chapter 1",
298
336
  get_chapter=lambda: Chapter(
299
337
  elements=[
300
- Text(kind=TextKind.BODY, content=["Main content..."]),
338
+ TextBlock(kind=TextKind.BODY, level=0, content=["Main content..."]),
301
339
  ]
302
340
  ),
303
341
  ),
@@ -390,12 +428,13 @@ class Chapter:
390
428
 
391
429
  `ContentBlock` is a union of:
392
430
 
393
- - **`Text`**: Text paragraph
431
+ - **`TextBlock`**: Text paragraph
394
432
  ```python
395
433
  @dataclass
396
- class Text:
397
- kind: TextKind # BODY | HEADLINE | QUOTE
398
- content: list[str | Mark | Formula] # Text with optional marks and inline formulas
434
+ class TextBlock:
435
+ kind: TextKind # BODY | HEADLINE | QUOTE
436
+ level: int # Heading level (0→h1, 1→h2, max h6; only for HEADLINE)
437
+ content: list[str | Mark | Formula | HTMLTag] # Text with optional marks, inline formulas, and HTML tags
399
438
  ```
400
439
 
401
440
  - **`Image`**: Image reference
@@ -420,6 +459,15 @@ class Chapter:
420
459
  latex_expression: str # LaTeX expression
421
460
  ```
422
461
 
462
+ - **`HTMLTag`**: HTML tag
463
+ ```python
464
+ @dataclass
465
+ class HTMLTag:
466
+ name: str # Tag name (e.g., "span", "div")
467
+ attributes: list[tuple[str, str]] = [] # List of (attribute, value) pairs
468
+ content: list[str | Mark | Formula | HTMLTag] = [] # Inner HTML content
469
+ ```
470
+
423
471
  #### `Footnote`
424
472
 
425
473
  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
- Text,
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
- "Text",
33
+ "TextBlock",
33
34
  "TextKind",
34
35
  "Table",
35
36
  "Formula",
37
+ "HTMLTag",
36
38
  "Image",
37
39
  "Footnote",
38
40
  "Mark",
@@ -7,15 +7,18 @@ from ..types import (
7
7
  Chapter,
8
8
  ContentBlock,
9
9
  Formula,
10
+ HTMLTag,
10
11
  Image,
11
12
  Mark,
12
13
  Table,
13
- Text,
14
+ TextBlock,
14
15
  TextKind,
15
16
  )
16
17
  from .gen_asset import process_formula, process_image, process_table
17
18
  from .xml_utils import serialize_element, set_epub_type
18
19
 
20
+ _MAX_HEADING_LEVEL = 6 # HTML standard defines heading levels from h1 to h6
21
+
19
22
 
20
23
  def generate_chapter(
21
24
  context: Context,
@@ -88,9 +91,10 @@ def _render_footnotes(
88
91
 
89
92
 
90
93
  def _render_content_block(context: Context, block: ContentBlock) -> Element | None:
91
- if isinstance(block, Text):
94
+ if isinstance(block, TextBlock):
92
95
  if block.kind == TextKind.HEADLINE:
93
- container = Element("h1")
96
+ heading_level = min(block.level + 1, _MAX_HEADING_LEVEL)
97
+ container = Element(f"h{heading_level}")
94
98
  elif block.kind == TextKind.QUOTE:
95
99
  container = Element("p")
96
100
  elif block.kind == TextKind.BODY:
@@ -98,8 +102,11 @@ def _render_content_block(context: Context, block: ContentBlock) -> Element | No
98
102
  else:
99
103
  raise ValueError(f"Unknown TextKind: {block.kind}")
100
104
 
101
- _render_text_content(context, container, block.content)
102
-
105
+ _render_text_content(
106
+ context=context,
107
+ parent=container,
108
+ content=block.content,
109
+ )
103
110
  if block.kind == TextKind.QUOTE:
104
111
  blockquote = Element("blockquote")
105
112
  blockquote.append(container)
@@ -120,7 +127,7 @@ def _render_content_block(context: Context, block: ContentBlock) -> Element | No
120
127
  return None
121
128
 
122
129
 
123
- def _render_text_content(context: Context, parent: Element, content: list[str | Mark | Formula]) -> None:
130
+ def _render_text_content(context: Context, parent: Element, content: list[str | Mark | Formula | HTMLTag]) -> None:
124
131
  """Render text content with inline citation marks."""
125
132
  current_element = parent
126
133
  for item in content:
@@ -136,6 +143,18 @@ def _render_text_content(context: Context, parent: Element, content: list[str |
136
143
  else:
137
144
  current_element.tail += item
138
145
 
146
+ elif isinstance(item, HTMLTag):
147
+ tag_element = Element(item.name)
148
+ for attr, value in item.attributes:
149
+ tag_element.set(attr, value)
150
+ _render_text_content(
151
+ context=context,
152
+ parent=tag_element,
153
+ content=item.content,
154
+ )
155
+ parent.append(tag_element)
156
+ current_element = tag_element
157
+
139
158
  elif isinstance(item, Formula):
140
159
  formula_element = process_formula(
141
160
  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, Text
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, Text):
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, Text):
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,12 @@ class Image:
107
107
  """Alt text (defaults to "image")"""
108
108
 
109
109
  @dataclass
110
- class Text:
110
+ class TextBlock:
111
111
  kind: TextKind
112
112
  """Kind of text block."""
113
- content: list[str | Mark | Formula]
113
+ level: int
114
+ """Heading level starting from 0 (only for HEADLINE: level 0 → h1, level 1 → h2, max h6; ignored for BODY and QUOTE)."""
115
+ content: list["str | Mark | Formula | HTMLTag"]
114
116
  """Text content with optional citation marks."""
115
117
 
116
118
  @dataclass
@@ -126,7 +128,7 @@ class Footnote:
126
128
  """Content blocks"""
127
129
 
128
130
 
129
- ContentBlock = Text | Table | Formula | Image
131
+ ContentBlock = TextBlock | Table | Formula | Image
130
132
  """Union of all content blocks that appear in main chapter content."""
131
133
 
132
134
  @dataclass
@@ -138,4 +140,17 @@ class Chapter:
138
140
  footnotes: list[Footnote] = field(default_factory=list)
139
141
  """Footnotes"""
140
142
 
141
- ChapterGetter = Callable[[], Chapter]
143
+ ChapterGetter = Callable[[], Chapter]
144
+
145
+ @dataclass
146
+ class HTMLTag:
147
+ """Generic HTML tag representation."""
148
+
149
+ name: str
150
+ """Tag name"""
151
+
152
+ attributes: list[tuple[str, str]] = field(default_factory=list)
153
+ """List of (attribute, value) pairs"""
154
+
155
+ content: list["str | Mark | Formula | HTMLTag"] = field(default_factory=list)
156
+ """Inner HTML content"""
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "epub-generator"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = "A simple Python EPUB 3.0 generator with a single API call"
5
5
  authors = ["Tao Zeyu <i@taozeyu.com>"]
6
6
  license = "MIT"
File without changes