1
0
Fork 0

Compare commits

...

7 Commits

Author SHA1 Message Date
vibe ec9ddf153c Add dependency info to README.md 2023-11-13 02:04:08 +01:00
vibe 27fa0f326f Update footer credits in template 2023-11-12 22:56:59 +01:00
vibe 7f2cfd5b31 Change file open mode to 'wb'
FFI used in the project uses bytes instead of str for writing purposes.
2023-11-12 22:56:21 +01:00
vibe 92a1286055 Fix healthcheck in python3
Subprocess returns stdout as bytes instead of str nowadays, so I
addressed it in a quick and dirty way.

The extra return in healthcheck function is added there to stop the app
from complaining about it and also because I discovered that the
solution for tracking printer status here is essentially hardcoded for
English, while my local test environment outputs lpstat logs
in Ukrainian.
2023-11-12 21:44:59 +01:00
vibe db76e8a64f WIP: Upgrade to Python3, change dependencies
Due to old pangocairo bindings seemingly not being updated anymore
I resorted to using cffi-based bindings for now.
Current problems with pango and pangocairo bindings is that they are
incomplete, and it seems like outside HTML parsing there are some broken
things about font rendering, which I need to deal with. I might look
into rebuilding this functionality using something else, maybe Pillow.

Health check was disabled for now so I can focus on image rendering.
2023-11-07 03:14:38 +01:00
vibe b366bb265b Initialize poetry in the project 2023-11-07 01:47:57 +01:00
vibe f8a294058f Rewrite README.md to be more descriptive 2023-11-06 02:01:44 +01:00
4 changed files with 64 additions and 27 deletions

View File

@ -1 +1,17 @@
Use the source, luke :^) # Hackerspace Label Printing Interface
Web interface for accessing label printers on the premises of Warsaw Hackerspace. Currently only supports DYMO LabelWriter 450.
## Dependencies
This project relies on external dependencies:
- `cairo`, `pango`, `pangocairo` for graphics manipulation
- `CUPS` for interfacing with the printer over local network
## Building and debugging
```
poetry install
poetry run python render.py
```

18
pyproject.toml Normal file
View File

@ -0,0 +1,18 @@
[tool.poetry]
name = "labelmaker"
version = "0.1.0"
description = "Web interface for accessing label printers available at hswaw"
authors = ["Your Name <you@example.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
flask = "^3.0.0"
cairocffi = "^1.6.1"
pangocffi = "^0.12.0"
pangocairocffi = "^0.7.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@ -1,15 +1,15 @@
import json
import math import math
import os import os
import StringIO import io
import subprocess import subprocess
import tempfile
import time import time
import cairo import cairocffi as cairo
import flask import flask
import pango from flask import json
import pangocairo import pangocffi as pango
import pangocairocffi as pangocairo
class App(flask.Flask): class App(flask.Flask):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -50,26 +50,26 @@ class Renderer(object):
if y != -1: if y != -1:
self.context.translate(x, y) self.context.translate(x, y)
pangocairo_context = pangocairo.CairoContext(self.context) self.context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
pangocairo_context.set_antialias(cairo.ANTIALIAS_SUBPIXEL) layout = pangocairo.create_layout(self.context)
layout = pangocairo_context.create_layout() layout.width = pango.units_from_double(self.width)
layout.set_width(self.width*pango.SCALE) layout.alignment = pango.Alignment.CENTER
layout.set_alignment(pango.ALIGN_CENTER)
if html: if html:
# Absolutely horrifying hack to fix broken text wrapping # Absolutely horrifying hack to fix broken text wrapping
layout.set_markup('<span font_desc="%s">%s</span>' % (fontname, text)) layout.apply_markup('<span font_desc="%s">%s</span>' % (fontname, text))
else: else:
font = pango.FontDescription(fontname) font = pango.FontDescription()
layout.set_font_description(font) font.family = fontname
layout.set_text(text) layout.font_description = font
layout.text = text
if y == -1: if y == -1:
self.context.translate(0, (self.height - layout.get_size()[1]/pango.SCALE)/2) self.context.translate(0, (self.height - pango.units_to_double(layout.get_size()[1]))/2)
self.context.set_source_rgb(0, 0, 0) self.context.set_source_rgb(0, 0, 0)
pangocairo_context.update_layout(layout) pangocairo.update_layout(self.context, layout)
pangocairo_context.show_layout(layout) pangocairo.show_layout(self.context, layout)
self.context.restore() self.context.restore()
@ -81,10 +81,10 @@ def healthcheck():
last_checked, last_status, last_details = app.health last_checked, last_status, last_details = app.health
if time.time() - last_checked < 1: if time.time() - last_checked < 1:
return last_status, last_details return last_status, last_details
output = subprocess.check_output(['lpstat', '-p', '-d']) output = subprocess.run(['lpstat', '-p', '-d'], capture_output=True).stdout
mark = False mark = False
for line in output.split('\n'): for line in output.split(b'\n'):
line = line.strip() line = line.strip().decode('utf-8')
if line.startswith('printer DYMO_LabelWriter_450'): if line.startswith('printer DYMO_LabelWriter_450'):
if 'is idle.' in line: if 'is idle.' in line:
return True, 'Idle' return True, 'Idle'
@ -98,6 +98,8 @@ def healthcheck():
app.health = (time.time(), False, line) app.health = (time.time(), False, line)
return False, line return False, line
mark = False mark = False
return False, 'Printer is down or there\'s something wrong with lpstat output.'
@app.route('/health') @app.route('/health')
def health(): def health():
@ -111,7 +113,7 @@ def stuff_preview(size):
r = Renderer() r = Renderer()
r.render_text(text, 'Sans {}'.format(size), 0, -1, html) r.render_text(text, 'Sans {}'.format(size), 0, -1, html)
sio = StringIO.StringIO() sio = io.BytesIO()
r.surface.write_to_png(sio) r.surface.write_to_png(sio)
sio.seek(0) sio.seek(0)
return flask.send_file(sio, mimetype='image/png') return flask.send_file(sio, mimetype='image/png')
@ -122,7 +124,7 @@ def stuff_print(size):
if not healthcheck()[0]: if not healthcheck()[0]:
return 'Printer is down.' return 'Printer is down.'
last = app.last last = app.last
print last, time.time() - last print((last, time.time() - last))
if time.time() - last < DELAY: if time.time() - last < DELAY:
return 'Please wait {} more seconds before next print.'.format(int(DELAY - (time.time() - last))) return 'Please wait {} more seconds before next print.'.format(int(DELAY - (time.time() - last)))
text = flask.request.args.get('text') text = flask.request.args.get('text')
@ -130,7 +132,7 @@ def stuff_print(size):
r = Renderer() r = Renderer()
r.render_text(text, 'Sans {}'.format(size), 0, -1, html) r.render_text(text, 'Sans {}'.format(size), 0, -1, html)
path = '/tmp/hslabel' path = '/tmp/hslabel'
f = open(path, 'w') f = open(path, 'wb')
r.surface.write_to_png(f) r.surface.write_to_png(f)
f.flush() f.flush()

View File

@ -90,8 +90,9 @@ $ curl -d "" http://label.waw.hackerspace.pl/stuff/print/60/?text=foobar&amp;htm
</div> </div>
<footer class="footer"> <footer class="footer">
<div class="container"> <div class="container">
<p class="text-muted" style="margin: 20px 0;">Cobbled together by <a href="https://q3k.org/">q3k</a>. Sauce <p class="text-muted" style="margin: 20px 0;">Cobbled together by <a href="https://q3k.org/">q3k</a>,
on <a href="https://code.hackerspace.pl/q3k/labelmaker">code.hackerspace.pl/q3k/labelmaker</a></p> updated to Python 3 by vibe. Sauce
on <a href="https://code.hackerspace.pl/hswaw/labelmaker">code.hackerspace.pl/hswaw/labelmaker</a></p>
</div> </div>
</footer> </footer>
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script> <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>