Module: Vivlio::Starter::CLI::Build::EpubBuilder
- Defined in:
- lib/vivlio/starter/cli/build/epub_builder.rb
Overview
EPUB ビルド用の中間ファイル生成モジュール
Constant Summary collapse
- EPUB_CONFIG_FILE =
EPUB 専用設定ファイル名
'vivliostyle.config.epub.js'- EPUB_ENTRIES_FILE =
EPUB 専用 entries ファイル名
'entries.epub.js'- EPUB_OUTPUT_FILE =
EPUB デフォルト出力ファイル名
'output.epub'- EXCLUDED_BASENAMES =
EPUB で除外する特殊ページ目次は EPUB リーダーが自動生成するため不要
%w[_toc].freeze
Class Method Summary collapse
- .build_cover_config_line(config, esc) ⇒ Object
-
.build_sequential_chapter_map(html_files) ⇒ Hash{String => Integer}
HTML ファイルの構成順から、ファイル名プレフィックス → 連番 のマッピングを構築 例: { “00” => 0, “08” => 1, “11” => 2, “21” => 3, “91” => 4, “99” => 5 }.
-
.cleanup! ⇒ void
EPUB 中間ファイルをクリーンアップする.
-
.collect_epub_htmls(base_dir, entries) ⇒ Array<String>
EPUB に含める HTML ファイルを収集する PDF 向けの構成から目次・裏表紙を除外.
-
.embed_cover?(epub_cfg) ⇒ String, Boolean
cover 設定行を生成する 新しい設定構造に対応.
-
.excluded_basename?(html_path) ⇒ Boolean
除外対象の basename かどうかを判定する.
-
.generate_epub_config! ⇒ String
EPUB 専用 vivliostyle.config.js を生成する cover.embed 設定に応じて表紙画像の埋め込みを制御.
-
.generate_epub_entries!(base_dir, entries) ⇒ Array<String>
EPUB 用 entries.js を生成する PDF 用の構成から目次・裏表紙を除外した EPUB 専用エントリを生成.
-
.post_process_index_glossary_for_epub!(html_files) ⇒ Array<String>
索引・用語集 HTML を EPUB 用に直接修正する ファイル名プレフィックス(00, 08, 11 等)を書籍構成順の連番(0, 1, 2 等)に変換.
-
.resolve_cover_image_path(config) ⇒ String?
表紙画像のパスを解決する 新しい設定構造に対応.
-
.resolve_page_size(config) ⇒ String
book.yml のページ設定から Vivliostyle CLI 用サイズ文字列を解決する vivliostyle.rb の resolve_vivliostyle_size から移植.
-
.rewrite_glossary_for_epub!(path, chapter_map) ⇒ Object
用語集 HTML を EPUB 用に書き換える - 空バックリンクに連番の章番号を挿入 CSS の ::before が “→ p.” を付加するため記号は不要.
-
.rewrite_index_for_epub!(path, chapter_map) ⇒ Object
索引 HTML を EPUB 用に書き換える - 空リンクに連番の章番号を挿入 - 連続するリンク間に “, ” 区切りを追加.
-
.write_epub_entries(base_dir, html_files) ⇒ Object
EPUB 用 entries.js をファイルに書き出す.
Class Method Details
.build_cover_config_line(config, esc) ⇒ Object
222 223 224 225 226 227 228 229 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 222 def build_cover_config_line(config, esc) return " // cover: 表紙埋め込みなし(epub.embed: false)\n" unless Common. cover_image = resolve_cover_image_path(config) return " // cover: 表紙画像が見つかりません\n" unless cover_image && File.exist?(cover_image) " cover: './#{esc.call(cover_image)}',\n" end |
.build_sequential_chapter_map(html_files) ⇒ Hash{String => Integer}
HTML ファイルの構成順から、ファイル名プレフィックス → 連番 のマッピングを構築例: { “00” => 0, “08” => 1, “11” => 2, “21” => 3, “91” => 4, “99” => 5 }
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 296 def build_sequential_chapter_map(html_files) mapping = {} seq = 0 html_files.each do |path| basename = File.basename(path, '.html') next unless basename.match?(/\A\d{2}-/) prefix = basename[0, 2] unless mapping.key?(prefix) mapping[prefix] = seq seq += 1 end end Common.log_info("[EPUB] 章番号マッピング: #{mapping.map { |k, v| "#{k}→#{v}" }.join(', ')}") mapping end |
.cleanup! ⇒ void
This method returns an undefined value.
EPUB 中間ファイルをクリーンアップする
361 362 363 364 365 366 367 368 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 361 def cleanup! [EPUB_CONFIG_FILE, EPUB_ENTRIES_FILE, EPUB_OUTPUT_FILE].each do |file| next unless File.exist?(file) FileUtils.rm_f(file) Common.log_info("[EPUB] #{file} を削除しました") end end |
.collect_epub_htmls(base_dir, entries) ⇒ Array<String>
EPUB に含める HTML ファイルを収集するPDF 向けの構成から目次・裏表紙を除外
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 131 def collect_epub_htmls(base_dir, entries) keep_numbers_main = Build::Utilities.chapter_numbers_for_book(entries) keep_numbers_appx = nil keep_numbers_post = nil if entries&.any? chapter_numbers = PdfBuilder.send(:extract_chapter_numbers, entries) keep_numbers_appx = chapter_numbers.select { |n| PdfBuilder::APPX_RANGE.include?(n) } keep_numbers_post = chapter_numbers.select { |n| PdfBuilder::POSTFACE_RANGE.include?(n) } end # 前書き(00-preface) preface_html = [File.join(base_dir, '00-preface.html')].select { File.exist?(it) } # 本文章 HTML(中扉を挿入) main_htmls = Build::ChapterConfig.htmls_for_range(base_dir, PdfBuilder::MAIN_RANGE, keep_numbers_main) main_htmls_with_parts = Build::PartTitleGenerator.insert_part_titles_into(main_htmls, base_dir) # 付録 appx_htmls = Build::ChapterConfig.htmls_for_range(base_dir, PdfBuilder::APPX_RANGE, keep_numbers_appx) # 用語集・索引(リフロー型ではページ番号は無意味だがリンクは有効) glossary_html = if IndexCommands.index_enabled? [File.join(base_dir, '_glossarypage.html')].select { File.exist?(it) } else [] end # 後書き post_htmls = Build::ChapterConfig.htmls_for_range(base_dir, PdfBuilder::POSTFACE_RANGE, keep_numbers_post) # 索引 index_html = if IndexCommands.index_enabled? [File.join(base_dir, '_indexpage.html')].select { File.exist?(it) } else [] end # 書籍構成順序: 前書き → [中扉+本文] → 付録 → 用語集 → 後書き → 索引 # ※ 目次(_toc)と裏表紙は除外 [ preface_html, main_htmls_with_parts, appx_htmls, glossary_html, post_htmls, index_html ].flatten.reject { excluded_basename?(it) } end |
.embed_cover?(epub_cfg) ⇒ String, Boolean
cover 設定行を生成する新しい設定構造に対応
EPUB 表紙埋め込みが有効かどうかを判定
218 219 220 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 218 def (epub_cfg) epub_cfg&.cover&. != false end |
.excluded_basename?(html_path) ⇒ Boolean
除外対象の basename かどうかを判定する
204 205 206 207 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 204 def excluded_basename?(html_path) basename = File.basename(html_path, '.html') EXCLUDED_BASENAMES.include?(basename) end |
.generate_epub_config! ⇒ String
EPUB 専用 vivliostyle.config.js を生成するcover.embed 設定に応じて表紙画像の埋め込みを制御
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 69 def generate_epub_config! Common.log_action('[EPUB] vivliostyle.config.epub.js を生成しています…') config = Common::CONFIG book_config = config.book # JS 文字列に安全に埋め込むための簡易エスケープ esc = ->(s) { s.to_s.gsub('\\', '\\\\').gsub("'", "\\'") } # メタデータを book セクションから取得 # book.title は存在しない場合がある(main_title + subtitle から合成) combined_title = [book_config&.main_title, book_config&.subtitle].compact.join(' ').strip title_raw = book_config.respond_to?(:title) ? book_config.title : nil title = if title_raw && !title_raw.to_s.strip.empty? title_raw else combined_title.empty? ? '書籍タイトル' : combined_title end = book_config&. || '著者名' language = book_config&.language || 'ja' # ページサイズを解決(vivliostyle.rb から移植) page_size = resolve_page_size(config) # 表紙画像の埋め込み設定を取得 cover_line = build_cover_config_line(config, esc) # 横書き固定(将来の縦書き対応に備えてハードコーディング) reading_progression = 'ltr' config_content = <<~JS import entries from './#{EPUB_ENTRIES_FILE}'; // @ts-check // EPUB 専用設定ファイル(自動生成・編集不要) /** @type {import('@vivliostyle/cli').VivliostyleConfigSchema} */ const vivliostyleConfig = { title: '#{esc.call(title)}', author: '#{esc.call()}', language: '#{esc.call(language)}', size: '#{esc.call(page_size)}', readingProgression: '#{esc.call(reading_progression)}', #{cover_line} entry: entries, output: [ './#{EPUB_OUTPUT_FILE}' ] }; export default vivliostyleConfig; JS File.write(EPUB_CONFIG_FILE, config_content) Common.log_success("[EPUB] #{EPUB_CONFIG_FILE} を生成しました") EPUB_CONFIG_FILE end |
.generate_epub_entries!(base_dir, entries) ⇒ Array<String>
EPUB 用 entries.js を生成するPDF 用の構成から目次・裏表紙を除外した EPUB 専用エントリを生成
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 47 def generate_epub_entries!(base_dir, entries) Common.log_action('[EPUB] entries.epub.js を生成しています…') chapter_htmls = collect_epub_htmls(base_dir, entries) if chapter_htmls.empty? Common.log_warn('[EPUB] 対象 HTML が見つかりません。スキップします。') return [] end # 索引・用語集を EPUB 用に書き換え(空リンクに連番テキストを挿入) post_process_index_glossary_for_epub!(chapter_htmls) write_epub_entries(base_dir, chapter_htmls) Common.log_success("[EPUB] entries.epub.js を生成しました(#{chapter_htmls.size} エントリ)") chapter_htmls end |
.post_process_index_glossary_for_epub!(html_files) ⇒ Array<String>
索引・用語集 HTML を EPUB 用に直接修正するファイル名プレフィックス(00, 08, 11 等)を書籍構成順の連番(0, 1, 2 等)に変換
276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 276 def post_process_index_glossary_for_epub!(html_files) chapter_map = build_sequential_chapter_map(html_files) html_files.each do |path| basename = File.basename(path, '.html') case basename when '_indexpage' rewrite_index_for_epub!(path, chapter_map) when '_glossarypage' rewrite_glossary_for_epub!(path, chapter_map) end end html_files end |
.resolve_cover_image_path(config) ⇒ String?
表紙画像のパスを解決する新しい設定構造に対応
236 237 238 239 240 241 242 243 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 236 def resolve_cover_image_path(config) covers_dir = config.directories&.covers || 'covers' theme = Common.cover_theme return nil unless theme image_name = "cover_#{theme}.jpg" File.join(covers_dir, image_name) end |
.resolve_page_size(config) ⇒ String
book.yml のページ設定から Vivliostyle CLI 用サイズ文字列を解決するvivliostyle.rb の resolve_vivliostyle_size から移植
250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 250 def resolve_page_size(config) page_cfg = config.respond_to?(:page) ? config.page : config[:page] return 'A5' unless page_cfg size_name = page_cfg[:size].to_s.strip.upcase return size_name unless size_name.empty? raw = page_cfg.respond_to?(:to_h) ? page_cfg.to_h : page_cfg w, h = Common.resolve_page_size(raw) "#{w} #{h}" end |
.rewrite_glossary_for_epub!(path, chapter_map) ⇒ Object
用語集 HTML を EPUB 用に書き換える
-
空バックリンクに連番の章番号を挿入CSS の ::before が “→ p.” を付加するため記号は不要
343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 343 def rewrite_glossary_for_epub!(path, chapter_map) html = File.read(path, encoding: 'utf-8') # 空の <a> タグに連番の章番号を挿入 html = html.gsub(%r{(<a\s+href="(\d{2})[^"]*"[^>]*)>\s*</a>}) do tag_open = ::Regexp.last_match(1) prefix = ::Regexp.last_match(2) chapter_num = chapter_map[prefix] || prefix.to_i "#{tag_open}>#{chapter_num}</a>" end File.write(path, html, encoding: 'utf-8') Common.log_info("[EPUB] #{File.basename(path)} を書き換えました(連番バックリンク挿入)") end |
.rewrite_index_for_epub!(path, chapter_map) ⇒ Object
索引 HTML を EPUB 用に書き換える
-
空リンクに連番の章番号を挿入
-
連続するリンク間に “, ” 区切りを追加
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 319 def rewrite_index_for_epub!(path, chapter_map) html = File.read(path, encoding: 'utf-8') # Step 1: 空の <a> タグに連番の章番号を挿入 html = html.gsub(%r{(<a\s+href="(\d{2})[^"]*"[^>]*)>\s*</a>}) do tag_open = ::Regexp.last_match(1) prefix = ::Regexp.last_match(2) chapter_num = chapter_map[prefix] || prefix.to_i "#{tag_open}>#{chapter_num}</a>" end # Step 2: 連続する </a><a を </a>, <a に変換(カンマ区切り) html = html.gsub(%r{</a>\s*(<a\s+href=")}, '</a>, \1') File.write(path, html, encoding: 'utf-8') Common.log_info("[EPUB] #{File.basename(path)} を書き換えました(連番リンク+区切り挿入)") end |
.write_epub_entries(base_dir, html_files) ⇒ Object
EPUB 用 entries.js をファイルに書き出す
185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/vivlio/starter/cli/build/epub_builder.rb', line 185 def write_epub_entries(base_dir, html_files) entries = html_files.map { CLI::EntriesCommands.build_entry(it) } File.open(File.join(base_dir, EPUB_ENTRIES_FILE), 'w') do |f| f.puts 'export default [' entries.each_with_index do |entry, i| f.puts ' {' f.puts %( "path": "#{entry[:path]}",) f.puts %( "title": "#{entry[:title]}") f.puts " }#{',' if i < entries.length - 1}" end f.puts ']' end end |