aalib-ruby 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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