marmots/ansi_cython.pyx

536 lines
17 KiB
Cython

# This file is part of MarMOTS.
#
# MarMOTS 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.
#
# MarMOTS 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 MarMOTS. If not,
# see <https://www.gnu.org/licenses/>.
#
# Copyright 2009, 2010, 2011, 2020 Jeremy Penner
import config
cdef extern from "stdio.h":
int sprintf(char *, char *,...)
cdef extern from "stdlib.h":
ctypedef unsigned long size_t
void free(void *ptr)
void *malloc(size_t size)
void *calloc(size_t nelem, size_t elsize)
void *realloc(void *ptr, size_t size)
size_t strlen(char *s)
char *strcpy(char *dest, char *src)
esc = '%s['%chr(27)
reset = '%s0m'%esc
cls = '%s2j'%esc
BLACK = 0
RED = 1
GREEN = 2
YELLOW = 3
BLUE = 4
MAGENTA = 5
CYAN = 6
WHITE = 7
FBRIGHT = 8
FBLINK = 16
rgcolorBg = range(8)
rgcolorFg = range(8)
rgcolorFg.extend([col | FBRIGHT for col in range(8)])
K_BACKSPACE = chr(8)
K_TAB = chr(9)
K_RETURN = chr(10)
K_NEWLINE = chr(13)
K_DEL = chr(127)
K_LEFT = 256
K_RIGHT = 257
K_UP = 258
K_DOWN = 259
K_HOME = 260
K_END = 261
K_PGUP = 262
K_PGDN = 263
def K_CTRL(ch):
return chr(ord(ch.upper()) - ord('@'))
def StrKey(ch):
if type(ch) == str:
return ord(ch)
return str(ch)
def Ctrl(ch):
ch = ch.upper()
assert ch != 'M' and ch != 'J' and ch != 'H'
return chr(ord(ch - 'A' + 1))
def FEnter(key):
return key == "\n" or key == "\r"
cdef struct SAch:
unsigned char ch
char fgCol
char bgCol
cdef union UAch:
int ach
SAch sach
cpdef MkAch(char *ch, char fgcol=WHITE, char bgcol=BLACK):
cdef UAch uach
uach.sach.ch = ch[0]
uach.sach.fgCol = fgcol
uach.sach.bgCol = bgcol
return uach.ach
# the class must be named Ach for backwards compatibility, but the hungarian name is OAch
class Ach(object):
def __init__(self, int ach):
cdef UAch uach
uach.ach = ach
self.ch = chr(uach.sach.ch)
self.fgCol = uach.sach.fgCol
self.bgCol = uach.sach.bgCol
def ToAch(self):
return MkAch(self.ch, self.fgCol, self.bgCol)
cdef AchFromOach(oach):
if oach == None:
return achBlankI
return MkAch(oach.ch, oach.fgCol, oach.bgCol)
cdef OachFromAch(int ach):
if ach == achBlankI:
return None
assert ach != achInvdI
return Ach(ach)
def ChFromAch(int ach):
cdef UAch uach
uach.ach = ach
return chr(uach.sach.ch)
achBlank = 0
achInvd = -1
cdef enum:
achBlankI = 0
achInvdI = -1
chesc = 27
FBRIGHTI = 8
FBLINKI = 16
cdef AstFromAch(int ach, int achPrev = achInvdI):
cdef UAch uach, uachPrev
uach.ach = ach
uachPrev.ach = achPrev
cdef char ast[60]
cdef char sgr[4]
cdef int csgr = 0
if (achPrev == achInvdI) or (uachPrev.sach.fgCol != uach.sach.fgCol):
fBright = uach.sach.fgCol & FBRIGHTI
if (achPrev == achInvdI) or (fBright != (uachPrev.sach.fgCol & FBRIGHTI)):
if fBright:
sgr[csgr] = 1
else:
sgr[csgr] = 0
achPrev = achInvdI
csgr = csgr + 1
sgr[csgr] = (uach.sach.fgCol & 7) + 30
csgr = csgr + 1
# fBlink = uach.sach.fgCol & FBLINKI
# if (achPrev == achInvdI) or (fBlink != uachPrev.sach.fgCol & FBLINKI):
# if fBlink:
# sgr[csgr] = 5
# else:
# sgr[csgr] = 25
# csgr = csgr + 1
if (achPrev == achInvdI) or (uachPrev.sach.bgCol != uach.sach.bgCol):
sgr[csgr] = uach.sach.bgCol + 40
csgr = csgr + 1
if csgr == 0:
sprintf(ast, "%c", uach.sach.ch)
elif csgr == 1:
sprintf(ast, "%c[%dm%c", chesc, sgr[0], uach.sach.ch)
elif csgr == 2:
sprintf(ast, "%c[%d;%dm%c", chesc, sgr[0], sgr[1], uach.sach.ch)
elif csgr == 3:
sprintf(ast, "%c[%d;%d;%dm%c", chesc, sgr[0], sgr[1], sgr[2], uach.sach.ch)
elif csgr == 4:
sprintf(ast, "%c[%d;%d;%d;%dm%c", chesc, sgr[0], sgr[1], sgr[2], sgr[3], uach.sach.ch)
else:
assert csgr >= 0 and csgr <= 4, "more sgr possibilities, please handle"
return ast
mpch_entity = [0, 9786, 9787, 9829, 9830, 9827, 9824, 8226, 9688, 9675, 9689, 9794, 9792, 9834, 9835, 9788, 9658, 9668, 8597, 8252,
182, 167, 9644, 8616, 8593, 8595, 8594, 8592, 8735, 8596, 9650, 9660, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102,
103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126,
8962, 199, 252, 233, 226, 228, 224, 229, 231, 234, 235, 232, 239, 238, 236, 196, 197, 201, 230, 198, 244, 246, 242, 251,
249, 255, 214, 220, 162, 163, 165, 8359, 402, 225, 237, 243, 250, 241, 209, 170, 186, 191, 8976, 172, 189, 188, 161, 171,
187, 9617, 9618, 9619, 9474, 9508, 9569, 9570, 9558, 9557, 9571, 9553, 9559, 9565, 9564, 9563, 9488, 9492, 9524, 9516,
9500, 9472, 9532, 9566, 9567, 9562, 9556, 9577, 9574, 9568, 9552, 9580, 9575, 9576, 9572, 9573, 9561, 9560, 9554, 9555,
9579, 9578, 9496, 9484, 9608, 9604, 9612, 9616, 9600, 945, 223, 915, 960, 931, 963, 181, 964, 934, 920, 937, 948, 8734,
966, 949, 8745, 8801, 177, 8805, 8804, 8992, 8993, 247, 8776, 176, 8729, 183, 8730, 8319, 178, 9632, 160]
setch_unsafe = set([ord('&'), ord('<'), ord('>'), ord('"'), ord("'")])
mpcol_hcol = ["#000000", "#AA0000", "#00AA00", "#AA5500", "#0000AA", "#AA00AA", "#00AAAA", "#AAAAAA",
"#555555", "#FF5555", "#55FF55", "#FFFF55", "#5555FF", "#FF55FF", "#55FFFF", "#FFFFFF"]
cdef HstFromAch(int ach, int achPrev = achInvdI):
cdef UAch uach, uachPrev
uach.ach = ach
uachPrev.ach = achPrev
stStyle = None
if achPrev == achInvdI or uachPrev.sach.fgCol != uach.sach.fgCol or uachPrev.sach.bgCol != uach.sach.bgCol:
stStyle = "color:" + mpcol_hcol[uach.sach.fgCol & 15] + ";background-color:" + mpcol_hcol[uach.sach.bgCol & 15]
rgst = []
if stStyle != None and achPrev != achInvdI:
rgst.append("</span>")
if stStyle != None:
rgst.append("<span style='" + stStyle + "'>")
if ach == achBlank:
rgst.append(' ')
else:
entity = mpch_entity[uach.sach.ch]
if entity == uach.sach.ch and not uach.sach.ch in setch_unsafe:
rgst.append(chr(uach.sach.ch))
else:
rgst.append("&#" + str(entity) + ";")
return ''.join(rgst)
class Pos(object):
def __init__(self, int w=config.W, int h=config.H, int x=1, int y=1):
self.i = PosI(w, h, x, y)
def X(self):
cdef PosI posI = self.i
return posI.X()
def Y(self):
cdef PosI posI = self.i
return posI.Y()
def Xz(self):
cdef PosI posI = self.i
return posI.xz
def Yz(self):
cdef PosI posI = self.i
return posI.yz
def Inc(self):
cdef PosI posI = self.i
posI.Inc()
def Up(self):
cdef PosI posI = self.i
return posI.Up()
def Down(self):
cdef PosI posI = self.i
return posI.Down()
def Left(self):
cdef PosI posI = self.i
return posI.Left()
def Right(self):
cdef PosI posI = self.i
return posI.Right()
def Clone(self):
cdef PosI posI = self.i
return posI.ToPos()
def Equals(self, other):
cdef PosI posIL = self.i
cdef PosI posIR = other.i
return posIL.xz == posIR.xz and posIL.yz == posIR.yz and posIL.w == posIR.w and posIL.h == posIR.h
def __setstate__(self, state):
self.i = PosI(state["w"], state["h"], state["xz"] + 1, state["yz"] + 1)
def __getstate__(self):
cdef PosI posI = self.i
return {"w": posI.w, "h": posI.h, "xz": posI.xz, "yz": posI.yz}
cdef class PosI(object):
cdef int w, h, xz, yz
def __init__(self, int w=config.W, int h=config.H, int x=1, int y=1):
self.w = w
self.h = h
self.xz = x - 1
self.yz = y - 1
cdef inline X(self): return self.xz + 1
cdef inline Y(self): return self.yz + 1
cdef inline Inc(self):
if self.xz == self.w - 1:
if self.yz < self.h:
self.xz = 0
self.yz = self.yz + 1
else:
self.xz = self.xz + 1
cdef inline Up(self):
if self.yz > 0:
self.yz = self.yz - 1
return True
cdef inline Down(self):
if self.yz < self.h - 1:
self.yz = self.yz + 1
return True
cdef inline Left(self):
if self.xz > 0:
self.xz = self.xz - 1
return True
cdef inline Right(self):
if self.xz < self.w - 1:
self.xz = self.xz + 1
return True
cdef ToPos(self):
return Pos(self.w, self.h, self.X(), self.Y())
class Ascr(object):
def __init__(self, int w=config.W, int h=config.H, int achFill = achBlankI):
cdef AscrI ascrI = AscrI(w,h)
self.i = ascrI
if achFill != achBlankI:
ascrI.Fill(achFill, w, h)
def __setstate__(self, state):
cdef AscrI ascrI = AscrI(state["w"], state["h"])
self.i = ascrI
cdef int iach = 0
for mpxz_oach in state["mpyzxz_ach"]:
for oach in mpxz_oach:
ascrI.rgach[iach] = AchFromOach(oach)
iach = iach + 1
def __getstate__(self):
cdef AscrI ascrI = self.i
cdef int iach = 0
mpyzxz_oach = []
for yz in range(ascrI.h):
mpxz_oach = []
for xz in range(ascrI.w):
mpxz_oach.append(OachFromAch(ascrI.rgach[iach]))
iach = iach + 1
mpyzxz_oach.append(mpxz_oach)
return {"w":ascrI.w, "h":ascrI.h, "mpyzxz_ach": mpyzxz_oach}
def W(self):
cdef AscrI ascrI = self.i
return ascrI.w
def H(self):
cdef AscrI ascrI = self.i
return ascrI.h
def ZPos(self, int xz, int yz):
cdef AscrI ascrI = self.i
return ascrI.ZPos(xz, yz)
def PutAch(self, int ach, int x, int y):
cdef AscrI ascrI = self.i
ascrI.PutAch(ach, x, y)
def GetAch(self, int x, int y):
cdef AscrI ascrI = self.i
return ascrI.rgach[ascrI.IAch(x - 1, y - 1)]
def PutRgach(self, rgach, int x, int y):
cdef AscrI ascrI = self.i
ascrI.PutRgach(rgach, x, y)
def PutSt(self, char *st, int x, int y, int colFg = WHITE, int colBg=BLACK):
cdef AscrI ascrI = self.i
ascrI.PutSt(st, x, y, colFg, colBg)
def Fill(self, int ach, int wIn, int hIn, int x = 1, int y = 1):
cdef AscrI ascrI = self.i
ascrI.Fill(ach, wIn, hIn, x, y)
def PutAscr(self, ascr, int x=1, int y=1):
cdef AscrI ascrI = self.i
ascrI.PutAscr(ascr.i, x, y)
def AstDiff(self, ascr):
cdef AscrI ascrI = self.i
return ascrI.AstDiff(ascr.i)
def Hst(self):
cdef AscrI ascrI = self.i
return ascrI.Hst()
def Ast(self):
return Ascr(self.W(), self.H(), achInvdI).AstDiff(self) #stupid implementation
# ANSI Screen
cdef class AscrI(object):
cdef int w, h
cdef int *rgach
def __cinit__(self, int w=config.W, int h=config.H, int achFill = achBlankI):
self.rgach = <int*>calloc(w * h, sizeof(int))
if achFill != achBlankI:
for iach in range(w * h):
self.rgach[iach] = achFill
self.w = w
self.h = h
def __dealloc__(self):
free(self.rgach)
cdef Zpos(self, int xz, int yz):
return Pos(self.w, self.h, xz + 1, yz + 1)
cdef inline IAch(self, int xz, int yz):
return xz + (self.w * yz)
cdef PutAch(self, int ach, int x, int y):
if not (x < 1 or y < 1 or x > self.w or y > self.h): #clip
self.rgach[self.IAch(x - 1, y - 1)] = ach
#self.mpyzxz_ach[y-1][x-1] = ach
cdef inline GetAch(self, int x, int y):
return self.rgach[self.IAch(x - 1, y - 1)]
cdef PutRgach(self, rgach, int x, int y):
cdef int xz = x - 1
cdef int iach
if xz < self.w:
if xz < 0:
rgach = rgach[min(-xz, len(rgach)):]
xz = 0
if xz + len(rgach) >= self.w:
rgach = rgach[:self.w - xz]
iach = self.IAch(xz, y - 1)
for ach in rgach:
if ach != achBlankI:
self.rgach[iach] = ach
iach = iach + 1
#self.mpyzxz_ach[y-1][xz:xz + len(rgach)] = rgach
cdef PutSt(self, char *st, int x, int y, int colFg = WHITE, int colBg=BLACK):
if y < 1 or y > self.h:
return
cdef int cch = len(st)
cdef int ichStart = 0
cdef int xz = x - 1
if xz < 0:
ichStart = (-xz)
cch = cch - ichStart
xz = 0
cch = min(cch, self.w - xz)
cdef UAch uach
uach.sach.fgCol = colFg
uach.sach.bgCol = colBg
cdef int iach = self.IAch(xz, y - 1)
for ich in range(ichStart, ichStart + cch):
uach.sach.ch = st[ich]
self.rgach[iach] = uach.ach
iach = iach + 1
#self.PutRgach([MkAch(ch, colFg, colBg) for ch in st], x, y)
cdef Fill(self, int ach, int wIn, int hIn, int x = 1, int y = 1):
if x > self.w or y > self.h or ach == achBlankI:
return
cdef int xz = max(x - 1, 0)
cdef int yz = max(y - 1, 0)
cdef int w = min(self.w - xz, wIn)
cdef int h = min(self.h - yz, hIn)
cdef int iach
for yzT in range(yz, yz + h):
iach = self.IAch(xz, yzT)
for xzT in range(w):
self.rgach[iach] = ach
iach = iach + 1
#self.mpyzxz_ach[yzT][xz:xz + w] = ach
cdef PutAscr(self, AscrI ascr, int x=1, int y=1):
if x > self.w or y > self.h:
return
cdef int xz = max(x - 1, 0)
cdef int yz = max(y - 1, 0)
cdef int w = min(self.w - xz, ascr.w)
cdef int h = min(self.h - yz, ascr.h)
cdef int yzOther = 0
cdef int iach, iachOther, ach
for yzT in range(yz, yz + h):
iach = self.IAch(xz, yzT)
iachOther = ascr.IAch(0, yzOther)
for xzT in range(w):
ach = ascr.rgach[iachOther]
if ach != achBlankI:
self.rgach[iach] = ach
iach = iach + 1
iachOther = iachOther + 1
yzOther = yzOther + 1
cdef AstDiff(self, AscrI ascr):
assert self.w == ascr.w and self.h == ascr.h
cdef int xz = 0
cdef int yz = 0
cdef int xzPred = -1
cdef int achPrev = achInvdI
cdef int achBeforeEOL = achInvdI
cdef int achOld, achNew, achNewRaw
cdef int achSpace = MkAch(' ')
rgast = []
for iach in range(self.w * self.h):
achOld = self.rgach[iach]
achNewRaw = ascr.rgach[iach]
if achNewRaw == achBlankI:
achNew = achSpace
else:
achNew = achNewRaw
if xz == self.w - 2:
achBeforeEOL = achNew
if achOld != achNewRaw:
if xz == self.w - 1:
# Linewrap avoidance algorithm:
# when drawing the last character on a line, draw it in the space occupied by the character before.
rgast.append("%s%d;%dH" % (esc, yz + 1, xz))
rgast.append(AstFromAch(achNew, achInvdI))
# then, move the cursor back onto the character we just drew, and perform an insert.
rgast.append("%sD%s@" % (esc, esc))
# finally, draw the character before the last character in the line.
rgast.append(AstFromAch(achBeforeEOL, achNew))
achPrev = achBeforeEOL
else:
if xz != xzPred:
xzPred = xz
rgast.append("%s%d;%dH" % (esc, yz + 1, xz + 1))
achPrev = achInvdI
rgast.append(AstFromAch(achNew, achPrev))
achPrev = achNew
xzPred = xzPred + 1
xz = xz + 1
if xz == self.w:
xz = 0
xzPred = -1
yz = yz + 1
return "".join(rgast)
cdef Hst(self):
rgst = ["<pre style='font-family:\"Courier New\", Courier, System, monospace'>"]
cdef int achPrev = achInvdI
cdef int xz = 0
cdef int ach
for iach in range(self.w * self.h):
ach = self.rgach[iach]
rgst.append(HstFromAch(ach, achPrev))
achPrev = ach
xz = xz + 1
if xz == self.w:
xz = 0
rgst.append("<br>")
rgst.append("</span></pre>")
return ''.join(rgst)
def FKeyPrintable(key):
return type(key) == str and (ord(key) >= 32 and ord(key) <= 126)