source: libcaca/trunk/ruby/cucul-canvas.c @ 2020

Last change on this file since 2020 was 2009, checked in by Pascal Terjan, 12 years ago
  • Finish Cucul::Dither
  • Property svn:eol-style set to native
File size: 17.7 KB
Line 
1/*
2 *  libcucul Ruby bindings
3 *  Copyright (c) 2007 Pascal Terjan <pterjan@linuxfr.org>
4 *
5 *  This library is free software. It comes without any warranty, to
6 *  the extent permitted by applicable law. You can redistribute it
7 *  and/or modify it under the terms of the Do What The Fuck You Want
8 *  To Public License, Version 2, as published by Sam Hocevar. See
9 *  http://sam.zoy.org/wtfpl/COPYING for more details.
10 */
11
12#include <ruby.h>
13#include <cucul.h>
14#include <errno.h>
15#include "cucul-dither.h"
16#include "cucul-font.h"
17#include "common.h"
18
19VALUE cCanvas;
20
21#define simple_func(x)                                  \
22static VALUE x (VALUE self)                             \
23{                                                       \
24    if( cucul_##x (_SELF) <0)                           \
25        rb_raise(rb_eRuntimeError, strerror(errno));    \
26                                                        \
27    return self;                                        \
28}
29
30#define get_int(x)                                      \
31static VALUE get_##x (VALUE self)                       \
32{                                                       \
33    return INT2NUM(cucul_get_##x (_SELF));              \
34}
35
36static void canvas_free(void * p)
37{
38    cucul_free_canvas((cucul_canvas_t *)p);
39}
40
41static VALUE canvas_alloc(VALUE klass)
42{
43    cucul_canvas_t *canvas;
44    VALUE obj;
45   
46    canvas = cucul_create_canvas(0, 0);
47    obj = Data_Wrap_Struct(klass, 0, canvas_free, canvas);
48   
49    return obj;
50}
51
52static VALUE canvas_initialize(VALUE self, VALUE width, VALUE height)
53{
54    unsigned int w, h;
55
56    w = NUM2INT(width);
57    h = NUM2INT(height);
58   
59    cucul_set_canvas_size(_SELF, w, h);
60   
61    return self;
62}
63
64get_int(canvas_height)
65get_int(canvas_width)
66
67static VALUE set_canvas_width(VALUE self, VALUE width)
68{
69    cucul_set_canvas_size(_SELF, NUM2INT(width), cucul_get_canvas_height(_SELF));
70    return width;
71}
72
73static VALUE set_canvas_width2(VALUE self, VALUE width)
74{
75    set_canvas_width(self, width);
76    return self;
77}
78
79static VALUE set_canvas_height(VALUE self, VALUE height)
80{
81    cucul_set_canvas_size(_SELF, cucul_get_canvas_width(_SELF), NUM2INT(height));
82    return height;
83}
84
85static VALUE set_canvas_height2(VALUE self, VALUE height)
86{
87    set_canvas_height(self, height);
88    return self;
89}
90
91static VALUE set_canvas_size(VALUE self, VALUE height, VALUE width)
92{
93    cucul_set_canvas_size(_SELF, NUM2INT(width), NUM2INT(height));
94    return self;
95}
96
97/****/
98
99static VALUE gotoxy(VALUE self, VALUE x, VALUE y)
100{
101    if( cucul_gotoxy(_SELF, NUM2INT(x), NUM2INT(y)) <0) {
102        rb_raise(rb_eRuntimeError, strerror(errno));
103    }
104    return self;
105}
106
107get_int(cursor_x)
108get_int(cursor_y)
109
110simple_func(clear_canvas)
111
112static VALUE put_char(VALUE self, VALUE x, VALUE y, VALUE ch)
113{
114    cucul_put_char(_SELF, NUM2INT(x), NUM2INT(y), NUM2ULONG(ch));
115    return self;
116}
117
118static VALUE get_char(VALUE self, VALUE x, VALUE y)
119{
120    unsigned long int ch;
121    ch = cucul_get_char(_SELF, NUM2INT(x), NUM2INT(y));
122    return INT2NUM(ch);
123}
124
125static VALUE put_str(VALUE self, VALUE x, VALUE y, VALUE str)
126{
127    cucul_put_str(_SELF, NUM2INT(x), NUM2INT(y), StringValuePtr(str));
128    return self;
129}
130
131static VALUE get_attr(VALUE self, VALUE x, VALUE y)
132{
133    unsigned long int ch;
134    ch = cucul_get_attr(_SELF, NUM2INT(x), NUM2INT(y));
135    return INT2NUM(ch);
136}
137
138static VALUE set_attr(VALUE self, VALUE attr)
139{
140    if(cucul_set_attr(_SELF, NUM2ULONG(attr)) <0)
141        rb_raise(rb_eRuntimeError, strerror(errno));
142
143    return self;
144}
145
146static VALUE set_attr2(VALUE self, VALUE attr)
147{
148    set_attr(self, attr);
149    return self;
150}
151
152static VALUE put_attr(VALUE self, VALUE x, VALUE y, VALUE attr)
153{
154    if(cucul_put_attr(_SELF, NUM2INT(x), NUM2INT(y), NUM2ULONG(attr)) <0)
155        rb_raise(rb_eRuntimeError, strerror(errno));
156
157    return self;
158}
159
160static VALUE set_color_ansi(VALUE self, VALUE fg, VALUE bg)
161{
162    if(cucul_set_color_ansi(_SELF, NUM2INT(fg), NUM2INT(bg)) <0)
163        rb_raise(rb_eRuntimeError, strerror(errno));
164
165    return self;
166}
167
168static VALUE set_color_argb(VALUE self, VALUE fg, VALUE bg)
169{
170    if(cucul_set_color_argb(_SELF, NUM2UINT(fg), NUM2UINT(bg)) <0) {
171        rb_raise(rb_eRuntimeError, strerror(errno));
172    }
173    return self;
174}
175
176static VALUE cprintf(int argc, VALUE* argv, VALUE self)
177{
178    int x, y;
179    VALUE rx, ry, format, rest, string;
180    rb_scan_args(argc, argv, "3*", &rx, &ry, &format, &rest);
181    x = NUM2INT(rx);
182    y = NUM2INT(ry);
183    string = rb_funcall2(rb_mKernel, rb_intern("sprintf"), argc-2, argv+2);
184    cucul_put_str(_SELF, x, y, StringValuePtr(string));
185    return self;
186}
187
188
189get_int(canvas_handle_x)
190get_int(canvas_handle_y)
191
192static VALUE set_canvas_handle(VALUE self, VALUE x, VALUE y)
193{
194    cucul_set_canvas_handle(_SELF, NUM2INT(x), NUM2INT(y));
195    return self;
196}
197
198static VALUE blit(int argc, VALUE* argv, VALUE self) {
199    VALUE x, y, src, mask;
200    cucul_canvas_t *csrc, *cmask;
201
202    rb_scan_args(argc, argv, "31", &x, &y, &src, &mask);
203
204    Check_Type(x, T_FIXNUM);
205    Check_Type(y, T_FIXNUM);
206
207    if(CLASS_OF(src) != cCanvas)
208    {
209        rb_raise(rb_eArgError, "src is not a Cucul::Canvas");
210    }
211    Data_Get_Struct(src, cucul_canvas_t, csrc);
212
213    if(!NIL_P(mask))
214    {
215        if(CLASS_OF(mask) != cCanvas)
216        {
217            rb_raise(rb_eArgError, "mask is not a Cucul::Canvas");
218        }
219        Data_Get_Struct(mask, cucul_canvas_t, cmask);
220    }
221    else
222        cmask = NULL;
223   
224    if(cucul_blit(_SELF, NUM2INT(x), NUM2INT(y), csrc, cmask)<0)
225        rb_raise(rb_eRuntimeError, strerror(errno));
226
227    return self;
228}
229
230static VALUE set_canvas_boundaries(VALUE self, VALUE x, VALUE y, VALUE w, VALUE h)
231{
232    if(cucul_set_canvas_boundaries(_SELF, NUM2INT(x), NUM2INT(y), NUM2UINT(w), NUM2UINT(h))<0)
233    {
234        rb_raise(rb_eRuntimeError, strerror(errno));
235    }
236    return self;
237}
238
239/****/
240
241simple_func(invert)
242simple_func(flip)
243simple_func(flop)
244simple_func(rotate_180)
245simple_func(rotate_left)
246simple_func(rotate_right)
247simple_func(stretch_left)
248simple_func(stretch_right)
249
250/****/
251
252static VALUE draw_line(VALUE self, VALUE x1, VALUE y1, VALUE x2, VALUE y2, VALUE ch)
253{
254    cucul_draw_line(_SELF, NUM2INT(x1), NUM2INT(y1), NUM2INT(x2), NUM2INT(y2),NUM2ULONG(ch));
255    return self;
256}
257
258static VALUE draw_polyline(VALUE self, VALUE points, VALUE ch)
259{
260    int i, n;
261    int *ax, *ay;
262    int error = 0;
263    VALUE v, x, y;
264
265    n = RARRAY(points)->len;
266
267    ax = (int*)malloc(n*sizeof(int));
268    if(!ax)
269        rb_raise(rb_eNoMemError,"Out of memory");
270
271    ay = (int*)malloc(n*sizeof(int));
272    if(!ay)
273    {
274        free(ax);
275        rb_raise(rb_eNoMemError,"Out of memory");
276    }
277
278    for(i=0; i<n; i++)
279    {
280        v = rb_ary_entry(points, i);
281        if((TYPE(v) == T_ARRAY) && (RARRAY(v)->len == 2))
282        {
283            x = rb_ary_entry(v,0);
284            y = rb_ary_entry(v,1);
285            if(rb_obj_is_kind_of(x, rb_cInteger) &&
286               rb_obj_is_kind_of(y, rb_cInteger))
287            {
288                ax[i] = NUM2INT(x);
289                ay[i] = NUM2INT(y);
290            } else
291                error = 1;
292        }
293        else
294            error = 1;
295    }
296
297    if(error)
298    {
299        free(ax);
300        free(ay);
301        rb_raise(rb_eArgError, "Invalid list of points");
302    }
303
304    n--;
305
306    cucul_draw_polyline(_SELF, ax, ay, n, NUM2ULONG(ch));
307
308    free(ax);
309    free(ay);
310
311    return self;
312}
313
314static VALUE draw_thin_line(VALUE self, VALUE x1, VALUE y1, VALUE x2, VALUE y2)
315{
316    cucul_draw_thin_line(_SELF, NUM2INT(x1), NUM2INT(y1), NUM2INT(x2), NUM2INT(y2));
317    return self;
318}
319
320static VALUE draw_thin_polyline(VALUE self, VALUE points)
321{
322    int i, n;
323    int *ax, *ay;
324    int error = 0;
325    VALUE v, x, y;
326
327    n = RARRAY(points)->len;
328
329    ax = (int*)malloc(n*sizeof(int));
330    if(!ax)
331        rb_raise(rb_eNoMemError,"Out of memory");
332
333    ay = (int*)malloc(n*sizeof(int));
334    if(!ay)
335    {
336        free(ax);
337        rb_raise(rb_eNoMemError,"Out of memory");
338    }
339
340    for(i=0; i<n; i++)
341    {
342        v = rb_ary_entry(points, i);
343        if((TYPE(v) == T_ARRAY) && (RARRAY(v)->len == 2))
344        {
345            x = rb_ary_entry(v,0);
346            y = rb_ary_entry(v,1);
347            if(rb_obj_is_kind_of(x, rb_cInteger) &&
348               rb_obj_is_kind_of(y, rb_cInteger))
349            {
350                ax[i] = NUM2INT(x);
351                ay[i] = NUM2INT(y);
352            } else
353                error = 1;
354        }
355        else
356            error = 1;
357    }
358
359    if(error)
360    {
361        free(ax);
362        free(ay);
363        rb_raise(rb_eArgError, "Invalid list of points");
364    }
365
366    n--;
367
368    cucul_draw_thin_polyline(_SELF, ax, ay, n);
369
370    free(ax);
371    free(ay);
372
373    return self;
374}
375
376static VALUE draw_circle(VALUE self, VALUE x, VALUE y, VALUE r, VALUE ch)
377{
378    cucul_draw_circle(_SELF, NUM2INT(x), NUM2INT(y), NUM2INT(r), NUM2ULONG(ch));
379    return self;
380}
381
382static VALUE draw_ellipse(VALUE self, VALUE x, VALUE y, VALUE a, VALUE b, VALUE ch)
383{
384    cucul_draw_ellipse(_SELF, NUM2INT(x), NUM2INT(y), NUM2INT(a), NUM2INT(b), NUM2ULONG(ch));
385    return self;
386}
387
388static VALUE draw_thin_ellipse(VALUE self, VALUE x, VALUE y, VALUE a, VALUE b)
389{
390    cucul_draw_thin_ellipse(_SELF, NUM2INT(x), NUM2INT(y), NUM2INT(a), NUM2INT(b));
391    return self;
392}
393
394static VALUE fill_ellipse(VALUE self, VALUE x, VALUE y, VALUE a, VALUE b, VALUE ch)
395{
396    cucul_fill_ellipse(_SELF, NUM2INT(x), NUM2INT(y), NUM2INT(a), NUM2INT(b), NUM2ULONG(ch));
397    return self;
398}
399
400static VALUE draw_box(VALUE self, VALUE x1, VALUE y1, VALUE x2, VALUE y2, VALUE ch)
401{
402    cucul_draw_box(_SELF, NUM2INT(x1), NUM2INT(y1), NUM2INT(x2), NUM2INT(y2), NUM2ULONG(ch));
403    return self;
404}
405
406static VALUE draw_thin_box(VALUE self, VALUE x1, VALUE y1, VALUE x2, VALUE y2)
407{
408    cucul_draw_thin_box(_SELF, NUM2INT(x1), NUM2INT(y1), NUM2INT(x2), NUM2INT(y2));
409    return self;
410}
411
412static VALUE draw_cp437_box(VALUE self, VALUE x1, VALUE y1, VALUE x2, VALUE y2)
413{
414    cucul_draw_cp437_box(_SELF, NUM2INT(x1), NUM2INT(y1), NUM2INT(x2), NUM2INT(y2));
415    return self;
416}
417
418static VALUE fill_box(VALUE self, VALUE x1, VALUE y1, VALUE x2, VALUE y2, VALUE ch)
419{
420    cucul_fill_box(_SELF, NUM2INT(x1), NUM2INT(y1), NUM2INT(x2), NUM2INT(y2), NUM2ULONG(ch));
421    return self;
422}
423
424static VALUE draw_triangle(VALUE self, VALUE x1, VALUE y1, VALUE x2, VALUE y2, VALUE x3, VALUE y3, VALUE ch)
425{
426    cucul_draw_triangle(_SELF, NUM2INT(x1), NUM2INT(y1), NUM2INT(x2), NUM2INT(y2),  NUM2INT(x3), NUM2INT(y3), NUM2ULONG(ch));
427    return self;
428}
429
430static VALUE draw_thin_triangle(VALUE self, VALUE x1, VALUE y1, VALUE x2, VALUE y2, VALUE x3, VALUE y3)
431{
432    cucul_draw_thin_triangle(_SELF, NUM2INT(x1), NUM2INT(y1), NUM2INT(x2), NUM2INT(y2),  NUM2INT(x3), NUM2INT(y3));
433    return self;
434}
435
436static VALUE fill_triangle(VALUE self, VALUE x1, VALUE y1, VALUE x2, VALUE y2, VALUE x3, VALUE y3, VALUE ch)
437{
438    cucul_fill_triangle(_SELF, NUM2INT(x1), NUM2INT(y1), NUM2INT(x2), NUM2INT(y2),  NUM2INT(x3), NUM2INT(y3), NUM2ULONG(ch));
439    return self;
440}
441
442static VALUE dither_bitmap(VALUE self, VALUE x, VALUE y, VALUE w, VALUE h, VALUE d, VALUE pixels)
443{
444    if(CLASS_OF(d) != cDither)
445        rb_raise(rb_eArgError, "d is not a Cucul::Dither");
446    Check_Type(pixels, T_STRING);
447
448    cucul_dither_bitmap(_SELF, NUM2INT(x), NUM2INT(y), NUM2INT(w), NUM2INT(h), DATA_PTR(d), StringValuePtr(pixels));
449    return self;
450}
451
452/****/
453
454get_int(frame_count)
455
456static VALUE set_frame(VALUE self, VALUE id)
457{
458    if(cucul_set_frame(_SELF, NUM2INT(id))<0)
459        rb_raise(rb_eArgError, strerror(errno));
460
461    return self;
462}
463
464static VALUE set_frame2(VALUE self, VALUE id)
465{
466    set_frame(self, id);
467    return self;
468}
469
470static VALUE get_frame_name(VALUE self)
471{
472    return rb_str_new2(cucul_get_frame_name(_SELF));
473}
474
475static VALUE set_frame_name(VALUE self, VALUE name)
476{
477    if(cucul_set_frame_name(_SELF, StringValuePtr(name))<0)
478        rb_raise(rb_eRuntimeError, strerror(errno));
479
480    return self;
481}
482
483static VALUE set_frame_name2(VALUE self, VALUE name)
484{
485    set_frame_name(self, name);
486    return self;
487}
488
489static VALUE create_frame(VALUE self, VALUE id)
490{
491    if(cucul_create_frame(_SELF, NUM2INT(id))<0) {
492        rb_raise(rb_eRuntimeError, strerror(errno));
493    }
494    return self;
495}
496
497static VALUE free_frame(VALUE self, VALUE id)
498{
499    if(cucul_free_frame(_SELF, NUM2INT(id))<0) {
500        rb_raise(rb_eArgError, strerror(errno));
501    }
502    return self;
503}
504
505/****/
506
507static VALUE render_canvas(VALUE self, VALUE font, VALUE width, VALUE height, VALUE pitch)
508{
509    void *buf;
510    cucul_font_t *f;
511    VALUE b;
512
513    if(CLASS_OF(font) != cFont)
514    {
515        rb_raise(rb_eArgError, "First argument is not a Cucul::Font");
516    }
517
518    buf = malloc(width*height*4);
519    if(buf == NULL)
520    {
521        rb_raise(rb_eNoMemError, "Out of memory");
522    }
523
524    f = DATA_PTR(font);
525    cucul_render_canvas(_SELF, f, buf, NUM2UINT(width), NUM2UINT(height), NUM2UINT(pitch));
526
527    b = rb_str_new(buf, width*height*4);
528    free(buf);
529    return b;
530}
531
532static VALUE import_memory(VALUE self, VALUE data, VALUE format)
533{
534    long int bytes;
535    bytes = cucul_import_memory (_SELF, StringValuePtr(data), RSTRING(StringValue(data))->len, StringValuePtr(format));
536    if(bytes <= 0)
537        rb_raise(rb_eRuntimeError, strerror(errno));
538
539    return self;
540}
541
542static VALUE import_file(VALUE self, VALUE filename, VALUE format)
543{
544    long int bytes;
545    bytes = cucul_import_file (_SELF, StringValuePtr(filename), StringValuePtr(format));
546    if(bytes <= 0)
547        rb_raise(rb_eRuntimeError, strerror(errno));
548
549    return self;
550}
551
552static VALUE export_memory(VALUE self, VALUE format)
553{
554    unsigned long int bytes;
555    void *result;
556    VALUE ret;
557    result = cucul_export_memory (_SELF, StringValuePtr(format), &bytes);
558    ret = rb_str_new(result, bytes);
559    free(result);
560    return ret;
561}
562
563get_singleton_double_list(export)
564get_singleton_double_list(import)
565
566/****/
567
568void Init_cucul_canvas(VALUE mCucul)
569{
570    cCanvas = rb_define_class_under(mCucul, "Canvas", rb_cObject);
571    rb_define_alloc_func(cCanvas, canvas_alloc);
572
573    rb_define_method(cCanvas, "initialize", canvas_initialize, 2);
574    rb_define_method(cCanvas, "width", get_canvas_width, 0);
575    rb_define_method(cCanvas, "width=", set_canvas_width, 1);
576    rb_define_method(cCanvas, "set_width", set_canvas_width2, 1);
577    rb_define_method(cCanvas, "height", get_canvas_height, 0);
578    rb_define_method(cCanvas, "height=", set_canvas_height, 1);
579    rb_define_method(cCanvas, "set_height", set_canvas_height2, 1);
580    rb_define_method(cCanvas, "set_size", set_canvas_size, 2);
581       
582    rb_define_method(cCanvas, "gotoxy", gotoxy, 2);
583    rb_define_method(cCanvas, "cursor_x", get_cursor_x, 0);
584    rb_define_method(cCanvas, "cursor_y", get_cursor_y, 0);
585    rb_define_method(cCanvas, "handle_x", get_canvas_handle_x, 0);
586    rb_define_method(cCanvas, "handle_y", get_canvas_handle_y, 0);
587    rb_define_method(cCanvas, "set_handle", set_canvas_handle, 2);
588    rb_define_method(cCanvas, "blit", blit, -1);
589    rb_define_method(cCanvas, "set_boundaries", set_canvas_boundaries, 4);
590
591    rb_define_method(cCanvas, "clear", clear_canvas, 0);
592   
593    rb_define_method(cCanvas, "put_char", put_char, 3);
594    rb_define_method(cCanvas, "get_char", get_char, 2);
595    rb_define_method(cCanvas, "put_str", put_str, 3);
596    rb_define_method(cCanvas, "printf", cprintf, -1);
597
598    rb_define_method(cCanvas, "get_attr", get_attr, 3);
599    rb_define_method(cCanvas, "attr=", set_attr, 1);
600    rb_define_method(cCanvas, "set_attr", set_attr2, 1);
601    rb_define_method(cCanvas, "put_attr", put_attr, 3);
602    rb_define_method(cCanvas, "set_color_ansi", set_color_ansi, 2);
603    rb_define_method(cCanvas, "set_color_argb", set_color_argb, 2);
604
605    rb_define_method(cCanvas, "invert", invert, 0);
606    rb_define_method(cCanvas, "flip", flip, 0);
607    rb_define_method(cCanvas, "flop", flop, 0);
608    rb_define_method(cCanvas, "rotate_180", rotate_180, 0);
609    rb_define_method(cCanvas, "rotate_left", rotate_left, 0);
610    rb_define_method(cCanvas, "rotate_right", rotate_right, 0);
611    rb_define_method(cCanvas, "stretch_left", stretch_left, 0);
612    rb_define_method(cCanvas, "stretch_right", stretch_right, 0);
613
614    rb_define_method(cCanvas, "draw_line", draw_line, 5);
615    rb_define_method(cCanvas, "draw_polyline", draw_polyline, 2);
616    rb_define_method(cCanvas, "draw_thin_line", draw_thin_line, 4);
617    rb_define_method(cCanvas, "draw_thin_polyline", draw_thin_polyline, 1);
618    rb_define_method(cCanvas, "draw_circle", draw_circle, 4);
619    rb_define_method(cCanvas, "draw_ellipse", draw_ellipse, 5);
620    rb_define_method(cCanvas, "draw_thin_ellipse", draw_thin_ellipse, 4);
621    rb_define_method(cCanvas, "fill_ellipse", fill_ellipse, 5);
622    rb_define_method(cCanvas, "draw_box", draw_box, 5);
623    rb_define_method(cCanvas, "draw_thin_box", draw_thin_box, 4);
624    rb_define_method(cCanvas, "draw_cp437_box", draw_cp437_box, 4);
625    rb_define_method(cCanvas, "fill_box", fill_box, 5);
626    rb_define_method(cCanvas, "draw_triangle", draw_triangle, 7);
627    rb_define_method(cCanvas, "draw_thin_triangle", draw_thin_triangle, 6);
628    rb_define_method(cCanvas, "fill_triangle", fill_triangle, 7);
629    rb_define_method(cCanvas, "dither_bitmap", dither_bitmap, 6);
630
631    rb_define_method(cCanvas, "frame_count", get_frame_count, 0);
632    rb_define_method(cCanvas, "frame=", set_frame, 1);
633    rb_define_method(cCanvas, "set_frame", set_frame2, 1);
634    rb_define_method(cCanvas, "frame_name", get_frame_name, 0);
635    rb_define_method(cCanvas, "frame_name=", set_frame_name, 1);
636    rb_define_method(cCanvas, "set_frame_name", set_frame_name2, 1);
637    rb_define_method(cCanvas, "create_frame", create_frame, 1);
638    rb_define_method(cCanvas, "free_frame", free_frame, 1);
639
640    rb_define_method(cCanvas, "render", render_canvas, 4);
641    rb_define_method(cCanvas, "import_memory", import_memory, 2);
642    rb_define_method(cCanvas, "import_file", import_file, 2);
643    rb_define_method(cCanvas, "export_memory", export_memory, 1);
644    rb_define_singleton_method(cCanvas, "export_list", export_list, 0);
645    rb_define_singleton_method(cCanvas, "import_list", import_list, 0);
646   
647}
648
Note: See TracBrowser for help on using the repository browser.