Spaces:
Paused
Paused
| from PIL import Image, ImageDraw, ImageFont | |
| config = { | |
| "ids": ["classic", "plain_white", "white_outline", "progressive_write"], | |
| "name": "Classic Styles", | |
| "panels": ["color", "font", "size", "position", "radius", "padding"], | |
| "default_font": "vazir" | |
| } | |
| def draw_frame(draw, img, width, height, style_config, lines, line_metrics, active_idx, font, color_parser, word_infos=None): | |
| style_name = style_config.name | |
| is_rtl = getattr(style_config, 'text_direction', 'rtl') != 'ltr' | |
| base_text_color = color_parser(style_config.primaryColor, (255, 255, 255, 255)) | |
| stroke_color = color_parser(style_config.outlineColor, (0, 0, 0, 255)) | |
| stroke_width = 0 | |
| if style_name == "plain_white": | |
| c_hex = style_config.styleColors.get('plain_white', '#ffffff') | |
| base_text_color = color_parser(c_hex, (255, 255, 255, 255)) | |
| elif style_name == "white_outline": | |
| c_hex = style_config.styleColors.get('white_outline', '#ffffff') | |
| base_text_color = color_parser(c_hex, (255, 255, 255, 255)) | |
| custom_stroke_hex = style_config.styleBgColors.get('white_outline_stroke', '#000000') | |
| stroke_color = color_parser(custom_stroke_hex, (0, 0, 0, 255)) | |
| stroke_width = max(2, int(style_config.fontSize / 12)) | |
| elif style_config.backType == 'outline': | |
| stroke_width = max(2, int(style_config.fontSize / 12)) | |
| 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 | |
| space_w = draw.textlength(" ", font=font) | |
| max_line_width = 0 | |
| for m in line_metrics: | |
| # محاسبه واقعی عرض خط با جمع زدن تک تک کلمات برای دقت صد در صد | |
| actual_w = sum(m["word_widths"]) + max(0, len(m["words"]) - 1) * space_w | |
| if actual_w > max_line_width: max_line_width = actual_w | |
| # --- رسم کادر کلی (Container) --- | |
| should_draw_box = False | |
| box_fill_color = (0,0,0,0) | |
| if style_name in ["classic", "progressive_write"] and style_config.backType in ['solid', 'transparent']: | |
| should_draw_box = True | |
| fill_color_tuple = color_parser(style_config.outlineColor, (0, 0, 0, 255)) | |
| if style_config.backType == 'transparent' and fill_color_tuple[3] == 255: | |
| fill_color_tuple = (fill_color_tuple[0], fill_color_tuple[1], fill_color_tuple[2], 160) | |
| box_fill_color = fill_color_tuple | |
| # استخراج محاسبات کادر به بیرون از شرط برای استفاده به عنوان مبنای رسم متن | |
| ratio = height / width | |
| box_center_y_adjustment = 0 | |
| if ratio > 1.6: box_center_y_adjustment = 5 | |
| elif ratio > 1.1: box_center_y_adjustment = 10 | |
| else: box_center_y_adjustment = 15 | |
| box_center_x = width / 2 + style_config.x | |
| # کاهش 16 پیکسل از عرض کادر برای جمعتر شدن در سمت چپ و راست | |
| box_width = max_line_width + (style_config.paddingX * 2) - 16 | |
| box_x1 = box_center_x - (box_width / 2) | |
| box_x2 = box_center_x + (box_width / 2) | |
| visual_top_correction = int(style_config.fontSize * 0.12) | |
| # نقطه مبنای رسم کادر (برای حفظ جایگاه دقیق متن) | |
| base_y1 = start_y_of_block - style_config.paddingY + box_center_y_adjustment + visual_top_correction | |
| # تنظیم دقیق ارتفاع کادر: کاهش تنها ۱۵ درصدی (برای متعادل شدن بالا و پایین کادر) | |
| box_y1 = base_y1 + (style_config.paddingY * 0.15) | |
| box_y2 = start_y_of_block + total_block_height + (style_config.paddingY * 0.85) + box_center_y_adjustment - (line_height_px * 0.3) | |
| if should_draw_box: | |
| draw.rounded_rectangle([box_x1, box_y1, box_x2, box_y2], radius=style_config.radius, fill=box_fill_color) | |
| # --- رسم متن --- | |
| # تراز کردن متن نسبت به نقطه مبنا (base_y1) تا با کوچک شدن کادر، متن جابجا نشود | |
| text_start_y = base_y1 + (style_config.paddingY * 0.35) | |
| vertical_corr = int(style_config.fontSize * 0.1) | |
| current_line_y = text_start_y - vertical_corr | |
| global_word_counter = 0 | |
| for line_idx, metrics in enumerate(line_metrics): | |
| # محاسبه عرض دقیق و واقعی خط برای وسطچین شدن کامل درون کادر | |
| actual_line_width = sum(metrics["word_widths"]) + max(0, len(metrics["words"]) - 1) * space_w | |
| if is_rtl: | |
| start_x = (width + actual_line_width) / 2 + style_config.x | |
| else: | |
| start_x = (width - actual_line_width) / 2 + style_config.x | |
| 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) if is_rtl else cursor_x | |
| # تنظیم رنگ پیشفرض | |
| current_word_color = base_text_color | |
| # --- تغییر جدید: اعمال رنگ کلمه فعال برای تمامی استایلهای کلاسیک و تایپی --- | |
| if style_name in ["white_outline", "plain_white", "classic", "progressive_write"] and active_idx != -1 and global_word_counter == active_idx and getattr(style_config, 'useActiveColor', True): | |
| active_hex = style_config.styleActiveColors.get(style_name, '#ffffff') | |
| current_word_color = color_parser(active_hex, base_text_color) | |
| # ------------------------------------------------------------ | |
| current_stroke_fill = stroke_color | |
| current_stroke_width = stroke_width | |
| 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: | |
| current_word_color = color_parser(w_obj.color, current_word_color) | |
| display_word = word | |
| draw_x = word_x | |
| should_draw_this_word = True | |
| is_typewriter = getattr(style_config, 'typewriter', False) | |
| if style_name == "progressive_write": | |
| if active_idx != -1 and global_word_counter > active_idx: | |
| should_draw_this_word = False | |
| elif is_typewriter and word_infos and global_word_counter < len(word_infos): | |
| w_info = word_infos[global_word_counter] | |
| current_t = getattr(style_config, 'current_render_time', 0.0) | |
| if current_t < w_info.start: | |
| should_draw_this_word = False | |
| elif current_t >= w_info.start and current_t < w_info.end: | |
| char_len = len(word) | |
| progress = (current_t - w_info.start) / max(0.001, w_info.end - w_info.start) | |
| visible_chars = min(char_len, int(progress * char_len) + 1) | |
| display_word = word[:visible_chars] | |
| try: sub_w = draw.textlength(display_word, font=font, direction=getattr(style_config, 'text_direction', 'rtl'), language=getattr(style_config, 'lang_code', 'fa')) | |
| except: sub_w = font.getlength(display_word) | |
| draw_x = (cursor_x - sub_w) if is_rtl else word_x | |
| if should_draw_this_word and display_word: | |
| draw.text((draw_x, text_y_pos), display_word, font=font, fill=current_word_color, | |
| stroke_width=current_stroke_width, stroke_fill=current_stroke_fill, | |
| direction=getattr(style_config, 'text_direction', 'rtl'), language=getattr(style_config, 'lang_code', 'fa')) | |
| # مکان نما باید جلو برود حتی اگر کلمه رسم نشده باشد (تا جای خالیاش حفظ شود) | |
| if is_rtl: | |
| cursor_x -= (w_len + space_w) | |
| else: | |
| cursor_x += (w_len + space_w) | |
| global_word_counter += 1 | |
| current_line_y += line_height_px |