1
0
Fork 0
mirror of synced 2024-07-04 14:31:09 -04:00
ultimate-vim/sources_non_forked/vim-minimap/autoload/drawille/drawille.py
2017-11-27 13:43:13 +08:00

418 lines
11 KiB
Python

# -*- coding: utf-8 -*-
# drawille is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# drawille is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with drawille. If not, see < http://www.gnu.org/licenses/ >.
#
# (C) 2014- by Adam Tauber, <asciimoo@gmail.com>
import math
import os
from sys import version_info
from collections import defaultdict
from time import sleep
import curses
IS_PY3 = version_info[0] == 3
if IS_PY3:
unichr = chr
"""
http://www.alanwood.net/unicode/braille_patterns.html
dots:
,___,
|1 4|
|2 5|
|3 6|
|7 8|
`````
"""
pixel_map = ((0x01, 0x08),
(0x02, 0x10),
(0x04, 0x20),
(0x40, 0x80))
# braille unicode characters starts at 0x2800
braille_char_offset = 0x2800
# http://stackoverflow.com/questions/566746/how-to-get-console-window-width-in-python
def getTerminalSize():
"""Returns terminal width, height
"""
env = os.environ
def ioctl_GWINSZ(fd):
try:
import fcntl, termios, struct
cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
except:
return
return cr
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
if not cr:
try:
fd = os.open(os.ctermid(), os.O_RDONLY)
cr = ioctl_GWINSZ(fd)
os.close(fd)
except:
pass
if not cr:
cr = (env.get('LINES', 25), env.get('COLUMNS', 80))
return int(cr[1]), int(cr[0])
def normalize(coord):
coord_type = type(coord)
if coord_type == int:
return coord
elif coord_type == float:
return int(round(coord))
else:
raise TypeError("Unsupported coordinate type <{0}>".format(type(coord)))
def intdefaultdict():
return defaultdict(int)
def get_pos(x, y):
"""Convert x, y to cols, rows"""
return normalize(x) // 2, normalize(y) // 4
class Canvas(object):
"""This class implements the pixel surface."""
def __init__(self, line_ending=os.linesep):
super(Canvas, self).__init__()
self.clear()
self.line_ending = line_ending
def clear(self):
"""Remove all pixels from the :class:`Canvas` object."""
self.chars = defaultdict(intdefaultdict)
def set(self, x, y):
"""Set a pixel of the :class:`Canvas` object.
:param x: x coordinate of the pixel
:param y: y coordinate of the pixel
"""
x = normalize(x)
y = normalize(y)
col, row = get_pos(x, y)
if type(self.chars[row][col]) != int:
return
self.chars[row][col] |= pixel_map[y % 4][x % 2]
def unset(self, x, y):
"""Unset a pixel of the :class:`Canvas` object.
:param x: x coordinate of the pixel
:param y: y coordinate of the pixel
"""
x = normalize(x)
y = normalize(y)
col, row = get_pos(x, y)
if type(self.chars[row][col]) == int:
self.chars[row][col] &= ~pixel_map[y % 4][x % 2]
if type(self.chars[row][col]) != int or self.chars[row][col] == 0:
del(self.chars[row][col])
if not self.chars.get(row):
del(self.chars[row])
def toggle(self, x, y):
"""Toggle a pixel of the :class:`Canvas` object.
:param x: x coordinate of the pixel
:param y: y coordinate of the pixel
"""
x = normalize(x)
y = normalize(y)
col, row = get_pos(x, y)
if type(self.chars[row][col]) != int or self.chars[row][col] & pixel_map[y % 4][x % 2]:
self.unset(x, y)
else:
self.set(x, y)
def set_text(self, x, y, text):
"""Set text to the given coords.
:param x: x coordinate of the text start position
:param y: y coordinate of the text start position
"""
col, row = get_pos(x, y)
for i,c in enumerate(text):
self.chars[row][col+i] = c
def get(self, x, y):
"""Get the state of a pixel. Returns bool.
:param x: x coordinate of the pixel
:param y: y coordinate of the pixel
"""
x = normalize(x)
y = normalize(y)
dot_index = pixel_map[y % 4][x % 2]
col, row = get_pos(x, y)
char = self.chars.get(row, {}).get(col)
if not char:
return False
if type(char) != int:
return True
return bool(char & dot_index)
def rows(self, min_x=None, min_y=None, max_x=None, max_y=None):
"""Returns a list of the current :class:`Canvas` object lines.
:param min_x: (optional) minimum x coordinate of the canvas
:param min_y: (optional) minimum y coordinate of the canvas
:param max_x: (optional) maximum x coordinate of the canvas
:param max_y: (optional) maximum y coordinate of the canvas
"""
if not self.chars.keys():
return []
minrow = min_y // 4 if min_y != None else min(self.chars.keys())
maxrow = (max_y - 1) // 4 if max_y != None else max(self.chars.keys())
mincol = min_x // 2 if min_x != None else min(min(x.keys()) for x in self.chars.values())
maxcol = (max_x - 1) // 2 if max_x != None else max(max(x.keys()) for x in self.chars.values())
ret = []
for rownum in range(minrow, maxrow+1):
if not rownum in self.chars:
ret.append('')
continue
maxcol = (max_x - 1) // 2 if max_x != None else max(self.chars[rownum].keys())
row = []
for x in range(mincol, maxcol+1):
char = self.chars[rownum].get(x)
if not char:
row.append(' ')
elif type(char) != int:
row.append(char)
else:
row.append(unichr(braille_char_offset+char))
ret.append(''.join(row))
return ret
def frame(self, min_x=None, min_y=None, max_x=None, max_y=None):
"""String representation of the current :class:`Canvas` object pixels.
:param min_x: (optional) minimum x coordinate of the canvas
:param min_y: (optional) minimum y coordinate of the canvas
:param max_x: (optional) maximum x coordinate of the canvas
:param max_y: (optional) maximum y coordinate of the canvas
"""
ret = self.line_ending.join(self.rows(min_x, min_y, max_x, max_y))
if IS_PY3:
return ret
return ret.encode('utf-8')
def line(x1, y1, x2, y2):
"""Returns the coords of the line between (x1, y1), (x2, y2)
:param x1: x coordinate of the startpoint
:param y1: y coordinate of the startpoint
:param x2: x coordinate of the endpoint
:param y2: y coordinate of the endpoint
"""
x1 = normalize(x1)
y1 = normalize(y1)
x2 = normalize(x2)
y2 = normalize(y2)
xdiff = max(x1, x2) - min(x1, x2)
ydiff = max(y1, y2) - min(y1, y2)
xdir = 1 if x1 <= x2 else -1
ydir = 1 if y1 <= y2 else -1
r = max(xdiff, ydiff)
for i in range(r+1):
x = x1
y = y1
if ydiff:
y += (float(i) * ydiff) / r * ydir
if xdiff:
x += (float(i) * xdiff) / r * xdir
yield (x, y)
def polygon(center_x=0, center_y=0, sides=4, radius=4):
degree = float(360) / sides
for n in range(sides):
a = n * degree
b = (n + 1) * degree
x1 = (center_x + math.cos(math.radians(a))) * (radius + 1) / 2
y1 = (center_y + math.sin(math.radians(a))) * (radius + 1) / 2
x2 = (center_x + math.cos(math.radians(b))) * (radius + 1) / 2
y2 = (center_y + math.sin(math.radians(b))) * (radius + 1) / 2
for x, y in line(x1, y1, x2, y2):
yield x, y
class Turtle(Canvas):
"""Turtle graphics interface
http://en.wikipedia.org/wiki/Turtle_graphics
"""
def __init__(self, pos_x=0, pos_y=0):
self.pos_x = pos_x
self.pos_y = pos_y
self.rotation = 0
self.brush_on = True
super(Turtle, self).__init__()
def up(self):
"""Pull the brush up."""
self.brush_on = False
def down(self):
"""Push the brush down."""
self.brush_on = True
def forward(self, step):
"""Move the turtle forward.
:param step: Integer. Distance to move forward.
"""
x = self.pos_x + math.cos(math.radians(self.rotation)) * step
y = self.pos_y + math.sin(math.radians(self.rotation)) * step
prev_brush_state = self.brush_on
self.brush_on = True
self.move(x, y)
self.brush_on = prev_brush_state
def move(self, x, y):
"""Move the turtle to a coordinate.
:param x: x coordinate
:param y: y coordinate
"""
if self.brush_on:
for lx, ly in line(self.pos_x, self.pos_y, x, y):
self.set(lx, ly)
self.pos_x = x
self.pos_y = y
def right(self, angle):
"""Rotate the turtle (positive direction).
:param angle: Integer. Rotation angle in degrees.
"""
self.rotation += angle
def left(self, angle):
"""Rotate the turtle (negative direction).
:param angle: Integer. Rotation angle in degrees.
"""
self.rotation -= angle
def back(self, step):
"""Move the turtle backwards.
:param step: Integer. Distance to move backwards.
"""
self.forward(-step)
# aliases
pu = up
pd = down
fd = forward
mv = move
rt = right
lt = left
bk = back
def animate(canvas, fn, delay=1./24, *args, **kwargs):
"""Animation automatition function
:param canvas: :class:`Canvas` object
:param fn: Callable. Frame coord generator
:param delay: Float. Delay between frames.
:param *args, **kwargs: optional fn parameters
"""
# python2 unicode curses fix
if not IS_PY3:
import locale
locale.setlocale(locale.LC_ALL, "")
def animation(stdscr):
for frame in fn(*args, **kwargs):
for x,y in frame:
canvas.set(x,y)
f = canvas.frame()
stdscr.addstr(0, 0, '{0}\n'.format(f))
stdscr.refresh()
if delay:
sleep(delay)
canvas.clear()
curses.wrapper(animation)