manetta
4 years ago
13 changed files with 534 additions and 99 deletions
@ -0,0 +1,2 @@ |
|||
#!/usr/bin/env python3 |
|||
|
@ -0,0 +1,6 @@ |
|||
#!/usr/bin/env python3 |
|||
|
|||
import utils |
|||
import patterns |
|||
import marks |
|||
import text |
@ -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)])) |
@ -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) |
@ -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 |
@ -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)) |
@ -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 |
@ -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) |
@ -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') |
Loading…
Reference in new issue