Skip to content

Commit

Permalink
Reworked save/load, ban invalid characters
Browse files Browse the repository at this point in the history
  • Loading branch information
halcy committed Aug 10, 2018
1 parent 52e3983 commit fa0b984
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 36 deletions.
78 changes: 51 additions & 27 deletions AnsiImage.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,18 @@ def shift_line(self, y = None, x = None, how_much = 1, fill_char = None):
new_line.append(copy.deepcopy(fill_char))
self.ansi_image[y] = new_line[:self.width]

def generate_ansi_char(self, in_char, fg_bright, bg_bright, fg, bg):
def generate_ansi_char(self, in_char, fg_bright, bg_bright, fg, bg, raw = False):
"""
Generate ansi char as array: char idx, fg pal idx, bg pal idx
"""
if fg_bright:
fg += 8
if bg_bright:
bg += 8
return [ord(in_char), fg, bg]
if raw == False:
return [ord(in_char), fg, bg]
else:
return [in_char, fg, bg]

def move_cursor(self, x = None, y = None, relative = True):
"""
Expand Down Expand Up @@ -314,17 +317,18 @@ def clear_image(self, new_width = None, new_height = None):
line.append(self.generate_ansi_char(' ', False, False, 0, 0))
self.ansi_image.append(line)

def load_ans(self, ansi_path):
def load_ans(self, ansi_path, wide_mode = False):
"""
Loads and parses and ansi file. Documentation of parse_ans applies.
Additionally assumes data is encoded as cp1252.
wide_mode makes it so the parser doesn't insert line breaks at 80 characters.
"""
with open(ansi_path, "r", encoding='cp1252') as f:
with open(ansi_path, "rb") as f:
ansi_data = f.read()
self.parse_ans(ansi_data)
self.parse_ans(ansi_data, wide_mode)
self.is_dirty = False

def parse_ans(self, ansi_str):
def parse_ans(self, ansi_bytes, wide_mode = False):
"""
Parses an .ans files content. Assumes well-formed, breaks if not so.
Expand All @@ -340,16 +344,16 @@ def parse_ans(self, ansi_str):
current_bg_bright = False
current_fg = 7
current_bg = 0
while char_idx < len(ansi_str) and ansi_str[char_idx] != '\x1a':
while char_idx < len(ansi_bytes) and ansi_bytes[char_idx] != 0x1A:
# Begin ansi escape
if ansi_str[char_idx] == "\x1b":
if ansi_bytes[char_idx] == 0x1B:
char_idx += 2
escape_param_str = ""
escape_char = ""
while not (ansi_str[char_idx] == 'm' or ansi_str[char_idx] == 'C'):
escape_param_str += ansi_str[char_idx]
while not (ansi_bytes[char_idx] == ord('m') or ansi_bytes[char_idx] == ord('C')):
escape_param_str += chr(ansi_bytes[char_idx])
char_idx += 1
escape_char = ansi_str[char_idx]
escape_char = chr(ansi_bytes[char_idx])
escape_params = list(map(int, escape_param_str.split(";")))

# SGR
Expand Down Expand Up @@ -387,20 +391,29 @@ def parse_ans(self, ansi_str):
continue

# End of line
if ansi_str[char_idx] == '\n':
if ansi_bytes[char_idx] == 10:
ansi_lines.append(ansi_line)
ansi_line = []
char_idx += 1
continue

# Normal character
ansi_line.append(self.generate_ansi_char(
ansi_str[char_idx],
ansi_bytes[char_idx],
current_fg_bright,
current_bg_bright,
current_fg,
current_bg
current_bg,
raw = True
))

# If not wide mode and we're at 80 characters, break up the line
if not wide_mode and len(ansi_line) == 80:
ansi_lines.append(ansi_line)
ansi_line = []
char_idx += 1
continue

char_idx += 1
if len(ansi_line) != 0:
ansi_lines.append(ansi_line)
Expand All @@ -419,37 +432,48 @@ def parse_ans(self, ansi_str):
self.width = len(ansi_lines[0])
self.height = len(ansi_lines)

def str_to_bytes(self, string):
"""
Basic string to list of bytes function. Valid only for printable < 128.
"""
byte_list = []
for char in string:
byte_list.append(ord(char))
return byte_list

def to_ans(self):
"""
Returns a textual ansi representation of the image.
Returns a byte-array text representation of the image.
No effort is made to reduce size at this time.
"""
ansi_str = ""
ansi_bytes = []
for y in range(0, self.height):
for x in range(0, self.width):
char_info = self.ansi_image[y][x]

ansi_str += "\x1b[0;"
ansi_bytes += [0x1b]
ansi_bytes += self.str_to_bytes('[0;')

if char_info[1] >= 8:
ansi_str += "1;"
ansi_str += str((char_info[1] % 8) + 30) + ";"
ansi_bytes += self.str_to_bytes('1;')
ansi_bytes += self.str_to_bytes(str((char_info[1] % 8) + 30) + ";")

if char_info[2] >= 8:
ansi_str += "5;"
ansi_str += str((char_info[2] % 8) + 40)
ansi_bytes += self.str_to_bytes("5;")
ansi_bytes += self.str_to_bytes(str((char_info[2] % 8) + 40))

ansi_str += "m"
ansi_str += chr(char_info[0])
ansi_str += "\n"
return ansi_str
ansi_bytes += self.str_to_bytes("m")
ansi_bytes += [char_info[0]]
if self.width < 80: # If we're at 80 characters we omit the newline.
ansi_bytes += [10]
return bytearray(ansi_bytes)

def save_ans(self, out_path):
"""
Writes .ans file from this images contents
"""
with open(out_path, "w", encoding='cp1252') as f:
with open(out_path, "wb") as f:
f.write(self.to_ans())

def to_bitmap(self, ansi_graphics, transparent = False, cursor = False):
Expand Down
10 changes: 8 additions & 2 deletions AnsiPalette.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def __init__(self, ansi_graphics):
self.cur_back = 0 # Black
self.char_idx = 0
self.chars = [176, 177, 178, 219, 223, 220, 221, 222, 254, 249, 32, 32] # Pablodraw default
self.invalid = [0, 8, 9, 10, 13, 26, 27, 255] # These will break acidview

def set_fore(self, fore):
"""
Expand Down Expand Up @@ -63,9 +64,12 @@ def get_char(self, idx = None, from_seq = False):
"""
if idx == None:
idx = self.char_idx

if from_seq == True:
return [self.chars[idx], self.fore(), self.back()]
else:
if idx in self.invalid:
idx = ord(' ')
return [idx, self.fore(), self.back()]

def get_char_image(self, idx = None, from_seq = False):
Expand Down Expand Up @@ -127,9 +131,11 @@ def get_character_image(self, width = 32, fore = None, back = None):
char_idx = 0
for y in range(0, height):
for x in range(0, width):
if char_idx < 256:
if char_idx < 256 and not char_idx in self.invalid:
sel_image.set_cell(char = char_idx, fore = fore, back = back, x = x, y = y)
char_idx += 1
else:
sel_image.set_cell(char = ord(' '), fore = fore, back = back, x = x, y = y)
char_idx += 1

sel_image.move_cursor(self.char_idx % width, self.char_idx // width, False)
return sel_image.to_bitmap(self.graphics, cursor = True, transparent = True)
Expand Down
28 changes: 21 additions & 7 deletions MainWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,8 @@ def newFile(self):
Create blank 80x24 document
"""
self.ansiImage = AnsiImage()
self.ansiImage.clear_image(80, 24)
self.ansiImage.clear_image(80, 256)

self.undoStack = []
self.redoStack = []
self.currentFileName = None
Expand All @@ -512,17 +513,30 @@ def openFile(self):
"""
Load an ansi file
"""
self.currentFileName = QtWidgets.QFileDialog.getOpenFileName(self, caption = "Open ANSI file", filter="ANSI Files (*.ans)")[0]
self.ansiImage.load_ans(self.currentFileName)
self.redisplayAnsi()
self.updateTitle()
loadFileName = QtWidgets.QFileDialog.getOpenFileName(self, caption = "Open ANSI file", filter="ANSI Files (*.ans);;Arbitrary width ANSI Files (*.ans);;All Files (*.*)")
wideMode = False
if loadFileName[1] == 'Arbitrary width ANSI Files (*.ans)':
wideMode = True
loadFileName = loadFileName[0]

if len(loadFileName) != 0:
self.currentFileName = loadFileName
self.ansiImage.load_ans(self.currentFileName, wideMode)
self.redisplayAnsi()
self.updateTitle()

def saveFileAs(self):
"""
Save an ansi file, with a certain name
"""
self.currentFileName = QtWidgets.QFileDialog.getSaveFileName(self, caption = "Save ANSI file", filter="ANSI Files (*.ans)")[0]
self.saveFile()
saveFileName = QtWidgets.QFileDialog.getSaveFileName(self, caption = "Save ANSI file", filter="ANSI Files (*.ans);;All Files (*.*)")
if saveFileName[1] == 'ANSI Files (*.ans)' and not saveFileName[0].endswith(".ans"):
saveFileName = saveFileName[0] + ".ans"
else:
saveFileName = saveFileName[0]
if len(saveFileName) != 0:
self.currentFileName = saveFileName
self.saveFile()

def saveFile(self):
"""
Expand Down

0 comments on commit fa0b984

Please sign in to comment.