~manuq/+junk/manuqs_interactives

« back to all changes in this revision

Viewing changes to casa_tomada_2/pyglet/text/runlist.py

  • Committer: Manuel Quiñones
  • Date: 2008-07-07 06:48:54 UTC
  • Revision ID: manuel.por.aca@gmail.com-20080707064854-0dpwpz7at6atdne3
Import of casa_tomada_2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# ----------------------------------------------------------------------------
 
2
# pyglet
 
3
# Copyright (c) 2006-2008 Alex Holkner
 
4
# All rights reserved.
 
5
 
6
# Redistribution and use in source and binary forms, with or without
 
7
# modification, are permitted provided that the following conditions 
 
8
# are met:
 
9
#
 
10
#  * Redistributions of source code must retain the above copyright
 
11
#    notice, this list of conditions and the following disclaimer.
 
12
#  * Redistributions in binary form must reproduce the above copyright 
 
13
#    notice, this list of conditions and the following disclaimer in
 
14
#    the documentation and/or other materials provided with the
 
15
#    distribution.
 
16
#  * Neither the name of pyglet nor the names of its
 
17
#    contributors may be used to endorse or promote products
 
18
#    derived from this software without specific prior written
 
19
#    permission.
 
20
#
 
21
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 
22
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 
23
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 
24
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 
25
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 
26
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 
27
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 
28
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 
29
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 
30
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 
31
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 
32
# POSSIBILITY OF SUCH DAMAGE.
 
33
# ----------------------------------------------------------------------------
 
34
'''Run list encoding utilities.
 
35
 
 
36
:since: pyglet 1.1
 
37
'''
 
38
 
 
39
__docformat__ = 'restructuredtext'
 
40
__version__ = '$Id: $'
 
41
 
 
42
class _Run(object):
 
43
    def __init__(self, value, count):
 
44
        self.value = value
 
45
        self.count = count
 
46
 
 
47
    def __repr__(self):
 
48
        return 'Run(%r, %d)' % (self.value, self.count)
 
49
 
 
50
class RunList(object):
 
51
    '''List of contiguous runs of values.
 
52
 
 
53
    A `RunList` is an efficient encoding of a sequence of values.  For
 
54
    example, the sequence ``aaaabbccccc`` is encoded as ``(4, a), (2, b),
 
55
    (5, c)``.  The class provides methods for modifying and querying the
 
56
    run list without needing to deal with the tricky cases of splitting and
 
57
    merging the run list entries.
 
58
 
 
59
    Run lists are used to represent formatted character data in pyglet.  A
 
60
    separate run list is maintained for each style attribute, for example,
 
61
    bold, italic, font size, and so on.  Unless you are overriding the
 
62
    document interfaces, the only interaction with run lists is via
 
63
    `RunIterator`.
 
64
 
 
65
    The length and ranges of a run list always refer to the character
 
66
    positions in the decoded list.  For example, in the above sequence,
 
67
    ``set_run(2, 5, 'x')`` would change the sequence to ``aaxxxbccccc``.
 
68
    '''
 
69
    def __init__(self, size, initial):
 
70
        '''Create a run list of the given size and a default value.
 
71
 
 
72
        :Parameters:
 
73
            `size` : int
 
74
                Number of characters to represent initially.
 
75
            `initial` : object
 
76
                The value of all characters in the run list.
 
77
 
 
78
        '''
 
79
        self.runs = [_Run(initial, size)]
 
80
 
 
81
    def insert(self, pos, length):
 
82
        '''Insert characters into the run list.
 
83
 
 
84
        The inserted characters will take on the value immediately preceding
 
85
        the insertion point (or the value of the first character, if `pos` is
 
86
        0).
 
87
 
 
88
        :Parameters:
 
89
            `pos` : int
 
90
                Insertion index
 
91
            `length` : int
 
92
                Number of characters to insert.
 
93
 
 
94
        '''
 
95
 
 
96
        i = 0
 
97
        for run in self.runs:
 
98
            if i <= pos <= i + run.count:
 
99
                run.count += length
 
100
            i += run.count
 
101
 
 
102
    def delete(self, start, end):
 
103
        '''Remove characters from the run list.
 
104
 
 
105
        :Parameters:
 
106
            `start` : int
 
107
                Starting index to remove from.
 
108
            `end` : int
 
109
                End index, exclusive.
 
110
 
 
111
        '''
 
112
        i = 0
 
113
        for run in self.runs:
 
114
            if end - start == 0:
 
115
                break
 
116
            if i <= start <= i + run.count:
 
117
                trim = min(end - start, i + run.count - start)
 
118
                run.count -= trim
 
119
                end -= trim
 
120
            i += run.count
 
121
        self.runs = [r for r in self.runs if r.count > 0]
 
122
 
 
123
        # Don't leave an empty list
 
124
        if not self.runs:
 
125
            self.runs = [_Run(run.value, 0)]
 
126
 
 
127
    def set_run(self, start, end, value):
 
128
        '''Set the value of a range of characters.
 
129
 
 
130
        :Parameters:
 
131
            `start` : int
 
132
                Start index of range.
 
133
            `end` : int
 
134
                End of range, exclusive.
 
135
            `value` : object
 
136
                Value to set over the range.
 
137
 
 
138
        '''
 
139
        if end - start <= 0:
 
140
            return
 
141
        
 
142
        # Find runs that need to be split
 
143
        i = 0
 
144
        start_i = None
 
145
        start_trim = 0
 
146
        end_i = None
 
147
        end_trim = 0
 
148
        for run_i, run in enumerate(self.runs):
 
149
            count = run.count
 
150
            if i < start < i + count:
 
151
                start_i = run_i
 
152
                start_trim = start - i
 
153
            if i < end < i + count:
 
154
                end_i = run_i
 
155
                end_trim = end - i
 
156
            i += count
 
157
        
 
158
        # Split runs
 
159
        if start_i is not None:
 
160
            run = self.runs[start_i]
 
161
            self.runs.insert(start_i, _Run(run.value, start_trim))
 
162
            run.count -= start_trim
 
163
            if end_i is not None:
 
164
                if end_i == start_i:
 
165
                    end_trim -= start_trim
 
166
                end_i += 1
 
167
        if end_i is not None:
 
168
            run = self.runs[end_i]
 
169
            self.runs.insert(end_i, _Run(run.value, end_trim))
 
170
            run.count -= end_trim
 
171
                
 
172
        # Set new value on runs
 
173
        i = 0
 
174
        for run in self.runs:
 
175
            if start <= i and i + run.count <= end: 
 
176
                run.value = value
 
177
            i += run.count 
 
178
 
 
179
        # Merge adjacent runs
 
180
        last_run = self.runs[0]
 
181
        for run in self.runs[1:]:
 
182
            if run.value == last_run.value:
 
183
                run.count += last_run.count
 
184
                last_run.count = 0
 
185
            last_run = run
 
186
 
 
187
        # Delete collapsed runs
 
188
        self.runs = [r for r in self.runs if r.count > 0]
 
189
 
 
190
    def __iter__(self):
 
191
        i = 0
 
192
        for run in self.runs:
 
193
            yield i, i + run.count, run.value
 
194
            i += run.count
 
195
 
 
196
    def get_run_iterator(self):
 
197
        '''Get an extended iterator over the run list.
 
198
 
 
199
        :rtype: `RunIterator`
 
200
        '''
 
201
        return RunIterator(self)
 
202
 
 
203
    def __getitem__(self, index):
 
204
        '''Get the value at a character position.
 
205
 
 
206
        :Parameters:
 
207
            `index` : int
 
208
                Index of character.  Must be within range and non-negative.
 
209
 
 
210
        :rtype: object
 
211
        '''
 
212
        i = 0
 
213
        for run in self.runs:
 
214
            if i <= index < i + run.count:
 
215
                return run.value
 
216
            i += run.count
 
217
 
 
218
        # Append insertion point
 
219
        if index == i:
 
220
            return self.runs[-1].value
 
221
 
 
222
        assert False, 'Index not in range'
 
223
 
 
224
    def __repr__(self):
 
225
        return str(list(self))
 
226
 
 
227
class AbstractRunIterator(object):
 
228
    '''Range iteration over `RunList`.
 
229
 
 
230
    `AbstractRunIterator` objects allow any monotonically non-decreasing
 
231
    access of the iteration, including repeated iteration over the same index.
 
232
    Use the ``[index]`` operator to get the value at a particular index within
 
233
    the document.  For example::
 
234
 
 
235
        run_iter = iter(run_list)
 
236
        value = run_iter[0]
 
237
        value = run_iter[0]       # non-decreasing access is OK
 
238
        value = run_iter[15]
 
239
        value = run_iter[17]
 
240
        value = run_iter[16]      # this is illegal, the index decreased.
 
241
 
 
242
    Using `AbstractRunIterator` to access increasing indices of the value runs
 
243
    is more efficient than calling `RunList.__getitem__` repeatedly.
 
244
 
 
245
    You can also iterate over monotonically non-decreasing ranges over the
 
246
    iteration.  For example::
 
247
        
 
248
        run_iter = iter(run_list)
 
249
        for start, end, value in run_iter.ranges(0, 20):
 
250
            pass
 
251
        for start, end, value in run_iter.ranges(25, 30):
 
252
            pass
 
253
        for start, end, value in run_iter.ranges(30, 40):
 
254
            pass
 
255
 
 
256
    Both start and end indices of the slice are required and must be positive.
 
257
    '''
 
258
 
 
259
    def __getitem__(self, index):
 
260
        '''Get the value at a given index.
 
261
 
 
262
        See the class documentation for examples of valid usage.
 
263
 
 
264
        :Parameters:
 
265
            `index` : int   
 
266
                Document position to query.
 
267
 
 
268
        :rtype: object
 
269
        '''
 
270
 
 
271
    def ranges(self, start, end):
 
272
        '''Iterate over a subrange of the run list.
 
273
 
 
274
        See the class documentation for examples of valid usage.
 
275
 
 
276
        :Parameters:
 
277
            `start` : int
 
278
                Start index to iterate from.
 
279
            `end` : int
 
280
                End index, exclusive.
 
281
 
 
282
        :rtype: iterator
 
283
        :return: Iterator over (start, end, value) tuples.
 
284
        '''
 
285
 
 
286
class RunIterator(AbstractRunIterator):
 
287
    def __init__(self, run_list):
 
288
        self.next = iter(run_list).next
 
289
        self.start, self.end, self.value = self.next()
 
290
 
 
291
    def __getitem__(self, index):
 
292
        while index >= self.end:
 
293
            self.start, self.end, self.value = self.next()
 
294
        return self.value
 
295
 
 
296
    def ranges(self, start, end):
 
297
        while start >= self.end:
 
298
            self.start, self.end, self.value = self.next()
 
299
        yield start, min(self.end, end), self.value
 
300
        while end > self.end:
 
301
            self.start, self.end, self.value = self.next()
 
302
            yield self.start, min(self.end, end), self.value
 
303
 
 
304
class OverriddenRunIterator(AbstractRunIterator):
 
305
    '''Iterator over a `RunIterator`, with a value temporarily replacing
 
306
    a given range.
 
307
    '''
 
308
    def __init__(self, base_iterator, start, end, value):
 
309
        '''Create a derived iterator.
 
310
 
 
311
        :Parameters:
 
312
            `start` : int
 
313
                Start of range to override
 
314
            `end` : int
 
315
                End of range to override, exclusive
 
316
            `value` : object
 
317
                Value to replace over the range
 
318
 
 
319
        '''
 
320
        self.iter = base_iterator
 
321
        self.override_start = start
 
322
        self.override_end = end
 
323
        self.override_value = value
 
324
 
 
325
    def ranges(self, start, end):
 
326
        if end <= self.override_start or start >= self.override_end:
 
327
            # No overlap
 
328
            for r in self.iter.ranges(start, end):
 
329
                yield r
 
330
        else:
 
331
            # Overlap: before, override, after
 
332
            if start < self.override_start < end:
 
333
                for r in self.iter.ranges(start, self.override_start):
 
334
                    yield r
 
335
            yield (max(self.override_start, start),
 
336
                   min(self.override_end, end),
 
337
                   self.override_value)
 
338
            if start < self.override_end < end:
 
339
                for r in self.iter.ranges(self.override_end, end):
 
340
                    yield r
 
341
        
 
342
    def __getitem__(self, index):
 
343
        if self.override_start <= index < self.override_end:
 
344
            return self.override_value
 
345
        else:
 
346
            return self.iter[index]
 
347
 
 
348
class FilteredRunIterator(AbstractRunIterator):
 
349
    '''Iterate over an `AbstractRunIterator` with filtered values replaced
 
350
    by a default value.
 
351
    '''
 
352
    def __init__(self, base_iterator, filter, default):
 
353
        '''Create a filtered run iterator.
 
354
 
 
355
        :Parameters:
 
356
            `base_iterator` : `AbstractRunIterator`
 
357
                Source of runs.
 
358
            `filter` : ``lambda object: bool``
 
359
                Function taking a value as parameter, and returning ``True``
 
360
                if the value is acceptable, and ``False`` if the default value
 
361
                should be substituted.
 
362
            `default` : object
 
363
                Default value to replace filtered values.
 
364
 
 
365
        '''
 
366
        self.iter = base_iterator
 
367
        self.filter = filter
 
368
        self.default = default
 
369
 
 
370
    def ranges(self, start, end):
 
371
        for start, end, value in self.iter.ranges(start, end):
 
372
            if self.filter(value):
 
373
                yield start, end, value
 
374
            else:
 
375
                yield start, end, self.default
 
376
 
 
377
    def __getitem__(self, index):
 
378
        value = self.iter[index]
 
379
        if self.filter(value):
 
380
            return value
 
381
        return self.default
 
382
 
 
383
class ZipRunIterator(AbstractRunIterator):
 
384
    '''Iterate over multiple run iterators concurrently.'''
 
385
    def __init__(self, range_iterators):
 
386
        self.range_iterators = range_iterators
 
387
 
 
388
    def ranges(self, start, end):
 
389
        iterators = [i.ranges(start, end) for i in self.range_iterators]
 
390
        starts, ends, values = zip(*[i.next() for i in iterators])
 
391
        starts = list(starts)
 
392
        ends = list(ends)
 
393
        values = list(values)
 
394
        while start < end:
 
395
            min_end = min(ends)
 
396
            yield start, min_end, values
 
397
            start = min_end
 
398
            for i, iterator in enumerate(iterators):
 
399
                if ends[i] == min_end:
 
400
                    starts[i], ends[i], values[i] = iterator.next()
 
401
 
 
402
    def __getitem__(self, index):
 
403
        return [i[index] for i in self.range_iterators]
 
404
 
 
405
class ConstRunIterator(AbstractRunIterator):
 
406
    '''Iterate over a constant value without creating a RunList.'''
 
407
    def __init__(self, length, value):
 
408
        self.length = length
 
409
        self.value = value
 
410
 
 
411
    def next(self):
 
412
        yield 0, self.length, self.value
 
413
 
 
414
    def ranges(self, start, end):
 
415
        yield start, end, self.value
 
416
 
 
417
    def __getitem__(self, index):
 
418
        return self.value