review-retrovert 0.2.2 → 0.9.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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/retrovert.yml +39 -0
  3. data/.gitignore +2 -2
  4. data/.ruby-version +1 -0
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +68 -2
  7. data/README.md +6 -3
  8. data/lib/review/retrovert/cli.rb +2 -0
  9. data/lib/review/retrovert/converter.rb +255 -16
  10. data/lib/review/retrovert/version.rb +1 -1
  11. data/review-retrovert.gemspec +1 -1
  12. data/testdata/mybook/.gitignore +7 -0
  13. data/testdata/mybook/README.md +43 -0
  14. data/testdata/mybook/Rakefile +16 -0
  15. data/testdata/mybook/catalog.yml +33 -0
  16. data/testdata/mybook/config-starter.yml +139 -0
  17. data/testdata/mybook/config.yml +375 -0
  18. data/testdata/mybook/contents/00-preface.re +83 -0
  19. data/testdata/mybook/contents/01-install.re +272 -0
  20. data/testdata/mybook/contents/02-tutorial.re +1008 -0
  21. data/testdata/mybook/contents/03-syntax.re +2613 -0
  22. data/testdata/mybook/contents/04-customize.re +728 -0
  23. data/testdata/mybook/contents/05-faq.re +328 -0
  24. data/testdata/mybook/contents/06-bestpractice.re +971 -0
  25. data/testdata/mybook/contents/91-compare.re +18 -0
  26. data/testdata/mybook/contents/92-filelist.re +119 -0
  27. data/testdata/mybook/contents/93-background.re +267 -0
  28. data/testdata/mybook/contents/99-postface.re +38 -0
  29. data/testdata/mybook/css/normalize.css +349 -0
  30. data/testdata/mybook/css/webstyle.css +514 -0
  31. data/testdata/mybook/images/03-syntax/favicon-16x16.png +0 -0
  32. data/testdata/mybook/images/03-syntax/figure_heretop.png +0 -0
  33. data/testdata/mybook/images/03-syntax/tw-icon1.jpg +0 -0
  34. data/testdata/mybook/images/03-syntax/tw-icon2.jpg +0 -0
  35. data/testdata/mybook/images/03-syntax/tw-icon3.jpg +0 -0
  36. data/testdata/mybook/images/03-syntax/tw-icon4.jpg +0 -0
  37. data/testdata/mybook/images/04-customize/caption_pagebreak.png +0 -0
  38. data/testdata/mybook/images/04-customize/chaptitlepage_sample.png +0 -0
  39. data/testdata/mybook/images/05-faq/codeblock_rpadding1.png +0 -0
  40. data/testdata/mybook/images/05-faq/codeblock_rpadding2.png +0 -0
  41. data/testdata/mybook/images/06-bestpractice/figure_heretop.png +0 -0
  42. data/testdata/mybook/images/06-bestpractice/font_beramono.png +0 -0
  43. data/testdata/mybook/images/06-bestpractice/heading_design1.png +0 -0
  44. data/testdata/mybook/images/06-bestpractice/heading_design2.png +0 -0
  45. data/testdata/mybook/images/06-bestpractice/margin_book.png +0 -0
  46. data/testdata/mybook/images/06-bestpractice/multiline-title.png +0 -0
  47. data/testdata/mybook/images/06-bestpractice/preface_numbered.png +0 -0
  48. data/testdata/mybook/images/06-bestpractice/program_border.png +0 -0
  49. data/testdata/mybook/images/06-bestpractice/sechead_design_4.png +0 -0
  50. data/testdata/mybook/images/06-bestpractice/titlepage-samples.png +0 -0
  51. data/testdata/mybook/images/93-background/bug913.png +0 -0
  52. data/testdata/mybook/images/93-background/slide2.png +0 -0
  53. data/testdata/mybook/images/cover_a5.pdf +0 -0
  54. data/testdata/mybook/images/cover_b5.pdf +0 -0
  55. data/testdata/mybook/images/tw-icon.jpg +0 -0
  56. data/testdata/mybook/layouts/layout.epub.erb +29 -0
  57. data/testdata/mybook/layouts/layout.html5.erb +106 -0
  58. data/testdata/mybook/layouts/layout.tex.erb +546 -0
  59. data/testdata/mybook/lib/hooks/beforetexcompile.rb +55 -0
  60. data/testdata/mybook/lib/ruby/review-builder.rb +503 -0
  61. data/testdata/mybook/lib/ruby/review-cli.rb +58 -0
  62. data/testdata/mybook/lib/ruby/review-compiler.rb +523 -0
  63. data/testdata/mybook/lib/ruby/review-epubmaker.rb +606 -0
  64. data/testdata/mybook/lib/ruby/review-htmlbuilder.rb +661 -0
  65. data/testdata/mybook/lib/ruby/review-latexbuilder.rb +782 -0
  66. data/testdata/mybook/lib/ruby/review-maker.rb +235 -0
  67. data/testdata/mybook/lib/ruby/review-monkeypatch.rb +91 -0
  68. data/testdata/mybook/lib/ruby/review-pdfmaker.rb +468 -0
  69. data/testdata/mybook/lib/ruby/review-textbuilder.rb +36 -0
  70. data/testdata/mybook/lib/ruby/review-tocparser.rb +285 -0
  71. data/testdata/mybook/lib/ruby/review-webmaker.rb +433 -0
  72. data/testdata/mybook/lib/tasks/mytasks.rake +31 -0
  73. data/testdata/mybook/lib/tasks/review.rake +142 -0
  74. data/testdata/mybook/lib/tasks/review.rake.orig +72 -0
  75. data/testdata/mybook/lib/tasks/starter.rake +326 -0
  76. data/testdata/mybook/locale.yml +6 -0
  77. data/testdata/mybook/review-ext.rb +206 -0
  78. data/testdata/mybook/sty/jumoline.sty +310 -0
  79. data/testdata/mybook/sty/mycolophon.sty +81 -0
  80. data/testdata/mybook/sty/mystyle.sty +8 -0
  81. data/testdata/mybook/sty/mytextsize.sty +61 -0
  82. data/testdata/mybook/sty/mytitlepage.sty +103 -0
  83. data/testdata/mybook/sty/reviewmacro.sty +60 -0
  84. data/testdata/mybook/sty/starter-codeblock.sty +332 -0
  85. data/testdata/mybook/sty/starter-color.sty +79 -0
  86. data/testdata/mybook/sty/starter-font.sty +112 -0
  87. data/testdata/mybook/sty/starter-heading.sty +514 -0
  88. data/testdata/mybook/sty/starter-note.sty +127 -0
  89. data/testdata/mybook/sty/starter-section.sty +262 -0
  90. data/testdata/mybook/sty/starter-toc.sty +72 -0
  91. data/testdata/mybook/sty/starter.sty +554 -0
  92. data/testdata/mybook/style.css +597 -0
  93. metadata +100 -3
@@ -0,0 +1,606 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ #
4
+ # Copyright (c) 2010-2017 Kenshi Muto and Masayoshi Takahashi
5
+ #
6
+ # This program is free software.
7
+ # You can distribute or modify this program under the terms of
8
+ # the GNU LGPL, Lesser General Public License version 2.1.
9
+ # For details of the GNU LGPL, see the file "COPYING".
10
+ #
11
+
12
+ require 'review/epubmaker'
13
+
14
+ require_relative './review-maker'
15
+
16
+
17
+ module ReVIEW
18
+
19
+
20
+ class EpubMaker < Maker
21
+
22
+ SCRIPT_NAME = "review-epubmaker"
23
+
24
+ def initialize(config_filename, optional_values={})
25
+ super
26
+ @htmltoc = nil
27
+ @buildlogtxt = 'build-log.txt'
28
+ @producer = ::EPUBMaker::Producer.new(@config)
29
+ @producer.load(config_filename)
30
+ @config = @producer.config
31
+ @config.maker = 'epubmaker'
32
+ end
33
+
34
+ def log(s)
35
+ puts s if debug?()
36
+ end
37
+
38
+ def prepare_build_dir()
39
+ dir = "#{@config['bookname']}-epub"
40
+ if debug?()
41
+ builddir = File.expand_path(dir, Dir.pwd)
42
+ FileUtils.rm_rf(builddir, secure: true) if File.exist?(builddir)
43
+ FileUtils.mkdir_p(builddir)
44
+ else
45
+ builddir = Dir.mktmpdir("#{dir}-")
46
+ end
47
+ #
48
+ if @config['imgmath']
49
+ math_dir = "./#{@config['imagedir']}/_review_math"
50
+ FileUtils.rm_rf(math_dir) if Dir.exist?(math_dir)
51
+ end
52
+ #
53
+ return builddir
54
+ end
55
+
56
+ def generate()
57
+ bookname = @config['bookname']
58
+ epubname = "#{bookname}.epub"
59
+ booktmpname = "#{bookname}-epub"
60
+
61
+ begin
62
+ @config.check_version(ReVIEW::VERSION)
63
+ rescue ReVIEW::ConfigError => e
64
+ warn e.message
65
+ end
66
+ log("I will produce #{epubname}.")
67
+
68
+ FileUtils.rm_f(epubname)
69
+
70
+ builddir = prepare_build_dir()
71
+ @builddir = builddir
72
+ begin
73
+ log("Created first temporary directory as #{builddir}.")
74
+
75
+ call_hook('hook_beforeprocess')
76
+
77
+ @htmltoc = ReVIEW::HTMLToc.new(builddir)
78
+ ## copy all files into builddir
79
+ copy_stylesheet()
80
+
81
+ copy_frontmatter()
82
+ call_hook('hook_afterfrontmatter')
83
+
84
+ build_body()
85
+ call_hook('hook_afterbody')
86
+
87
+ copy_backmatter()
88
+ call_hook('hook_afterbackmatter')
89
+
90
+ ## push contents in builddir into @producer
91
+ push_contents()
92
+
93
+ imagedir = @config['imagedir']
94
+ imagedir_dest = File.join(builddir, imagedir)
95
+ if @config['epubmaker']['verify_target_images'].present?
96
+ verify_target_images()
97
+ copy_images(imagedir, builddir)
98
+ else
99
+ copy_images(imagedir, imagedir_dest)
100
+ end
101
+
102
+ copy_resources('covers', imagedir_dest)
103
+ copy_resources('adv', imagedir_dest)
104
+ copy_resources(@config['fontdir'], "#{builddir}/fonts", @config['font_ext'])
105
+
106
+ call_hook('hook_aftercopyimage')
107
+
108
+ @producer.import_imageinfo(imagedir_dest, builddir)
109
+ @producer.import_imageinfo("#{builddir}/fonts", builddir, @config['font_ext'])
110
+
111
+ check_image_size(@config['image_maxpixels'], @config['image_ext'])
112
+
113
+ if debug?()
114
+ epubtmpdir = File.join(builddir, booktmpname)
115
+ FileUtils.mkdir(epubtmpdir)
116
+ else
117
+ epubtmpdir = nil
118
+ end
119
+ log('Call ePUB producer.')
120
+ @producer.produce(epubname, builddir, epubtmpdir)
121
+ log('Finished.')
122
+ rescue ApplicationError => e
123
+ raise if debug?()
124
+ error(e.message)
125
+ ensure
126
+ FileUtils.remove_entry_secure(builddir) unless debug?()
127
+ end
128
+ end
129
+
130
+ def call_hook(hook_name)
131
+ filename = @config['epubmaker'][hook_name]
132
+ log("Call #{hook_name}. (#{filename})")
133
+ return unless filename.present?
134
+ return unless File.exist?(filename)
135
+ return unless FileTest.executable?(filename)
136
+ if ENV['REVIEW_SAFE_MODE'].to_i & 0x1 != 0
137
+ warn 'hook is prohibited in safe mode. ignored.'
138
+ else
139
+ system(filename, @builddir)
140
+ end
141
+ end
142
+
143
+ def verify_target_images()
144
+ ## なんでメソッド名が「verify_xxx」なのに副作用があるの?アホ?
145
+ items = @config['epubmaker']['force_include_images']
146
+ @producer.contents.each do |content|
147
+ File.open("#{@builddir}/#{content.file}") do |f|
148
+ if content.media == 'application/xhtml+xml'
149
+ REXML::Document.new(File.new(f)).each_element('//img') do |e|
150
+ items << e.attributes['src']
151
+ content.properties << 'svg' if e.attributes['src'] =~ /svg\Z/i
152
+ end
153
+ elsif content.media == 'text/css'
154
+ f.each_line do |l|
155
+ l.scan(/url\((.+?)\)/) { items << $1.strip }
156
+ end
157
+ else
158
+ end
159
+ end
160
+ end
161
+ @config['epubmaker']['force_include_images'] = items.compact.sort.uniq
162
+ end
163
+
164
+ def copy_images(resdir, destdir, allow_exts=nil)
165
+ return nil unless File.exist?(resdir)
166
+ allow_exts ||= @config['image_ext']
167
+ FileUtils.mkdir_p(destdir)
168
+ if @config['epubmaker']['verify_target_images'].present?
169
+ @config['epubmaker']['force_include_images'].each do |file|
170
+ unless File.exist?(file)
171
+ warn "#{file} is not found, skip." if file !~ /\Ahttp[s]?:/
172
+ next
173
+ end
174
+ basedir = File.dirname(file)
175
+ dirpath = "#{destdir}/#{basedir}"
176
+ FileUtils.mkdir_p(dirpath)
177
+ log("Copy #{file} to the temporary directory.")
178
+ FileUtils.cp(file, dirpath)
179
+ end
180
+ else
181
+ recursive_copy_files(resdir, destdir, allow_exts)
182
+ end
183
+ end
184
+
185
+ def copy_resources(resdir, destdir, allow_exts=nil)
186
+ return nil unless File.exist?(resdir)
187
+ allow_exts ||= @config['image_ext']
188
+ FileUtils.mkdir_p(destdir)
189
+ recursive_copy_files(resdir, destdir, allow_exts)
190
+ end
191
+
192
+ def recursive_copy_files(resdir, destdir, allow_exts)
193
+ allow_exts_rexp = /\.(#{allow_exts.join('|')})\Z/i
194
+ Dir.open(resdir) do |dir|
195
+ dir.each do |fname|
196
+ srcfile = "#{resdir}/#{fname}"
197
+ next if fname.start_with?('.')
198
+ if File.directory?(srcfile)
199
+ recursive_copy_files(srcfile, "#{destdir}/#{fname}", allow_exts)
200
+ elsif fname =~ allow_exts_rexp
201
+ FileUtils.mkdir_p(destdir)
202
+ log("Copy #{srcfile} to the temporary directory.")
203
+ FileUtils.cp(srcfile, destdir)
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ def check_compile_status
210
+ return unless @compile_errors
211
+
212
+ $stderr.puts 'compile error, No EPUB file output.'
213
+ exit 1
214
+ end
215
+
216
+ def new_builder()
217
+ builder = ReVIEW::EPUBBuilder.new()
218
+ builder.starter_config = @starter_config
219
+ return builder
220
+ end
221
+
222
+ def build_body()
223
+ @precount = 0
224
+ @bodycount = 0
225
+ @postcount = 0
226
+
227
+ @manifeststr = ''
228
+ @ncxstr = ''
229
+ @tocdesc = []
230
+
231
+ basedir = @basedir
232
+ base_path = Pathname.new(basedir)
233
+ book = ReVIEW::Book.load(basedir)
234
+ book.config = @config
235
+ @book = book
236
+ @converter = ReVIEW::Converter.new(book, new_builder())
237
+ @compile_errors = nil
238
+ book.parts.each do |part|
239
+ if part.name.present?
240
+ if part.file?
241
+ build_chap(part, base_path, 'part')
242
+ else
243
+ htmlfile = "part_#{part.number}.#{@config['htmlext']}"
244
+ build_part(part, htmlfile)
245
+ title = ReVIEW::I18n.t('part', part.number)
246
+ title += ReVIEW::I18n.t('chapter_postfix') + part.name.strip if part.name.strip.present?
247
+ @htmltoc.add_item(0, htmlfile, title, chaptype: 'part')
248
+ write_buildlogtxt(htmlfile, '')
249
+ end
250
+ end
251
+ #
252
+ part.chapters.each do |chap|
253
+ build_chap(chap, base_path, nil)
254
+ end
255
+ end
256
+ check_compile_status
257
+ end
258
+
259
+ def new_renderer()
260
+ return EPUBRenderer.new(@config, @book, @basedir, @starter_config)
261
+ end
262
+
263
+ def build_part(part, htmlfile)
264
+ log("Create #{htmlfile} from a template.")
265
+ new_renderer().generate_part_page(part, File.join(@builddir, htmlfile))
266
+ end
267
+
268
+ def build_chap(chap, base_path, chaptype)
269
+ chaptype ||= _chapter_type(chap)
270
+ filename = chaptype == 'part' ? chap.path : \
271
+ Pathname.new(chap.path).relative_path_from(base_path).to_s
272
+ chap_id = _chapter_id(chap, chaptype)
273
+ #
274
+ htmlfile = "#{chap_id}.#{@config['htmlext']}"
275
+ write_buildlogtxt(htmlfile, filename)
276
+ log("Create #{htmlfile} from #{filename}.")
277
+ #
278
+ if (params_str = @config['params']).present?
279
+ warn "'params:' in config.yml is obsoleted."
280
+ if params_str =~ /stylesheet=/
281
+ warn "stylesheets should be defined in 'stylesheet:', not in 'params:'"
282
+ end
283
+ end
284
+ #
285
+ begin
286
+ @converter.convert(filename, File.join(@builddir, htmlfile))
287
+ write_info_body(htmlfile, chaptype)
288
+ remove_hidden_title(htmlfile)
289
+ rescue => e
290
+ @compile_errors = true
291
+ warn "compile error in #{filename} (#{e.class})"
292
+ warn e.message
293
+ end
294
+ end
295
+
296
+ def _chapter_type(chap)
297
+ return 'pre' if chap.on_predef?
298
+ return 'post' if chap.on_appendix?
299
+ return 'body'
300
+ end
301
+
302
+ def _chapter_id(chap, chaptype)
303
+ id = File.basename(chap.path).sub(/\.re\z/, '')
304
+ if @config['epubmaker']['rename_for_legacy']
305
+ if chaptype == 'part';
306
+ elsif chap.on_predef? ; id = 'pre%02d' % (@precount += 1)
307
+ elsif chap.on_appendix? ; id = 'post%02d' % (@postcount += 1)
308
+ else ; id = 'chap%02d' % (@bodycount += 1)
309
+ end
310
+ end
311
+ return id
312
+ end
313
+
314
+ def remove_hidden_title(htmlfile)
315
+ File.open("#{@builddir}/#{htmlfile}", 'r+') do |f|
316
+ html = _remove_hidden_title(f.read())
317
+ f.rewind()
318
+ f.print(html)
319
+ f.truncate(f.tell)
320
+ end
321
+ end
322
+
323
+ def _remove_hidden_title(html)
324
+ html = html.gsub(/<h\d .*?hidden=['"]true['"].*?>.*?<\/h\d>\n/, '')
325
+ html = html.gsub(/(<h\d .*?)\s*notoc=['"]true['"]\s*(.*?>.*?<\/h\d>\n)/, '\1\2')
326
+ return html
327
+ end
328
+
329
+ def detect_properties(path)
330
+ doc = File.open(path) {|f| REXML::Document.new(f) }
331
+ has_math = REXML::XPath.first(doc, '//m:math', 'm' => 'http://www.w3.org/1998/Math/MathML')
332
+ has_svg = REXML::XPath.first(doc, '//s:svg', 's' => 'http://www.w3.org/2000/svg')
333
+ properties = []
334
+ properties << 'mathml' if has_math
335
+ properties << 'svg' if has_svg
336
+ return properties
337
+ end
338
+
339
+ def write_info_body(filename, chaptype)
340
+ headlines = []
341
+ path = File.join(@builddir, filename)
342
+ File.open(path) do |htmlio|
343
+ REXML::Document.parse_stream(htmlio, ReVIEWHeaderListener.new(headlines))
344
+ end
345
+ properties = detect_properties(path)
346
+ prop_str = ''
347
+ prop_str = ',properties=' + properties.join(' ') if properties.present?
348
+ headlines.each_with_index do |headline, i|
349
+ headline['level'] = 0 if chaptype == 'part' && headline['level'] == 1
350
+ if i == 0
351
+ opts = {chaptype: chaptype + prop_str, force_include: true}
352
+ _add_headline_item(headline, filename, nil, opts)
353
+ else
354
+ opts = {chaptype: chaptype}
355
+ _add_headline_item(headline, filename, headline['id'], opts)
356
+ end
357
+ end
358
+ end
359
+
360
+ def _add_headline_item(headline, filename, id, opts)
361
+ opts[:notoc] = headline['notoc']
362
+ entry = id ? "#{filename}\##{id}" : filename
363
+ @htmltoc.add_item(headline['level'], entry, headline['title'], opts)
364
+ end
365
+ private :_add_headline_item
366
+
367
+ def push_contents()
368
+ toclevel = @config['toclevel']
369
+ @htmltoc.each_item do |level, file, title, args|
370
+ if level.to_i <= toclevel || args[:force_include]
371
+ log("Push #{file} to ePUB contents.")
372
+ x = nil
373
+ hash = {'file'=>file, 'level'=>level.to_i, 'title'=>title, 'chaptype'=>args[:chaptype]}
374
+ hash['id'] = x if (x = args[:id]).present?
375
+ hash['properties'] = x.split(' ') if (x = args[:properties]).present?
376
+ hash['notoc'] = x if (x = args[:notoc]).present?
377
+ @producer.contents.push(::EPUBMaker::Content.new(hash))
378
+ end
379
+ end
380
+ end
381
+
382
+ def copy_stylesheet()
383
+ return if @config['stylesheet'].empty?
384
+ @config['stylesheet'].each do |sfile|
385
+ FileUtils.cp(sfile, @builddir)
386
+ @producer.contents.push(::EPUBMaker::Content.new('file' => sfile))
387
+ end
388
+ end
389
+
390
+ def copy_frontmatter()
391
+ cover = @config['cover']
392
+ if cover.present?
393
+ FileUtils.cp(cover, @builddir) if File.exist?(cover)
394
+ end
395
+ #
396
+ if @config['titlepage']
397
+ titlepage = "titlepage.#{@config['htmlext']}"
398
+ titlefile = @config['titlefile']
399
+ FileUtils.cp(titlefile, @builddir) if titlefile.present?
400
+ build_titlepage(titlepage) unless titlefile.present?
401
+ _add_pre_item(titlepage, 'titlepagetitle')
402
+ end
403
+ #
404
+ original_titlefile = @config['originaltitlefile']
405
+ if original_titlefile.present? && File.exist?(original_titlefile)
406
+ FileUtils.cp(original_titlefile, @builddir)
407
+ _add_pre_item(original_titlefile, 'originaltitle')
408
+ end
409
+ #
410
+ creditfile = @config['creditfile']
411
+ if creditfile.present? && File.exist?(creditfile)
412
+ FileUtils.cp(creditfile, @builddir)
413
+ _add_pre_item(creditfile, 'credittitle')
414
+ end
415
+ #
416
+ true
417
+ end
418
+
419
+ def _add_pre_item(filename, key)
420
+ @htmltoc.add_item(1, File.basename(filename), @producer.res.v(key), chaptype: 'pre')
421
+ end
422
+
423
+ def build_titlepage(htmlfile)
424
+ # TODO: should be created via epubcommon
425
+ new_renderer().generate_title_page("#{@builddir}/#{htmlfile}")
426
+ end
427
+
428
+ def _join_names(names)
429
+ sep = ReVIEW::I18n.t('names_splitter')
430
+ return [names].flatten.join(sep)
431
+ end
432
+
433
+ def copy_backmatter()
434
+ if (profile = @config['profile'])
435
+ FileUtils.cp(profile, @builddir)
436
+ _add_post_item(profile, 'profiletitle')
437
+ end
438
+ #
439
+ if (advfile = @config['advfile'])
440
+ FileUtils.cp(advfile, @builddir)
441
+ _add_post_item(advfile, 'advtitle')
442
+ end
443
+ #
444
+ if (colophon = @config['colophon'])
445
+ colophon_page = File.join(@builddir, "colophon.#{@config['htmlext']}")
446
+ if colophon.is_a?(String) # FIXME: should let obsolete this style?
447
+ FileUtils.cp(colophon, colophon_page)
448
+ else
449
+ File.open(colophon_page, 'w') {|f| @producer.colophon(f) }
450
+ end
451
+ _add_post_item(colophon_page, 'colophontitle')
452
+ end
453
+ #
454
+ if (backcover = @config['backcover'])
455
+ FileUtils.cp(backcover, @builddir)
456
+ _add_post_item(backcover, 'backcovertitle')
457
+ end
458
+ #
459
+ true
460
+ end
461
+
462
+ def _add_post_item(filename, key)
463
+ @htmltoc.add_item(1, File.basename(filename), @producer.res.v(key), chaptype: 'post')
464
+ end
465
+
466
+ def write_buildlogtxt(htmlfile, reviewfile)
467
+ File.open("#{@builddir}/#{@buildlogtxt}", 'a') { |f| f.puts "#{htmlfile},#{reviewfile}" }
468
+ end
469
+
470
+ def check_image_size(maxpixels, allow_exts = nil)
471
+ begin
472
+ require 'image_size'
473
+ rescue LoadError
474
+ return nil
475
+ end
476
+ require 'find'
477
+ allow_exts ||= @config['image_ext']
478
+
479
+ extre = Regexp.new('\\.(' + allow_exts.delete_if { |t| %w[ttf woff otf].include?(t) }.join('|') + ')', Regexp::IGNORECASE)
480
+ Find.find(@builddir) do |fname|
481
+ next unless fname.match(extre)
482
+ img = ImageSize.path(fname)
483
+ next if img.width.nil? || img.width * img.height <= maxpixels
484
+ h = Math.sqrt(img.height * maxpixels / img.width)
485
+ w = maxpixels / h
486
+ fname.sub!("#{@builddir}/", '')
487
+ warn "#{fname}: #{img.width}x#{img.height} exceeds a limit. suggeted value is #{w.to_i}x#{h.to_i}"
488
+ end
489
+
490
+ true
491
+ end
492
+
493
+
494
+ class EPUBRenderer < BaseRenderer
495
+
496
+ def initialize(config, book, basedir, starter_config)
497
+ super
498
+ @language = @config['language']
499
+ @stylesheets = @config['stylesheet']
500
+ end
501
+
502
+ protected
503
+
504
+ def layout_template_name()
505
+ #if @config['htmlversion'].to_i == 5
506
+ # './html/layout-html5.html.erb'
507
+ #else
508
+ # './html/layout-xhtml1.html.erb'
509
+ #end
510
+ "layout.epub.erb"
511
+ end
512
+
513
+ def escape(s)
514
+ return CGI.escapeHTML(s)
515
+ end
516
+
517
+ public
518
+
519
+ def generate_title_page(filepath)
520
+ c = @config
521
+ title = escape(c.name_of('booktitle'))
522
+ body = []
523
+ body << "<div class=\"titlepage\">\n"
524
+ body << "<h1 class=\"tp-title\">#{escape c.name_of('booktitle')}</h1>\n"
525
+ body << "<h2 class=\"tp-subtitle\">#{escape c.name_of('subtitle')}</h2>\n" if c['subtitle']
526
+ body << "<h2 class=\"tp-author\">#{escape _join_names(c.names_of('aut'))}</h2>\n" if c['aut']
527
+ body << "<h3 class=\"tp-publisher\">#{escape _join_names(c.names_of('pbl'))}</h3>\n" if c['pbl']
528
+ body << "</div>"
529
+ generate_file(filepath, {title: title, body: body.join()})
530
+ end
531
+
532
+ def generate_part_page(part, filepath)
533
+ part_number = i18n('part', part.number)
534
+ part_name = part.name.strip()
535
+ body = []
536
+ body << "<div class=\"part\">\n"
537
+ body << "<h1 class=\"part-number\">#{escape part_number}</h1>\n"
538
+ body << "<h2 class=\"part-title\">#{escape part_name}</h2>\n" if part_name.present?
539
+ body << "</div>\n"
540
+ generate_file(filepath, {body: body.join()})
541
+ end
542
+
543
+ private
544
+
545
+ def _join_names(names)
546
+ sep = i18n('names_splitter')
547
+ return [names].flatten.join(sep)
548
+ end
549
+
550
+ end
551
+
552
+
553
+ class ReVIEWHeaderListener
554
+ include REXML::StreamListener
555
+
556
+ def initialize(headlines)
557
+ @headlines = headlines
558
+ _clear()
559
+ end
560
+
561
+ def _clear()
562
+ @content = ''
563
+ @level = nil
564
+ @id = nil
565
+ @notoc = nil
566
+ end
567
+ private :_clear
568
+
569
+ HEADING_REXP = /\Ah(\d+)\z/
570
+
571
+ def tag_start(name, attrs)
572
+ v = nil
573
+ if name =~ HEADING_REXP
574
+ raise "#{name}, #{attrs}" if @level.present?
575
+ @level = $1.to_i
576
+ @id = v if (v = attrs['id']).present?
577
+ @notoc = v if (v = attrs['notoc']).present?
578
+ elsif @level
579
+ if name == 'img'
580
+ @content << v if (v = attrs['alt']).present?
581
+ elsif name == 'a'
582
+ @id = v if (v = attrs['id']).present?
583
+ end
584
+ end
585
+ end
586
+
587
+ def tag_end(name)
588
+ if name =~ HEADING_REXP
589
+ if @id
590
+ attrs = {'level'=>@level, 'id'=>@id, 'title'=>@content, 'notoc'=>@notoc}
591
+ @headlines.push(attrs)
592
+ end
593
+ _clear()
594
+ end
595
+ true
596
+ end
597
+
598
+ def text(text)
599
+ @content << text.gsub("\t", ' ') if @level
600
+ end
601
+
602
+ end
603
+
604
+ end
605
+
606
+ end