///|
/// 文字列内の ^@BM マーカーの位置を見つける
fn find_bm_marker(content~ : String) -> Int? {
let chars = content.to_array()
let len = chars.length()
// "^@BM" (4文字: ^ @ B M) の位置を探す
if len < 4 {
return None
}
let mut i = 0
while i < len - 3 {
if chars[i] == '^' &&
chars[i + 1] == '@' &&
chars[i + 2] == 'B' &&
chars[i + 3] == 'M' {
return Some(i)
}
i = i + 1
}
None
}
///|
/// @BM形式の文字列から画像情報をパースする
fn parse_image_from_bm_string(
content~ : String,
base~ : EntityBase,
start_x~ : Double,
start_y~ : Double,
angle~ : Double,
size_x~ : Double,
size_y~ : Double,
) -> Image? {
// ^@BM の位置を見つける
let bm_offset = match find_bm_marker(content~) {
Some(offset) => offset
None => return None
}
// "^@BM" (4文字) を除去
let chars = content.to_array()
if chars.length() <= bm_offset + 4 {
return None
}
let path_and_params = String::from_array(chars[bm_offset + 4:])
// カンマで分割
let parts = split_by_comma(path_and_params~)
if parts.length() < 1 {
return None
}
// 最初の部分がファイルパス
let raw_path = parts[0]
let image_path = normalize_image_path(raw_path~)
// 幅と高さ: JWWはsize_x, size_yを使用(または後続パラメータ)
let width = if parts.length() >= 2 {
parse_double_or_default(parts[1], size_x)
} else {
size_x
}
let height = if parts.length() >= 3 {
parse_double_or_default(parts[2], size_y)
} else {
size_y
}
Some({
base,
image_path,
x: start_x,
y: start_y,
width,
height,
rotation: angle,
})
}
///|
/// 文字列をカンマで分割する
fn split_by_comma(path_and_params~ : String) -> Array[String] {
let result = Array::new()
let mut current = ""
for c in path_and_params {
if c == ',' {
result.push(current)
current = ""
} else {
current = current + c.to_string()
}
}
if current != "" {
result.push(current)
}
result
}
///|
/// 画像パスを正規化する(%temp%などの環境変数プレフィックスを処理)
fn normalize_image_path(raw_path~ : String) -> String {
let all_chars = raw_path.to_array()
// %temp%プレフィックスを除去
let chars = if raw_path.has_prefix("%temp%") && all_chars.length() > 6 {
Array::from_iter(all_chars[6:].iter())
} else {
all_chars
}
// アンダースコアをスラッシュに変換(JWW特有のパス変換)
// 例: C_Users_... -> C:/Users/...
let mut normalized = ""
let mut prev_was_letter = false
for c in chars {
if c == '_' && prev_was_letter {
normalized = normalized + "/"
prev_was_letter = false
} else {
normalized = normalized + c.to_string()
prev_was_letter = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
}
}
normalized
}
///|
/// 文字列をDoubleにパースする(失敗時はデフォルト値を返す)
fn parse_double_or_default(s : String, default : Double) -> Double {
@strconv.parse_double(s) catch {
_ => default
}
}
///|
/// 画像データのフォーマットをマジックバイトから検出する
fn detect_image_format(data~ : Bytes) -> ImageFormat {
if data.length() < 4 {
return ImageFormat::Unknown
}
let b0 = data[0].to_int()
let b1 = data[1].to_int()
let b2 = data[2].to_int()
let b3 = data[3].to_int()
// JPEG: FF D8 FF
if b0 == 0xFF && b1 == 0xD8 && b2 == 0xFF {
return ImageFormat::Jpeg
}
// PNG: 89 50 4E 47 (...PNG)
if b0 == 0x89 && b1 == 0x50 && b2 == 0x4E && b3 == 0x47 {
return ImageFormat::Png
}
// BMP: 42 4D (BM)
if b0 == 0x42 && b1 == 0x4D {
return ImageFormat::Bmp
}
// GIF: 47 49 46 38 (GIF8)
if b0 == 0x47 && b1 == 0x49 && b2 == 0x46 && b3 == 0x38 {
return ImageFormat::Gif
}
ImageFormat::Unknown
}
///|
/// 単一の同梱画像をパースする
fn parse_single_embedded_image(
reader~ : Reader,
index~ : UInt,
) -> EmbeddedImage? {
// ファイルサイズを読み取り (DWORD = 4バイト)
if reader.remaining() < 4 {
return None
}
let file_size = reader.read_dword()
// ファイルサイズの妥当性チェック
// 0バイトまたは過度に大きい場合はエラー
if file_size == 0U || file_size > 100_000_000U {
// 100MB上限
return None
}
let file_size_int = file_size.reinterpret_as_int()
// 残りデータが不足している場合はNoneを返す
if reader.remaining() < file_size_int {
return None
}
// 画像データを読み取り
let image_data = reader.read_bytes(n=file_size_int)
// データが空の場合はNoneを返す
if image_data.length() == 0 {
return None
}
// 画像フォーマットを検出
let format = detect_image_format(data=image_data)
Some({ index, file_size, data: image_data, format })
}
///|
/// 同梱画像リストをパースする (Ver.7.00+)
fn parse_embedded_images(
data~ : Bytes,
offset~ : Int,
version~ : UInt,
) -> Array[EmbeddedImage] {
// Version 7.00未満では空配列を返す
if version < 700 {
return Array::new()
}
// offsetがデータ範囲外の場合は空配列を返す
if offset >= data.length() {
return Array::new()
}
let image_data = Bytes::from_array(data.to_array()[offset:data.length()])
let reader = Reader::new(data=image_data)
// データが4バイト未満の場合は空配列を返す
if reader.remaining() < 4 {
return Array::new()
}
// 画像数を読み取り
let count = reader.read_dword()
// カウントが異常な値の場合は空配列を返す(保護)
// 同梱画像は通常数個程度
if count > 1000U {
return Array::new()
}
let embedded_images = Array::new()
// 各画像をパース
for i in 0.. embedded_images.push(img)
None => () // エラー時はスキップして継続
}
}
embedded_images
}