Merge branch 'overlay_ruby' into develop
# Conflicts: # src-python/config.py
This commit is contained in:
@@ -186,7 +186,7 @@ def _overlay_small_validator(val, inst):
|
|||||||
new[key] = float(v)
|
new[key] = float(v)
|
||||||
elif key in ['display_duration','fadeout_duration'] and isinstance(v,int):
|
elif key in ['display_duration','fadeout_duration'] and isinstance(v,int):
|
||||||
new[key] = v
|
new[key] = v
|
||||||
elif key in ['opacity','ui_scaling'] and isinstance(v,(int,float)):
|
elif key in ['opacity','ui_scaling', 'ruby_font_scale', 'ruby_line_spacing'] and isinstance(v,(int,float)):
|
||||||
new[key] = float(v)
|
new[key] = float(v)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ def _overlay_large_validator(val, inst):
|
|||||||
new[key] = float(v)
|
new[key] = float(v)
|
||||||
elif key in ['display_duration','fadeout_duration'] and isinstance(v,int):
|
elif key in ['display_duration','fadeout_duration'] and isinstance(v,int):
|
||||||
new[key] = v
|
new[key] = v
|
||||||
elif key in ['opacity','ui_scaling'] and isinstance(v,(int,float)):
|
elif key in ['opacity','ui_scaling', 'ruby_font_scale', 'ruby_line_spacing'] and isinstance(v,(int,float)):
|
||||||
new[key] = float(v)
|
new[key] = float(v)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
@@ -737,6 +737,8 @@ class Config:
|
|||||||
"opacity": 1.0,
|
"opacity": 1.0,
|
||||||
"ui_scaling": 1.0,
|
"ui_scaling": 1.0,
|
||||||
"tracker": "HMD",
|
"tracker": "HMD",
|
||||||
|
"ruby_font_scale": 0.5,
|
||||||
|
"ruby_line_spacing": 4,
|
||||||
}
|
}
|
||||||
self._OVERLAY_LARGE_LOG = False
|
self._OVERLAY_LARGE_LOG = False
|
||||||
self._OVERLAY_LARGE_LOG_SETTINGS = {
|
self._OVERLAY_LARGE_LOG_SETTINGS = {
|
||||||
@@ -751,6 +753,8 @@ class Config:
|
|||||||
"opacity": 1.0,
|
"opacity": 1.0,
|
||||||
"ui_scaling": 1.0,
|
"ui_scaling": 1.0,
|
||||||
"tracker": "LeftHand",
|
"tracker": "LeftHand",
|
||||||
|
"ruby_font_scale": 0.5,
|
||||||
|
"ruby_line_spacing": 4,
|
||||||
}
|
}
|
||||||
self._OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES = False
|
self._OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES = False
|
||||||
self._SEND_MESSAGE_TO_VRC = True
|
self._SEND_MESSAGE_TO_VRC = True
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ class Controller:
|
|||||||
|
|
||||||
elif isinstance(message, str) and len(message) > 0:
|
elif isinstance(message, str) and len(message) > 0:
|
||||||
translation = []
|
translation = []
|
||||||
transliteration_message: List[Any] = []
|
transliteration_message = []
|
||||||
transliteration_translation = []
|
transliteration_translation = []
|
||||||
if model.checkKeywords(message):
|
if model.checkKeywords(message):
|
||||||
self.run(
|
self.run(
|
||||||
@@ -383,7 +383,9 @@ class Controller:
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
translation,
|
translation,
|
||||||
config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]
|
config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO],
|
||||||
|
transliteration_message,
|
||||||
|
transliteration_translation
|
||||||
)
|
)
|
||||||
model.updateOverlayLargeLog(overlay_image)
|
model.updateOverlayLargeLog(overlay_image)
|
||||||
else:
|
else:
|
||||||
@@ -392,7 +394,9 @@ class Controller:
|
|||||||
message,
|
message,
|
||||||
config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"],
|
config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"],
|
||||||
translation,
|
translation,
|
||||||
config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]
|
config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO],
|
||||||
|
transliteration_message,
|
||||||
|
transliteration_translation
|
||||||
)
|
)
|
||||||
model.updateOverlayLargeLog(overlay_image)
|
model.updateOverlayLargeLog(overlay_image)
|
||||||
|
|
||||||
@@ -426,7 +430,7 @@ class Controller:
|
|||||||
)
|
)
|
||||||
elif isinstance(message, str) and len(message) > 0:
|
elif isinstance(message, str) and len(message) > 0:
|
||||||
translation = []
|
translation = []
|
||||||
transliteration_message: List[Any] = []
|
transliteration_message = []
|
||||||
transliteration_translation = []
|
transliteration_translation = []
|
||||||
if model.checkKeywords(message):
|
if model.checkKeywords(message):
|
||||||
self.run(
|
self.run(
|
||||||
@@ -511,6 +515,8 @@ class Controller:
|
|||||||
None,
|
None,
|
||||||
translation,
|
translation,
|
||||||
config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO],
|
config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO],
|
||||||
|
transliteration_message,
|
||||||
|
transliteration_translation
|
||||||
)
|
)
|
||||||
model.updateOverlaySmallLog(overlay_image)
|
model.updateOverlaySmallLog(overlay_image)
|
||||||
else:
|
else:
|
||||||
@@ -519,6 +525,8 @@ class Controller:
|
|||||||
language,
|
language,
|
||||||
translation,
|
translation,
|
||||||
config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO],
|
config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO],
|
||||||
|
transliteration_message,
|
||||||
|
transliteration_translation
|
||||||
)
|
)
|
||||||
model.updateOverlaySmallLog(overlay_image)
|
model.updateOverlaySmallLog(overlay_image)
|
||||||
|
|
||||||
@@ -530,6 +538,9 @@ class Controller:
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
translation,
|
translation,
|
||||||
|
config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO],
|
||||||
|
transliteration_message,
|
||||||
|
transliteration_translation
|
||||||
)
|
)
|
||||||
model.updateOverlayLargeLog(overlay_image)
|
model.updateOverlayLargeLog(overlay_image)
|
||||||
else:
|
else:
|
||||||
@@ -538,7 +549,9 @@ class Controller:
|
|||||||
message,
|
message,
|
||||||
language,
|
language,
|
||||||
translation,
|
translation,
|
||||||
config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]
|
config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO],
|
||||||
|
transliteration_message,
|
||||||
|
transliteration_translation
|
||||||
)
|
)
|
||||||
model.updateOverlayLargeLog(overlay_image)
|
model.updateOverlayLargeLog(overlay_image)
|
||||||
|
|
||||||
@@ -703,6 +716,8 @@ class Controller:
|
|||||||
None,
|
None,
|
||||||
translation,
|
translation,
|
||||||
config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO],
|
config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO],
|
||||||
|
transliteration_message,
|
||||||
|
transliteration_translation
|
||||||
)
|
)
|
||||||
model.updateOverlayLargeLog(overlay_image)
|
model.updateOverlayLargeLog(overlay_image)
|
||||||
else:
|
else:
|
||||||
@@ -712,6 +727,8 @@ class Controller:
|
|||||||
config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"],
|
config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"],
|
||||||
translation,
|
translation,
|
||||||
config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO],
|
config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO],
|
||||||
|
transliteration_message,
|
||||||
|
transliteration_translation
|
||||||
)
|
)
|
||||||
model.updateOverlayLargeLog(overlay_image)
|
model.updateOverlayLargeLog(overlay_image)
|
||||||
|
|
||||||
|
|||||||
97
src-python/docs/overlay_ruby.md
Normal file
97
src-python/docs/overlay_ruby.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# 小型ログ ルビ表示機能 (Ruby Overlay for Small Log)
|
||||||
|
|
||||||
|
## 概要
|
||||||
|
小型ログ (Small Log Overlay) に日本語原文が含まれる場合、ローマ字(hepburn) を上段、ひらがな(hira) を下段として原文メッセージの上に 2 段のルビを表示できます。翻訳行には現段階ではルビを付与しません。
|
||||||
|
|
||||||
|
## 有効化条件
|
||||||
|
- 原文 `message` が存在し空文字列でない。
|
||||||
|
- `model.createOverlayImageSmallLog` 内で自動的に `convertMessageToTransliteration(..., hiragana=True, romaji=True)` を呼び出しトークン生成。
|
||||||
|
- 生成されたトークンに `hepburn` または `hira` が含まれる。
|
||||||
|
|
||||||
|
## 大ログ (Large Log) への拡張
|
||||||
|
|
||||||
|
大ログについてもトークン単位のルビ描画をサポートしました。`createOverlayImageLargeLog` / `createTextboxLargeLog` 系の API に以下のような追加引数が入り、同等のルビ出力が可能です。
|
||||||
|
|
||||||
|
- transliteration_tokens: Optional[List[dict]] — 原文用トークン(orig/hira/hepburn)
|
||||||
|
- translation_transliteration_tokens: Optional[List[List[dict]] | List[dict]] — 翻訳ごとのトークン配列、もしくは先頭翻訳用の平坦な List[dict]
|
||||||
|
- ruby_font_scale: float — ルビのフォント倍率(原文フォントサイズに対するスケール)
|
||||||
|
- ruby_line_spacing: int — ルビ行間ピクセル
|
||||||
|
|
||||||
|
対応挙動:
|
||||||
|
|
||||||
|
- 原文のみが存在し、その原文にトークンがあれば原文にトークン単位ルビを振ります。
|
||||||
|
|
||||||
|
- 原文と翻訳の両方が存在する場合は、原則として原文のルビを抑止し、翻訳側にルビを振ります(翻訳側にトークンがある場合)。
|
||||||
|
|
||||||
|
- `translation_transliteration_tokens` は二通りの入力形式を受け付けます:
|
||||||
|
|
||||||
|
- List[List[dict]] — 各翻訳行ごとの tokens 配列(推奨)
|
||||||
|
|
||||||
|
- List[dict] — 平坦な tokens 配列(最初の翻訳行に適用されます)
|
||||||
|
|
||||||
|
フォールバック:
|
||||||
|
|
||||||
|
- トークン単位レイアウトで横幅がはみ出す or 改行がある場合は、既存のブロックルビ(romaji 上 / hira 下 を 1 行ブロックで表示)へ自動フォールバックします。
|
||||||
|
|
||||||
|
注意点:
|
||||||
|
|
||||||
|
- 既存の表示ロジックの互換性を保つため、引数は省略可能です(None/[])。
|
||||||
|
- フラットな `translation_transliteration_tokens` を渡す場合は最初の翻訳にのみ適用されます。複数翻訳に個別のルビを渡す場合は List[List[dict]] 形式で与えてください。
|
||||||
|
|
||||||
|
## 設定キー (`config.OVERLAY_SMALL_LOG_SETTINGS`)
|
||||||
|
| キー | 型 | 初期値 | 説明 |
|
||||||
|
| ---- | --- | ------ | ---- |
|
||||||
|
| ruby_font_scale | float | 0.5 | ルビ文字サイズ倍率 (原文フォントサイズ * 倍率)。安全範囲 0.05〜3.0 |
|
||||||
|
| ruby_line_spacing | int | 4 | ローマ字行とひらがな行の垂直スペース (px)。0〜200 |
|
||||||
|
|
||||||
|
## レイアウト仕様
|
||||||
|
|
||||||
|
1. ルビブロック (romaji 上 / hiragana 下) を中央揃えで描画。
|
||||||
|
|
||||||
|
2. その下に従来の本文テキストボックスを縦方向に連結。
|
||||||
|
|
||||||
|
3. フォントファミリは本文と同一 (言語に対応する NotoSans 系)。
|
||||||
|
|
||||||
|
4. ルビが存在しない場合は従来表示のみ。
|
||||||
|
|
||||||
|
## フォールバック
|
||||||
|
|
||||||
|
- ルビ生成中に例外が発生した場合はログを記録し、ルビ無しで本文のみ表示。
|
||||||
|
|
||||||
|
- トークンが空の場合(両方 False など)は従来表示。
|
||||||
|
|
||||||
|
## 例
|
||||||
|
|
||||||
|
以下は `createOverlayImageLargeLog` を使って、翻訳側にだけルビを渡す例(平坦な tokens を渡す場合と翻訳ごとの tokens を渡す場合):
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 平坦な tokens を渡して最初の翻訳に適用
|
||||||
|
overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!", "Japanese", ["Hello, World!"], ["English"], transliteration_tokens=[], translation_transliteration_tokens=[
|
||||||
|
{"orig": "こんにちは", "hira": "こんにちは", "hepburn": "konnichiha"},
|
||||||
|
{"orig": "世界", "hira": "せかい", "hepburn": "sekai"},
|
||||||
|
])
|
||||||
|
|
||||||
|
# 翻訳ごとに tokens を与える(推奨)
|
||||||
|
overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!", "Japanese", ["Hello, World!"], ["English"], transliteration_tokens=[], translation_transliteration_tokens=[
|
||||||
|
[
|
||||||
|
{"orig": "Hello", "hira": "", "hepburn": "Hello"},
|
||||||
|
{"orig": "World", "hira": "", "hepburn": "World"},
|
||||||
|
]
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 今後の拡張候補
|
||||||
|
- 翻訳行へのルビ付与オプション。
|
||||||
|
- トークン単位での幅センタリングと折り返し。
|
||||||
|
- 高度な幅計測 (可変幅フォント対応改善)。
|
||||||
|
|
||||||
|
## 簡易テスト
|
||||||
|
`src-python/overlay_ruby_test.py` を実行すると `overlay_small_ruby_test.png` が生成され、縦順と配置を確認できます。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# PowerShell (仮想環境有効化後)
|
||||||
|
python src-python/overlay_ruby_test.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意
|
||||||
|
UI スケーリングは OpenVR 側の表示サイズのみ変更し、画像内部フォントサイズは直接変更しません。ルビの視認性が低い場合は `ruby_font_scale` を調整してください。
|
||||||
@@ -944,13 +944,33 @@ class Model:
|
|||||||
self.speaker_energy_recorder.stop()
|
self.speaker_energy_recorder.stop()
|
||||||
self.speaker_energy_recorder = None
|
self.speaker_energy_recorder = None
|
||||||
|
|
||||||
def createOverlayImageSmallLog(self, message:Optional[str], your_language:Optional[str], translation:list, target_language:Optional[dict]) -> object:
|
def createOverlayImageSmallLog(self, message:Optional[str], your_language:Optional[str], translation:list, target_language:Optional[dict], transliteration_message:Optional[dict] = None, transliteration_translation:Optional[list] = None) -> object:
|
||||||
self.ensure_initialized()
|
self.ensure_initialized()
|
||||||
# target_language may be provided as dict or None
|
# Normalize target_language dict -> list
|
||||||
target_language_list = []
|
target_language_list = []
|
||||||
if isinstance(target_language, dict):
|
if isinstance(target_language, dict):
|
||||||
target_language_list = [data["language"] for data in target_language.values() if data.get("enable") is True]
|
target_language_list = [data["language"] for data in target_language.values() if data.get("enable") is True]
|
||||||
return self.overlay_image.createOverlayImageSmallLog(message, your_language, translation, target_language_list)
|
|
||||||
|
# Fetch ruby settings from config (with safe defaults if missing)
|
||||||
|
ruby_font_scale = config.OVERLAY_SMALL_LOG_SETTINGS.get("ruby_font_scale", 0.5)
|
||||||
|
ruby_line_spacing = config.OVERLAY_SMALL_LOG_SETTINGS.get("ruby_line_spacing", 4)
|
||||||
|
|
||||||
|
# 翻訳行ルビ (任意) が指定されていれば渡す。後方互換のため None / 不正型は空リストに。
|
||||||
|
if not isinstance(transliteration_message, list):
|
||||||
|
transliteration_message = []
|
||||||
|
if not isinstance(transliteration_translation, list):
|
||||||
|
transliteration_translation = [[] for _ in translation]
|
||||||
|
|
||||||
|
return self.overlay_image.createOverlayImageSmallLog(
|
||||||
|
message,
|
||||||
|
your_language,
|
||||||
|
translation,
|
||||||
|
target_language_list,
|
||||||
|
transliteration_message=transliteration_message,
|
||||||
|
transliteration_translation=transliteration_translation,
|
||||||
|
ruby_font_scale=ruby_font_scale,
|
||||||
|
ruby_line_spacing=ruby_line_spacing,
|
||||||
|
)
|
||||||
|
|
||||||
def createOverlayImageSmallMessage(self, message):
|
def createOverlayImageSmallMessage(self, message):
|
||||||
self.ensure_initialized()
|
self.ensure_initialized()
|
||||||
@@ -1003,13 +1023,13 @@ class Model:
|
|||||||
if (self.overlay.settings[size]["ui_scaling"] != config.OVERLAY_SMALL_LOG_SETTINGS["ui_scaling"]):
|
if (self.overlay.settings[size]["ui_scaling"] != config.OVERLAY_SMALL_LOG_SETTINGS["ui_scaling"]):
|
||||||
self.overlay.updateUiScaling(config.OVERLAY_SMALL_LOG_SETTINGS["ui_scaling"], size)
|
self.overlay.updateUiScaling(config.OVERLAY_SMALL_LOG_SETTINGS["ui_scaling"], size)
|
||||||
|
|
||||||
def createOverlayImageLargeLog(self, message_type:str, message:Optional[str], your_language:Optional[str], translation:list, target_language:Optional[dict]=None):
|
def createOverlayImageLargeLog(self, message_type:str, message:Optional[str], your_language:Optional[str], translation:list, target_language:Optional[dict]=None, transliteration_message:Optional[list]=None, transliteration_translation:Optional[list]=None) -> object:
|
||||||
self.ensure_initialized()
|
self.ensure_initialized()
|
||||||
# normalize target_language dict -> list of language strings
|
# normalize target_language dict -> list of language strings
|
||||||
target_language_list = []
|
target_language_list = []
|
||||||
if isinstance(target_language, dict):
|
if isinstance(target_language, dict):
|
||||||
target_language_list = [data["language"] for data in target_language.values() if data.get("enable") is True]
|
target_language_list = [data["language"] for data in target_language.values() if data.get("enable") is True]
|
||||||
return self.overlay_image.createOverlayImageLargeLog(message_type, message, your_language, translation, target_language_list)
|
return self.overlay_image.createOverlayImageLargeLog(message_type, message, your_language, translation, target_language_list, transliteration_message, transliteration_translation)
|
||||||
|
|
||||||
def createOverlayImageLargeMessage(self, message):
|
def createOverlayImageLargeMessage(self, message):
|
||||||
self.ensure_initialized()
|
self.ensure_initialized()
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ class OverlayImage:
|
|||||||
self.root_path = os_path.join(os_path.dirname(__file__), "fonts")
|
self.root_path = os_path.join(os_path.dirname(__file__), "fonts")
|
||||||
else:
|
else:
|
||||||
raise FileNotFoundError("Font directory not found.")
|
raise FileNotFoundError("Font directory not found.")
|
||||||
|
# Simple in-memory font cache to avoid repeated truetype loading cost.
|
||||||
|
self._font_cache = {}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def concatenateImagesVertically(img1: Image, img2: Image, margin: int = 0) -> Image:
|
def concatenateImagesVertically(img1: Image, img2: Image, margin: int = 0) -> Image:
|
||||||
@@ -71,29 +73,159 @@ class OverlayImage:
|
|||||||
}
|
}
|
||||||
return colors
|
return colors
|
||||||
|
|
||||||
def createTextboxSmallLog(self, text: str, language: str, text_color: Tuple[int, int, int], base_width: int, base_height: int, font_size: int) -> Image:
|
def _get_font(self, font_family: str, size: int) -> ImageFont.FreeTypeFont:
|
||||||
font_family = self.LANGUAGES.get(language, self.LANGUAGES["Default"])
|
|
||||||
img = Image.new("RGBA", (base_width, base_height), (0, 0, 0, 0))
|
|
||||||
draw = ImageDraw.Draw(img)
|
|
||||||
|
|
||||||
font_path = os_path.join(self.root_path, font_family)
|
font_path = os_path.join(self.root_path, font_family)
|
||||||
font = ImageFont.truetype(font_path, font_size)
|
key = (font_path, size)
|
||||||
|
if key not in self._font_cache:
|
||||||
|
self._font_cache[key] = ImageFont.truetype(font_path, size)
|
||||||
|
return self._font_cache[key]
|
||||||
|
|
||||||
text_width = draw.textlength(text, font)
|
def createTextboxSmallLog(self, text: str, language: str, text_color: Tuple[int, int, int], base_width: int, base_height: int, font_size: int) -> Image:
|
||||||
character_width = text_width // len(text)
|
if text is None:
|
||||||
character_line_num = int((base_width // character_width) - 12)
|
text = ""
|
||||||
if len(text) > character_line_num:
|
font_family = self.LANGUAGES.get(language, self.LANGUAGES["Default"])
|
||||||
text = "\n".join([text[i:i + character_line_num] for i in range(0, len(text), character_line_num)])
|
font = self._get_font(font_family, font_size)
|
||||||
text_height = font_size * (len(text.split("\n")) + 1) + 20
|
|
||||||
|
# Initial image for width measurement
|
||||||
|
img_tmp = Image.new("RGBA", (base_width, base_height), (0, 0, 0, 0))
|
||||||
|
draw_tmp = ImageDraw.Draw(img_tmp)
|
||||||
|
try:
|
||||||
|
text_width = draw_tmp.textlength(text, font) if len(text) > 0 else 1
|
||||||
|
character_width = max(1, text_width // max(1, len(text)))
|
||||||
|
character_line_num = int((base_width // character_width) - 12)
|
||||||
|
if len(text) > character_line_num and character_line_num > 0:
|
||||||
|
text = "\n".join([text[i:i + character_line_num] for i in range(0, len(text), character_line_num)])
|
||||||
|
except Exception:
|
||||||
|
errorLogging()
|
||||||
|
lines = text.split("\n") if text else [""]
|
||||||
|
text_height = font_size * (len(lines) + 1) + 20
|
||||||
img = Image.new("RGBA", (base_width, text_height), (0, 0, 0, 0))
|
img = Image.new("RGBA", (base_width, text_height), (0, 0, 0, 0))
|
||||||
draw = ImageDraw.Draw(img)
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
text_x = base_width // 2
|
text_x = base_width // 2
|
||||||
text_y = text_height // 2
|
text_y = text_height // 2
|
||||||
draw.text((text_x, text_y), text, text_color, anchor="mm", stroke_width=0, font=font, align="center")
|
draw.text((text_x, text_y), text, text_color, anchor="mm", stroke_width=0, font=font, align="center")
|
||||||
return img
|
return img
|
||||||
|
|
||||||
def createOverlayImageSmallLog(self, message: str, your_language: str, translation: List[str] = [], target_language: List[str] = []) -> Image:
|
def renderRubyBlock(self, transliteration: List[dict], language: str, base_width: int, base_font_size: int, ruby_font_scale: float, ruby_line_spacing: int, text_color: Tuple[int, int, int]) -> Optional[Image.Image]:
|
||||||
|
# Build romaji and hiragana lines.
|
||||||
|
romaji_line = " ".join([t.get("hepburn", "") for t in transliteration if t.get("hepburn")])
|
||||||
|
hira_line = " ".join([t.get("hira", "") for t in transliteration if t.get("hira")])
|
||||||
|
if not romaji_line and not hira_line:
|
||||||
|
return None
|
||||||
|
font_family = self.LANGUAGES.get(language, self.LANGUAGES["Default"])
|
||||||
|
ruby_size = max(1, int(base_font_size * ruby_font_scale))
|
||||||
|
font_ruby = self._get_font(font_family, ruby_size)
|
||||||
|
# Symmetric outer padding so ruby block has breathing room top/bottom
|
||||||
|
outer_padding = 10
|
||||||
|
# Measure widths to center lines independently.
|
||||||
|
img_tmp = Image.new("RGBA", (base_width, ruby_size * 2 + ruby_line_spacing + outer_padding * 2), (0, 0, 0, 0))
|
||||||
|
draw_tmp = ImageDraw.Draw(img_tmp)
|
||||||
|
romaji_width = draw_tmp.textlength(romaji_line, font_ruby) if romaji_line else 0
|
||||||
|
hira_width = draw_tmp.textlength(hira_line, font_ruby) if hira_line else 0
|
||||||
|
romaji_x = (base_width - romaji_width) // 2
|
||||||
|
hira_x = (base_width - hira_width) // 2
|
||||||
|
# Construct final ruby image with symmetric padding
|
||||||
|
ruby_height = outer_padding + ruby_size * (2 if hira_line and romaji_line else 1) + (ruby_line_spacing if hira_line and romaji_line else 0) + outer_padding
|
||||||
|
ruby_img = Image.new("RGBA", (base_width, ruby_height), (0, 0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(ruby_img)
|
||||||
|
current_y = outer_padding + ruby_size // 2
|
||||||
|
if romaji_line:
|
||||||
|
draw.text((romaji_x + romaji_width // 2, current_y), romaji_line, text_color, anchor="mm", font=font_ruby)
|
||||||
|
current_y += ruby_size + (ruby_line_spacing if hira_line else 0)
|
||||||
|
if hira_line:
|
||||||
|
draw.text((hira_x + hira_width // 2, current_y), hira_line, text_color, anchor="mm", font=font_ruby)
|
||||||
|
return ruby_img
|
||||||
|
|
||||||
|
def createTextboxSmallLogWithRubyTokens(self, message: str, transliteration: List[dict], language: str, text_color: Tuple[int, int, int], base_width: int, font_size: int, ruby_font_scale: float, ruby_line_spacing: int, ruby_original_spacing: int) -> Image:
|
||||||
|
"""Render a single textbox (original message) with per-token centered ruby (romaji above hiragana) over each original token.
|
||||||
|
|
||||||
|
Fallback: if wrapping would occur (message too wide) or tokens mismatch, revert to block-level ruby (renderRubyBlock + createTextboxSmallLog).
|
||||||
|
"""
|
||||||
|
if not message or not transliteration:
|
||||||
|
return self.createTextboxSmallLog(message, language, text_color, base_width, self.getUiSizeSmallLog()["height"], font_size)
|
||||||
|
|
||||||
|
# Obtain font instances
|
||||||
|
font_family = self.LANGUAGES.get(language, self.LANGUAGES["Default"])
|
||||||
|
font_orig = self._get_font(font_family, font_size)
|
||||||
|
ruby_size = max(1, int(font_size * ruby_font_scale))
|
||||||
|
font_ruby = self._get_font(font_family, ruby_size)
|
||||||
|
|
||||||
|
# Token width measurement
|
||||||
|
draw_tmp_img = Image.new("RGBA", (1, 1), (0, 0, 0, 0))
|
||||||
|
draw_tmp = ImageDraw.Draw(draw_tmp_img)
|
||||||
|
token_infos = []
|
||||||
|
total_width = 0
|
||||||
|
for tok in transliteration:
|
||||||
|
orig = tok.get("orig", "")
|
||||||
|
if not orig:
|
||||||
|
continue
|
||||||
|
hira = tok.get("hira", "")
|
||||||
|
romaji = tok.get("hepburn", "")
|
||||||
|
orig_w = max(1, int(draw_tmp.textlength(orig, font_orig)))
|
||||||
|
hira_w = max(0, int(draw_tmp.textlength(hira, font_ruby))) if hira else 0
|
||||||
|
romaji_w = max(0, int(draw_tmp.textlength(romaji, font_ruby))) if romaji else 0
|
||||||
|
layout_w = max(orig_w, hira_w, romaji_w) # allocate width so ruby lines never overflow neighboring token
|
||||||
|
token_infos.append((orig, hira, romaji, layout_w))
|
||||||
|
total_width += layout_w
|
||||||
|
|
||||||
|
if not token_infos:
|
||||||
|
# Fallback
|
||||||
|
ruby_block = self.renderRubyBlock(transliteration, language, base_width, font_size, ruby_font_scale, ruby_line_spacing, text_color)
|
||||||
|
base_img = self.createTextboxSmallLog(message, language, text_color, base_width, self.getUiSizeSmallLog()["height"], font_size)
|
||||||
|
if ruby_block:
|
||||||
|
return self.concatenateImagesVertically(ruby_block, base_img)
|
||||||
|
return base_img
|
||||||
|
|
||||||
|
# Simple wrapping detection: if total width exceeds base_width * 0.9 → fallback
|
||||||
|
if total_width > base_width * 0.9:
|
||||||
|
ruby_block = self.renderRubyBlock(transliteration, language, base_width, font_size, ruby_font_scale, ruby_line_spacing, text_color)
|
||||||
|
base_img = self.createTextboxSmallLog(message, language, text_color, base_width, self.getUiSizeSmallLog()["height"], font_size)
|
||||||
|
if ruby_block:
|
||||||
|
return self.concatenateImagesVertically(ruby_block, base_img)
|
||||||
|
return base_img
|
||||||
|
|
||||||
|
# Compute left start for centering complete line
|
||||||
|
start_x = (base_width - total_width) // 2
|
||||||
|
# Vertical positioning
|
||||||
|
# Symmetric outer padding: make top padding equal to bottom padding (previously top was 4, bottom ~10)
|
||||||
|
outer_padding = 10 # uniform top & bottom padding for visual balance
|
||||||
|
ruby_lines_count = 0
|
||||||
|
has_romaji_any = any(r for (_, _, r, _) in token_infos)
|
||||||
|
has_hira_any = any(h for (_, h, _, _) in token_infos)
|
||||||
|
if has_romaji_any:
|
||||||
|
ruby_lines_count += 1
|
||||||
|
if has_hira_any:
|
||||||
|
ruby_lines_count += 1
|
||||||
|
# Height calculation (replace asymmetric 4/10 with symmetric outer_padding)
|
||||||
|
ruby_block_height = ruby_lines_count * ruby_size + (ruby_line_spacing if ruby_lines_count == 2 else 0)
|
||||||
|
total_height = outer_padding + ruby_block_height + ruby_original_spacing + font_size + outer_padding
|
||||||
|
img = Image.new("RGBA", (base_width, total_height), (0, 0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
|
# Y centers
|
||||||
|
current_y = outer_padding + ruby_size // 2
|
||||||
|
romaji_y = current_y if has_romaji_any else None
|
||||||
|
hira_y = None
|
||||||
|
if has_romaji_any and has_hira_any:
|
||||||
|
hira_y = romaji_y + ruby_size + ruby_line_spacing
|
||||||
|
elif has_hira_any:
|
||||||
|
hira_y = current_y
|
||||||
|
|
||||||
|
orig_y = outer_padding + ruby_block_height + ruby_original_spacing + font_size // 2
|
||||||
|
|
||||||
|
# Draw tokens sequentially
|
||||||
|
cursor_x = start_x
|
||||||
|
for orig, hira, romaji, w in token_infos:
|
||||||
|
token_center_x = cursor_x + w // 2
|
||||||
|
if romaji_y is not None and romaji:
|
||||||
|
draw.text((token_center_x, romaji_y), romaji, text_color, anchor="mm", font=font_ruby)
|
||||||
|
if hira_y is not None and hira:
|
||||||
|
draw.text((token_center_x, hira_y), hira, text_color, anchor="mm", font=font_ruby)
|
||||||
|
draw.text((token_center_x, orig_y), orig, text_color, anchor="mm", font=font_orig)
|
||||||
|
cursor_x += w
|
||||||
|
return img
|
||||||
|
|
||||||
|
def createOverlayImageSmallLog(self, message: str, your_language: str, translation: List[str] = [], target_language: List[str] = [], transliteration_message: List[dict] = [], transliteration_translation: List[List[dict]] = [], ruby_font_scale: float = 0.5, ruby_line_spacing: int = 4) -> Image:
|
||||||
# UI設定を取得
|
# UI設定を取得
|
||||||
ui_size = self.getUiSizeSmallLog()
|
ui_size = self.getUiSizeSmallLog()
|
||||||
width, height, font_size = ui_size["width"], ui_size["height"], ui_size["font_size"]
|
width, height, font_size = ui_size["width"], ui_size["height"], ui_size["font_size"]
|
||||||
@@ -107,29 +239,66 @@ class OverlayImage:
|
|||||||
textbox_images = []
|
textbox_images = []
|
||||||
|
|
||||||
# 翻訳がある場合
|
# 翻訳がある場合
|
||||||
|
# Use improved per-token placement if possible; else fallback to previous block approach.
|
||||||
|
ruby_original_spacing = 2 # Narrow vertical gap between hiragana block and original text.
|
||||||
if translation and target_language:
|
if translation and target_language:
|
||||||
# 元のメッセージがある場合は追加
|
|
||||||
if message:
|
if message:
|
||||||
textbox_images.append(
|
base_msg_img = self.createTextboxSmallLog(message, your_language, text_color, width, height, font_size)
|
||||||
self.createTextboxSmallLog(message, your_language, text_color, width, height, font_size)
|
textbox_images.append(base_msg_img)
|
||||||
)
|
for trans, lang, translite in zip(translation, target_language, transliteration_translation):
|
||||||
|
try:
|
||||||
# 翻訳をすべて追加
|
trans_img = self.createTextboxSmallLogWithRubyTokens(
|
||||||
for trans, lang in zip(translation, target_language):
|
trans,
|
||||||
textbox_images.append(
|
translite,
|
||||||
self.createTextboxSmallLog(trans, lang, text_color, width, height, font_size)
|
lang,
|
||||||
)
|
text_color,
|
||||||
|
width,
|
||||||
|
font_size,
|
||||||
|
ruby_font_scale,
|
||||||
|
ruby_line_spacing,
|
||||||
|
ruby_original_spacing,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
errorLogging()
|
||||||
|
trans_img = self.createTextboxSmallLog(trans, lang, text_color, width, height, font_size)
|
||||||
|
textbox_images.append(trans_img)
|
||||||
else:
|
else:
|
||||||
# 翻訳がない場合は元のメッセージのみ
|
# 翻訳無しモード
|
||||||
textbox_images.append(
|
if message and transliteration_message:
|
||||||
self.createTextboxSmallLog(message, your_language, text_color, width, height, font_size)
|
try:
|
||||||
)
|
base_msg_img = self.createTextboxSmallLogWithRubyTokens(
|
||||||
|
message,
|
||||||
|
transliteration_message,
|
||||||
|
your_language,
|
||||||
|
text_color,
|
||||||
|
width,
|
||||||
|
font_size,
|
||||||
|
ruby_font_scale,
|
||||||
|
ruby_line_spacing,
|
||||||
|
ruby_original_spacing,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
errorLogging()
|
||||||
|
base_msg_img = self.createTextboxSmallLog(message, your_language, text_color, width, height, font_size)
|
||||||
|
try:
|
||||||
|
ruby_img = self.renderRubyBlock(transliteration_message, your_language, width, font_size, ruby_font_scale, ruby_line_spacing, text_color)
|
||||||
|
if ruby_img is not None:
|
||||||
|
base_msg_img = self.concatenateImagesVertically(ruby_img, base_msg_img)
|
||||||
|
except Exception:
|
||||||
|
errorLogging()
|
||||||
|
else:
|
||||||
|
base_msg_img = self.createTextboxSmallLog(message, your_language, text_color, width, height, font_size)
|
||||||
|
textbox_images.append(base_msg_img)
|
||||||
|
|
||||||
# すべてのテキストボックスを縦に結合
|
# すべてのテキストボックスを縦に結合
|
||||||
img = textbox_images[0]
|
img = textbox_images[0]
|
||||||
for textbox_img in textbox_images[1:]:
|
for textbox_img in textbox_images[1:]:
|
||||||
img = self.concatenateImagesVertically(img, textbox_img)
|
img = self.concatenateImagesVertically(img, textbox_img)
|
||||||
|
|
||||||
|
# 画像周囲にUIパディングを追加して、文字が端に張り付かないようにする
|
||||||
|
ui_outer_padding = 50
|
||||||
|
img = self.addImageMargin(img, ui_outer_padding, ui_outer_padding, ui_outer_padding, ui_outer_padding, (0, 0, 0, 0))
|
||||||
|
|
||||||
# 角丸背景を作成
|
# 角丸背景を作成
|
||||||
background = Image.new("RGBA", img.size, (0, 0, 0, 0))
|
background = Image.new("RGBA", img.size, (0, 0, 0, 0))
|
||||||
draw = ImageDraw.Draw(background)
|
draw = ImageDraw.Draw(background)
|
||||||
@@ -191,6 +360,93 @@ class OverlayImage:
|
|||||||
draw.multiline_text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font, align=align)
|
draw.multiline_text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font, align=align)
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
def createTextboxLargeLogWithRubyTokens(self, message_type: str, size: str, message: str, transliteration: List[dict], language: str, ruby_font_scale: float, ruby_line_spacing: int) -> Image:
|
||||||
|
"""Render a large-log textbox with per-token centered ruby above each original token.
|
||||||
|
|
||||||
|
Falls back to block-level ruby if message wraps or tokens mismatch.
|
||||||
|
"""
|
||||||
|
ui_size = self.getUiSizeLargeLog()
|
||||||
|
font_size = ui_size["font_size_large"] if size == "large" else ui_size["font_size_small"]
|
||||||
|
text_color = self.getUiColorLargeLog()[f"text_color_{size}"]
|
||||||
|
font_family = self.LANGUAGES.get(language, self.LANGUAGES["Default"])
|
||||||
|
font_orig = self._get_font(font_family, font_size)
|
||||||
|
ruby_size = max(1, int(font_size * ruby_font_scale))
|
||||||
|
font_ruby = self._get_font(font_family, ruby_size)
|
||||||
|
|
||||||
|
# Simple guard
|
||||||
|
if not message or not transliteration:
|
||||||
|
return self.createTextImageLargeLog(message_type, size, message, language)
|
||||||
|
|
||||||
|
# Reject multiline for per-token layout; fallback to block ruby
|
||||||
|
if "\n" in message:
|
||||||
|
ruby_block = self.renderRubyBlock(transliteration, language, ui_size["width"], font_size, ruby_font_scale, ruby_line_spacing, text_color)
|
||||||
|
base_img = self.createTextImageLargeLog(message_type, size, message, language)
|
||||||
|
if ruby_block is not None:
|
||||||
|
return self.concatenateImagesVertically(ruby_block, base_img)
|
||||||
|
return base_img
|
||||||
|
|
||||||
|
# Measure token widths
|
||||||
|
draw_tmp_img = Image.new("RGBA", (1, 1), (0, 0, 0, 0))
|
||||||
|
draw_tmp = ImageDraw.Draw(draw_tmp_img)
|
||||||
|
token_infos = []
|
||||||
|
total_width = 0
|
||||||
|
for tok in transliteration:
|
||||||
|
orig = tok.get("orig", "")
|
||||||
|
if not orig:
|
||||||
|
continue
|
||||||
|
hira = tok.get("hira", "")
|
||||||
|
romaji = tok.get("hepburn", "")
|
||||||
|
orig_w = max(1, int(draw_tmp.textlength(orig, font_orig)))
|
||||||
|
hira_w = max(0, int(draw_tmp.textlength(hira, font_ruby))) if hira else 0
|
||||||
|
romaji_w = max(0, int(draw_tmp.textlength(romaji, font_ruby))) if romaji else 0
|
||||||
|
layout_w = max(orig_w, hira_w, romaji_w)
|
||||||
|
token_infos.append((orig, hira, romaji, layout_w))
|
||||||
|
total_width += layout_w
|
||||||
|
|
||||||
|
# Fallback if nothing to render or would overflow
|
||||||
|
base_width = ui_size["width"]
|
||||||
|
if not token_infos or total_width > base_width * 0.9:
|
||||||
|
ruby_block = self.renderRubyBlock(transliteration, language, base_width, font_size, ruby_font_scale, ruby_line_spacing, text_color)
|
||||||
|
base_img = self.createTextImageLargeLog(message_type, size, message, language)
|
||||||
|
if ruby_block is not None:
|
||||||
|
return self.concatenateImagesVertically(ruby_block, base_img)
|
||||||
|
return base_img
|
||||||
|
|
||||||
|
# Determine start_x according to message type (left for receive, right-align for send)
|
||||||
|
start_x = 0 if message_type == "receive" else (base_width - total_width)
|
||||||
|
|
||||||
|
# Vertical layout
|
||||||
|
outer_padding = 10
|
||||||
|
has_romaji_any = any(r for (_, _, r, _) in token_infos)
|
||||||
|
has_hira_any = any(h for (_, h, _, _) in token_infos)
|
||||||
|
ruby_lines_count = (1 if has_romaji_any else 0) + (1 if has_hira_any else 0)
|
||||||
|
ruby_block_height = ruby_lines_count * ruby_size + (ruby_line_spacing if ruby_lines_count == 2 else 0)
|
||||||
|
total_height = outer_padding + ruby_block_height + font_size + outer_padding
|
||||||
|
img = Image.new("RGBA", (base_width, total_height), (0, 0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
|
# y positions
|
||||||
|
current_y = outer_padding + ruby_size // 2
|
||||||
|
romaji_y = current_y if has_romaji_any else None
|
||||||
|
hira_y = None
|
||||||
|
if has_romaji_any and has_hira_any:
|
||||||
|
hira_y = romaji_y + ruby_size + ruby_line_spacing
|
||||||
|
elif has_hira_any:
|
||||||
|
hira_y = current_y
|
||||||
|
orig_y = outer_padding + ruby_block_height + font_size // 2
|
||||||
|
|
||||||
|
# Draw tokens
|
||||||
|
cursor_x = start_x
|
||||||
|
for orig, hira, romaji, w in token_infos:
|
||||||
|
token_center_x = cursor_x + w // 2
|
||||||
|
if romaji_y is not None and romaji:
|
||||||
|
draw.text((token_center_x, romaji_y), romaji, text_color, anchor="mm", font=font_ruby)
|
||||||
|
if hira_y is not None and hira:
|
||||||
|
draw.text((token_center_x, hira_y), hira, text_color, anchor="mm", font=font_ruby)
|
||||||
|
draw.text((token_center_x, orig_y), orig, text_color, anchor="mm", font=font_orig)
|
||||||
|
cursor_x += w
|
||||||
|
return img
|
||||||
|
|
||||||
def createTextImageMessageType(self, message_type: str, date_time: str) -> Image:
|
def createTextImageMessageType(self, message_type: str, date_time: str) -> Image:
|
||||||
ui_size = self.getUiSizeLargeLog()
|
ui_size = self.getUiSizeLargeLog()
|
||||||
font_size = ui_size["font_size_small"]
|
font_size = ui_size["font_size_small"]
|
||||||
@@ -221,7 +477,7 @@ class OverlayImage:
|
|||||||
draw.text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font)
|
draw.text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font)
|
||||||
return img
|
return img
|
||||||
|
|
||||||
def createTextboxLargeLog(self, message_type: str, message: Optional[str] = None, your_language: Optional[str] = None, translation: List[str] = [], target_language: List[str] = [], date_time: Optional[str] = None) -> Image:
|
def createTextboxLargeLog(self, message_type: str, message: Optional[str] = None, your_language: Optional[str] = None, translation: List[str] = [], target_language: List[str] = [], date_time: Optional[str] = None, transliteration_message: Optional[List[dict]] = None, transliteration_translation: Optional[List[List[dict]]] = None, ruby_font_scale: float = 0.5, ruby_line_spacing: int = 4) -> Image:
|
||||||
# テキスト画像のリストを作成
|
# テキスト画像のリストを作成
|
||||||
images = [self.createTextImageMessageType(message_type, date_time)]
|
images = [self.createTextImageMessageType(message_type, date_time)]
|
||||||
|
|
||||||
@@ -229,20 +485,25 @@ class OverlayImage:
|
|||||||
if translation and target_language:
|
if translation and target_language:
|
||||||
# 元のメッセージがある場合は小さいサイズで追加
|
# 元のメッセージがある場合は小さいサイズで追加
|
||||||
if message is not None:
|
if message is not None:
|
||||||
images.append(
|
small_img = self.createTextImageLargeLog(message_type, "small", message, your_language)
|
||||||
self.createTextImageLargeLog(message_type, "small", message, your_language)
|
images.append(small_img)
|
||||||
)
|
|
||||||
|
|
||||||
# 翻訳をすべて大きいサイズで追加
|
# 翻訳をすべて大きいサイズで追加
|
||||||
for trans, lang in zip(translation, target_language):
|
for trans, lang, translite in zip(translation, target_language, transliteration_translation):
|
||||||
images.append(
|
try:
|
||||||
self.createTextImageLargeLog(message_type, "large", trans, lang)
|
large_img = self.createTextboxLargeLogWithRubyTokens(message_type, "large", trans, translite, lang, ruby_font_scale, ruby_line_spacing)
|
||||||
)
|
except Exception:
|
||||||
|
errorLogging()
|
||||||
|
large_img = self.createTextImageLargeLog(message_type, "large", trans, lang)
|
||||||
|
images.append(large_img)
|
||||||
else:
|
else:
|
||||||
# 翻訳がない場合は元のメッセージのみ
|
# 翻訳がない場合は元のメッセージのみ
|
||||||
images.append(
|
try:
|
||||||
self.createTextImageLargeLog(message_type, "large", message, your_language)
|
large_img = self.createTextboxLargeLogWithRubyTokens(message_type, "large", message, transliteration_message, your_language, ruby_font_scale, ruby_line_spacing)
|
||||||
)
|
except Exception:
|
||||||
|
errorLogging()
|
||||||
|
large_img = self.createTextImageLargeLog(message_type, "large", message, your_language)
|
||||||
|
images.append(large_img)
|
||||||
|
|
||||||
# すべてのテキスト画像を縦に結合
|
# すべてのテキスト画像を縦に結合
|
||||||
combined_img = images[0]
|
combined_img = images[0]
|
||||||
@@ -251,7 +512,7 @@ class OverlayImage:
|
|||||||
|
|
||||||
return combined_img
|
return combined_img
|
||||||
|
|
||||||
def createOverlayImageLargeLog(self, message_type: str, message: Optional[str] = None, your_language: Optional[str] = None, translation: List[str] = [], target_language: List[str] = []) -> Image:
|
def createOverlayImageLargeLog(self, message_type: str, message: Optional[str] = None, your_language: Optional[str] = None, translation: List[str] = [], target_language: List[str] = [], transliteration_message: List[dict] = [], transliteration_translation: List[List[dict]] = [], ruby_font_scale: float = 0.5, ruby_line_spacing: int = 4) -> Image:
|
||||||
ui_color = self.getUiColorLargeLog()
|
ui_color = self.getUiColorLargeLog()
|
||||||
background_color = ui_color["background_color"]
|
background_color = ui_color["background_color"]
|
||||||
background_outline_color = ui_color["background_outline_color"]
|
background_outline_color = ui_color["background_outline_color"]
|
||||||
@@ -267,6 +528,8 @@ class OverlayImage:
|
|||||||
"your_language": your_language,
|
"your_language": your_language,
|
||||||
"translation": translation,
|
"translation": translation,
|
||||||
"target_language": target_language,
|
"target_language": target_language,
|
||||||
|
"transliteration_message": transliteration_message,
|
||||||
|
"transliteration_translation": transliteration_translation,
|
||||||
"datetime": datetime.now().strftime("%H:%M")
|
"datetime": datetime.now().strftime("%H:%M")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -280,7 +543,12 @@ class OverlayImage:
|
|||||||
log["your_language"],
|
log["your_language"],
|
||||||
log["translation"],
|
log["translation"],
|
||||||
log["target_language"],
|
log["target_language"],
|
||||||
log["datetime"]) for log in self.message_log
|
log["datetime"],
|
||||||
|
transliteration_message=log.get("transliteration_message", [{}]),
|
||||||
|
transliteration_translation=log.get("transliteration_translation", [{}]),
|
||||||
|
ruby_font_scale=ruby_font_scale,
|
||||||
|
ruby_line_spacing=ruby_line_spacing,
|
||||||
|
) for log in self.message_log
|
||||||
]
|
]
|
||||||
|
|
||||||
img = imgs[0]
|
img = imgs[0]
|
||||||
@@ -297,18 +565,103 @@ class OverlayImage:
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
overlay = OverlayImage()
|
overlay = OverlayImage()
|
||||||
img = overlay.createOverlayImageSmallLog("Hello, World!", "English", "こんにちは、世界!", "Japanese")
|
# Basic small log test (with translation list form)
|
||||||
|
img = overlay.createOverlayImageSmallLog("Hello, World!", "English", ["こんにちは、世界!"], ["Japanese"], [], [[]])
|
||||||
img.save("overlay_small.png")
|
img.save("overlay_small.png")
|
||||||
img = overlay.createOverlayImageLargeLog("send", "Hello, World!", "English", "こんにちは、世界!", "Japanese")
|
|
||||||
img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!", "Japanese", "Hello, World!", "English")
|
# Ruby small log test (Japanese original with transliteration tokens)
|
||||||
img = overlay.createOverlayImageLargeLog("send", "Hello, World!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "English", "aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああこんにちは、世界!", "Japanese")
|
ruby_tokens = [
|
||||||
img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "Japanese", "Hello, World!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "English")
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
img = overlay.createOverlayImageLargeLog("send", "Hello, World!", "English", "こんにちは、世界!", "Japanese")
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!", "Japanese", "Hello, World!", "English")
|
]
|
||||||
img = overlay.createOverlayImageLargeLog("send", "Hello, World!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "English", "aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああこんにちは、世界!", "Japanese")
|
# Ruby on original + ruby on translation example
|
||||||
img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "Japanese", "Hello, World!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "English")
|
translation_tokens = [
|
||||||
img = overlay.createOverlayImageLargeLog("send", "Hello, World!", "English", "こんにちは、世界!", "Japanese")
|
[
|
||||||
img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!", "Japanese", "Hello, World!", "English")
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
img = overlay.createOverlayImageLargeLog("send", "Hello, World!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "English", "aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああこんにちは、世界!", "Japanese")
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "Japanese", "Hello, World!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "English")
|
],
|
||||||
|
[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
]
|
||||||
|
]
|
||||||
|
img_ruby = overlay.createOverlayImageSmallLog(
|
||||||
|
"慮る",
|
||||||
|
"Japanese",
|
||||||
|
["慮る", "慮る", "慮る"],
|
||||||
|
["Japanese", "Japanese", "Japanese"],
|
||||||
|
transliteration_message=ruby_tokens,
|
||||||
|
transliteration_translation=translation_tokens,
|
||||||
|
ruby_font_scale=0.5,
|
||||||
|
ruby_line_spacing=4,
|
||||||
|
)
|
||||||
|
img_ruby.save("overlay_small_ruby.png")
|
||||||
|
|
||||||
|
# Large log tests (adjusted to pass translation/target_language as lists)
|
||||||
|
img = overlay.createOverlayImageLargeLog("send", "Hello, World!", "English", ["こんにちは、世界!"], ["Japanese"], transliteration_message=[], transliteration_translation=[])
|
||||||
|
img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!", "Japanese", ["Hello, World!"], ["English"], transliteration_message=[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
], transliteration_translation=[
|
||||||
|
[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
]
|
||||||
|
])
|
||||||
|
long_en = "Hello, World!"
|
||||||
|
long_jp = "こんにちは、世界!"
|
||||||
|
img = overlay.createOverlayImageLargeLog("send", long_en, "English", [long_jp], ["Japanese"], transliteration_message=[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
], transliteration_translation=[[]])
|
||||||
|
img = overlay.createOverlayImageLargeLog("receive", long_jp, "Japanese", [long_en], ["English"], transliteration_message=[], transliteration_translation=[
|
||||||
|
[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
]
|
||||||
|
])
|
||||||
|
img = overlay.createOverlayImageLargeLog("send", "Hello, World!", "English", ["こんにちは、世界!"], ["Japanese"], transliteration_message=[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
], transliteration_translation=[[]])
|
||||||
|
|
||||||
|
img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!", "Japanese", ["Hello, World!"], ["English"], transliteration_message=[
|
||||||
|
{"orig": "こんにちは", "hira": "こんにちは", "hepburn": "konnichiha"},
|
||||||
|
{"orig": "世界", "hira": "sekai", "hepburn": "sekai"},
|
||||||
|
], transliteration_translation=[
|
||||||
|
[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
]
|
||||||
|
])
|
||||||
|
img = overlay.createOverlayImageLargeLog("send", long_en, "English", [long_jp], ["Japanese"], transliteration_message=[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
], transliteration_translation=[
|
||||||
|
[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
]
|
||||||
|
])
|
||||||
|
img = overlay.createOverlayImageLargeLog("receive", long_jp, "Japanese", [long_en, long_en, long_en], ["English", "English", "English"], transliteration_message=[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
], transliteration_translation=[
|
||||||
|
[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{"orig": "慮", "hira": "おもんぱか", "hepburn": "omonpaka"},
|
||||||
|
{"orig": "る", "hira": "る", "hepburn": "ru"},
|
||||||
|
]
|
||||||
|
])
|
||||||
img.save("overlay_large.png")
|
img.save("overlay_large.png")
|
||||||
Reference in New Issue
Block a user