Zirnavis92 / styles /falling_words.py
Opera10's picture
Update styles/falling_words.py
5158043 verified
Raw
History Blame Contribute Delete
7.72 kB
from PIL import Image, ImageDraw, ImageFont
config = {
"ids": ["falling_words"],
"name": "Falling Words",
"panels": ["color", "font", "size", "position", "radius", "padding"],
"default_font": "vazir"
}
# قالب نمایش زنده در مرورگر (با انیمیشن CSS)
frontend_template = """
<span style="
display: inline-block;
vertical-align: middle;
margin: 0 4px;
animation: dropIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
opacity: 0;
transform: translateY(-20px);
font-family: inherit;
">
{{WORD}}
</span>
<style>
@keyframes dropIn {
0% { opacity: 0; transform: translateY(-30px); }
100% { opacity: 1; transform: translateY(0); }
}
</style>
"""
def draw_frame(draw, img, width, height, style_config, lines, line_metrics, active_idx, font, color_parser, word_infos=None):
# دریافت رنگ متن فقط از تنظیمات اختصاصی (پیش‌فرض سفید مطلق) - قطع ارتباط با استایل‌های دیگر
text_hex = style_config.styleColors.get('falling_words', '#FFFFFF')
base_text_color = color_parser(text_hex, (255, 255, 255, 255))
# خواندن رنگ کادر از استایل (پیش‌فرض نارنجی F77D57)
custom_bg_hex = style_config.styleBgColors.get('falling_words', '#F77D57')
box_color = color_parser(custom_bg_hex, (247, 125, 87, 255))
line_height_px = int(style_config.fontSize * 1.5)
total_block_height = len(lines) * line_height_px
bottom_reference = height - style_config.marginV
start_y_of_block = bottom_reference - total_block_height
# محاسبه عرض کادر
max_line_width = 0
for m in line_metrics:
if m["width"] > max_line_width: max_line_width = m["width"]
box_center_x = width / 2 + (style_config.x or 0)
box_width = max_line_width + (style_config.paddingX * 2)
# ۱. محاسبه نسبت ابعاد ویدیو
ratio = height / width
# ۲. تنظیم موقعیت عمودی (اگر عدد مثبت باشد پایین می‌رود و اگر منفی باشد بالا)
if ratio > 1.6:
# مخصوص ویدیوهای عمودی (طبق تست شما: ۱۲)
box_center_y_adjustment = 12
elif ratio > 1.1:
# مخصوص ویدیوهای مربعی
box_center_y_adjustment = 8
else:
# مخصوص ویدیوهای افقی
box_center_y_adjustment = 5
# ۳. اصلاحیه بصری سقف کادر (تراز عمودی متن)
visual_top_correction = int(style_config.fontSize * 0.12)
# ۳. لبه‌های چپ و راست
box_x1 = box_center_x - (box_width / 2)
box_x2 = box_center_x + (box_width / 2)
# ۴. مختصات سقف و کف (فرمول دقیق کلاسیک)
box_y1 = start_y_of_block - style_config.paddingY + box_center_y_adjustment + visual_top_correction
box_y2 = start_y_of_block + total_block_height + style_config.paddingY + box_center_y_adjustment - (line_height_px * 0.3)
# --- رفع باگ: جلوگیری از منفی شدن ارتفاع کادر در پدینگ‌های کم ---
if box_y2 <= box_y1:
box_y2 = box_y1 + 1 # حداقل ۱ پیکسل ارتفاع داشته باشد تا ارور ندهد
# رسم کادر (شرط len(lines) اضافه شد تا در سکوت اصلا کادر نکشد)
if len(lines) > 0 and style_config.backType in ['solid', 'transparent']:
if style_config.backType == 'transparent' and box_color[3] == 255:
box_color = (box_color[0], box_color[1], box_color[2], 160)
draw.rounded_rectangle([box_x1, box_y1, box_x2, box_y2], radius=style_config.radius, fill=box_color)
# --- رسم کلمات با منطق الگوبرداری شده از آبی نئون ---
text_start_y = box_y1 + (style_config.paddingY * 0.35)
vertical_corr = int(style_config.fontSize * 0.1)
current_line_y = text_start_y - vertical_corr
space_w = draw.textlength(" ", font=font)
global_word_counter = 0
current_render_time = getattr(style_config, 'current_render_time', 0.0)
for line_idx, enumerate_metrics in enumerate(line_metrics):
metrics = enumerate_metrics
start_x = (width + metrics["width"]) / 2 + (style_config.x or 0)
cursor_x = start_x
text_y_pos = current_line_y
for w_i, word in enumerate(metrics["words"]):
w_len = metrics["word_widths"][w_i]
word_x = cursor_x - w_len
should_draw = False
y_offset = 0
alpha_factor = 1.0
# منطق اصلی نمایش کلمه
if word_infos and global_word_counter < len(word_infos):
w_info = word_infos[global_word_counter]
if current_render_time >= w_info.start:
should_draw = True
# انیمیشن سقوط فقط برای کلمه‌ای که تازه شروع شده یا در حال اجراست
time_passed = current_render_time - w_info.start
anim_dur = 0.3 # زمان سقوط (ثانیه)
if time_passed < anim_dur:
progress = time_passed / anim_dur
# انیمیشن نرم Ease-Out
ease = 1 - (1 - progress) * (1 - progress)
y_offset = -40 * (1 - ease)
alpha_factor = ease
else:
y_offset = 0
alpha_factor = 1.0
else:
# کلماتی که هنوز زمانشان نرسیده نباید رسم شوند
should_draw = False
else:
# بک‌آپ برای حالتی که اطلاعات کلمه نیست (در ادیتور)
if active_idx != -1:
should_draw = (global_word_counter <= active_idx)
else:
should_draw = False # اصلاح مشکل نمایش یکباره: در گپ‌ها چیزی نشان نده
if should_draw:
# تعیین رنگ پایه
this_color = base_text_color
# اگر کلمه فعال است (زمان رندر بین شروع و پایان کلمه است)
if getattr(style_config, 'useActiveColor', True) and w_info and current_render_time >= w_info.start and current_render_time < w_info.end:
active_hex = style_config.styleActiveColors.get('falling_words', '#FFEB3B')
this_color = color_parser(active_hex, (255, 235, 59, 255))
if word_infos and global_word_counter < len(word_infos):
w_obj = word_infos[global_word_counter]
if hasattr(w_obj, 'color') and w_obj.color:
this_color = color_parser(w_obj.color, this_color)
# ترکیب شفافیت انیمیشن با شفافیت اصلی رنگ
final_alpha = int(this_color[3] * alpha_factor)
final_color = (this_color[0], this_color[1], this_color[2], final_alpha)
# حذف آفست‌های مخرب و هاردکد شده (متن کاملاً تابع کادر است)
draw.text((word_x, text_y_pos + y_offset), word, font=font, fill=final_color, direction='rtl', language='fa')
cursor_x -= (w_len + space_w)
global_word_counter += 1
current_line_y += line_height_px