File size: 5,639 Bytes
f6dcb1f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
130
131
132
133
134
135
136
import gradio as gr
import re
import textwrap
from io import BytesIO

# Các thư viện cho việc tạo ảnh từ văn bản
from PIL import Image, ImageDraw, ImageFont
import matplotlib.pyplot as plt


def render_plain_text_image(text, font_size, width, height, bg_color, text_color):
    """
    Render văn bản thông thường (không chứa LaTeX) thành ảnh bằng Pillow.
    Nếu chiều cao (height) do người dùng nhập vào không đủ để chứa text, 
    chương trình sẽ tự động điều chỉnh lại (với căn giữa theo chiều dọc).
    """
    # Thử load font TrueType; nếu không có, dùng font mặc định
    try:
        font = ImageFont.truetype("arial.ttf", font_size)
    except Exception:
        font = ImageFont.load_default()

    # Tạo ảnh tạm để đo kích thước của text
    temp_img = Image.new("RGB", (width, 100), color=bg_color)
    draw = ImageDraw.Draw(temp_img)
    # Ước tính chiều rộng của một ký tự (giả sử "A")
    char_width, _ = draw.textsize("A", font=font)
    # Số ký tự tối đa trên 1 dòng ước tính từ chiều rộng ảnh
    max_chars = max(1, width // char_width)
    # Tự động ngắt dòng theo số ký tự
    lines = textwrap.wrap(text, width=max_chars)
    
    # Tính tổng chiều cao cần dùng cho các dòng text
    line_heights = []
    for line in lines:
        _, lh = draw.textsize(line, font=font)
        line_heights.append(lh)
    total_text_height = sum(line_heights)
    
    # Nếu chiều cao được truyền vào không đủ, ta dùng chiều cao tối thiểu
    if height is None or height < total_text_height + 20:
        height = total_text_height + 20

    # Tạo ảnh nền với kích thước (width x height)
    img = Image.new("RGB", (width, height), color=bg_color)
    draw = ImageDraw.Draw(img)
    # Căn giữa theo chiều dọc: tính lề trên
    y_text = (height - total_text_height) // 2
    for line in lines:
        line_width, line_height = draw.textsize(line, font=font)
        # Căn giữa theo chiều ngang
        x_text = (width - line_width) // 2
        draw.text((x_text, y_text), line, font=font, fill=text_color)
        y_text += line_height
    return img


def render_math_image(text, font_size, width, height, bg_color, text_color):
    """
    Render văn bản chứa LaTeX (có dấu $ … $) thành ảnh bằng matplotlib.
    Chúng ta dùng matplotlib để hiển thị công thức (mathtext) theo kiểu toán học.
    """
    dpi = 100
    # Chuyển kích thước từ pixel sang inch (1 inch = dpi pixel)
    fig_width = width / dpi
    fig_height = height / dpi
    fig = plt.figure(figsize=(fig_width, fig_height), dpi=dpi)
    ax = plt.axes([0, 0, 1, 1])
    ax.axis('off')
    fig.patch.set_facecolor(bg_color)
    # Dùng hàm text() với thuộc tính wrap=True và căn giữa
    ax.text(0.5, 0.5, text, color=text_color, fontsize=font_size,
            ha='center', va='center', wrap=True)
    buf = BytesIO()
    # Lưu figure vào bộ nhớ với một chút padding
    plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0.1)
    plt.close(fig)
    buf.seek(0)
    from PIL import Image
    img = Image.open(buf)
    return img


def text_to_image(file, text, font_size, width, height, bg_color, text_color):
    """
    Hàm chính: nếu người dùng upload file thì đọc text từ file; nếu không thì dùng nội dung
    nhập trực tiếp. Nếu văn bản có chứa định dạng LaTeX (dấu $ … $) thì dùng hàm render_math_image,
    ngược lại dùng render_plain_text_image.
    """
    # Nếu file được upload, ưu tiên sử dụng nội dung file
    if file is not None:
        try:
            # Trong Gradio, file đầu vào có thể là dict chứa key "name"
            if isinstance(file, dict):
                file_path = file["name"]
            else:
                file_path = file
            with open(file_path, 'r', encoding='utf-8') as f:
                text = f.read()
        except Exception as e:
            return f"Error reading file: {e}"
    if not text:
        return "Chưa có nội dung text được nhập!"

    # Nếu phát hiện chuỗi LaTeX (có dấu $ … $) thì dùng mode math
    if re.search(r'\$(.*?)\$', text):
        return render_math_image(text, font_size, width, height, bg_color, text_color)
    else:
        return render_plain_text_image(text, font_size, width, height, bg_color, text_color)


# Xây dựng giao diện Gradio
iface = gr.Interface(
    fn=text_to_image,
    inputs=[
        gr.File(label="Upload file chứa text (tùy chọn)"),
        gr.Textbox(label="Nhập text", lines=10, placeholder="Nhập text của bạn tại đây..."),
        gr.Slider(10, 100, step=1, value=20, label="Font Size"),
        gr.Slider(200, 2000, step=10, value=800, label="Chiều rộng ảnh (pixel)"),
        gr.Slider(100, 2000, step=10, value=600, label="Chiều cao ảnh (pixel)"),
        gr.ColorPicker(value="#ffffff", label="Màu nền"),
        gr.ColorPicker(value="#000000", label="Màu chữ")
    ],
    outputs=gr.Image(type="pil"),
    title="Text-to-Image Converter",
    description=(
        "Upload một file chứa text hoặc nhập trực tiếp text. "
        "Nếu text có chứa biểu thức LaTeX (ví dụ: $\\int_a^b f(x)dx$) "
        "thì nó sẽ được render theo kiểu toán (math). "
        "Các tuỳ chọn bên dưới cho phép bạn điều chỉnh đầu ra."
    )
)

if __name__ == "__main__":
    iface.launch()