File: aewan.py

package info (click to toggle)
aewan 1.0.01-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 816 kB
  • sloc: ansic: 4,439; python: 122; makefile: 67; sh: 14
file content (185 lines) | stat: -rw-r--r-- 6,615 bytes parent folder | download | duplicates (8)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# Copyright (c) 2004 Bruno T. C. de Oliveira
# 
# LICENSE INFORMATION:
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
# 
# This program 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
# General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
# Copyright (c) 2002 Bruno T. C. de Oliveira
# 
# INFORMAES DE LICENA:
# Este programa  um software de livre distribuio; voc pode
# redistribu-lo e/ou modific-lo sob os termos da GNU General
# Public License, conforme publicado pela Free Software Foundation,
# pela verso 2 da licena ou qualquer verso posterior.
# 
# Este programa  distribudo na esperana de que ele ser til
# aos seus usurios, porm, SEM QUAISQUER GARANTIAS; sem sequer
# a garantia implcita de COMERCIABILIDADE ou DE ADEQUAO A
# QUALQUER FINALIDADE ESPECFICA. Consulte a GNU General Public
# License para obter mais detalhes (uma cpia acompanha este
# programa, armazenada no arquivo COPYING).
 
import sys, os, string
import gzip
import curses

class AeError(Exception):
   def __init__(self, arg):
      Exception.__init__(self, arg)

class AeDoc:
   """
   Represents an aewan document loaded from a file.
   Attributes:

   layers[] - list of layers (AeLayer objects)
   """
   def __init__(self, file):
      """
      Creates a new aewan document object by loading it from the
      specified file. The file must be in AES format, as generated
      by the ae2aes conversion utility. The file may be gzip-compressed.
      
      If there is a format-related error while loading the document, 
      raises an AeError. If there is an I/O related error, raises an IOError.
      """
      self.layers = []

      f = gzip.open(file, 'r')
      _read_marker(f, "AES v1.0")
      _read_marker(f, "begin document")

      layercount = _read_field_int(f, "layer-count")
      for i in range(layercount):
         self.layers.append(AeLayer(f))
      
      _read_marker(f, "end document")


class AeLayer:
   """
   Represents each of the layers in an aewan document. Attributes:

   name
   width
   height
   visible - 1 if layer is marked as visible, 0 otherwise
   transparent - 1 if layer is marked as transparent, 0 otherwise

   char[height][width] - the character at each position in the layer
   attr[height][width] - the attribute of each position in the layer

   Please note that the char and attr matrices are addressed
   with indexes [y][x], NOT the more intuitive [x][y].
   """
   def __init__(self, f):
      """
      Initializes a new layer by loading its data from file object f.
      Raises an AeError if a format-related error occurs, or an IOError
      if an i/o-related error occurs.
      """
      _read_marker(f, "begin layer")
      self.name        = _read_field(f, "name")
      self.width       = _read_field_int(f, "width")
      self.height      = _read_field_int(f, "height")
      self.visible     = _read_field_int(f, "visible")
      self.transparent = _read_field_int(f, "transparent")
      _read_marker(f, "begin canvas")

      self.char = []
      self.attr = []
      for y in range(self.height):
         a = string.split(string.strip(f.readline()))
         if len(a) != self.width * 2:
            raise AeError("Canvas line with insufficient cells:\n" + a)

         char_row = []
         attr_row = []
         for x in range(self.width):
            try:
               char_row.append(int(a[x * 2], 16))
               attr_row.append(int(a[x * 2 + 1], 16))
            except ValueError:
               raise AeError("Error: map cell x=%d, y=%d has invalid data\n"
                             "(not a hexadecimal number)" % (x,y))

         self.char.append(char_row)
         self.attr.append(attr_row)
      
      _read_marker(f, "end canvas")
      _read_marker(f, "end layer")

   def render(self, wnd, dest_x, dest_y, dest_w, dest_h, src_x, src_y, \
              setattr_cb):
      """
      Render the contents of this layer using curses. The image will
      be rendered on the curses window identified by wnd, and will
      be confined to the rectangle defined by the dest_* arguments.
      The image will be translated in such a way that the left-top
      corner of the destination rectangle will show the layer cell
      whose coordinates are src_x, src_y.

      In order to set curses attributes for each cell, this function
      will call setattr_cb passing it the window and the attribute as 
      parameters.
      """
      for y in range(dest_h):
         wnd.move(y + dest_y, dest_x)
         for x in range(dest_w):
            sx = x + src_x
            sy = y + src_y
            if 0 <= sx < self.width and 0 <= sy < self.height:
               ch = self.char[sy][sx]
               if 0 <= ch < 32: ch = 32 # don't display control chars
               # FIXME: this doesn't display special line-graphics
               # characters. Ideally, we should convert special
               # characters into the appropriate ACS_* characters
               # at this point.

               at = self.attr[sy][sx]
            else:
               ch = 0x20
               at = 0x70

            setattr_cb(wnd, at)
            wnd.addch(chr(ch))


# ------------------------------------------------------------------------
def _read_marker(f, str):
   line = string.strip(f.readline())
   if line == str: return
   raise AeError("Incorrectly formatted AES document.\n"
                 "Expected marker: '%s'\nBut found: '%s'\n" % (str,line))

def _read_field(f, fieldname):
   line = string.strip(f.readline())
   a = string.split(line, maxsplit=1)

   if len(a) != 2:
      raise AeError("Incorrect field syntax in AES document:\n%s\n" % line) 

   if a[0] != fieldname:
      raise AeError("AES field name mismatch.\nExpected: '%s'\nFound: '%s'" \
                  % (fieldname, a[0]))
   
   return a[1]
   
def _read_field_int(f, fieldname):
   x = _read_field(f, fieldname);
   try:
      return int(x)
   except ValueError:
      raise AeError("AES field type mismatch: expected an integer\n"
                    "Field name: '%s'\nValue: '%s'" % (fieldname, x))