File size: 14,317 Bytes
f4892cf
4eeb4da
8f38a6d
12f8f41
 
8f38a6d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6ffa77a
8f38a6d
 
f4892cf
8f38a6d
f4892cf
efe0312
8f38a6d
f4892cf
8f38a6d
 
 
 
f4892cf
efe0312
f4892cf
8f38a6d
12f8f41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8f38a6d
12f8f41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8f38a6d
12f8f41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8f38a6d
12f8f41
 
 
 
 
 
 
 
 
 
 
8f38a6d
12f8f41
 
 
4eeb4da
12f8f41
 
8f38a6d
12f8f41
 
 
 
 
8f38a6d
12f8f41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8f38a6d
12f8f41
 
 
8f38a6d
12f8f41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8f38a6d
 
 
 
 
 
 
f4892cf
6ffa77a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f4892cf
 
6ffa77a
 
17b2eb4
6ffa77a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17b2eb4
6ffa77a
 
 
 
 
 
 
 
 
 
 
 
 
 
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
import gradio as gr
import os
import tempfile
import traceback
import gc
from PIL import Image, ImageDraw, ImageFont

def draw_horizontal_lines(draw, width, height, spacing=60, color=(230, 230, 230)):
    """Draws horizontal lines on the image."""
    for y in range(0, height, spacing):
        draw.line([(0, y), (width, y)], fill=color, width=2)

def draw_dot_grid(draw, width, height, spacing=50, color=(220, 220, 220)):
    """Draws a dot grid on the image."""
    for x in range(0, width, spacing):
        for y in range(0, height, spacing):
            draw.ellipse([(x-2, y-2), (x+2, y+2)], fill=color)

def draw_lattice_grid(draw, width, height, spacing=100, color=(235, 235, 235)):
    """Draws a lattice/graph paper grid on the image."""
    # Draw vertical lines
    for x in range(0, width, spacing):
        draw.line([(x, 0), (x, height)], fill=color, width=2)
    # Draw horizontal lines  
    for y in range(0, height, spacing):
        draw.line([(0, y), (width, y)], fill=color, width=2)

def text_to_images_mcp(text_content, style='lines', font_path=None):
    """
    Converts text to images and returns a list of image objects.
    
    Args:
        text_content (str): The text to be converted.
        style (str): The background style ('plain', 'lines', 'dots', 'grid').
        font_path (str, optional): The path to a .ttf font file.
    
    Returns:
        list: List of PIL Image objects for display in Gradio.
    """
    try:
        # Input validation
        if not text_content or not text_content.strip():
            return []
        
        # Limit text length to prevent memory issues
        if len(text_content) > 10000:  # 10k character limit
            text_content = text_content[:10000] + "\n\n[Text truncated due to length limit]"
        
        # --- Configuration ---
        IMG_WIDTH = 1080
        IMG_HEIGHT = 1080
        BACKGROUND_COLOR = (255, 255, 255)
        TEXT_COLOR = (10, 10, 10)
        STYLE_COLOR = (225, 225, 225)
        
        PADDING_X = 80
        PADDING_Y = 80
        FONT_SIZE = 48
        LINE_SPACING = 20
        
        # Limit maximum number of pages to prevent memory issues
        MAX_PAGES = 10
        
        # --- Font Loading ---
        font = None
        try:
            if font_path and os.path.exists(font_path):
                font = ImageFont.truetype(font_path, FONT_SIZE)
            else:
                font_paths_to_try = [
                    "Arial.ttf", "arial.ttf",
                    "/System/Library/Fonts/Supplemental/Arial.ttf",
                    "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
                ]
                for f_path in font_paths_to_try:
                    try:
                        font = ImageFont.truetype(f_path, FONT_SIZE)
                        break
                    except (IOError, OSError):
                        continue
            if not font:
                font = ImageFont.load_default()
        except Exception as e:
            print(f"Font loading error: {e}")
            font = ImageFont.load_default()

        # --- Text Wrapping Logic ---
        drawable_width = IMG_WIDTH - 2 * PADDING_X
        paragraphs = [p.strip() for p in text_content.strip().split('\n') if p.strip()]

        if not paragraphs:
            return []

        all_lines_and_breaks = []
        for i, paragraph in enumerate(paragraphs):
            words = paragraph.split()
            current_line = ""
            for word in words:
                try:
                    # Handle very long words
                    if hasattr(font, 'getlength') and font.getlength(word) > drawable_width:
                        temp_word = ""
                        for char in word:
                            if font.getlength(temp_word + char) > drawable_width:
                                all_lines_and_breaks.append(temp_word)
                                temp_word = char
                            else:
                                temp_word += char
                        word = temp_word
                    
                    # Check line length
                    test_line = current_line + " " + word if current_line else word
                    if hasattr(font, 'getlength'):
                        line_width = font.getlength(test_line)
                    else:
                        line_width = len(test_line) * 12  # Rough estimate
                    
                    if line_width <= drawable_width:
                        current_line = test_line
                    else:
                        if current_line:
                            all_lines_and_breaks.append(current_line.strip())
                        current_line = word
                except Exception as e:
                    print(f"Text wrapping error: {e}")
                    # Fallback to simple character limit
                    if len(current_line + " " + word) <= 80:  # Rough character limit
                        current_line += " " + word
                    else:
                        all_lines_and_breaks.append(current_line.strip())
                        current_line = word
            
            if current_line:
                all_lines_and_breaks.append(current_line.strip())
            if i < len(paragraphs) - 1:
                all_lines_and_breaks.append(None)  # Paragraph break

        # --- Image Generation ---
        img_count = 0
        page_content = []
        y_text = PADDING_Y
        
        try:
            if hasattr(font, 'getbbox'):
                line_height = font.getbbox("A")[3] - font.getbbox("A")[1]
            else:
                line_height = FONT_SIZE
        except Exception:
            line_height = FONT_SIZE
        
        PARAGRAPH_SPACING = line_height
        generated_images = []

        def create_image_page(content, page_num):
            """Helper function to create and return a single image page."""
            try:
                img = Image.new('RGB', (IMG_WIDTH, IMG_HEIGHT), color=BACKGROUND_COLOR)
                draw = ImageDraw.Draw(img)

                # Draw the selected background style first
                if style == 'lines':
                    line_style_spacing = line_height + LINE_SPACING
                    draw_horizontal_lines(draw, IMG_WIDTH, IMG_HEIGHT, spacing=line_style_spacing, color=STYLE_COLOR)
                elif style == 'dots':
                    draw_dot_grid(draw, IMG_WIDTH, IMG_HEIGHT, color=STYLE_COLOR)
                elif style == 'grid':
                    draw_lattice_grid(draw, IMG_WIDTH, IMG_HEIGHT, color=STYLE_COLOR)

                # Draw the text on top of the background
                current_y = PADDING_Y
                for page_item in content:
                    if page_item is not None:
                        try:
                            draw.text((PADDING_X, current_y), page_item, font=font, fill=TEXT_COLOR)
                            current_y += line_height + LINE_SPACING
                        except Exception as e:
                            print(f"Text drawing error: {e}")
                            current_y += line_height + LINE_SPACING
                    else:
                        current_y += PARAGRAPH_SPACING
                
                # Optimize image for web delivery
                img = img.convert('RGB')
                generated_images.append(img)
                return img
            except Exception as e:
                print(f"Image creation error: {e}")
                # Return a simple error image
                error_img = Image.new('RGB', (IMG_WIDTH, IMG_HEIGHT), color=(255, 255, 255))
                error_draw = ImageDraw.Draw(error_img)
                try:
                    error_draw.text((PADDING_X, PADDING_Y), f"Error creating page {page_num}", font=font, fill=(255, 0, 0))
                except:
                    pass
                return error_img

        # Process content with page limit
        for item in all_lines_and_breaks:
            if img_count >= MAX_PAGES:
                break
                
            is_break = item is None
            item_height = PARAGRAPH_SPACING if is_break else line_height
            
            if y_text + item_height > IMG_HEIGHT - PADDING_Y:
                img_count += 1
                create_image_page(page_content, img_count)

                page_content = [item]
                y_text = PADDING_Y + item_height + (0 if is_break else LINE_SPACING)
            else:
                page_content.append(item)
                y_text += item_height + (0 if is_break else LINE_SPACING)
                
        if page_content and img_count < MAX_PAGES:
            img_count += 1
            create_image_page(page_content, img_count)

        # Memory cleanup
        gc.collect()
        
        return generated_images[:MAX_PAGES]  # Ensure we don't exceed limit
        
    except Exception as e:
        print(f"Critical error in text_to_images_mcp: {e}")
        print(traceback.format_exc())
        # Return a simple error image
        try:
            error_img = Image.new('RGB', (1080, 1080), color=(255, 255, 255))
            error_draw = ImageDraw.Draw(error_img)
            error_draw.text((80, 80), f"Error processing text: {str(e)[:100]}", fill=(255, 0, 0))
            return [error_img]
        except:
            return []

# Sample text for demonstration
sample_text = """In the heart of a bustling city, there lived a clockmaker named Alistair. His shop, a quaint corner of tranquility amidst the urban chaos, was filled with the gentle ticking of countless timepieces. Each clock was a masterpiece, a testament to his dedication and skill. But Alistair held a secret. One of his clocks, an old grandfather clock in the corner, did not just tell time. It told stories.

Every midnight, as the city slept, the clock would chime, not with bells, but with whispers of forgotten tales. Stories of ancient kings, lost love, and adventures in lands woven from starlight and dreams. Alistair would sit by the fire, listening, his heart filled with the magic of the past. He was the keeper of time, and in turn, time had made him its confidant.

One day, a young girl with eyes as curious as a cat's wandered into his shop. She wasn't interested in the shiny new watches but was drawn to the grandfather clock. "What's its story?" she asked, her voice soft. Alistair smiled, for he knew he had found the next guardian of the stories. The legacy of the whispering clock would live on."""

def create_app():
    """Create and configure the Gradio app with better error handling."""
    
    # Create the demo with enhanced error handling
    demo = gr.Interface(
        fn=text_to_images_mcp,
        inputs=[
            gr.Textbox(
                value=sample_text,
                lines=10,
                max_lines=20,
                label="Text Content",
                placeholder="Enter your long-form text here (max 10,000 characters)..."
            ),
            gr.Dropdown(
                choices=['plain', 'lines', 'dots', 'grid'],
                value='lines',
                label="Background Style",
                info="Choose the background style for your images"
            ),
            gr.Textbox(
                value="",
                label="Custom Font Path (Optional)",
                placeholder="/path/to/your/font.ttf",
                info="Leave empty to use system default font"
            )
        ],
        outputs=[
            gr.Gallery(
                label="Generated Images (Max 10 pages)",
                show_label=True,
                elem_id="gallery",
                columns=2,
                rows=2,
                object_fit="contain",
                height="auto",
                show_download_button=True
            )
        ],
        title="πŸ“– Text to Images Converter (MCP Compatible)",
        description="Transform long-form text into a series of attractive, readable images. Simply paste your text, choose a background style, and preview the generated images. You can download individual images by clicking on them. Limited to 10,000 characters and 10 pages for optimal performance. This app functions as an MCP server for LLM integration.",
        examples=[
            [sample_text, 'lines', ''],
            [sample_text, 'dots', ''],
            [sample_text, 'grid', ''],
            [sample_text, 'plain', '']
        ],
        cache_examples=False,
        show_api=True
    )
    
    return demo

if __name__ == "__main__":
    # Set up environment for MCP server
    os.environ["GRADIO_MCP_SERVER"] = "True"
    
    # Create the app
    demo = create_app()
    
    # Launch with multiple fallback strategies
    launch_configs = [
        # Strategy 1: Full MCP server with explicit settings
        {
            "mcp_server": True,
            "server_name": "0.0.0.0",
            "server_port": 7860,
            "share": False,
            "debug": False,
            "show_error": False,
            "quiet": True
        },
        # Strategy 2: Environment variable only
        {
            "server_name": "0.0.0.0",
            "server_port": 7860,
            "share": False,
            "debug": False,
            "show_error": False,
            "quiet": True
        },
        # Strategy 3: Minimal configuration
        {
            "server_name": "0.0.0.0",
            "server_port": 7860,
            "share": False
        },
        # Strategy 4: Default configuration
        {}
    ]
    
    launched = False
    for i, config in enumerate(launch_configs):
        try:
            print(f"Attempting launch strategy {i+1}...")
            demo.launch(**config)
            launched = True
            print(f"Successfully launched with strategy {i+1}")
            break
        except Exception as e:
            print(f"Launch strategy {i+1} failed: {e}")
            if i < len(launch_configs) - 1:
                print("Trying next strategy...")
            continue
    
    if not launched:
        print("All launch strategies failed. Please check your environment and dependencies.")
        print("You may need to install gradio[mcp] or update your gradio version.")