Added image support

This commit is contained in:
Ignacio Rivero 2025-06-22 18:17:28 -03:00
parent f1ef93798e
commit 5367624808

82
app.py
View File

@ -1,6 +1,7 @@
import io import io
import tempfile import tempfile
import subprocess import subprocess
import requests
from flask import Flask, render_template_string, request, send_from_directory from flask import Flask, render_template_string, request, send_from_directory
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
import re import re
@ -286,27 +287,48 @@ HTML_FORM = '''
<li><b>Encabezado chico:</b> <code>### Título</code></li> <li><b>Encabezado chico:</b> <code>### Título</code></li>
<li><b>Lista con viñetas:</b> <code>- Elemento</code></li> <li><b>Lista con viñetas:</b> <code>- Elemento</code></li>
<li><b>Lista numerada:</b> <code>1. Elemento</code></li> <li><b>Lista numerada:</b> <code>1. Elemento</code></li>
<li><b>Imágen:</b> <code>![Texto alternativo](Enlace a imágen)</code></li>
<li><b>Salto de línea:</b> Deje una línea vacía</li> <li><b>Salto de línea:</b> Deje una línea vacía</li>
</ul> </ul>
</div> </div>
</div> </div>
''' '''
def bleh_image_from_url(url):
resp = requests.get(url, stream=True)
resp.raise_for_status()
bleh = subprocess.Popen(
["./bleh", "-o", "-", "-mode", "1bpp", "-d", "floyd", "-"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = bleh.communicate(resp.content)
if bleh.returncode != 0:
raise RuntimeError(f"Driver failed: {err.decode()}")
img = Image.open(io.BytesIO(out)).convert("L")
# Optionally check width, pad/resize if needed
if img.width != IMAGE_WIDTH:
img = img.resize((IMAGE_WIDTH, img.height), Image.LANCZOS)
return img
def parse_line(line): def parse_line(line):
if re.match(r'^\s*---\s*$', line): image_match = re.match(r"^!\[(.*?)\]\((.+?)\)", line)
return ('hr', []) if image_match:
header_match = re.match(r"^(#{1,3}) +(.*)", line) alt = image_match.group(1)
if header_match: url = image_match.group(2)
header_level = len(header_match.group(1)) return ('image', url, alt)
line = header_match.group(2) if re.match(r'^\s*---\s*$', line):
return ('header', header_level, parse_segments(line)) return ('hr', [])
bullet_match = re.match(r"^\s*([-*\u2022]) +(.*)", line) header_match = re.match(r"^(#{1,3}) +(.*)", line)
if bullet_match: if header_match:
return ('bullet', parse_segments(bullet_match.group(2))) header_level = len(header_match.group(1))
ordered_match = re.match(r"^\s*(\d+)\. +(.*)", line) line = header_match.group(2)
if ordered_match: return ('header', header_level, parse_segments(line))
return ('ordered', int(ordered_match.group(1)), parse_segments(ordered_match.group(2))) bullet_match = re.match(r"^\s*([-*\u2022]) +(.*)", line)
return ('text', parse_segments(line)) if bullet_match:
return ('bullet', parse_segments(bullet_match.group(2)))
ordered_match = re.match(r"^\s*(\d+)\. +(.*)", line)
if ordered_match:
return ('ordered', int(ordered_match.group(1)), parse_segments(ordered_match.group(2)))
return ('text', parse_segments(line))
def parse_segments(line): def parse_segments(line):
# Handle escaped asterisks: replace them with a placeholder # Handle escaped asterisks: replace them with a placeholder
@ -401,7 +423,13 @@ def render(md):
lines_out.append(('blank', [])) lines_out.append(('blank', []))
continue continue
tag = parse_line(src_line) tag = parse_line(src_line)
if tag[0] == 'hr': if tag[0] == 'image':
try:
image = bleh_image_from_url(tag[1])
lines_out.append(('image', image))
except Exception as e:
lines_out.append(('text', [('text', f"[Imagen inválida: {e}]")], font, FONT_SIZE))
elif tag[0] == 'hr':
lines_out.append(('hr',)) lines_out.append(('hr',))
elif tag[0] == 'header': elif tag[0] == 'header':
header_level = tag[1] header_level = tag[1]
@ -443,12 +471,18 @@ def render(md):
for wrapped in wrap_segments(segments, font, font_bold, font_italic, font_bolditalic, IMAGE_WIDTH): for wrapped in wrap_segments(segments, font, font_bold, font_italic, font_bolditalic, IMAGE_WIDTH):
lines_out.append(('text', wrapped, font, FONT_SIZE)) lines_out.append(('text', wrapped, font, FONT_SIZE))
height = sum( # Compute total height, including images
item[3] if item[0] in ('header', 'text', 'bullet', 'ordered') else height = 10 # Top margin
10 if item[0] == 'hr' else for item in lines_out:
FONT_SIZE if item[0] in ('header', 'text', 'bullet', 'ordered'):
for item in lines_out height += item[3]
) + 10 elif item[0] == 'hr':
height += 10
elif item[0] == 'blank':
height += FONT_SIZE
elif item[0] == 'image':
img = item[1]
height += img.height + 10 # add margin below image
image = Image.new("L", (IMAGE_WIDTH, height), 255) image = Image.new("L", (IMAGE_WIDTH, height), 255)
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
@ -500,6 +534,12 @@ def render(md):
draw.text((x, y), text, font=f, fill=0) draw.text((x, y), text, font=f, fill=0)
x += f.getbbox(text)[2] - f.getbbox(text)[0] x += f.getbbox(text)[2] - f.getbbox(text)[0]
y += sz y += sz
elif item[0] == 'image':
img = item[1]
# Center image horizontally if narrower
img_x = (IMAGE_WIDTH - img.width) // 2 if img.width < IMAGE_WIDTH else 0
image.paste(img, (img_x, y))
y += img.height + 10 # vertical margin after image
return image return image
@app.route("/", methods=["GET", "POST"]) @app.route("/", methods=["GET", "POST"])