aalib-ruby 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,76 @@
1
+ require 'rake'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/rdoctask'
4
+ require 'spec/rake/spectask'
5
+ require 'fileutils'
6
+ include FileUtils
7
+
8
+ class String
9
+ def first_line
10
+ first = ''
11
+ each_line { |line| first = line; break }
12
+ first
13
+ end
14
+ end
15
+
16
+ NAME = "aalib-ruby"
17
+ VERS = `darcs list tags`.first_line.chomp
18
+ PKG = "#{NAME}-#{VERS}"
19
+ RDOC_OPTS = ['--quiet', '--title', 'AA-lib for Ruby', '--main', 'README', '--inline-source']
20
+ PKG_FILES = `darcs list files`.split("\n")
21
+ GEMSPEC =
22
+ Gem::Specification.new do |s|
23
+ s.name = NAME
24
+ s.version = VERS
25
+ s.platform = Gem::Platform::RUBY
26
+ s.has_rdoc = true
27
+ s.rdoc_options += RDOC_OPTS
28
+ s.extra_rdoc_files = ["README", "ChangeLog", "COPYING"]
29
+ s.summary = 'a graphics context and input library for text terminals'
30
+ s.description = s.summary
31
+ s.author = 'Patrick Mahoney'
32
+ s.email = 'pat@polycrystal.org'
33
+ s.homepage = 'http://aalib-ruby.rubyforge.org/'
34
+ s.files = PKG_FILES
35
+ end
36
+
37
+ desc "Run the tests"
38
+ task :default => [:test]
39
+
40
+ desc "Generates tar.gz and gem packages"
41
+ task :package
42
+
43
+ task :test => [:spec]
44
+
45
+ desc "Runs all tests"
46
+ Spec::Rake::SpecTask.new do |t|
47
+ t.spec_files = FileList['spec/**/*spec.rb']
48
+ end
49
+
50
+ desc "Compile documentation with RDoc"
51
+ task :doc => [:rdoc]
52
+
53
+ Rake::RDocTask.new do |rdoc|
54
+ rdoc.rdoc_dir = 'doc/'
55
+ rdoc.options += RDOC_OPTS
56
+ rdoc.main = "README"
57
+ rdoc.rdoc_files.add ['README', 'ChangeLog', 'COPYING', 'lib/**/*.rb']
58
+ end
59
+
60
+ Rake::GemPackageTask.new(GEMSPEC) do |p|
61
+ p.need_tar = true
62
+ p.gem_spec = GEMSPEC
63
+ end
64
+
65
+ task "lib" do
66
+ directory "lib"
67
+ end
68
+
69
+ task :install do
70
+ sh %{rake package}
71
+ sh %{sudo gem install pkg/#{NAME}-#{VERS}}
72
+ end
73
+
74
+ task :uninstall do
75
+ sh %{sudo gem uninstall #{NAME}}
76
+ end
@@ -0,0 +1,36 @@
1
+ require 'aalib'
2
+
3
+ hp = AAlib::HardwareParams.new
4
+ rp = AAlib::RenderParams.new
5
+
6
+ AAlib.parseoptions(hp, rp) or abort AAlib.help
7
+ aa = AAlib.autoinit(hp) or abort "failed to initialize AA-lib"
8
+ begin
9
+ aa.autoinitkbd # set up keyboard support
10
+
11
+ # Fill screen with diagonal gradient
12
+ width = aa.imgwidth
13
+ height = aa.imgheight
14
+ height.times do |y|
15
+ width.times do |x|
16
+ aa.putpixel(x, y, 127*(x.to_f/width) + 127*(y.to_f/height))
17
+ end
18
+ end
19
+
20
+ rp.randomval = 25
21
+ aa.render(rp)
22
+
23
+ msg = ' AA-lib: the ascii-art library '
24
+ blank = ' ' * msg.size
25
+ attr = AAlib::Attr::BOLD
26
+ col = (aa.scrwidth - msg.size)/2
27
+ row = aa.scrheight/2
28
+ aa.puts(col, row - 1, attr, blank)
29
+ aa.puts(col, row, attr, msg)
30
+ aa.puts(col, row + 1, attr, blank)
31
+ aa.flush
32
+
33
+ aa.getkey # wait for any key to exit
34
+ ensure
35
+ aa.close
36
+ end
@@ -0,0 +1,45 @@
1
+ require 'aalib'
2
+
3
+ hp = AAlib::HardwareParams.new
4
+ rp = AAlib::RenderParams.new
5
+
6
+ AAlib.parseoptions(hp, rp) or abort AAlib.help
7
+
8
+ file = "aalib-save-demo.txt"
9
+ savedata = AAlib::SaveData.new(file, "text", AAlib::SaveFormat::USE_PAGES)
10
+ aa = AAlib.init(AAlib.save_driver, hp, savedata)
11
+
12
+ aa or abort "failed to initialize AA-lib"
13
+
14
+ begin
15
+ # Fill screen with diagonal gradient
16
+ width = aa.imgwidth
17
+ height = aa.imgheight
18
+ height.times do |y|
19
+ width.times do |x|
20
+ aa.putpixel(x, y, 127*(x.to_f/width) + 127*(y.to_f/height))
21
+ end
22
+ end
23
+
24
+ rp.randomval = 25
25
+ aa.render(rp)
26
+
27
+ msg = ' AA-lib: the ascii-art library '
28
+ blank = ' ' * msg.size
29
+ attr = AAlib::Attr::BOLD
30
+ col = (aa.scrwidth - msg.size)/2
31
+ row = aa.scrheight/2
32
+ aa.puts(col, row - 1, attr, blank)
33
+ aa.puts(col, row, attr, msg)
34
+ aa.puts(col, row + 1, attr, blank)
35
+
36
+ if File.exists?(file)
37
+ print "File exists: \"#{file}\" Will not overwrite.\n"
38
+ else
39
+ print "Saving \"#{file}\" ... "
40
+ aa.flush # Saves file to disk
41
+ print "ok.\n"
42
+ end
43
+ ensure
44
+ aa.close
45
+ end
@@ -0,0 +1,1088 @@
1
+ # AAlib-Ruby brings graphics to the text terminal. AAlib-Ruby provides both a
2
+ # graphics context rendered as ascii-art and keyboard and mouse input.
3
+ # AAlib-Ruby supports a number of text-only display drivers such as Curses,
4
+ # SLang, and X11.
5
+ #
6
+ # This is a DL based wrapper around the AA-lib C library. C struct alignment
7
+ # issues may cause problems on certain platforms when accessing variables of
8
+ # some objects.
9
+ #
10
+ # Author:: Patrick Mahoney (mailto:pat@polycrystal.org)
11
+ # Copyright:: Copyright (c) 2007 Patrick Mahoney
12
+ # License:: Distributes under the same terms as Ruby
13
+ #
14
+ # See README for a usage overview.
15
+
16
+ require 'dl'
17
+
18
+ module DL #:nodoc:all
19
+ class << self
20
+ # Attempts to open each library in the Array libs with DL.dlopen. Returns
21
+ # the first that is successfully opened. This is a convenience function
22
+ # for compatibility with different platforms where the dynamic library may
23
+ # be named differently.
24
+ def tryopen(libs)
25
+ dllib = nil
26
+ errs = Array.new
27
+
28
+ libs.each do |lib|
29
+ begin
30
+ dllib = DL.dlopen lib
31
+ rescue => err
32
+ errs << err
33
+ end
34
+ break if dllib
35
+ end
36
+
37
+ unless dllib
38
+ msg = errs.collect{ |e| e.message }.join('; ')
39
+ raise RuntimeError.new("failed to open library: (#{msg})")
40
+ else
41
+ dllib
42
+ end
43
+ end
44
+ end
45
+
46
+ class PtrData
47
+ def string?
48
+ p self.data_type
49
+ data_type[1] == 'S'
50
+ end
51
+ end
52
+ end
53
+
54
+ # This module provides initialization methods and other miscellaneous methods.
55
+
56
+ module AAlib
57
+ class Error < RuntimeError; end
58
+
59
+ module Foreign #:nodoc:all
60
+ class << self
61
+
62
+ # Defines a method named sym that calls the given function from DL:Handle
63
+ # lib using the DL function signature sig. This method is modeled after
64
+ # a similar method in Rubinius' FFI.
65
+ #
66
+ def attach_function(lib, func, sym, sig)
67
+ self.class.send(:define_method, sym) do |*args|
68
+ # print "calling #{func}("
69
+ # if args
70
+ # argstr = args.collect do |a|
71
+ # if a.kind_of? Array
72
+ # a.inspect
73
+ # elsif a.kind_of? DL::PtrData
74
+ # a.class
75
+ # else
76
+ # "'#{a}'"
77
+ # end
78
+ # end
79
+ # print argstr.join(', ')
80
+ # end
81
+ # print ")\n"
82
+ lib[func, sig].call(*args)
83
+ end
84
+ end
85
+
86
+ # Defines a method named _sym_ that returns the global variable _global_
87
+ # from DL:Handle lib.
88
+ #
89
+ def attach_global(lib, global, sym)
90
+ self.class.send(:define_method, sym) do
91
+ lib[global]
92
+ end
93
+ end
94
+ end
95
+
96
+ LIB = DL.tryopen ['libaa.so.1', 'libaa.so', 'libaa']
97
+
98
+ # Global vars
99
+ attach_global LIB, 'aa_help', :help
100
+ attach_global LIB, 'aa_defparams', :defparams
101
+ attach_global LIB, 'aa_defrenderparams', :defrenderparams
102
+
103
+ attach_global LIB, 'aa_drivers', :drivers
104
+ attach_global LIB, 'save_d', :save_driver
105
+ attach_global LIB, 'mem_d', :mem_driver
106
+
107
+ attach_global LIB, 'aa_formats', :formats
108
+
109
+ # Functions
110
+ attach_function LIB, 'aa_parseoptions', :parseoptions, 'IPPPa'
111
+ attach_function LIB, 'aa_init', :init, 'PPPP'
112
+ attach_function LIB, 'aa_close', :close, '0P'
113
+
114
+ attach_function LIB, 'aa_autoinit', :autoinit, 'PP'
115
+ attach_function LIB, 'aa_autoinitkbd', :autoinitkbd, 'IPI'
116
+ attach_function LIB, 'aa_autoinitmouse', :autoinitmouse, 'IPI'
117
+
118
+ attach_function LIB, 'aa_image', :image, 'PP'
119
+ attach_function LIB, 'aa_text', :text, 'PP'
120
+ attach_function LIB, 'aa_attrs', :attrs, 'PP'
121
+ attach_function LIB, 'aa_currentfont', :font, 'PP'
122
+
123
+ attach_function LIB, 'aa_scrwidth', :scrwidth, 'IP'
124
+ attach_function LIB, 'aa_scrheight', :scrheight, 'IP'
125
+ attach_function LIB, 'aa_imgwidth', :imgwidth, 'IP'
126
+ attach_function LIB, 'aa_imgheight', :imgheight, 'IP'
127
+ attach_function LIB, 'aa_mmwidth', :mmwidth, 'IP'
128
+ attach_function LIB, 'aa_mmheight', :mmheight, 'IP'
129
+
130
+ attach_function LIB, 'aa_fastrender', :fastrender, '0PIIII'
131
+ attach_function LIB, 'aa_render', :render, '0PPIIII'
132
+ attach_function LIB, 'aa_flush', :flush, '0P'
133
+
134
+ attach_function LIB, 'aa_putpixel', :putpixel, '0PIII'
135
+ attach_function LIB, 'aa_puts', :puts, '0PIIIS'
136
+
137
+ attach_function LIB, 'aa_getevent', :getevent, 'IPI'
138
+ attach_function LIB, 'aa_getkey', :getkey, 'IPI'
139
+ attach_function LIB, 'aa_resize', :resize, 'IP'
140
+ attach_function LIB, 'aa_resizehandler', :resize_handler, '0PP'
141
+
142
+ attach_function LIB, 'aa_gotoxy', :gotoxy, '0PII'
143
+ attach_function LIB, 'aa_hidecursor', :hidecursor, '0P'
144
+ attach_function LIB, 'aa_showcursor', :showcursor, '0P'
145
+ attach_function LIB, 'aa_getmouse', :getmouse, '0PPPP'
146
+ attach_function LIB, 'aa_hidemouse', :hidemouse, '0P'
147
+ end
148
+
149
+ # AAlib key code
150
+
151
+ module Key
152
+ RESIZE = 258
153
+ MOUSE = 259
154
+ UP = 300
155
+ DOWN = 301
156
+ LEFT = 302
157
+ RIGHT = 303
158
+ BACKSPACE = 304
159
+ UNKNOWN = 400
160
+ RELEASE = 65536
161
+ end
162
+
163
+ # AAlib mouse events
164
+
165
+ module Mouse
166
+ BUTTON1 = 1
167
+ BUTTON2 = 2
168
+ BUTTON3 = 4
169
+ MOVEMASK = 1
170
+ PRESSMASK = 2
171
+ PRESSEDMOVEMASK = 4
172
+ ALLMASK = 7
173
+ HIDECURSOR = 8
174
+ end
175
+
176
+ SENDRELEASE = 1
177
+ KBDALLMASK = 1
178
+
179
+ # Attribute masks
180
+
181
+ module AttrMask
182
+ NORMAL = 1
183
+ DIM = 2
184
+ BOLD = 4
185
+ BOLDFONT = 8
186
+ REVERSE = 16
187
+ ALL = 128
188
+ EIGHT = 256
189
+ EXTENDED = (ALL|EIGHT)
190
+ end
191
+
192
+ # Text attributes for AAlib#puts and other text methods.
193
+
194
+ module Attr
195
+ NORMAL = 0
196
+ DIM = 1
197
+ BOLD = 2
198
+ BOLDFONT = 3
199
+ REVERSE = 4
200
+ SPECIAL = 5
201
+ end
202
+
203
+ # Dithering modes for AAlib#render.
204
+
205
+ module Dither
206
+ NONE = 0
207
+ ERRORDISTRIB = 1
208
+ FLOYD_S = 2
209
+ DITHERTYPES = 3 # Number of supported types
210
+ end
211
+
212
+ module ArgumentChecks #:nodoc:all
213
+ class << self
214
+ def included(receiver)
215
+ receiver.extend ClassMethods
216
+ end
217
+ end
218
+
219
+ module ClassMethods
220
+ def check_type(arg, argname, expected)
221
+ if not arg.kind_of?(expected)
222
+ msg = "#{argname}: wrong argument type #{arg.class} "
223
+ msg += "(expected #{expected})"
224
+ raise TypeError.new(msg)
225
+ end
226
+ end
227
+
228
+ def check_hardware_params(hp)
229
+ check_type(hp, "hardware_params", AAlib::HardwareParams)
230
+ end
231
+
232
+ def check_render_params(rp)
233
+ check_type(rp, "render_params", AAlib::RenderParams)
234
+ end
235
+ end
236
+ end
237
+
238
+ include ArgumentChecks #:nodoc:
239
+
240
+ class << self
241
+
242
+ # Returns the AAlib command line options help text. Note that the text is
243
+ # formatted to 74 columns.
244
+
245
+ def help
246
+ help = Foreign.help
247
+ help.struct!('S', :text)
248
+ help[:text].to_s
249
+ end
250
+
251
+ # Parse commandline options from the given Array of Strings, _argv_,
252
+ # parsing AAlib options and removing them from the array. Fills in
253
+ # _hardware_params_ and _render_params_ with appropriate values based on
254
+ # the commandline arguments. It is expected that these parameters be used
255
+ # to initialize AAlib and render graphics.
256
+ #
257
+ # Note that this function replaces the strings in the given argv with new,
258
+ # duplicate strings.
259
+
260
+ def parseoptions(hardware_params, render_params, argv=ARGV)
261
+ check_hardware_params(hardware_params)
262
+ check_render_params(render_params)
263
+
264
+ cargv = [$0, argv].flatten # C argv includes $0
265
+
266
+ cargc = DL.malloc(DL.sizeof('I'))
267
+ cargc.struct!('I', :num)
268
+ cargc[:num] = cargv.size
269
+
270
+ r,rs = Foreign.parseoptions(hardware_params, render_params,
271
+ cargc, cargv)
272
+
273
+ if r == 1 # success
274
+ rs[2].struct!('I', :num)
275
+ len = rs[2][:num]
276
+
277
+ newargv = rs[3].to_a('S', len)
278
+ newargv.shift # remove $0 that we added previously
279
+ argv.replace newargv
280
+ true
281
+ else
282
+ false
283
+ end
284
+ end
285
+
286
+ # Initializes AA-lib. Attempts to find an available output driver
287
+ # supporting the optionally specified _hardware_params_. First attempts to
288
+ # initialize the recommended drivers and then in order drivers available in
289
+ # the AAlib.drivers array.
290
+ #
291
+ # Returns an AAlib::Context on success or nil on failure.
292
+
293
+ def autoinit(hardware_params=HardwareParams::DEFAULT)
294
+ check_hardware_params(hardware_params)
295
+
296
+ ptr, garbage = Foreign.autoinit(hardware_params)
297
+ Context.new(ptr)
298
+ end
299
+
300
+ # Initializes AA-lib using the specified _driver_ from AAlib.drivers and
301
+ # optionally specified _hardware_params_. Use _driver_opts_ to pass extra
302
+ # options to a hardware driver. For example, pass an instance of
303
+ # AAlib::SaveData when using AAlib.save_driver. Note that _driver_opts_ is
304
+ # a required argument when using the save driver.
305
+ #
306
+ # Returns an AAlib::Context on success or nil on failure.
307
+
308
+ def init(driver, hardware_params=HardwareParams::DEFAULT, driver_opts=nil)
309
+ check_type(driver, "driver", AAlib::Driver)
310
+
311
+ if driver == AAlib.save_driver
312
+ if driver_opts == nil
313
+ msg = "AAlib::SaveData required as third argument when using save driver"
314
+ raise ArgumentError.new(msg)
315
+ else
316
+ check_type(driver_opts, "driver_opts", AAlib::SaveData)
317
+ end
318
+ end
319
+
320
+ check_hardware_params(hardware_params)
321
+
322
+ ptr, garbage = Foreign.init(driver, hardware_params, driver_opts)
323
+ Context.new(ptr)
324
+ end
325
+
326
+ # Returns array of supported display drivers. See AAlib::Driver.
327
+
328
+ def drivers
329
+ array_from_null_terminated_c(Foreign.drivers, Driver)
330
+ end
331
+
332
+ # Returns array of supported save formats. See AAlib::SaveFormat.
333
+
334
+ def formats
335
+ array_from_null_terminated_c(Foreign.formats, SaveFormat)
336
+ end
337
+
338
+ # Returns an AAlib::Driver for an in-memory context for custom ascii-art
339
+ # output. should be passed to AAlib.init.
340
+
341
+ def memory_driver
342
+ AAlib::Driver.new(Foreign.mem_driver)
343
+ end
344
+
345
+ # Returns an AAlib::Driver for saving a AAlib::Context to disk in various
346
+ # formats; should be passed to AAlib.init.
347
+
348
+ def save_driver
349
+ AAlib::Driver.new(Foreign.save_driver)
350
+ end
351
+
352
+ # Returns a grey value (0-255) approximating the brightness of the given
353
+ # RGB value where _r_, _g_, and _b_ range from 0-255.
354
+
355
+ #def color_from_rgb(r, g, b)
356
+ # (r*30 + g*59 +b*11) >> 8
357
+ #end
358
+
359
+ # Converts a NULL terminated C pointer array into a Ruby array of objects
360
+ # made by talling target_class.new() on each DL::PtrData in the C array.
361
+
362
+ def array_from_null_terminated_c(ptr, target_class) #:nodoc:
363
+ targets = Array.new
364
+ loop do
365
+ ptr.struct!('P', :target)
366
+ target = ptr[:target]
367
+ break unless target
368
+ targets << target_class.new(target)
369
+ ptr += DL.sizeof('P')
370
+ end
371
+ targets
372
+ end
373
+ end
374
+
375
+ # Adds initialization and some other features to DL::PtrData. On
376
+ # initialization, PtrData is defined (struct!) using the given TYPE and NAMES
377
+ # that may be used alone or
378
+ # via StructAccessors.
379
+
380
+ class CPtr < DL::PtrData
381
+ include ArgumentChecks #:nodoc:
382
+ # TYPE = '' # subclasses should define this
383
+
384
+ class << self
385
+
386
+ # Allocates memory based on the class constant TYPE specified in DL style
387
+ # returning an uninitialized CPtr object.
388
+
389
+ def new_ptr
390
+ malloc(DL.sizeof(const_get(:TYPE)))
391
+ end
392
+
393
+ private
394
+
395
+ def struct_field(sym, writable=false)
396
+ define_method(sym) do
397
+ self[sym]
398
+ end
399
+
400
+ if writable
401
+ define_method((sym.to_s + '=').to_sym) do |val|
402
+ self[sym] = val
403
+ end
404
+ end
405
+ end
406
+
407
+ def struct_reader(*syms)
408
+ syms.each { |sym| struct_field(sym) }
409
+ end
410
+
411
+ def struct_accessor(*syms)
412
+ syms.each { |sym| struct_field(sym, true) }
413
+ end
414
+ end
415
+
416
+ # Initializes a CPtr based on the TYPE and NAMES definitions of the current
417
+ # class. If a subclass of CPtr defines the NAMES constant, then _with_ptr_
418
+ # will be initialized using DL::PtrData#struct! with the defined TYPE and
419
+ # NAMES so that members may be accessed using CPtr#[] with string or symbol
420
+ # names.
421
+ #
422
+ # If no _with_ptr_ is given, a new pointer is allocated using the
423
+ # CPtr.new_ptr or the new_ptr method of a subclass of CPtr.
424
+
425
+ def initialize(with_ptr=nil)
426
+ with_ptr ||= self.class.new_ptr
427
+ super(with_ptr)
428
+ if self.class.const_defined? :NAMES
429
+ struct! self.class.const_get(:TYPE), *(self.class.const_get(:NAMES))
430
+ end
431
+ end
432
+
433
+ # When called with integral arguments, returns data from the pointer
434
+ # similar to String#[]. If passed one _int_, returns the integer value of
435
+ # the character stored at that index. If passed two _ints_, returns the
436
+ # substring of length _len_ starting at position _key_.
437
+ #
438
+ # When called with a Symbol or String, returns a DL::PtrData of the denoted
439
+ # member. If a subclass defines TYPE and NAMES and the type of the member
440
+ # is String, it will be returned as a String using DL::PtrData#to_s.
441
+
442
+ def [](key, len=0)
443
+ if key.kind_of? Integer
444
+ if len == 0
445
+ # We'd like this to behave more like String#[] where a single numeric
446
+ # index gets you the char type integer at the given index. Ruby
447
+ # 1.9's DL possible fixes the need for this.
448
+ super(key, 1)[0]
449
+ else
450
+ super(key, len)
451
+ end
452
+ elsif (names = self.class.const_get(:NAMES)) &&
453
+ (type = self.class.const_get(:TYPE)) &&
454
+ (type[(names.index(key)), 1] == 'S')
455
+ super(key).to_s
456
+ else
457
+ super(key)
458
+ end
459
+ end
460
+
461
+ # Identical to DL::PtrData#[]= except that it raises an error on frozen
462
+ # objects.
463
+ #
464
+ # With two _ints_, _key_ and _val_, sets the value at _key_ to _val_. With
465
+ # one _int_ and one _str_ sets the characters begining at _key_ to those of
466
+ # _str_. With two _ints_, _key_ and _num_, and one _str_, copies at most
467
+ # _num_ characters of _str_, extending with zeroes if _len_ is greater than
468
+ # the length of _str_.
469
+ #
470
+ # When called with a Symbol or String, sets the value of the denoted
471
+ # member. A subclass must have defined TYPE and NAMES so that DL::PtrData
472
+ # knows how to handle each member.
473
+
474
+ def []=(key, num, val=nil)
475
+ if frozen?
476
+ @assignment_will_raise_error = true
477
+ elsif val
478
+ super(key, num, val)
479
+ else
480
+ val = num
481
+ super(key, val)
482
+ end
483
+ end
484
+
485
+ def inspect #:nodoc:
486
+ if self.class.const_defined? :NAMES
487
+ values = Array.new
488
+ names = self.class.const_get(:NAMES)
489
+ names.each_with_index do |name, i|
490
+ label = name.to_s
491
+ value = self[name]
492
+ values << "%s=%s" % [label, value.inspect]
493
+ end
494
+
495
+ "#<%s:%x %s>" % [self.class, ~object_id, values.join(' ')]
496
+ else
497
+ super
498
+ end
499
+ end
500
+ end
501
+
502
+ class RenderParams < CPtr
503
+ TYPE = 'IIFIII' #:nodoc:
504
+ NAMES = [:bright, :contrast, :gamma, :dither, :inversion, :randomval] #:nodoc:
505
+
506
+ struct_accessor *NAMES
507
+
508
+ # Notably defined before we redifine initialize() to take no arguments
509
+ DEFAULT = new(Foreign.defrenderparams).freeze
510
+
511
+ def initialize
512
+ super
513
+ copy_from(DEFAULT)
514
+ end
515
+
516
+ def copy_from(other)
517
+ NAMES.each do |get|
518
+ set = (get.to_s + '=').to_sym
519
+ self.send(set, other.send(get))
520
+ end
521
+ end
522
+ end
523
+
524
+ class HardwareParams < CPtr
525
+ TYPE = 'P' + 'I'*11 + 'D'*2 #:nodoc:
526
+ NAMES = [:font, :supported, :minwidth, :minheight,
527
+ :maxwidth, :maxheight, :recwidth, :recheight,
528
+ :mmwidth, :mmheight, :width, :height, :dimmul, :boldmul] #:nodoc:
529
+
530
+ struct_accessor *NAMES
531
+
532
+ # Notably defined before we redifine initialize()
533
+ DEFAULT = new(Foreign.defparams).freeze #:nodoc:
534
+
535
+ def initialize(with_ptr=nil)
536
+ if with_ptr
537
+ super(with_ptr)
538
+ else
539
+ super()
540
+ copy_from(DEFAULT)
541
+ end
542
+ end
543
+
544
+ def copy_from(other)
545
+ NAMES.each do |get|
546
+ set = (get.to_s + '=').to_sym
547
+ self.send(set, other.send(get))
548
+ end
549
+ end
550
+
551
+ def font
552
+ self[:font] ? Font.new(self[:font]) : nil
553
+ end
554
+ end
555
+
556
+ class Font < CPtr
557
+ TYPE = 'PISS' #:nodoc:
558
+ NAMES = [:data, :height, :name, :shortname] #:nodoc:
559
+ struct_reader *(NAMES - [:data])
560
+ end
561
+
562
+ # Read-only output driver. If you wish to pass a driver option to
563
+ # AAlib::Context.init, select a driver from the Array returned by
564
+ # AAlib.drivers.
565
+
566
+ class Driver < CPtr
567
+ TYPE = 'SS' #:nodoc:
568
+ NAMES = [:shortname, :name] #:nodoc:
569
+ struct_reader :shortname, :name
570
+
571
+ def initialize(ptr) #:nodoc:
572
+ super(ptr)
573
+ end
574
+ end
575
+
576
+ # A graphics context opened using a particular driver along with any keyboard
577
+ # and mouse drivers. Two buffers are maintained: the image buffer and the
578
+ # screen or text buffer. AAlib::Context#putpixel is the primary method to
579
+ # draw on the image buffer. AAlib::Context#render converts the image buffer
580
+ # to text, writing the result to the text buffer. Text may be written
581
+ # directly to the text buffer using AAlib::Context#puts and similar. Note
582
+ # that text written this way may be overwritten by AAlib::Context#render.
583
+ # Finally, AAlib::Context#flush writes the text buffer to the screen.
584
+
585
+ class Context < CPtr
586
+ TYPE = 'PPP' + HardwareParams::TYPE*2 + 'IIIIPPPPPPIIIIIIIPPPP' #:nodoc:
587
+
588
+ NAMES = [:driver, :kbddriver, :mousedriver, #:nodoc:
589
+ HardwareParams::NAMES.collect {|sym| ("hardware_params_" + sym.to_s).to_sym },
590
+ HardwareParams::NAMES.collect {|sym| ("driver_hardware_params_" + sym.to_s).to_sym },
591
+ :mulx, :muly, :imgwidth, :imgheight,
592
+ :imagebuffer, :textbuffer, :attrbuffer, :table,
593
+ :filltable, :parameters, :cursorx, :cursory, :cursorstate,
594
+ :mousex, :mousey, :buttons, :mousemode, :resizehandler,
595
+ :driverdata, :kbddriverdata, :mousedriverdata].flatten
596
+
597
+ # Returns the ratio of the widths of the image and text buffers.
598
+ def mulx; end
599
+
600
+ # Returns the ratio of the heights of the image and text buffers.
601
+ def muly; end
602
+
603
+ struct_reader :kbddriver, :parameters, :mulx, :muly
604
+
605
+ # Closes the graphics context and resets the terminal if necessary. Also
606
+ # performs any keyboard and mouse uninitialization.
607
+
608
+ def close
609
+ Foreign.close(self)
610
+ nil
611
+ end
612
+
613
+ # Initializes the keyboard for capture of key press events. _Release_
614
+ # determines whether or not one is interested in key release events as
615
+ # well. Note that key releases are unavailable when using a text terminal
616
+ # (curses or slang drivers).
617
+
618
+ def autoinitkbd(release=false)
619
+ mode = 0
620
+ mode |= SENDRELEASE if release
621
+ r,rs = Foreign.autoinitkbd(self, mode)
622
+ unless r == 1
623
+ raise Error.new("failed to initialize keyboard")
624
+ end
625
+ end
626
+
627
+ def autoinitmouse
628
+ mode = 0
629
+ r,rs = Foreign.autoinitmouse(self, mode)
630
+ unless r == 1
631
+ raise Error.new("failed to initialize mouse")
632
+ end
633
+ end
634
+
635
+ # Returns a HardwareParams object representing the context's current
636
+ # requested hardware params.
637
+
638
+ def hardware_params
639
+ HardwareParams.new(self + DL.sizeof('PPP'))
640
+ end
641
+
642
+ # Returns a HardwareParams object representing the context's current
643
+ # hardware params as reported by the display driver.
644
+
645
+ def driver_hardware_params
646
+ HardwareParams.new(self + DL.sizeof('PPP') + DL.sizeof(HardwareParams::TYPE))
647
+ end
648
+
649
+ # Width of the screen and text buffer in characters.
650
+
651
+ def scrwidth
652
+ Foreign.scrwidth(self)[0]
653
+ end
654
+
655
+ # Height of the screen and text buffer in characters.
656
+
657
+ def scrheight
658
+ Foreign.scrheight(self)[0]
659
+ end
660
+
661
+ # Width of the image buffer in pixels. Note pixels are non-square. Use
662
+ # mmwidth and mmheight to get the size of the screen in millimeters.
663
+
664
+ def imgwidth
665
+ Foreign.imgwidth(self)[0]
666
+ end
667
+
668
+ # Height of the image buffer in pixels. Note pixels are non-square. Use
669
+ # mmwidth and mmheight to get the size of the screen in millimeters.
670
+
671
+ def imgheight
672
+ Foreign.imgheight(self)[0]
673
+ end
674
+
675
+ # Width of the screen in millimeters. Note this gives incorrect values on
676
+ # text terminals where AAlib cannot get screen size or font information.
677
+
678
+ def mmwidth
679
+ Foreign.mmwidth(self)[0]
680
+ end
681
+
682
+ # Height of the screen in millimeters. Note this gives incorrect values on
683
+ # text terminals where AAlib cannot get screen size or font information.
684
+
685
+ def mmheight
686
+ Foreign.mmheight(self)[0]
687
+ end
688
+
689
+ # Returns the editable image buffer as AAlib::CPtr (imgheight rows of
690
+ # imgwidth chars packed into a single string)
691
+
692
+ def image
693
+ CPtr.new(Foreign.image(self)[0])
694
+ end
695
+
696
+ # Returns the editable text buffer as AAlib::CPtr (scrheight rows of
697
+ # scrwidth characters packed in single string). Writes to this buffer will
698
+ # appear on screen after a #flush operation.
699
+
700
+ def text
701
+ CPtr.new(Foreign.text(self)[0])
702
+ end
703
+
704
+ # Returns the editable attr buffer as AAlib::CPtr (scrheight rows of
705
+ # scrwidth chars packed in a single string). Writes to this buffer will
706
+ # modify text attributes on screen after a #flush operation.
707
+
708
+ def attrs
709
+ CPtr.new(Foreign.attrs(self)[0])
710
+ end
711
+
712
+ # Returns the current font.
713
+
714
+ def font
715
+ Font.new(Foreign.font(self)[0])
716
+ end
717
+
718
+ # Converts image buffer coordinates into text buffer coordinates.
719
+
720
+ def img2scr(imgx, imgy)
721
+ [imgx/mulx, imgy/muly]
722
+ end
723
+
724
+ # Converts the image buffer to ASCII on the text buffer. _Renderparams_
725
+ # should be a RenderParams specifying the parameters. Screen coordinates
726
+ # (x1, y1) and (x2, y2) define the column and row of the top left and
727
+ # bottom right corners of the area to be rendered, respectively.
728
+ #
729
+ # Note that #flush must be called to flush to the screen.
730
+ #
731
+ # The first call may take some time as the rendering tables are produced.
732
+
733
+ def render(render_params, x1=0, y1=0, x2=scrwidth, y2=scrheight)
734
+ AAlib.check_render_params(render_params)
735
+
736
+ Foreign.render(self, render_params, x1, y1, x2, y2)
737
+ self
738
+ end
739
+
740
+ # See #render. This method performs a faster render using the default
741
+ # rendering parameters. Screen coordinates (x1, y1) and (x2, y2)
742
+ # define the column and row of the top left and bottom right corners of the
743
+ # area to be rendered, respectively.
744
+
745
+ def fastrender(x1=0, y1=0, x2=scrwidth, y2=scrheight)
746
+ Foreign.fastrender(self, x1, y1, x2, y2)
747
+ self
748
+ end
749
+
750
+ # Flushes the text buffer to the screen, making it visible. Note that the
751
+ # image buffer is transformed to ascii-art and written to the text buffer
752
+ # during #render.
753
+ #
754
+ # When using the save driver, this method causes the file to be written
755
+ # out.
756
+
757
+ def flush
758
+ Foreign.flush(self)
759
+ self
760
+ end
761
+
762
+ # Draw a pixel at _x_,_y_ in image coords in the desired _color_ (0-255).
763
+
764
+ def putpixel(x, y, color)
765
+ Foreign.putpixel(self, x, y, color)
766
+ self
767
+ end
768
+
769
+ # Draw starting at _x_,_y_ in image coords using the pixels from _str_.
770
+
771
+ def putpixels(x, y, str)
772
+ pos = x + y*imgwidth
773
+
774
+ # Sanity check
775
+ if pos > imgwidth*imgheight
776
+ pos = imgwidth*imgheight
777
+ elsif pos < 0
778
+ pos = 0
779
+ end
780
+ if pos+str.length > imgwidth*imgheight
781
+ str = str[0, imgwidth*imgheight - pos]
782
+ end
783
+
784
+ image[pos] = str
785
+ self
786
+ end
787
+
788
+ # Returns pixels from the imagebuffer from _x_,_y_ of length _len_ in
789
+ # image coords and returns them as a String.
790
+
791
+ def pixels(x, y, len)
792
+ pos = x + y*imgwidth
793
+
794
+ # Sanity check
795
+ if pos > imgwidth*imgheight
796
+ pos = imgwidth*imgheight
797
+ elsif pos < 0
798
+ pos = 0
799
+ end
800
+ if pos+len > imgwidth*imgheight
801
+ len = imgwidth*imgheight - pos
802
+ end
803
+
804
+ image[pos, len]
805
+ end
806
+
807
+ # Writes string _str_ at _x_,_y_ in screen coords with attribute _attr_, a
808
+ # AAlib::Attr.
809
+
810
+ def puts(x, y, attr, str)
811
+ Foreign.puts(self, x, y, attr, str)
812
+ self
813
+ end
814
+
815
+ # Text in the box bounded by x1, y1 and x2, y2 in screen coords
816
+ # is converted to an approximation of pixels on the image buffer to
817
+ # facilitate image based manipulation of text.
818
+ #
819
+ # Note that a render operation must have been performed prior to calling
820
+ # this method; this method will not work unless the rendering tables have
821
+ # been calculated.
822
+ #
823
+ # This is a hack based on the backconvert function used in BB, the AA-lib
824
+ # demo. This is the technique used by the pager at the end of BB that
825
+ # fades text in and out when paging. It may not work in different versions
826
+ # of AA-lib as it depends on some details not specified in the API.
827
+ #
828
+ # See also #pixels_from_text
829
+
830
+ def backconvert(x1=0, y1=0, x2=scrwidth, y2=scrheight)
831
+ sw = scrwidth
832
+ size = DL.sizeof('I'*5)
833
+ parameters = self[:parameters]
834
+
835
+ (y1...y2).each do |y| # "..." excludes last value; ".." includes it
836
+ (x1...x2).each do |x|
837
+ pos = x + y*sw
838
+ # n a unique int representing a char + attributes (ascii values range
839
+ # from 0-255; adding 256*a where a is 0,1,2,3,4 (no SPECIAL) makes 5
840
+ # pixel value for each ascii char depending on the attribute) and we
841
+ # somehow use that backwards in a lookup table
842
+ attr = attrs[pos, 1][0]
843
+ attr = Attr::REVERSE if attr == Attr::SPECIAL # Can't backconv SPECIAL
844
+ n = text[pos, 1][0] + 256*attr
845
+
846
+ p = parameters[n*size, size].unpack('I*')
847
+
848
+ putpixel(x*2, y*2, p[1])
849
+ putpixel(x*2+1, y*2, p[0])
850
+ putpixel(x*2, y*2+1, p[3])
851
+ putpixel(x*2+1, y*2+1, p[2])
852
+ end
853
+ end
854
+ self
855
+ end
856
+
857
+ # Returns two pixel-strings that, when drawn on the image buffer one above
858
+ # the other, will render to a rough approximation of the text of length
859
+ # _len_ currently on the text buffer at _x_, _y_ in screen coords. In
860
+ # other words, this converts text into pixels that may be painted and
861
+ # otherwise manipulated on the image buffer.
862
+ #
863
+ # Note that a render operation must have been performed prior to calling
864
+ # this method; this method will not work unless the rendering tables have
865
+ # been calculated.
866
+ #
867
+ # See also #backconvert
868
+
869
+ def pixels_from_text(x, y, len)
870
+ pos = x + y*scrwidth
871
+
872
+ # Sanity check
873
+ if pos > scrwidth*scrheight
874
+ pos = scrwidth*scrheight
875
+ elsif pos < 0
876
+ pos = 0
877
+ end
878
+ if pos+len > scrwidth*scrheight
879
+ len = scrwidth*scrheight - pos
880
+ end
881
+
882
+ sw = scrwidth
883
+ size = DL.sizeof('I'*5)
884
+ parameters = self[:parameters]
885
+
886
+ bytes1 = " "*len*2
887
+ bytes2 = " "*len*2
888
+
889
+ len.times do |i|
890
+ pos = i + x + y*sw
891
+ # n is a unique int representing a char with attributes (ascii values range
892
+ # from 0-255; adding 256*a where a is 0,1,2,3,4 (no SPECIAL) makes 5
893
+ # pixel value for each ascii char, one for each possible attribute) and we
894
+ # somehow use that backwards in a lookup table
895
+ attr = attrs[pos, 1][0]
896
+ attr = Attr::REVERSE if attr == Attr::SPECIAL # Can't backconv SPECIAL
897
+ n = text[pos, 1][0] + 256*attr
898
+
899
+ p = parameters[n*size, size].unpack('I*')
900
+
901
+ # These indices make no sense I know, but that's how aalib has them. I
902
+ # can't decipher the reason, but I'm sure it's a good one.
903
+ bytes1[i*2] = p[1]
904
+ bytes1[i*2+1] = p[0]
905
+ bytes2[i*2] = p[3]
906
+ bytes2[i*2+1] = p[2]
907
+ end
908
+
909
+ [bytes1, bytes2]
910
+ end
911
+
912
+ # Register a block to handle screen resize events. The block will be
913
+ # called with the Context when the screen size is changed and should
914
+ # perform any redrawing necessary.
915
+
916
+ def handle_resize
917
+ handler = DL.callback('0P') do |ptr|
918
+ resize
919
+ yield self
920
+ end
921
+ Foreign.resize_handler(self, handler)
922
+ end
923
+
924
+ # Gets the next event. If _wait_ is true, then #getevent will wait until
925
+ # an event occurs, otherwise it will return immediately.
926
+
927
+ def getevent(wait=true)
928
+ check_kbd_init
929
+ cwait = wait ? 1 : 0
930
+ r,rs = Foreign.getevent(self, cwait)
931
+ r
932
+ end
933
+
934
+ # Gets the next key event. If _wait_ is true, then #getevent will wait
935
+ # until an event occurs, otherwise it will return immediately.
936
+
937
+ def getkey(wait=true)
938
+ check_kbd_init
939
+ cwait = wait ? 1 : 0
940
+ r,rs = Foreign.getkey(self, cwait)
941
+ r
942
+ end
943
+
944
+ # Moves the hardware cursor (if any) to position _x_, _y_ in screen coords.
945
+ # To see the effect, #flush must be called.
946
+
947
+ def gotoxy(x, y)
948
+ Foreign.gotoxy(self, x, y)
949
+ self
950
+ end
951
+
952
+ # Hides the hardware cursor (if any). Returns self.
953
+
954
+ def hidecursor
955
+ Foreign.hidecursor(self)
956
+ self
957
+ end
958
+
959
+ # Shoes the hardware cursor (if any). Returns self.
960
+
961
+ def showcursor
962
+ Foreign.showcursor(self)
963
+ self
964
+ end
965
+
966
+ # Returns an array of three values: the x,y location of the mouse in screen
967
+ # coords and the button mask of the mouse.
968
+
969
+ def getmouse
970
+ Foreign.getmouse(self, x, y, b)
971
+ self
972
+ end
973
+
974
+ # Hides the mouse pointer (if any). Returns self.
975
+
976
+ def hidemouse
977
+ Foreign.hidemouse(self)
978
+ self
979
+ end
980
+
981
+ # Resizes the image and text buffers and performs any other necessary
982
+ # updates after a resize event. This is automaically called prior to
983
+ # running any resize handler.
984
+
985
+ def resize
986
+ Foreign.resize(self)
987
+ self
988
+ end
989
+
990
+ # Copies the image buffer from _other_ to the image buffer of _self_. Both
991
+ # image buffers should be the same size.
992
+
993
+ def copy_image_from(other)
994
+ imgheight.times do |row|
995
+ image[row*imgwidth] = other.image[other.imgwidth*row, imgwidth]
996
+ end
997
+ end
998
+
999
+ private
1000
+
1001
+ def check_kbd_init
1002
+ unless kbddriver
1003
+ raise Error.new("cannot get events with uninitialized keyboard")
1004
+ end
1005
+ end
1006
+ end
1007
+
1008
+ # Output format used by AAlib.save
1009
+
1010
+ class SaveFormat < CPtr
1011
+ TYPE = 'IIIIIIPSS' #:nodoc:
1012
+ NAMES = [:width, :height, :pagewidth, :pageheight,
1013
+ :flags, :supported, :font, :formatname, :extension] #:nodoc:
1014
+
1015
+ # Flag to enable multiple pages
1016
+ USE_PAGES = 1
1017
+ # Flag to use normal spaces (?)
1018
+ NORMAL_SPACES = 8
1019
+
1020
+ # Returns the name of the format
1021
+ def formatname; end
1022
+
1023
+ # Returns the file extension including the dot.
1024
+ def extension; end
1025
+
1026
+ struct_accessor :width, :height, :pagewidth, :pageheight
1027
+ struct_accessor :flags, :supported
1028
+ struct_reader :formatname, :extension
1029
+
1030
+ class << self
1031
+ # Searches available save formats and returns the first that includes
1032
+ # _pattern_, a Regexp or String. Raises exception if none found.
1033
+
1034
+ def find(pattern)
1035
+ if pattern.kind_of? Regexp
1036
+ regexp = pattern
1037
+ else
1038
+ regexp = /#{pattern}/i
1039
+ end
1040
+
1041
+ AAlib.formats.each do |format|
1042
+ return format if format.formatname =~ regexp
1043
+ end
1044
+
1045
+ raise AAlib::Error.new("Could not find output format matching #{pattern}")
1046
+ end
1047
+ end
1048
+ end
1049
+
1050
+ # Encapsulation of data required to save a file to disk.
1051
+
1052
+ class SaveData < CPtr
1053
+ TYPE = 'SPP' #:nodoc:
1054
+ NAMES = [:name, :format, :file] #:nodoc:
1055
+
1056
+ # Returns the file name that will be used for saving.
1057
+ def name; end
1058
+
1059
+ struct_accessor :name, :format
1060
+
1061
+ # Initializes a new SaveData to save to file _name_ with SaveFormat
1062
+ # _format_. Passing a string _format_ uses the format returned by
1063
+ # AAlib::SaveFormat.find. See AAlib::SaveFormat for options set with
1064
+ # flags; multiple flags should be logically OR'ed.
1065
+ #
1066
+ # The AA-lib docs claim that file extensions are added automatically, but
1067
+ # this appears to be false.
1068
+
1069
+ def initialize(name, format, formatflags=0)
1070
+ super()
1071
+
1072
+ self.name = name
1073
+
1074
+ if format.kind_of? AAlib::SaveFormat
1075
+ self.format = format
1076
+ else
1077
+ self.format = AAlib::SaveFormat.find(format)
1078
+ end
1079
+
1080
+ self.format.flags = formatflags
1081
+ end
1082
+
1083
+ # Returns the AAlib::SaveFormat that will be used for saving
1084
+ def format
1085
+ SaveFormat.new(self[:format])
1086
+ end
1087
+ end
1088
+ end