Module: VivlioStarter::CLI::PreProcessCommands::ImagePathNormalizer
- Defined in:
- lib/vivlio_starter/cli/pre_process/image_path_normalizer.rb
Overview
画像パス正規化・プレースホルダー生成モジュール
Constant Summary collapse
- NO_IMAGE_PLACEHOLDER_SVG =
<<~SVG.freeze <svg width="600" height="400" viewBox="0 0 600 400" fill="none" xmlns="http://www.w3.org/2000/svg"> <defs> <linearGradient id="vivlioTextGradient" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" style="stop-color:#4a86e8;stop-opacity:1" /> <stop offset="100%" style="stop-color:#1c4587;stop-opacity:1" /> </linearGradient> <linearGradient id="starterTextGradient" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" style="stop-color:#6aa84f;stop-opacity:1" /> <stop offset="100%" style="stop-color:#38761d;stop-opacity:1" /> </linearGradient> <linearGradient id="backgroundGradient" x1="0%" y1="0%" x2="0%" y2="100%"> <stop offset="0%" style="stop-color:#E0F2F7;stop-opacity:1" /> <stop offset="100%" style="stop-color:#E8F5E9;stop-opacity:1" /> </linearGradient> </defs> <rect x="0" y="0" width="600" height="400" fill="url(#backgroundGradient)" /> <text#{' '} x="300"#{' '} y="140"#{' '} font-family="Arial, sans-serif"#{' '} font-size="72"#{' '} font-weight="bold" text-anchor="middle" dominant-baseline="middle" > <tspan fill="url(#vivlioTextGradient)">filename.webp</tspan> </text> <text#{' '} x="300"#{' '} y="260"#{' '} font-family="Arial, sans-serif"#{' '} font-size="72"#{' '} font-weight="bold" text-anchor="middle" dominant-baseline="middle" > <tspan fill="url(#starterTextGradient)">No Image</tspan> </text> </svg> SVG
Class Method Summary collapse
-
.build_source_image_line_map(source_path) ⇒ Object
元ファイルから画像名 → 行番号のマップを構築する。 pre_process で行数が変わる前の正しい行番号を取得するため。 コード領域の除外は Masking(唯一の実装)に委ね、地の文の画像記法のみ拾う。.
-
.fix_image_paths(content, filename, source_path: nil) ⇒ Object
Markdown 内の画像リンクを生成規約に合わせて正規化する コードブロック・インラインコード内は MarkdownUtils.extract_code_spans で退避し、 変換対象から除外する。.
-
.image_exists_for?(normalized_path) ⇒ Boolean
画像ディレクトリ内の拡張子違いを含めて存在を確認する.
-
.placeholder_image_path(missing_image_path = nil) ⇒ Object
プレースホルダーSVGを使用してデータURIを生成する.
-
.resolved_placeholder_or_path(alt_text, normalized_path, source_filename = nil, line_number = nil, original_image_name = nil) ⇒ Object
既存画像なら正規化パス(ワークスペース相対 = asset_prefix 前置)を、 無い場合はプレースホルダー(data URI・prefix 不要)を返す。 存在確認とログはルート基準の images/...
-
.sanitize_placeholder_text(filename) ⇒ Object
プレースホルダーに差し込むファイル名をサニタイズする.
-
.svg_to_data_uri(svg_content) ⇒ Object
SVGコンテンツをURLエンコードした data URI に変換する.
Class Method Details
.build_source_image_line_map(source_path) ⇒ Object
元ファイルから画像名 → 行番号のマップを構築する。 pre_process で行数が変わる前の正しい行番号を取得するため。 コード領域の除外は Masking(唯一の実装)に委ね、地の文の画像記法のみ拾う。
160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/vivlio_starter/cli/pre_process/image_path_normalizer.rb', line 160 def build_source_image_line_map(source_path) return {} unless source_path && File.exist?(source_path) map = {} source = File.read(source_path, encoding: 'utf-8') Masking.each_prose_line(source) do |line, lineno| line.scan(/!\[[^\]]*\]\(([^)]+)\)/) do image_name = File.basename(::Regexp.last_match(1)) map[image_name] ||= lineno end end map end |
.fix_image_paths(content, filename, source_path: nil) ⇒ Object
Markdown 内の画像リンクを生成規約に合わせて正規化する コードブロック・インラインコード内は MarkdownUtils.extract_code_spans で退避し、 変換対象から除外する。
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 124 125 126 127 128 129 |
# File 'lib/vivlio_starter/cli/pre_process/image_path_normalizer.rb', line 80 def fix_image_paths(content, filename, source_path: nil) chapter_dir = filename.sub(/\.md$/, '') # 元ファイルの行番号マップを構築(画像名 → 元ファイルでの行番号) source_line_map = build_source_image_line_map(source_path) # コードブロック・インラインコードを退避して変換対象から除外する protected_text, spans = MarkdownUtils.extract_code_spans(content) # 行番号を保持しながら画像記法を正規化する # プレースホルダーは改行を含まないため、行番号は元のコンテンツと一致する lines = protected_text.lines transformed_lines = lines.each_with_index.map do |line, idx| line_number = idx + 1 line.gsub(%r{!\[(.*?)\]\((?!https?://)([^)]+)\)}) do alt_text = ::Regexp.last_match(1) image_path = ::Regexp.last_match(2) # ワークスペース相対(asset_prefix 付き)で既に正規化済みなら再変換しない(冪等) prefix = Common.asset_prefix if !prefix.empty? && image_path.start_with?(prefix) "" elsif image_path.start_with?('images/data/') # DataImageResolver が確定させたビルド生成物参照(spec §3.4 の無 prefix 形)。 # 実体はワークスペース html/images/data/ にあり、ルート images/ には無い。 # asset_prefix 前置・.webp 寄せ・存在チェックのいずれも行わず素通しする。 "" else # すでに images/ から始まる場合はそのまま。相対パスは images/<章ディレクトリ>/ に正規化 normalized = if image_path.start_with?('images/') image_path else "images/#{chapter_dir}/#{image_path}" end # 生成物ポリシーに合わせて拡張子を .webp に寄せる(png/jpg のみ対象) normalized = normalized.sub(/\.(png|jpe?g)\z/i, '.webp') original_image_name = File.basename(image_path) # 元ファイルの行番号があればそちらを使う source_ln = source_line_map[original_image_name] || line_number resolved_placeholder_or_path(alt_text, normalized, filename, source_ln, original_image_name) end end end # 退避したコードブロック・インラインコードを復元する MarkdownUtils.restore_code_spans(transformed_lines.join, spans) end |
.image_exists_for?(normalized_path) ⇒ Boolean
画像ディレクトリ内の拡張子違いを含めて存在を確認する
175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/vivlio_starter/cli/pre_process/image_path_normalizer.rb', line 175 def image_exists_for?(normalized_path) relative_path = normalized_path.sub(%r{\Aimages/}, '') base_path = File.(relative_path, Common::IMAGES_DIR) # SVGの場合は直接チェック return File.exist?(base_path) if base_path.end_with?('.svg') # その他の画像形式は拡張子違いをチェック base_without_ext = base_path.sub(/\.webp\z/i, '') %w[.webp .png .jpg .jpeg].any? do |ext| File.exist?("#{base_without_ext}#{ext}") end end |
.placeholder_image_path(missing_image_path = nil) ⇒ Object
プレースホルダーSVGを使用してデータURIを生成する
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/vivlio_starter/cli/pre_process/image_path_normalizer.rb', line 190 def placeholder_image_path(missing_image_path = nil) unless missing_image_path return 'data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%2F%3E' end begin filename = File.basename(missing_image_path) replacement = sanitize_placeholder_text(filename) svg_with_filename = NO_IMAGE_PLACEHOLDER_SVG.gsub('filename.webp', replacement) svg_to_data_uri(svg_with_filename) rescue StandardError => e Common.log_warn("プレースホルダー画像の生成に失敗しました: #{e.class}: #{e.}") 'data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%2F%3E' end end |
.resolved_placeholder_or_path(alt_text, normalized_path, source_filename = nil, line_number = nil, original_image_name = nil) ⇒ Object
既存画像なら正規化パス(ワークスペース相対 = asset_prefix 前置)を、 無い場合はプレースホルダー(data URI・prefix 不要)を返す。 存在確認とログはルート基準の images/... パスで行う(著者に分かる表記のため)。
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/vivlio_starter/cli/pre_process/image_path_normalizer.rb', line 134 def resolved_placeholder_or_path(alt_text, normalized_path, source_filename = nil, line_number = nil, original_image_name = nil) return "" if image_exists_for?(normalized_path) image_name = original_image_name || File.basename(normalized_path) if source_filename && line_number Common.log_error( "#{source_filename}:#{line_number} - 画像 '#{image_name}' が見つかりません(代替画像を使用します)", detail: "画像の場所: #{normalized_path}" ) else # 位置情報が取れない場合のフォールバック Common.log_error( "画像 '#{image_name}' が見つかりません(代替画像を使用します)", detail: "画像の場所: #{normalized_path}" ) end placeholder_path = placeholder_image_path(normalized_path) "" end |
.sanitize_placeholder_text(filename) ⇒ Object
プレースホルダーに差し込むファイル名をサニタイズする
207 208 209 210 211 |
# File 'lib/vivlio_starter/cli/pre_process/image_path_normalizer.rb', line 207 def sanitize_placeholder_text(filename) text = filename.to_s.strip text = 'missing image' if text.empty? CGI.escapeHTML(text) end |
.svg_to_data_uri(svg_content) ⇒ Object
SVGコンテンツをURLエンコードした data URI に変換する
214 215 216 217 218 |
# File 'lib/vivlio_starter/cli/pre_process/image_path_normalizer.rb', line 214 def svg_to_data_uri(svg_content) escaped = CGI.escape(svg_content.encode('utf-8')) escaped = escaped.gsub('+', '%20') "data:image/svg+xml;charset=utf-8,#{escaped}" end |