forked from hswaw/labelmaker
Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
vibe | ec9ddf153c | |
vibe | 27fa0f326f | |
vibe | 7f2cfd5b31 | |
vibe | 92a1286055 | |
vibe | db76e8a64f | |
vibe | b366bb265b | |
vibe | f8a294058f |
18
README.md
18
README.md
|
@ -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
|
||||||
|
```
|
|
@ -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"
|
50
render.py
50
render.py
|
@ -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()
|
||||||
|
|
|
@ -90,8 +90,9 @@ $ curl -d "" http://label.waw.hackerspace.pl/stuff/print/60/?text=foobar&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>
|
||||||
|
|
Loading…
Reference in New Issue