asciidoctor-epub3 1.0.0.alpha.1
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.
- checksums.yaml +7 -0
- data/LICENSE.adoc +22 -0
- data/NOTICE.adoc +53 -0
- data/README.adoc +744 -0
- data/Rakefile +78 -0
- data/bin/adb-push-ebook +25 -0
- data/bin/asciidoctor-epub3 +15 -0
- data/data/fonts/assorted-icons.ttf +0 -0
- data/data/fonts/fontawesome-icons.ttf +0 -0
- data/data/fonts/mplus1mn-bold-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-bolditalic-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-italic-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-regular-ascii-conums.ttf +0 -0
- data/data/fonts/mplus1p-bold-latin-cyrillic.ttf +0 -0
- data/data/fonts/mplus1p-bold-latin-ext.ttf +0 -0
- data/data/fonts/mplus1p-bold-latin.ttf +0 -0
- data/data/fonts/mplus1p-bold-multilingual.ttf +0 -0
- data/data/fonts/mplus1p-light-latin-cyrillic.ttf +0 -0
- data/data/fonts/mplus1p-light-latin-ext.ttf +0 -0
- data/data/fonts/mplus1p-light-latin.ttf +0 -0
- data/data/fonts/mplus1p-light-multilingual.ttf +0 -0
- data/data/fonts/mplus1p-regular-latin-cyrillic.ttf +0 -0
- data/data/fonts/mplus1p-regular-latin-ext.ttf +0 -0
- data/data/fonts/mplus1p-regular-latin.ttf +0 -0
- data/data/fonts/mplus1p-regular-multilingual.ttf +0 -0
- data/data/fonts/notoserif-bold-latin-cyrillic.ttf +0 -0
- data/data/fonts/notoserif-bold-latin-ext.ttf +0 -0
- data/data/fonts/notoserif-bold-latin.ttf +0 -0
- data/data/fonts/notoserif-bold-multilingual.ttf +0 -0
- data/data/fonts/notoserif-bolditalic-latin-cyrillic.ttf +0 -0
- data/data/fonts/notoserif-bolditalic-latin-ext.ttf +0 -0
- data/data/fonts/notoserif-bolditalic-latin.ttf +0 -0
- data/data/fonts/notoserif-bolditalic-multilingual.ttf +0 -0
- data/data/fonts/notoserif-italic-latin-cyrillic.ttf +0 -0
- data/data/fonts/notoserif-italic-latin-ext.ttf +0 -0
- data/data/fonts/notoserif-italic-latin.ttf +0 -0
- data/data/fonts/notoserif-italic-multilingual.ttf +0 -0
- data/data/fonts/notoserif-regular-latin-cyrillic.ttf +0 -0
- data/data/fonts/notoserif-regular-latin-ext.ttf +0 -0
- data/data/fonts/notoserif-regular-latin.ttf +0 -0
- data/data/fonts/notoserif-regular-multilingual.ttf +0 -0
- data/data/images/default-avatar.jpg +0 -0
- data/data/images/default-avatar.png +0 -0
- data/data/images/default-avatar.svg +67 -0
- data/data/images/default-cover-large.png +0 -0
- data/data/images/default-cover.png +0 -0
- data/data/images/default-cover.svg +53 -0
- data/data/images/default-headshot.jpg +0 -0
- data/data/images/default-headshot.png +0 -0
- data/data/samples/asciidoctor-epub3-readme.adoc +744 -0
- data/data/samples/asciidoctor-js-extension.adoc +46 -0
- data/data/samples/asciidoctor-js-introduction.adoc +91 -0
- data/data/samples/i18n.adoc +161 -0
- data/data/samples/images/asciidoctor-js-chrome-extension.png +0 -0
- data/data/samples/images/avatars/graphitefriction.png +0 -0
- data/data/samples/images/avatars/mogztter.png +0 -0
- data/data/samples/images/avatars/mojavelinux.png +0 -0
- data/data/samples/images/correct-text-justification.png +0 -0
- data/data/samples/images/incorrect-text-justification.png +0 -0
- data/data/samples/images/screenshots/chapter-title-day.png +0 -0
- data/data/samples/images/screenshots/chapter-title.png +0 -0
- data/data/samples/images/screenshots/figure-admonition.png +0 -0
- data/data/samples/images/screenshots/section-title-paragraph.png +0 -0
- data/data/samples/images/screenshots/sidebar.png +0 -0
- data/data/samples/images/screenshots/table.png +0 -0
- data/data/samples/images/screenshots/text.png +0 -0
- data/data/samples/sample-book.adoc +20 -0
- data/data/samples/sample-content.adoc +168 -0
- data/data/styles/color-palette.css +28 -0
- data/data/styles/epub3-css3-only.css +161 -0
- data/data/styles/epub3-fonts.css +94 -0
- data/data/styles/epub3.css +1293 -0
- data/lib/asciidoctor-epub3.rb +5 -0
- data/lib/asciidoctor-epub3/converter.rb +859 -0
- data/lib/asciidoctor-epub3/core_ext/string.rb +7 -0
- data/lib/asciidoctor-epub3/font_icon_map.rb +376 -0
- data/lib/asciidoctor-epub3/packager.rb +466 -0
- data/lib/asciidoctor-epub3/spine_item_processor.rb +72 -0
- data/lib/asciidoctor-epub3/version.rb +5 -0
- data/scripts/generate-font-subsets.pe +225 -0
- metadata +192 -0
@@ -0,0 +1,376 @@
|
|
1
|
+
module Asciidoctor
|
2
|
+
module Epub3
|
3
|
+
# Map of Font Awesome icon names to unicode characters
|
4
|
+
FontIconMap = {
|
5
|
+
glass: '\f000',
|
6
|
+
music: '\f001',
|
7
|
+
search: '\f002',
|
8
|
+
envelope_o: '\f003',
|
9
|
+
heart: '\f004',
|
10
|
+
star: '\f005',
|
11
|
+
star_o: '\f006',
|
12
|
+
user: '\f007',
|
13
|
+
film: '\f008',
|
14
|
+
th_large: '\f009',
|
15
|
+
th: '\f00a',
|
16
|
+
th_list: '\f00b',
|
17
|
+
check: '\f00c',
|
18
|
+
times: '\f00d',
|
19
|
+
search_plus: '\f00e',
|
20
|
+
search_minus: '\f010',
|
21
|
+
power_off: '\f011',
|
22
|
+
signal: '\f012',
|
23
|
+
cog: '\f013',
|
24
|
+
trash_o: '\f014',
|
25
|
+
home: '\f015',
|
26
|
+
file_o: '\f016',
|
27
|
+
clock_o: '\f017',
|
28
|
+
road: '\f018',
|
29
|
+
download: '\f019',
|
30
|
+
arrow_circle_o_down: '\f01a',
|
31
|
+
arrow_circle_o_up: '\f01b',
|
32
|
+
inbox: '\f01c',
|
33
|
+
play_circle_o: '\f01d',
|
34
|
+
repeat: '\f01e',
|
35
|
+
refresh: '\f021',
|
36
|
+
list_alt: '\f022',
|
37
|
+
lock: '\f023',
|
38
|
+
flag: '\f024',
|
39
|
+
headphones: '\f025',
|
40
|
+
volume_off: '\f026',
|
41
|
+
volume_down: '\f027',
|
42
|
+
volume_up: '\f028',
|
43
|
+
qrcode: '\f029',
|
44
|
+
barcode: '\f02a',
|
45
|
+
tag: '\f02b',
|
46
|
+
tags: '\f02c',
|
47
|
+
book: '\f02d',
|
48
|
+
bookmark: '\f02e',
|
49
|
+
print: '\f02f',
|
50
|
+
camera: '\f030',
|
51
|
+
font: '\f031',
|
52
|
+
bold: '\f032',
|
53
|
+
italic: '\f033',
|
54
|
+
text_height: '\f034',
|
55
|
+
text_width: '\f035',
|
56
|
+
align_left: '\f036',
|
57
|
+
align_center: '\f037',
|
58
|
+
align_right: '\f038',
|
59
|
+
align_justify: '\f039',
|
60
|
+
list: '\f03a',
|
61
|
+
outdent: '\f03b',
|
62
|
+
indent: '\f03c',
|
63
|
+
video_camera: '\f03d',
|
64
|
+
picture_o: '\f03e',
|
65
|
+
pencil: '\f040',
|
66
|
+
map_marker: '\f041',
|
67
|
+
adjust: '\f042',
|
68
|
+
tint: '\f043',
|
69
|
+
pencil_square_o: '\f044',
|
70
|
+
share_square_o: '\f045',
|
71
|
+
check_square_o: '\f046',
|
72
|
+
arrows: '\f047',
|
73
|
+
step_backward: '\f048',
|
74
|
+
fast_backward: '\f049',
|
75
|
+
backward: '\f04a',
|
76
|
+
play: '\f04b',
|
77
|
+
pause: '\f04c',
|
78
|
+
stop: '\f04d',
|
79
|
+
forward: '\f04e',
|
80
|
+
fast_forward: '\f050',
|
81
|
+
step_forward: '\f051',
|
82
|
+
eject: '\f052',
|
83
|
+
chevron_left: '\f053',
|
84
|
+
chevron_right: '\f054',
|
85
|
+
plus_circle: '\f055',
|
86
|
+
minus_circle: '\f056',
|
87
|
+
times_circle: '\f057',
|
88
|
+
check_circle: '\f058',
|
89
|
+
question_circle: '\f059',
|
90
|
+
info_circle: '\f05a',
|
91
|
+
crosshairs: '\f05b',
|
92
|
+
times_circle_o: '\f05c',
|
93
|
+
check_circle_o: '\f05d',
|
94
|
+
ban: '\f05e',
|
95
|
+
arrow_left: '\f060',
|
96
|
+
arrow_right: '\f061',
|
97
|
+
arrow_up: '\f062',
|
98
|
+
arrow_down: '\f063',
|
99
|
+
share: '\f064',
|
100
|
+
expand: '\f065',
|
101
|
+
compress: '\f066',
|
102
|
+
plus: '\f067',
|
103
|
+
minus: '\f068',
|
104
|
+
asterisk: '\f069',
|
105
|
+
exclamation_circle: '\f06a',
|
106
|
+
gift: '\f06b',
|
107
|
+
leaf: '\f06c',
|
108
|
+
fire: '\f06d',
|
109
|
+
eye: '\f06e',
|
110
|
+
eye_slash: '\f070',
|
111
|
+
exclamation_triangle: '\f071',
|
112
|
+
plane: '\f072',
|
113
|
+
calendar: '\f073',
|
114
|
+
random: '\f074',
|
115
|
+
comment: '\f075',
|
116
|
+
magnet: '\f076',
|
117
|
+
chevron_up: '\f077',
|
118
|
+
chevron_down: '\f078',
|
119
|
+
retweet: '\f079',
|
120
|
+
shopping_cart: '\f07a',
|
121
|
+
folder: '\f07b',
|
122
|
+
folder_open: '\f07c',
|
123
|
+
arrows_v: '\f07d',
|
124
|
+
arrows_h: '\f07e',
|
125
|
+
bar_chart_o: '\f080',
|
126
|
+
twitter_square: '\f081',
|
127
|
+
facebook_square: '\f082',
|
128
|
+
camera_retro: '\f083',
|
129
|
+
key: '\f084',
|
130
|
+
cogs: '\f085',
|
131
|
+
comments: '\f086',
|
132
|
+
thumbs_o_up: '\f087',
|
133
|
+
thumbs_o_down: '\f088',
|
134
|
+
star_half: '\f089',
|
135
|
+
heart_o: '\f08a',
|
136
|
+
sign_out: '\f08b',
|
137
|
+
linkedin_square: '\f08c',
|
138
|
+
thumb_tack: '\f08d',
|
139
|
+
external_link: '\f08e',
|
140
|
+
sign_in: '\f090',
|
141
|
+
trophy: '\f091',
|
142
|
+
github_square: '\f092',
|
143
|
+
upload: '\f093',
|
144
|
+
lemon_o: '\f094',
|
145
|
+
phone: '\f095',
|
146
|
+
square_o: '\f096',
|
147
|
+
bookmark_o: '\f097',
|
148
|
+
phone_square: '\f098',
|
149
|
+
twitter: '\f099',
|
150
|
+
facebook: '\f09a',
|
151
|
+
github: '\f09b',
|
152
|
+
unlock: '\f09c',
|
153
|
+
credit_card: '\f09d',
|
154
|
+
rss: '\f09e',
|
155
|
+
hdd_o: '\f0a0',
|
156
|
+
bullhorn: '\f0a1',
|
157
|
+
bell: '\f0f3',
|
158
|
+
certificate: '\f0a3',
|
159
|
+
hand_o_right: '\f0a4',
|
160
|
+
hand_o_left: '\f0a5',
|
161
|
+
hand_o_up: '\f0a6',
|
162
|
+
hand_o_down: '\f0a7',
|
163
|
+
arrow_circle_left: '\f0a8',
|
164
|
+
arrow_circle_right: '\f0a9',
|
165
|
+
arrow_circle_up: '\f0aa',
|
166
|
+
arrow_circle_down: '\f0ab',
|
167
|
+
globe: '\f0ac',
|
168
|
+
wrench: '\f0ad',
|
169
|
+
tasks: '\f0ae',
|
170
|
+
filter: '\f0b0',
|
171
|
+
briefcase: '\f0b1',
|
172
|
+
arrows_alt: '\f0b2',
|
173
|
+
users: '\f0c0',
|
174
|
+
link: '\f0c1',
|
175
|
+
cloud: '\f0c2',
|
176
|
+
flask: '\f0c3',
|
177
|
+
scissors: '\f0c4',
|
178
|
+
files_o: '\f0c5',
|
179
|
+
paperclip: '\f0c6',
|
180
|
+
floppy_o: '\f0c7',
|
181
|
+
square: '\f0c8',
|
182
|
+
bars: '\f0c9',
|
183
|
+
list_ul: '\f0ca',
|
184
|
+
list_ol: '\f0cb',
|
185
|
+
strikethrough: '\f0cc',
|
186
|
+
underline: '\f0cd',
|
187
|
+
table: '\f0ce',
|
188
|
+
magic: '\f0d0',
|
189
|
+
truck: '\f0d1',
|
190
|
+
pinterest: '\f0d2',
|
191
|
+
pinterest_square: '\f0d3',
|
192
|
+
google_plus_square: '\f0d4',
|
193
|
+
google_plus: '\f0d5',
|
194
|
+
money: '\f0d6',
|
195
|
+
caret_down: '\f0d7',
|
196
|
+
caret_up: '\f0d8',
|
197
|
+
caret_left: '\f0d9',
|
198
|
+
caret_right: '\f0da',
|
199
|
+
columns: '\f0db',
|
200
|
+
sort: '\f0dc',
|
201
|
+
sort_asc: '\f0dd',
|
202
|
+
sort_desc: '\f0de',
|
203
|
+
envelope: '\f0e0',
|
204
|
+
linkedin: '\f0e1',
|
205
|
+
undo: '\f0e2',
|
206
|
+
gavel: '\f0e3',
|
207
|
+
tachometer: '\f0e4',
|
208
|
+
comment_o: '\f0e5',
|
209
|
+
comments_o: '\f0e6',
|
210
|
+
bolt: '\f0e7',
|
211
|
+
sitemap: '\f0e8',
|
212
|
+
umbrella: '\f0e9',
|
213
|
+
clipboard: '\f0ea',
|
214
|
+
lightbulb_o: '\f0eb',
|
215
|
+
exchange: '\f0ec',
|
216
|
+
cloud_download: '\f0ed',
|
217
|
+
cloud_upload: '\f0ee',
|
218
|
+
user_md: '\f0f0',
|
219
|
+
stethoscope: '\f0f1',
|
220
|
+
suitcase: '\f0f2',
|
221
|
+
bell_o: '\f0a2',
|
222
|
+
coffee: '\f0f4',
|
223
|
+
cutlery: '\f0f5',
|
224
|
+
file_text_o: '\f0f6',
|
225
|
+
building_o: '\f0f7',
|
226
|
+
hospital_o: '\f0f8',
|
227
|
+
ambulance: '\f0f9',
|
228
|
+
medkit: '\f0fa',
|
229
|
+
fighter_jet: '\f0fb',
|
230
|
+
beer: '\f0fc',
|
231
|
+
h_square: '\f0fd',
|
232
|
+
plus_square: '\f0fe',
|
233
|
+
angle_double_left: '\f100',
|
234
|
+
angle_double_right: '\f101',
|
235
|
+
angle_double_up: '\f102',
|
236
|
+
angle_double_down: '\f103',
|
237
|
+
angle_left: '\f104',
|
238
|
+
angle_right: '\f105',
|
239
|
+
angle_up: '\f106',
|
240
|
+
angle_down: '\f107',
|
241
|
+
desktop: '\f108',
|
242
|
+
laptop: '\f109',
|
243
|
+
tablet: '\f10a',
|
244
|
+
mobile: '\f10b',
|
245
|
+
circle_o: '\f10c',
|
246
|
+
quote_left: '\f10d',
|
247
|
+
quote_right: '\f10e',
|
248
|
+
spinner: '\f110',
|
249
|
+
circle: '\f111',
|
250
|
+
reply: '\f112',
|
251
|
+
github_alt: '\f113',
|
252
|
+
folder_o: '\f114',
|
253
|
+
folder_open_o: '\f115',
|
254
|
+
smile_o: '\f118',
|
255
|
+
frown_o: '\f119',
|
256
|
+
meh_o: '\f11a',
|
257
|
+
gamepad: '\f11b',
|
258
|
+
keyboard_o: '\f11c',
|
259
|
+
flag_o: '\f11d',
|
260
|
+
flag_checkered: '\f11e',
|
261
|
+
terminal: '\f120',
|
262
|
+
code: '\f121',
|
263
|
+
reply_all: '\f122',
|
264
|
+
mail_reply_all: '\f122',
|
265
|
+
star_half_o: '\f123',
|
266
|
+
location_arrow: '\f124',
|
267
|
+
crop: '\f125',
|
268
|
+
code_fork: '\f126',
|
269
|
+
chain_broken: '\f127',
|
270
|
+
question: '\f128',
|
271
|
+
info: '\f129',
|
272
|
+
exclamation: '\f12a',
|
273
|
+
superscript: '\f12b',
|
274
|
+
subscript: '\f12c',
|
275
|
+
eraser: '\f12d',
|
276
|
+
puzzle_piece: '\f12e',
|
277
|
+
microphone: '\f130',
|
278
|
+
microphone_slash: '\f131',
|
279
|
+
shield: '\f132',
|
280
|
+
calendar_o: '\f133',
|
281
|
+
fire_extinguisher: '\f134',
|
282
|
+
rocket: '\f135',
|
283
|
+
maxcdn: '\f136',
|
284
|
+
chevron_circle_left: '\f137',
|
285
|
+
chevron_circle_right: '\f138',
|
286
|
+
chevron_circle_up: '\f139',
|
287
|
+
chevron_circle_down: '\f13a',
|
288
|
+
html5: '\f13b',
|
289
|
+
css3: '\f13c',
|
290
|
+
anchor: '\f13d',
|
291
|
+
unlock_alt: '\f13e',
|
292
|
+
bullseye: '\f140',
|
293
|
+
ellipsis_h: '\f141',
|
294
|
+
ellipsis_v: '\f142',
|
295
|
+
rss_square: '\f143',
|
296
|
+
play_circle: '\f144',
|
297
|
+
ticket: '\f145',
|
298
|
+
minus_square: '\f146',
|
299
|
+
minus_square_o: '\f147',
|
300
|
+
level_up: '\f148',
|
301
|
+
level_down: '\f149',
|
302
|
+
check_square: '\f14a',
|
303
|
+
pencil_square: '\f14b',
|
304
|
+
external_link_square: '\f14c',
|
305
|
+
share_square: '\f14d',
|
306
|
+
compass: '\f14e',
|
307
|
+
caret_square_o_down: '\f150',
|
308
|
+
caret_square_o_up: '\f151',
|
309
|
+
caret_square_o_right: '\f152',
|
310
|
+
eur: '\f153',
|
311
|
+
gbp: '\f154',
|
312
|
+
usd: '\f155',
|
313
|
+
inr: '\f156',
|
314
|
+
jpy: '\f157',
|
315
|
+
rub: '\f158',
|
316
|
+
krw: '\f159',
|
317
|
+
btc: '\f15a',
|
318
|
+
file: '\f15b',
|
319
|
+
file_text: '\f15c',
|
320
|
+
sort_alpha_asc: '\f15d',
|
321
|
+
sort_alpha_desc: '\f15e',
|
322
|
+
sort_amount_asc: '\f160',
|
323
|
+
sort_amount_desc: '\f161',
|
324
|
+
sort_numeric_asc: '\f162',
|
325
|
+
sort_numeric_desc: '\f163',
|
326
|
+
thumbs_up: '\f164',
|
327
|
+
thumbs_down: '\f165',
|
328
|
+
youtube_square: '\f166',
|
329
|
+
youtube: '\f167',
|
330
|
+
xing: '\f168',
|
331
|
+
xing_square: '\f169',
|
332
|
+
youtube_play: '\f16a',
|
333
|
+
dropbox: '\f16b',
|
334
|
+
stack_overflow: '\f16c',
|
335
|
+
instagram: '\f16d',
|
336
|
+
flickr: '\f16e',
|
337
|
+
adn: '\f170',
|
338
|
+
bitbucket: '\f171',
|
339
|
+
bitbucket_square: '\f172',
|
340
|
+
tumblr: '\f173',
|
341
|
+
tumblr_square: '\f174',
|
342
|
+
long_arrow_down: '\f175',
|
343
|
+
long_arrow_up: '\f176',
|
344
|
+
long_arrow_left: '\f177',
|
345
|
+
long_arrow_right: '\f178',
|
346
|
+
apple: '\f179',
|
347
|
+
windows: '\f17a',
|
348
|
+
android: '\f17b',
|
349
|
+
linux: '\f17c',
|
350
|
+
dribbble: '\f17d',
|
351
|
+
skype: '\f17e',
|
352
|
+
foursquare: '\f180',
|
353
|
+
trello: '\f181',
|
354
|
+
female: '\f182',
|
355
|
+
male: '\f183',
|
356
|
+
gittip: '\f184',
|
357
|
+
sun_o: '\f185',
|
358
|
+
moon_o: '\f186',
|
359
|
+
archive: '\f187',
|
360
|
+
bug: '\f188',
|
361
|
+
vk: '\f189',
|
362
|
+
weibo: '\f18a',
|
363
|
+
renren: '\f18b',
|
364
|
+
pagelines: '\f18c',
|
365
|
+
stack_exchange: '\f18d',
|
366
|
+
arrow_circle_o_right: '\f18e',
|
367
|
+
arrow_circle_o_left: '\f190',
|
368
|
+
caret_square_o_left: '\f191',
|
369
|
+
dot_circle_o: '\f192',
|
370
|
+
wheelchair: '\f193',
|
371
|
+
vimeo_square: '\f194',
|
372
|
+
try: '\f195',
|
373
|
+
plus_square_o: '\f196'
|
374
|
+
}
|
375
|
+
end
|
376
|
+
end
|
@@ -0,0 +1,466 @@
|
|
1
|
+
require_relative 'core_ext/string'
|
2
|
+
autoload :FileUtils, 'fileutils'
|
3
|
+
autoload :Open3, 'open3'
|
4
|
+
autoload :Shellwords, 'shellwords'
|
5
|
+
|
6
|
+
module Asciidoctor
|
7
|
+
module Epub3
|
8
|
+
module GepubBuilderMixin
|
9
|
+
DATA_DIR = ::File.expand_path(::File.join ::File.dirname(__FILE__), '..', '..', 'data')
|
10
|
+
SAMPLES_DIR = ::File.join DATA_DIR, 'samples'
|
11
|
+
WordJoiner = Epub3::WordJoiner
|
12
|
+
FromHtmlSpecialCharsMap = ContentConverter::FromHtmlSpecialCharsMap
|
13
|
+
FromHtmlSpecialCharsRx = ContentConverter::FromHtmlSpecialCharsRx
|
14
|
+
CsvDelimiterRx = /\s*,\s*/
|
15
|
+
DefaultCoverImage = 'images/default-cover.png'
|
16
|
+
InlineImageMacroRx = /^image:(.*?)\[(.*?)\]$/
|
17
|
+
|
18
|
+
def sanitized_doctitle doc, target = :plain
|
19
|
+
return (doc.attr 'untitled-label') unless doc.header?
|
20
|
+
title = case target
|
21
|
+
when :attribute_cdata
|
22
|
+
doc.doctitle(sanitize: true).gsub('"', '"')
|
23
|
+
when :element_cdata
|
24
|
+
doc.doctitle sanitize: true
|
25
|
+
when :pcdata
|
26
|
+
doc.doctitle
|
27
|
+
when :plain
|
28
|
+
doc.doctitle(sanitize: true).gsub(FromHtmlSpecialCharsRx, FromHtmlSpecialCharsMap)
|
29
|
+
end
|
30
|
+
title.gsub WordJoiner, ''
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_theme_assets doc
|
34
|
+
builder = self
|
35
|
+
format = @format
|
36
|
+
workdir = if doc.attr? 'epub3-stylesdir'
|
37
|
+
::File.join doc.attr('docdir'), doc.attr('epub3-stylesdir')
|
38
|
+
else
|
39
|
+
::File.join DATA_DIR, 'styles'
|
40
|
+
end
|
41
|
+
|
42
|
+
# TODO improve design/UX of custom theme functionality, including custom fonts
|
43
|
+
resources workdir: workdir do
|
44
|
+
file 'styles/epub3.css' => (builder.postprocess_css_file 'epub3.css', format)
|
45
|
+
file 'styles/epub3-css3-only.css' => (builder.postprocess_css_file 'epub3-css3-only.css', format)
|
46
|
+
end
|
47
|
+
|
48
|
+
resources workdir: DATA_DIR do
|
49
|
+
#file 'styles/epub3.css' => (builder.postprocess_css_file 'styles/epub3.css', format)
|
50
|
+
#file 'styles/epub3-css3-only.css' => (builder.postprocess_css_file 'styles/epub3-css3-only.css', format)
|
51
|
+
font_list, font_css = builder.select_fonts 'styles/epub3-fonts.css', (doc.attr 'scripts', 'latin')
|
52
|
+
file 'styles/epub3-fonts.css' => font_css
|
53
|
+
with_media_type 'application/x-font-ttf' do
|
54
|
+
files(*font_list)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_cover_image doc
|
60
|
+
imagesdir = (doc.attr 'imagesdir', '.').chomp '/'
|
61
|
+
imagesdir = (imagesdir == '.' ? nil : %(#{imagesdir}/))
|
62
|
+
|
63
|
+
if (front_cover_image = doc.attr 'front-cover-image')
|
64
|
+
if front_cover_image =~ InlineImageMacroRx
|
65
|
+
front_cover_image = %(#{imagesdir}#{$1})
|
66
|
+
end
|
67
|
+
workdir = doc.attr 'docdir'
|
68
|
+
else
|
69
|
+
front_cover_image = DefaultCoverImage
|
70
|
+
workdir = DATA_DIR
|
71
|
+
end
|
72
|
+
|
73
|
+
resources workdir: workdir do
|
74
|
+
cover_image %(#{imagesdir}jacket/cover#{::File.extname front_cover_image}) => front_cover_image
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# NOTE must be called within the ordered block
|
79
|
+
def add_cover_page doc, spine_builder, book
|
80
|
+
imagesdir = (doc.attr 'imagesdir', '.').chomp '/'
|
81
|
+
imagesdir = (imagesdir == '.' ? nil : %(#{imagesdir}/))
|
82
|
+
|
83
|
+
img = (doc.attr 'front-cover-image') || DefaultCoverImage
|
84
|
+
|
85
|
+
if img =~ InlineImageMacroRx
|
86
|
+
img = %(#{imagesdir}#{$1})
|
87
|
+
# TODO use proper attribute parser
|
88
|
+
_, w, h = $2.split ',', 3
|
89
|
+
end
|
90
|
+
|
91
|
+
w ||= 1050
|
92
|
+
h ||= 1600
|
93
|
+
img_path = %(#{imagesdir}jacket/cover#{::File.extname img})
|
94
|
+
# NOTE SVG wrapper maintains aspect ratio and confines image to view box
|
95
|
+
content = %(<!DOCTYPE html>
|
96
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="en" lang="en">
|
97
|
+
<head>
|
98
|
+
<meta charset="UTF-8"/>
|
99
|
+
<title>#{sanitized_doctitle doc, :element_cdata}</title>
|
100
|
+
<style type="text/css">
|
101
|
+
@page {
|
102
|
+
margin: 0;
|
103
|
+
}
|
104
|
+
html {
|
105
|
+
margin: 0 !important;
|
106
|
+
padding: 0 !important;
|
107
|
+
}
|
108
|
+
body {
|
109
|
+
margin: 0;
|
110
|
+
padding: 0 !important;
|
111
|
+
text-align: center;
|
112
|
+
}
|
113
|
+
body > svg {
|
114
|
+
/* prevent bleed onto second page (removes descender space) */
|
115
|
+
display: block;
|
116
|
+
}
|
117
|
+
</style>
|
118
|
+
</head>
|
119
|
+
<body epub:type="cover"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
120
|
+
width="100%" height="100%" viewBox="0 0 #{w} #{h}" preserveAspectRatio="xMidYMid meet">
|
121
|
+
<image width="#{w}" height="#{h}" xlink:href="#{img_path}"/>
|
122
|
+
</svg></body>
|
123
|
+
</html>).to_ios
|
124
|
+
# GitDen expects a cover.xhtml, so add it to the spine
|
125
|
+
spine_builder.file 'cover.xhtml' => content
|
126
|
+
spine_builder.id 'cover'
|
127
|
+
# clearly a deficiency of gepub that it does not match the id correctly
|
128
|
+
book.spine.itemref_by_id['item_cover1'].idref = 'cover'
|
129
|
+
end
|
130
|
+
|
131
|
+
def add_images_from_front_matter
|
132
|
+
if ::File.exist? 'front-matter.html'
|
133
|
+
::File.read('front-matter.html').scan(/<img src="(.+?)"/) do
|
134
|
+
resources do
|
135
|
+
file $1
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def add_front_matter_page doc, spine_builder, builder, format
|
142
|
+
if ::File.exist? 'front-matter.html'
|
143
|
+
spine_builder.file 'front-matter.html' => (builder.postprocess_xhtml_file 'front-matter.html', format)
|
144
|
+
(spine_builder.instance_variable_get :@last_defined_item).properties << 'svg'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# FIXME don't add same image more than once
|
149
|
+
# FIXME add inline images
|
150
|
+
def add_content_images doc, images
|
151
|
+
docimagesdir = (doc.attr 'imagesdir', '.').chomp '/'
|
152
|
+
docimagesdir = (docimagesdir == '.' ? nil : %(#{docimagesdir}/))
|
153
|
+
|
154
|
+
resources(workdir: (doc.attr 'docdir')) do
|
155
|
+
images.each do |image|
|
156
|
+
imagesdir = (image.document.attr 'imagesdir', '.').chomp '/'
|
157
|
+
imagesdir = (imagesdir == '.' ? nil : %(#{imagesdir}/))
|
158
|
+
image_path = %(#{imagesdir}#{image.attr 'target'})
|
159
|
+
if image_path.start_with? %(#{docimagesdir}jacket/cover.)
|
160
|
+
warn %(The image path #{image_path} is reserved for the cover artwork. Ignoring conflicting image from content.)
|
161
|
+
elsif ::File.readable? image_path
|
162
|
+
file image_path
|
163
|
+
else
|
164
|
+
warn %(Image not found or not readable: #{image_path})
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def add_profile_images doc, usernames
|
171
|
+
spine = @spine
|
172
|
+
imagesdir = (doc.attr 'imagesdir', '.').chomp '/'
|
173
|
+
imagesdir = (imagesdir == '.' ? nil : %(#{imagesdir}/))
|
174
|
+
|
175
|
+
resources workdir: DATA_DIR do
|
176
|
+
file %(#{imagesdir}avatars/default.jpg) => %(images/default-avatar.jpg)
|
177
|
+
file %(#{imagesdir}headshots/default.jpg) => %(images/default-headshot.jpg)
|
178
|
+
end
|
179
|
+
|
180
|
+
resources(workdir: (doc.attr 'docdir')) do
|
181
|
+
usernames.each do |username|
|
182
|
+
if ::File.readable?(avatar = %(#{imagesdir}avatars/#{username}.jpg))
|
183
|
+
file avatar
|
184
|
+
else
|
185
|
+
warn %(Avatar #{avatar} not found or readable. Falling back to default avatar for #{username}.)
|
186
|
+
::Dir.chdir DATA_DIR do
|
187
|
+
file avatar => %(images/default-avatar.jpg)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
if ::File.readable? (headshot = %(#{imagesdir}headshots/#{username}.jpg))
|
192
|
+
file headshot
|
193
|
+
else
|
194
|
+
warn %(Headshot #{headshot} not found or readable. Falling back to default headshot for #{username}.)
|
195
|
+
::Dir.chdir DATA_DIR do
|
196
|
+
file headshot => %(images/default-headshot.jpg)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
=begin
|
201
|
+
spine.each do |item|
|
202
|
+
username = (item.attr 'username') || 'default'
|
203
|
+
avatar_target = %(#{imagesdir}avatars/#{username}.jpg)
|
204
|
+
if ::File.readable?(avatar = %(#{item.attr 'docname'}/avatar.jpg))
|
205
|
+
file avatar_target => avatar
|
206
|
+
else
|
207
|
+
warn %(Avatar #{avatar} not found or not readable. Falling back to default avatar for #{username}.)
|
208
|
+
::Dir.chdir DATA_DIR do
|
209
|
+
file avatar_target => %(images/default-avatar.jpg)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
if ::File.readable? (headshot = %(#{item.attr 'docname'}/headshot.jpg))
|
213
|
+
file headshot
|
214
|
+
# TODO default headshot?
|
215
|
+
end
|
216
|
+
end
|
217
|
+
=end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def add_content doc
|
222
|
+
builder = self
|
223
|
+
spine = @spine
|
224
|
+
format = @format
|
225
|
+
resources(workdir: (doc.attr 'docdir')) do
|
226
|
+
builder.add_images_from_front_matter
|
227
|
+
# QUESTION should we move navigation_document to the Packager class? seems to make sense
|
228
|
+
#nav 'nav.xhtml' => (builder.postprocess_xhtml doc.converter.navigation_document(doc, spine), format)
|
229
|
+
nav 'nav.xhtml' => (builder.postprocess_xhtml ::Asciidoctor::Converter::Factory.default.create('epub3-xhtml5').navigation_document(doc, spine), format)
|
230
|
+
ordered do
|
231
|
+
builder.add_cover_page doc, self, @book unless format == :kf8
|
232
|
+
builder.add_front_matter_page doc, self, builder, format
|
233
|
+
spine.each_with_index do |item, i|
|
234
|
+
content_path = %(#{item.id || (item.attr 'docname')}.xhtml)
|
235
|
+
file content_path => (builder.postprocess_xhtml item.convert, format)
|
236
|
+
# NOTE heading for ePub2 navigation file; toc.ncx requires headings to be plain text
|
237
|
+
heading builder.sanitized_doctitle(item)
|
238
|
+
@last_defined_item.properties << 'svg' if ((item.attr 'epub-properties') || []).include? 'svg'
|
239
|
+
#linear 'yes' if i == 0
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def collect_keywords doc, spine
|
246
|
+
([doc] + spine).map do |item|
|
247
|
+
if item.attr? 'keywords'
|
248
|
+
(item.attr 'keywords').split CsvDelimiterRx
|
249
|
+
else
|
250
|
+
[]
|
251
|
+
end
|
252
|
+
end.flatten.uniq
|
253
|
+
end
|
254
|
+
|
255
|
+
# Swap fonts in CSS based on the value of the document attribute 'scripts',
|
256
|
+
# then return the list of fonts as well as the font CSS.
|
257
|
+
def select_fonts filename, scripts = 'latin'
|
258
|
+
font_css = ::File.read(filename)
|
259
|
+
font_css = font_css.gsub(/(?<=-)latin(?=\.ttf\))/, scripts) unless scripts == 'latin'
|
260
|
+
font_list = font_css.scan(/url\(\.\.\/(.+\.ttf)\);$/).flatten
|
261
|
+
return [font_list, font_css.to_ios]
|
262
|
+
end
|
263
|
+
|
264
|
+
def postprocess_css_file filename, format
|
265
|
+
return filename unless format == :kf8
|
266
|
+
postprocess_css ::File.read(filename), format
|
267
|
+
end
|
268
|
+
|
269
|
+
def postprocess_css content, format
|
270
|
+
return content.to_ios unless format == :kf8
|
271
|
+
# TODO convert regular expressions to constants
|
272
|
+
content
|
273
|
+
.gsub(/^ -webkit-column-break-.*\n/, '')
|
274
|
+
.gsub(/^ max-width: .*\n/, '')
|
275
|
+
.to_ios
|
276
|
+
end
|
277
|
+
|
278
|
+
def postprocess_xhtml_file filename, format
|
279
|
+
return filename unless format == :kf8
|
280
|
+
postprocess_xhtml ::File.read(filename), format
|
281
|
+
end
|
282
|
+
|
283
|
+
# NOTE Kindle requires that
|
284
|
+
# <meta charset="utf-8"/>
|
285
|
+
# be converted to
|
286
|
+
# <meta http-equiv="Content-Type" content="application/xml+xhtml; charset=UTF-8"/>
|
287
|
+
def postprocess_xhtml content, format
|
288
|
+
return content.to_ios unless format == :kf8
|
289
|
+
# TODO convert regular expressions to constants
|
290
|
+
content
|
291
|
+
.gsub(/<meta charset="(.+?)"\/>/, '<meta http-equiv="Content-Type" content="application/xml+xhtml; charset=\1"/>')
|
292
|
+
.gsub(/<img([^>]+) style="width: (\d\d)%;"/, '<img\1 style="width: \2%; height: \2%;"')
|
293
|
+
.gsub(/<script type="text\/javascript">.*?<\/script>\n?/m, '')
|
294
|
+
.to_ios
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
class Packager
|
299
|
+
KINDLEGEN = ENV['KINDLEGEN'] || 'kindlegen'
|
300
|
+
EPUBCHECK = ENV['EPUBCHECK'] || %(epubcheck#{::Gem.win_platform? ? '.bat' : '.sh'})
|
301
|
+
EpubExtensionRx = /\.epub$/
|
302
|
+
Kf8ExtensionRx = /-kf8\.epub$/
|
303
|
+
|
304
|
+
def initialize spine_doc, spine, format = :epub3, options = {}
|
305
|
+
@document = spine_doc
|
306
|
+
@spine = spine || []
|
307
|
+
@format = format
|
308
|
+
end
|
309
|
+
|
310
|
+
def package options = {}
|
311
|
+
doc = @document
|
312
|
+
spine = @spine
|
313
|
+
fmt = @format
|
314
|
+
dest = @dest_dir = options[:to_dir] || (File.expand_path ::Dir.pwd)
|
315
|
+
|
316
|
+
images = spine.map {|item| (item.find_by context: :image) || [] }.flatten
|
317
|
+
usernames = spine.map {|item| item.attr 'username' }.compact.uniq
|
318
|
+
# FIXME authors should be aggregated already on parent document
|
319
|
+
authors = if doc.attr? 'authors'
|
320
|
+
(doc.attr 'authors').split(GepubBuilderMixin::CsvDelimiterRx).concat(spine.map {|item| item.attr 'author' }).uniq
|
321
|
+
else
|
322
|
+
[]
|
323
|
+
end
|
324
|
+
|
325
|
+
builder = ::GEPUB::Builder.new do
|
326
|
+
extend GepubBuilderMixin
|
327
|
+
@document = doc
|
328
|
+
@spine = spine
|
329
|
+
@format = fmt
|
330
|
+
@book.epub_backward_compat = (fmt != :kf8)
|
331
|
+
|
332
|
+
language(doc.attr 'lang', 'en')
|
333
|
+
id 'pub-language'
|
334
|
+
|
335
|
+
if doc.attr? 'uuid'
|
336
|
+
unique_identifier doc.attr('uuid'), 'pub-identifier', 'uuid'
|
337
|
+
else
|
338
|
+
unique_identifier doc.id, 'pub-identifier', 'uuid'
|
339
|
+
end
|
340
|
+
# replace with next line once the attributes argument is supported
|
341
|
+
#unique_identifier doc.id, 'pub-id', 'uuid', 'scheme' => 'xsd:string'
|
342
|
+
|
343
|
+
title sanitized_doctitle(doc)
|
344
|
+
id 'pub-title'
|
345
|
+
|
346
|
+
# FIXME this logic needs some work
|
347
|
+
if doc.attr? 'publisher'
|
348
|
+
publisher(publisher_name = doc.attr('publisher'))
|
349
|
+
# marc role: Book producer (see http://www.loc.gov/marc/relators/relaterm.html)
|
350
|
+
creator doc.attr('producer', publisher_name), 'bkp'
|
351
|
+
else
|
352
|
+
# NOTE Use producer as both publisher and producer if publisher isn't specified
|
353
|
+
if doc.attr? 'producer'
|
354
|
+
producer_name = doc.attr 'producer'
|
355
|
+
publisher producer_name
|
356
|
+
# marc role: Book producer (see http://www.loc.gov/marc/relators/relaterm.html)
|
357
|
+
creator producer_name, 'bkp'
|
358
|
+
# NOTE Use author as creator if both publisher or producer are absent
|
359
|
+
elsif doc.attr? 'author'
|
360
|
+
# marc role: Author (see http://www.loc.gov/marc/relators/relaterm.html)
|
361
|
+
creator doc.attr('author'), 'aut'
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
if doc.attr? 'creator'
|
366
|
+
# marc role: Creator (see http://www.loc.gov/marc/relators/relaterm.html)
|
367
|
+
creator doc.attr('creator'), 'cre'
|
368
|
+
else
|
369
|
+
# marc role: Manufacturer (see http://www.loc.gov/marc/relators/relaterm.html)
|
370
|
+
# QUESTION should this be bkp?
|
371
|
+
creator 'Asciidoctor', 'mfr'
|
372
|
+
end
|
373
|
+
|
374
|
+
# TODO getting author list should be a method on Asciidoctor API
|
375
|
+
contributors(*authors)
|
376
|
+
|
377
|
+
if doc.attr? 'revdate'
|
378
|
+
# TODO ensure this is a real date
|
379
|
+
date(doc.attr 'revdate')
|
380
|
+
else
|
381
|
+
date ::Time.now.strftime('%Y-%m-%dT%H:%M:%SZ')
|
382
|
+
end
|
383
|
+
|
384
|
+
if doc.attr? 'description'
|
385
|
+
description(doc.attr 'description')
|
386
|
+
end
|
387
|
+
|
388
|
+
(collect_keywords doc, spine).each do |s|
|
389
|
+
subject s
|
390
|
+
end
|
391
|
+
|
392
|
+
if doc.attr? 'source'
|
393
|
+
source(doc.attr 'source')
|
394
|
+
end
|
395
|
+
|
396
|
+
if doc.attr? 'copyright'
|
397
|
+
rights(doc.attr 'copyright')
|
398
|
+
end
|
399
|
+
|
400
|
+
#add_metadata 'ibooks:specified-fonts', true
|
401
|
+
|
402
|
+
add_theme_assets doc
|
403
|
+
add_cover_image doc
|
404
|
+
add_profile_images doc, usernames
|
405
|
+
# QUESTION move add_content_images to add_content method?
|
406
|
+
add_content_images doc, images
|
407
|
+
add_content doc
|
408
|
+
end
|
409
|
+
|
410
|
+
::FileUtils.mkdir_p dest unless ::File.directory? dest
|
411
|
+
|
412
|
+
epub_file = ::File.expand_path %(#{doc.attr 'docname'}#{fmt == :kf8 ? '-kf8' : nil}.epub), dest
|
413
|
+
builder.generate_epub epub_file
|
414
|
+
puts %(Wrote #{fmt.upcase} to #{epub_file})
|
415
|
+
if options[:extract]
|
416
|
+
extract_dir = epub_file.sub EpubExtensionRx, ''
|
417
|
+
::FileUtils.remove_dir extract_dir if ::File.directory? extract_dir
|
418
|
+
::Dir.mkdir extract_dir
|
419
|
+
::Dir.chdir extract_dir do
|
420
|
+
::Zip::File.open epub_file do |entries|
|
421
|
+
entries.each do |entry|
|
422
|
+
next unless entry.file?
|
423
|
+
unless (entry_dir = ::File.dirname entry.name) == '.' || (::File.directory? entry_dir)
|
424
|
+
::FileUtils.mkdir_p entry_dir
|
425
|
+
end
|
426
|
+
entry.extract
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
puts %(Extracted #{fmt.upcase} to #{extract_dir})
|
431
|
+
end
|
432
|
+
|
433
|
+
if fmt == :kf8
|
434
|
+
distill_epub_to_mobi epub_file
|
435
|
+
elsif options[:validate]
|
436
|
+
validate_epub epub_file
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
# QUESTION how to enable the -c2 flag? (enables ~3-5% compression)
|
441
|
+
def distill_epub_to_mobi epub_file
|
442
|
+
kindlegen_cmd = KINDLEGEN
|
443
|
+
unless ::File.executable? kindlegen_cmd
|
444
|
+
require 'kindlegen' unless defined? ::Kindlegen
|
445
|
+
kindlegen_cmd = ::Kindlegen.command
|
446
|
+
end
|
447
|
+
mobi_file = ::File.basename(epub_file).sub Kf8ExtensionRx, '.mobi'
|
448
|
+
::Open3.popen2e(::Shellwords.join [kindlegen_cmd, '-o', mobi_file, epub_file]) {|input, output, wait_thr|
|
449
|
+
output.each {|line| puts line }
|
450
|
+
}
|
451
|
+
puts %(Wrote MOBI to #{::File.join ::File.dirname(epub_file), mobi_file})
|
452
|
+
end
|
453
|
+
|
454
|
+
def validate_epub epub_file
|
455
|
+
epubcheck_cmd = EPUBCHECK
|
456
|
+
unless ::File.executable? epubcheck_cmd
|
457
|
+
epubcheck_cmd = ::Gem.bin_path 'epubcheck', 'epubcheck'
|
458
|
+
end
|
459
|
+
# NOTE epubcheck gem doesn't support epubcheck command options; enable -quiet once supported
|
460
|
+
::Open3.popen2e(::Shellwords.join [epubcheck_cmd, epub_file]) {|input, output, wait_thr|
|
461
|
+
output.each {|line| puts line }
|
462
|
+
}
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|