Added Banner Mode, updated requirements.txt (oops)
This commit is contained in:
parent
638228a88d
commit
84c0be9742
44
app.py
44
app.py
@ -20,6 +20,7 @@ FONT_SIZE = 24
|
|||||||
HEADER_SIZE_1 = 56
|
HEADER_SIZE_1 = 56
|
||||||
HEADER_SIZE_2 = 34
|
HEADER_SIZE_2 = 34
|
||||||
HEADER_SIZE_3 = 28
|
HEADER_SIZE_3 = 28
|
||||||
|
BANNER_FONT_SIZE = 300
|
||||||
IMAGE_WIDTH = 384
|
IMAGE_WIDTH = 384
|
||||||
BULLET_CHAR = "• "
|
BULLET_CHAR = "• "
|
||||||
|
|
||||||
@ -390,6 +391,12 @@ HTML_FORM = '''
|
|||||||
<label for="userimg" style="display: inline-block; margin-bottom: 0.5em;">Subir imagen (opcional):</label><br>
|
<label for="userimg" style="display: inline-block; margin-bottom: 0.5em;">Subir imagen (opcional):</label><br>
|
||||||
<input type="file" name="userimg" accept="image/png, image/jpeg"><br>
|
<input type="file" name="userimg" accept="image/png, image/jpeg"><br>
|
||||||
<div class="options">
|
<div class="options">
|
||||||
|
<div class="options-bannermode">
|
||||||
|
<label for="bannermode">
|
||||||
|
<input type="checkbox" name="bannermode" value="1" {% if current_bannermode %}checked{% endif %}>
|
||||||
|
Modo Banner (vertical)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="options-dithering">
|
<div class="options-dithering">
|
||||||
<label for="dithering">Dithering:</label>
|
<label for="dithering">Dithering:</label>
|
||||||
<select name="dithering">
|
<select name="dithering">
|
||||||
@ -619,10 +626,11 @@ def wrap_segments(segments, font, font_bold, font_italic, font_bolditalic, max_w
|
|||||||
if line:
|
if line:
|
||||||
yield line
|
yield line
|
||||||
|
|
||||||
def render(md, dithering, printmode, uploaded_img_bytes=None):
|
def render(md, dithering, printmode, uploaded_img_bytes=None, bannermode=False):
|
||||||
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
|
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
|
||||||
font_bold = ImageFont.truetype(FONT_PATH_BOLD, FONT_SIZE)
|
font_bold = ImageFont.truetype(FONT_PATH_BOLD, FONT_SIZE)
|
||||||
font_italic = ImageFont.truetype(FONT_PATH_OBLIQUE, FONT_SIZE)
|
font_italic = ImageFont.truetype(FONT_PATH_OBLIQUE, FONT_SIZE)
|
||||||
|
font_banner = ImageFont.truetype(FONT_PATH_BOLD, BANNER_FONT_SIZE)
|
||||||
try:
|
try:
|
||||||
font_bolditalic = ImageFont.truetype(FONT_PATH_BOLDITALIC, FONT_SIZE)
|
font_bolditalic = ImageFont.truetype(FONT_PATH_BOLDITALIC, FONT_SIZE)
|
||||||
except:
|
except:
|
||||||
@ -630,7 +638,33 @@ def render(md, dithering, printmode, uploaded_img_bytes=None):
|
|||||||
font_h1 = ImageFont.truetype(FONT_PATH_BOLD, HEADER_SIZE_1)
|
font_h1 = ImageFont.truetype(FONT_PATH_BOLD, HEADER_SIZE_1)
|
||||||
font_h2 = ImageFont.truetype(FONT_PATH_BOLD, HEADER_SIZE_2)
|
font_h2 = ImageFont.truetype(FONT_PATH_BOLD, HEADER_SIZE_2)
|
||||||
font_h3 = ImageFont.truetype(FONT_PATH_BOLD, HEADER_SIZE_3)
|
font_h3 = ImageFont.truetype(FONT_PATH_BOLD, HEADER_SIZE_3)
|
||||||
|
|
||||||
|
if bannermode:
|
||||||
|
# Remove line breaks for single-line banner
|
||||||
|
md = md.replace('\r\n', ' ') # should change this if we're ever on windows
|
||||||
|
# Only render as a single text line (ignore markdown except bold/italic)
|
||||||
|
segments = parse_segments(md)
|
||||||
|
# Calculate total width needed
|
||||||
|
x = 0
|
||||||
|
for style, text in segments:
|
||||||
|
f = font_banner
|
||||||
|
x += f.getbbox(text)[2] - f.getbbox(text)[0]
|
||||||
|
width = max(x, 1)
|
||||||
|
# Create image: width x 384 (height is printer width)
|
||||||
|
image = Image.new("L", (width, IMAGE_WIDTH), 255)
|
||||||
|
draw = ImageDraw.Draw(image)
|
||||||
|
x = 0
|
||||||
|
y = (IMAGE_WIDTH - BANNER_FONT_SIZE) // 2 # Center vertically
|
||||||
|
for style, text in segments:
|
||||||
|
f = font_banner
|
||||||
|
draw.text((x, y), text, font=f, fill=0)
|
||||||
|
x += f.getbbox(text)[2] - f.getbbox(text)[0]
|
||||||
|
# Rotate so text is vertical
|
||||||
|
image = image.rotate(270, expand=True)
|
||||||
|
return image
|
||||||
|
|
||||||
lines_out = []
|
lines_out = []
|
||||||
|
|
||||||
for src_line in md.splitlines():
|
for src_line in md.splitlines():
|
||||||
if src_line.strip() == '':
|
if src_line.strip() == '':
|
||||||
lines_out.append(('blank', []))
|
lines_out.append(('blank', []))
|
||||||
@ -763,6 +797,8 @@ def render(md, dithering, printmode, uploaded_img_bytes=None):
|
|||||||
img_x = (IMAGE_WIDTH - img.width) // 2 if img.width < IMAGE_WIDTH else 0
|
img_x = (IMAGE_WIDTH - img.width) // 2 if img.width < IMAGE_WIDTH else 0
|
||||||
image.paste(img, (img_x, y))
|
image.paste(img, (img_x, y))
|
||||||
y += img.height + 10 # vertical margin after image
|
y += img.height + 10 # vertical margin after image
|
||||||
|
if bannermode:
|
||||||
|
image = image.rotate(270, expand=True)
|
||||||
return image
|
return image
|
||||||
|
|
||||||
@app.route("/", methods=["GET", "POST"])
|
@app.route("/", methods=["GET", "POST"])
|
||||||
@ -804,10 +840,12 @@ def index():
|
|||||||
md = request.form["md"]
|
md = request.form["md"]
|
||||||
dithering = request.form.get("dithering", "floyd")
|
dithering = request.form.get("dithering", "floyd")
|
||||||
printmode = request.form.get("printmode", "1bpp")
|
printmode = request.form.get("printmode", "1bpp")
|
||||||
image = render(md, dithering, printmode, uploaded_img_bytes)
|
bannermode = bool(request.form.get("bannermode"))
|
||||||
|
image = render(md, dithering, printmode, uploaded_img_bytes, bannermode=bannermode)
|
||||||
session['dithering'] = dithering
|
session['dithering'] = dithering
|
||||||
session['printmode'] = printmode
|
session['printmode'] = printmode
|
||||||
session['rotation'] = rotation
|
session['rotation'] = rotation
|
||||||
|
session['bannermode'] = bannermode
|
||||||
buf = io.BytesIO()
|
buf = io.BytesIO()
|
||||||
image.save(buf, format="PNG")
|
image.save(buf, format="PNG")
|
||||||
buf.seek(0)
|
buf.seek(0)
|
||||||
@ -841,7 +879,7 @@ def index():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = f"Failed to print: {e}"
|
error = f"Failed to print: {e}"
|
||||||
return render_template_string(HTML_FORM, dithering_modes=DITHERING_MODES, img=img_data, default_md=md,
|
return render_template_string(HTML_FORM, dithering_modes=DITHERING_MODES, img=img_data, default_md=md,
|
||||||
printed=printed, error=error, current_dithering=session.get('dithering', 'floyd'), current_rotation=session.get('rotation', 0), current_printmode=session.get('printmode', '1bpp'))
|
printed=printed, error=error, current_dithering=session.get('dithering', 'floyd'), current_rotation=session.get('rotation', 0), current_printmode=session.get('printmode', '1bpp'), current_bannermode=session.get('bannermode', False))
|
||||||
|
|
||||||
@app.route('/manifest.json')
|
@app.route('/manifest.json')
|
||||||
def manifest():
|
def manifest():
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
Flask
|
Flask
|
||||||
markdown
|
markdown
|
||||||
Pillow
|
Pillow
|
||||||
|
requests
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user