Browse Source

adding word-friendly linebreaks to the lineprinter output + fixing the infinite print loop

master
manetta 3 years ago
parent
commit
5cd98e3e06
  1. 104
      print/start.py
  2. 7
      print/static/stylesheet.css
  3. 25
      print/templates/printed.html
  4. 2
      print/tools/asciiWriter/__init__.py
  5. 6
      print/tools/asciiWriter/asciiWriter.py
  6. 49
      print/tools/asciiWriter/draw.py
  7. 24
      print/tools/asciiWriter/marks.py
  8. 67
      print/tools/asciiWriter/patterns.py
  9. 55
      print/tools/asciiWriter/text.py
  10. 61
      print/tools/asciiWriter/utils.py
  11. 137
      print/tools/asciiWriter/wrap_single_line.py
  12. 95
      print/tools/televex/televex.py
  13. 1
      requirements.txt

104
print/start.py

@ -1,31 +1,21 @@
# https://github.com/python-escpos/python-escpos
# https://python-escpos.readthedocs.io/en/latest/
import sys
import os
import re
import flask
from flask import request
import flask_apscheduler
import urllib
import json
from datetime import date, datetime
from escpos.escpos import Escpos
import urllib, json
from escpos import printer
from tools.televex import televex
# Create the application.
APP = flask.Flask(__name__)
# Connect to the printer
# Get the printer's USB initializing code with $ sudo lsbusb
lp = printer.Usb(0x4b8,0xe15)
buffer = printer.Dummy()
lp, lp_printer = televex.connect()
# Define the JSON databased for storing printed items
db = 'printed.json'
if not os.path.exists(db):
with open(db, 'w') as f:
f.write(json.dumps({ "printed" : [] }, indent=4))
# Set up a JSON database for storing printed items
db = televex.database('printed.json')
# Check the Multifeeder regulary (every 10 min)
# https://github.com/viniciuschiele/flask-apscheduler
@ -35,32 +25,8 @@ scheduler.api_enabled = False
scheduler.init_app(APP)
scheduler.start()
def update_db(item):
try:
items = json.loads(open(db).read())
except:
# Hmm ...
items = { "printed" : [] }
if item not in items['printed']:
new_item = True
with open(db, 'w') as out:
items['printed'].append(item)
out.write(json.dumps(items, indent=4))
out.close()
else:
new_item = False
return new_item
def html2plain(html):
plaintext = re.sub('<[^<]+?>', '', html)
return plaintext
@scheduler.task('interval', id='check', minutes=10)
@scheduler.task('interval', id='check', minutes=1)
def check():
try:
url = 'https://multi.vvvvvvaria.org/API/latest/10'
response = urllib.request.urlopen(url).read()
@ -69,59 +35,23 @@ def check():
feed = []
for post in feed:
# add post to database
year = post['published_parsed'][0]
month = post['published_parsed'][1]
day = post['published_parsed'][2]
post_date = date(year, month, day)
post_date_str = post_date.strftime('%Y-%m-%d')
plaintext = html2plain(post['summary'])
item = {
'source' : post['feed_details']['rss'],
'date' : post_date_str,
'printed' : plaintext
}
new_item = update_db(item)
# add RSS post to printing buffer
if new_item == True:
buffer.text(plaintext)
buffer.cut()
break
# print
lp._raw(buffer.output)
source = post['feed_details']['rss']
txt = post['summary']
televex.print_now(txt, source, db, post=post, lp=lp, lp_printer=lp_printer)
@APP.route('/print/', methods=['GET', 'POST'])
def print():
txt = request.args.get('printing', '')
plaintext = request.args.get('printing', '')
if plaintext:
# add plaintext to printing buffer
buffer.text(plaintext)
buffer.cut()
# add plaintext to database
date = datetime.now()
date_str = date.strftime('%Y-%m-%d_%H-%M-%S')
item = {
'source' : 'TeleVex',
'date' : date_str,
'printed' : plaintext
}
update_db(item)
# print
lp._raw(buffer.output)
if txt:
source = 'TeleVex'
televex.print_now(txt, source, db, lp=lp, lp_printer=lp_printer)
return flask.redirect(flask.url_for('print'))
return flask.render_template('index.html')
@APP.route('/printed/', methods=['GET', 'POST'])
@APP.route('/printed/', methods=['GET'])
def printed():
items = json.loads(open(db).read())
return flask.render_template('printed.html', items=items)

7
print/static/stylesheet.css

@ -13,4 +13,11 @@ div#multifeeder div.multipost{
}
div#webcam img{
width:500px;
}
pre{
margin: 1em 0;
padding: 0;
font-size: 14px;
word-break: break-all;
white-space: pre-wrap;
}

25
print/templates/printed.html

@ -8,23 +8,24 @@
<body>
<div id="main">
<h1><a href="/print/">print</a>, printed</h1>
<h1><a href="/print/">print</a>, printed</h1>
<div id="multifeeder">
<h2>TeleVex recently printed:</h2>
<div>(last 25)</div>
<br>
<h2>TeleVex recently printed:</h2>
<pre>
---
(last 25)
(this is a de-bugging tool that most likely will disappear again)
---</pre>
{% set count = 25 %}
{% for x in range(1, 25) %}
{% set item = items["printed"][-x] %}
{% if item %}
<div class="msg">
<strong>Source</strong>: {{ item.source }}<br>
<strong>Date</strong>: {{ item.date }}<br>
<strong>Printed</strong>: {{ item.printed }}<br>
<br>
</div>
<div class="msg">
<strong>Source</strong>: {{ item.source }}<br>
<strong>Date</strong>: {{ item.date }}<br>
<strong>Printed</strong>:
<pre>{{ item.printed | replace('\n', '<br>') | safe }}</pre>
</div>
{% endif %}
{% set count = count - 1 %}
{% endfor %}

2
print/tools/asciiWriter/__init__.py

@ -0,0 +1,2 @@
#!/usr/bin/env python3

6
print/tools/asciiWriter/asciiWriter.py

@ -0,0 +1,6 @@
#!/usr/bin/env python3
import utils
import patterns
import marks
import text

49
print/tools/asciiWriter/draw.py

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
from utils import make_lines, merge, print_lines, rotate, visit, visit_horizontal
from patterns import diagonal, horizontal, vertical, sinus_horizontal, sinus_vertical, image
from marks import random_mark, sequence_mark, space
from random import choice, random
import math
# marks = ['┼', '│', '░', '▓', 'X', '■', '≡', '·', '¦', ' ']
# blank = ' '
width = 75
height = 50
mark = sequence_mark('O.P.E.N D.E.S.I.G.N C.O.U.R.S.E ')
layers = []
# layers.append(visit(make_lines(width, height), image('blobs-small.png'), mark, space(' ')))
for offset in range(-50, 50, 15):
lines = [[] for l in range(height)]
sinus = sinus_vertical(period=50, amplitude=25, offset=offset, offset_t=random())
layers.append(visit(make_lines(width, height), sinus, sequence_mark(' K A S K G E N T '), space()))
for offset in range(-43, 57, 15):
lines = [[] for l in range(height)]
sinus = sinus_vertical(period=40, amplitude=10, offset=offset, offset_t=.5+random())
layers.append(visit(make_lines(width, height), sinus, mark, space()))
print_lines(merge(width, height, space()(), layers))
# for line in overlay(50, 50, ' ', [rotate(merged), merged]):
# stdout.write('{}\n'.format(''.join(line)))
# sinus = sinus_horizontal(period=30, amplitude=8)
# for x in range(width):
# for y in range(height):
# lines[y].append(sinus(x, y, width, height, mark, space()))
# for line in lines:
# stdout.write('{}\n'.format(''.join(line)))
# lines = [[draw(x, y, marks) for x in range(width)] for y in range(height)]
# sys.sdout.write('\n'.join([''.join(line) for line in lines]))
# sys.sdout.write('\n'.join([''.join([draw(x, y, marks) for x in range(width)]) for y in range(height)]))

24
print/tools/asciiWriter/marks.py

@ -0,0 +1,24 @@
from random import choice
def random (marks=['']):
def func ():
return choice(marks)
return func
def sentence (text):
chars = list(text)
def f():
char = chars.pop(0)
chars.append(char)
return char
return f
def single (char):
def f():
return char
return f
def space (space=' '):
return single(space)

67
print/tools/asciiWriter/patterns.py

@ -0,0 +1,67 @@
import math
# # Linear
def diagonal():
def f (x, y, width, height, mark, blank):
if x == math.floor((y / float(height)) * width):
return mark()
else:
return blank()
return f
# Cross
def cross ():
def f (x, y, width, height, mark, blank):
pos = math.floor((y / float(height)) * width)
if x == pos or (width - 1) - pos == x:
return mark()
else:
return blank()
return f
def horizontal (position):
def f (x, y, width, height, mark, blank):
return mark() if position == y else blank()
return f
def vertical (position):
def f (x, y, width, height, mark, blank):
return mark() if position == x else blank()
return f
# Sinus
def sinus_vertical (period=0.2, amplitude=0.5, offset_t=0, offset=0):
period = (period / (math.pi * 2))
def f (x, y, width, height, mark, blank):
middle = (width - 1) * .5
to_mark = math.floor(middle + math.sin(offset_t + y / period) * amplitude)
return mark() if (x + offset) == to_mark else blank()
return f
def sinus_horizontal (period=0.2, amplitude=0.5, offset_t=0, offset=0):
period = (period / (math.pi * 2))
def f (x, y, width, height, mark, blank):
middle = (height - 1) * .5
to_mark = math.floor(middle + math.sin(offset_t + x / period) * amplitude)
return mark() if y + offset == to_mark else blank()
return f
def image (path, threshold=128):
from PIL import Image
im = Image.open(path).convert('L')
image_width, image_height = im.size
pixels = im.load()
def f (x, y, width, height, mark, blank):
if x < image_width and y < image_height:
if pixels[x, y] < threshold:
return mark()
return blank()
return f

55
print/tools/asciiWriter/text.py

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
from .wrap_single_line import wrap_single_line
from .utils import translate, merge
def make_column(text, line_width=50, height=200, use_hyphenator=None, line_offset=0):
lines = []
remaining = text
while remaining and len(lines) < height:
if callable(line_width):
width = line_width(len(lines), height)
else:
width = line_width
if callable(line_offset):
offset = line_offset(len(lines), height)
else:
offset = line_offset
line, remaining = wrap_single_line(remaining, width, use_hyphenator=use_hyphenator, replace_whitespace=False, drop_whitespace=True)
line = list(line)
if offset != 0:
line = [None for _ in range(offset)] + line
lines.append(line)
return lines, remaining
def make_multi_column(text, height=200, column_width=40, column_count=2, column_gap=5, use_hyphenator=None, space_char=None):
# todo: vertical offset?
remaining = text
i = 0
columns = []
while remaining and i < column_count:
column, remaining = make_column(remaining, line_width=column_width, height=height, use_hyphenator=use_hyphenator)
if i > 0:
offset = (column_width + column_gap) * i
column = translate(column, x=offset, y=0)
columns.append(column)
i += 1
width = (column_width + column_gap) * column_count
lines = merge(width, height, space_char, columns)
return lines, remaining
if __name__ == '__main__':
print(make_column('Hello world!', line_width=25, height=10))

61
print/tools/asciiWriter/utils.py

@ -0,0 +1,61 @@
from sys import stdout
def rotate(layer):
new_width = len(layer)
new_height = len(layer[0])
rotated = [['' for x in range(new_width)] for l in range(new_height)]
for y in range(len(layer)):
for x in range(len(layer[y])):
rotated[x][y] = layer[y][x]
return rotated
def merge(width, height, space_char, layers):
output = [[space_char for x in range(width)] for y in range(height)]
for layer in layers:
for y in range(min(len(layer), height)):
for x in range(min(len(layer[y]), width)):
if layer[y][x] and layer[y][x] != space_char:
output[y][x] = layer[y][x]
return output
# Make a multidimensional array
# with the given dimensions
def make_lines (width, height, fill_char = ''):
return [[ fill_char for _ in range(width) ] for __ in range(height)]
def visit (lines, callback, mark, blank):
height = len(lines)
width = len(lines[0])
for y in range(height):
for x in range(width):
lines[y][x] = callback(x, y, width, height, mark, blank)
return lines
def visit_horizontal (lines, callback, mark, blank):
height = len(lines)
width = len(lines[0])
for x in range(width):
for y in range(height):
lines[y][x] = callback(x, y, width, height, mark, blank)
return lines
def print_lines (lines):
for line in lines:
stdout.write('{}\n'.format(''.join(line)))
def translate(shape, x=0, y=0):
## TODO implement a negative translation?
translated = [[] for _ in range(y)]
for line in shape:
translated.append([None for _ in range(x)] + line)
return translated

137
print/tools/asciiWriter/wrap_single_line.py

@ -0,0 +1,137 @@
"""
Based on the textwrap2 module included in the PyHyphen library:
https://pypi.org/project/PyHyphen
"""
import textwrap
class TextWrapper(textwrap.TextWrapper):
"""
This class extends the Python 3 standard library's TextWrapper and adds an optional
use_hyphenator to its constructor arguments.
"""
def __init__(self, *args, **kwargs):
self.use_hyphenator = kwargs.pop("use_hyphenator", None)
super().__init__(*args, **kwargs)
def _wrap_chunks(self, chunks):
lines = []
if (chunks):
"""Override the mother class method.
Most of that method is directly copied from the original class, except
for the part with use_hyphenator.
"""
if self.width <= 0:
raise ValueError("invalid width %r (must be > 0)" % self.width)
if self.max_lines is not None:
if self.max_lines > 1:
indent = self.subsequent_indent
else:
indent = self.initial_indent
if len(indent) + len(self.placeholder.lstrip()) > self.width:
raise ValueError("placeholder too large for max width")
# Arrange in reverse order so items can be efficiently popped
# from a stack of chucks.
chunks.reverse()
# Start the list of chunks that will make up the current line.
# cur_len is just the length of all the chunks in cur_line.
cur_line = []
cur_len = 0
# Figure out which static string will prefix this line.
if lines:
indent = self.subsequent_indent
else:
indent = self.initial_indent
# Maximum width for this line.
width = self.width - len(indent)
# First chunk on line is whitespace -- drop it, unless this
# is the very beginning of the text (ie. no lines started yet).
if self.drop_whitespace and chunks[-1].strip() == '' and chunks[-1][0] != '\n': # and lines:
del chunks[-1]
while chunks:
l = len(chunks[-1])
# Check for a newline character
if chunks[-1] == '\n':
return indent + ''.join(cur_line), ''.join(reversed(chunks[:-1]))
elif chunks[-1][0] == '\n':
return indent + ''.join(cur_line), ''.join(reversed(chunks[:-1] + [chunks[-1][1:]]))
# Can at least squeeze this chunk onto the current line.
if cur_len + l <= width:
cur_line.append(chunks.pop())
cur_len += l
# Nope, this line is full.
# But try hyphenation.
else:
if self.use_hyphenator and (width - cur_len >= 2):
hyphenated_chunk = self.use_hyphenator.wrap(chunks[-1], width - cur_len)
if hyphenated_chunk:
cur_line.append(hyphenated_chunk[0])
chunks[-1] = hyphenated_chunk[1]
break
# The current line is full, and the next chunk is too big to
# fit on *any* line (not just this one).
if chunks and len(chunks[-1]) > width:
self._handle_long_word(chunks, cur_line, cur_len, width)
cur_len = sum(map(len, cur_line))
# If the last chunk on this line is all whitespace, drop it.
if self.drop_whitespace and cur_line and cur_line[-1].strip() == '':
cur_len -= len(cur_line[-1])
del cur_line[-1]
if cur_line:
if (self.max_lines is None or
len(lines) + 1 < self.max_lines or
(not chunks or
self.drop_whitespace and
len(chunks) == 1 and
not chunks[0].strip()) and cur_len <= width):
# Convert current line back to a string and store it in
# list of all lines (return value).
return indent + ''.join(cur_line), ''.join(reversed(chunks))
else:
while cur_line:
if (cur_line[-1].strip() and
cur_len + len(self.placeholder) <= width):
cur_line.append(self.placeholder)
return indent + ''.join(cur_line), ''.join(reversed(chunks))
cur_len -= len(cur_line[-1])
del cur_line[-1]
else:
if lines:
prev_line = lines[-1].rstrip()
if (len(prev_line) + len(self.placeholder) <=
self.width):
lines[-1] = prev_line + self.placeholder
return indent + self.placeholder.lstrip(), ''.join(reversed(chunks))
else:
return '', ''
def wrap_single_line (text, width=70, **kwargs):
w = TextWrapper(width=width, **kwargs)
return w.wrap(text)
if __name__ == '__main__':
from hyphen import Hyphenator
h_en = Hyphenator('en_US')
line, remaining = wrap_single_line('https://stackoverflow.com/questions/3940128/how-can-i-reverse-a-list-in-python', width=46, use_hyphenator=h_en)
print(line, remaining)

95
print/tools/televex/televex.py

@ -0,0 +1,95 @@
import sys, os, re, json
from datetime import datetime, date
import html2text
from escpos import printer
from tools.asciiWriter.text import make_column
from tools.asciiWriter.utils import merge, print_lines, make_lines, translate
def connect():
# Get the printer's USB initializing code with $ sudo lsbusb
try:
lp = printer.Usb(0x4b8,0xe15)
lp_printer = True
except:
lp = None
lp_printer = False
return lp, lp_printer
def database(db):
if not os.path.exists(db):
with open(db, 'w') as f:
f.write(json.dumps({ "printed" : [] }, indent=4))
return db
def html2plain(html):
# remove HTML tags
txt = re.sub(r'<.*?>', '', html)
# make line breaks
# https://git.vvvvvvaria.org/mb/ascii-art-but-with-unicode
width = 48
layers = []
lines, remaining = make_column(txt, line_width=width)
layers.append(lines)
merged = merge(width, len(lines), ' ', layers)
txt = ''
for line in merged:
txt += '{}\n'.format(''.join(line))
return txt
def update_db(txt, source, db, post=None):
try:
items = json.loads(open(db).read())
except:
items = { "printed" : [] } # Hmm ...
# save the publishing date of this post
if post:
year = post['published_parsed'][0]
month = post['published_parsed'][1]
day = post['published_parsed'][2]
post_date = date(year, month, day)
date_str = post_date.strftime('%Y-%m-%d')
post = None
else:
post_date = datetime.now()
date_str = post_date.strftime('%Y-%m-%d_%H-%M-%S')
# clean txt from HTML tags
txt = html2plain(txt)
# add txt to database
# and check if this item is new
item = {
'source' : source,
'date' : date_str,
'printed' : txt
}
if item not in items['printed']:
new_item = True
with open(db, 'w') as out:
items['printed'].append(item)
out.write(json.dumps(items, indent=4))
out.close()
else:
new_item = False
return new_item, txt
def print_now(txt, source, db, post=None, lp=None, lp_printer=None):
# check if this is a new item
new_item, txt = update_db(txt, source, db, post=post)
# if this item is new
# then print!
if new_item == True:
if lp_printer == True:
lp.text(txt)
lp.cut()
else:
# or print in the terminal!
sys.stderr.write('Printing output to the terminal:\n\n'+txt+'\n\n')

1
requirements.txt

@ -5,3 +5,4 @@ pypandoc
python-escpos
flask
Flask-APScheduler
html2text
Loading…
Cancel
Save