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,95 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ Spec::Runner.configure do |config|
4
+ config.before :each do
5
+ @aa = AAlib.init(AAlib.memory_driver)
6
+ end
7
+
8
+ config.after :each do
9
+ @aa.close if @aa
10
+ end
11
+ end
12
+
13
+ describe "AAlib::Context#mulx" do
14
+ it "returns the ratio of image and screen widths" do
15
+ @aa.mulx.should == @aa.imgwidth / @aa.scrwidth
16
+ end
17
+ end
18
+
19
+ describe "AAlib::Context#muly" do
20
+ it "returns the ratio of image and screen heights" do
21
+ @aa.mulx.should == @aa.imgwidth / @aa.scrwidth
22
+ end
23
+ end
24
+
25
+ describe "AAlib::Context#imgwidth" do
26
+ it "returns the width of the image buffer" do
27
+ hp = AAlib::HardwareParams.new
28
+ hp.width = 100
29
+ aa = AAlib.init(AAlib.memory_driver, hp)
30
+ begin
31
+ aa.imgwidth.should == 100 * aa.mulx
32
+ ensure
33
+ aa.close
34
+ end
35
+ end
36
+ end
37
+
38
+ describe "AAlib::Context#imgheight" do
39
+ it "returns the width of the image buffer" do
40
+ hp = AAlib::HardwareParams.new
41
+ hp.height = 100
42
+ aa = AAlib.init(AAlib.memory_driver, hp)
43
+ begin
44
+ aa.imgheight.should == 100 * aa.muly
45
+ ensure
46
+ aa.close
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "AAlib::Context#render" do
52
+ it "raises TypeError with wrong render_params" do
53
+ lambda { @aa.render(Object.new) }.should raise_error(TypeError)
54
+ end
55
+ end
56
+
57
+ describe "AAlib::Context#putpixel" do
58
+ it "puts a pixel to the image buffer" do
59
+ 50.times do
60
+ color = rand(256)
61
+ x = rand(@aa.imgwidth)
62
+ y = rand(@aa.imgheight)
63
+ @aa.putpixel(x, y, color)
64
+ @aa.image[y*@aa.imgwidth + x].should == color
65
+ end
66
+ end
67
+ end
68
+
69
+ describe "AAlib::Context#copy_image_from" do
70
+ before :each do
71
+ @aa1 = AAlib.init(AAlib.memory_driver)
72
+ @aa2 = AAlib.init(AAlib.memory_driver)
73
+ end
74
+
75
+ after :each do
76
+ @aa1.close
77
+ @aa2.close
78
+ end
79
+
80
+ it "copies the image buffer from another AAlib::Context" do
81
+ pixels = Hash.new
82
+ 100.times do
83
+ x = rand(@aa2.imgwidth)
84
+ y = rand(@aa2.imgheight)
85
+ color = rand(256)
86
+ @aa2.putpixel(x, y, color)
87
+ pixels[[x,y]] = color
88
+ end
89
+
90
+ @aa1.copy_image_from(@aa2)
91
+ pixels.each do |xy, color|
92
+ @aa1.image[@aa1.imgwidth*xy[1] + xy[0]].should == color
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,89 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'aalib'
3
+
4
+ describe "AAlib::SaveFormat.find" do
5
+ it "finds formats when given the full formatname" do
6
+ format = AAlib::SaveFormat.find("Text file")
7
+ format.should be_an_instance_of(AAlib::SaveFormat)
8
+ format.formatname.should == "Text file"
9
+ format.extension.should == ".txt"
10
+
11
+ format = AAlib::SaveFormat.find("Pure html")
12
+ format.should be_an_instance_of(AAlib::SaveFormat)
13
+ format.formatname.should == "Pure html"
14
+ format.extension.should == ".html"
15
+
16
+ ## NOTE: the "seqences" typo are hard coded into AA-lib v 1.4
17
+ format = AAlib::SaveFormat.find("ANSI escape seqences")
18
+ format.should be_an_instance_of(AAlib::SaveFormat)
19
+ format.formatname.should == "ANSI escape seqences"
20
+ format.extension.should == ".ansi"
21
+ end
22
+
23
+ it "finds formats when given the partial formatname" do
24
+ format = AAlib::SaveFormat.find("Text")
25
+ format.should be_an_instance_of(AAlib::SaveFormat)
26
+ format.formatname.should == "Text file"
27
+ format.extension.should == ".txt"
28
+
29
+ format = AAlib::SaveFormat.find("html")
30
+ format.should be_an_instance_of(AAlib::SaveFormat)
31
+ format.formatname.should == "Pure html"
32
+ format.extension.should == ".html"
33
+
34
+ format = AAlib::SaveFormat.find("ANSI")
35
+ format.should be_an_instance_of(AAlib::SaveFormat)
36
+ format.formatname.should == "ANSI escape seqences"
37
+ format.extension.should == ".ansi"
38
+ end
39
+
40
+ it "finds formats ignoring case" do
41
+ format = AAlib::SaveFormat.find("tEXT")
42
+ format.should be_an_instance_of(AAlib::SaveFormat)
43
+ format.formatname.should == "Text file"
44
+ format.extension.should == ".txt"
45
+
46
+ format = AAlib::SaveFormat.find("HTML")
47
+ format.should be_an_instance_of(AAlib::SaveFormat)
48
+ format.formatname.should == "Pure html"
49
+ format.extension.should == ".html"
50
+
51
+ format = AAlib::SaveFormat.find("ansi")
52
+ format.should be_an_instance_of(AAlib::SaveFormat)
53
+ format.formatname.should == "ANSI escape seqences"
54
+ format.extension.should == ".ansi"
55
+ end
56
+
57
+ it "finds formats when given a Regexp" do
58
+ format = AAlib::SaveFormat.find(/text/i)
59
+ format.should be_an_instance_of(AAlib::SaveFormat)
60
+ format.formatname.should == "Text file"
61
+ format.extension.should == ".txt"
62
+
63
+ format = AAlib::SaveFormat.find(/HP.*big/)
64
+ format.should be_an_instance_of(AAlib::SaveFormat)
65
+ format.formatname.should == "HP laser jet - A4 big font"
66
+ format.extension.should == ".hp"
67
+
68
+ format = AAlib::SaveFormat.find(/IRC.*II/)
69
+ format.should be_an_instance_of(AAlib::SaveFormat)
70
+ format.formatname.should == "For catting to an IRC channel II"
71
+ format.extension.should == ".irc"
72
+ end
73
+ end
74
+
75
+ describe "AAlib::SaveFormat.formatname" do
76
+ it "returns the formatname" do
77
+ format = AAlib::SaveFormat.find("text")
78
+ format.should be_an_instance_of(AAlib::SaveFormat)
79
+ format.formatname.should == "Text file"
80
+ end
81
+ end
82
+
83
+ describe "AAlib::SaveFormat.extension" do
84
+ it "returns the file extension including the dot" do
85
+ format = AAlib::SaveFormat.find("text")
86
+ format.should be_an_instance_of(AAlib::SaveFormat)
87
+ format.extension.should == ".txt"
88
+ end
89
+ end
@@ -0,0 +1,34 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'aalib'
3
+
4
+ describe "The save procedure" do
5
+ before :each do
6
+ @file = File.join(File.dirname(__FILE__), "scratch", "test_save")
7
+ end
8
+
9
+ after :each do
10
+ # File.delete(@file) if File.exists?(@file)
11
+ end
12
+
13
+ it "saves a graphics context as text using the save driver" do
14
+ format = AAlib::SaveFormat.find("text")
15
+ data = AAlib::SaveData.new(@file, format)
16
+
17
+ hp = AAlib::HardwareParams.new
18
+ rp = AAlib::RenderParams.new
19
+
20
+ context = AAlib.init(AAlib.save_driver, hp, data)
21
+ draw_diagonal_gradient(context)
22
+ context.render(rp)
23
+ puts_hello_world(context)
24
+ context.flush
25
+
26
+ context.close
27
+
28
+ efile = File.join(File.dirname(__FILE__), "hello_world.txt")
29
+ expected = File.open(efile) { |f| f.read }
30
+ result = File.open(@file) { |f| f.read }
31
+
32
+ result.should == expected
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'aalib'
3
+
4
+ describe "AAlib::SaveData.new" do
5
+ it "Creates a new instance of SaveData" do
6
+ file = "file.txt"
7
+ data = AAlib::SaveData.new(file, "text")
8
+ data.should be_an_instance_of(AAlib::SaveData)
9
+ data.name.should == "file.txt"
10
+ end
11
+
12
+ it "Uses AAlib::SaveFormat.find when given a string format name" do
13
+ file = "file.txt"
14
+ data = AAlib::SaveData.new(file, "text")
15
+ data.should be_an_instance_of(AAlib::SaveData)
16
+ data.name.should == file
17
+ data.format.should == AAlib::SaveFormat.find("text")
18
+ end
19
+
20
+ it "Uses AAlib::SaveFormat.find when given a regexp format name" do
21
+ file = "file.txt"
22
+ data = AAlib::SaveData.new(file, /text/i)
23
+ data.should be_an_instance_of(AAlib::SaveData)
24
+ data.name.should == file
25
+ data.format.should == AAlib::SaveFormat.find(/text/i)
26
+ end
27
+ end
@@ -0,0 +1,168 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'aalib'
3
+
4
+ describe "AAlib.autoinit" do
5
+ # We can't check this so much because it might initialize Curses on our terminal
6
+ #
7
+ it "raises TypeError with wrong hardware_params" do
8
+ clo = lambda { AAlib.autoinit(Object.new) }
9
+ clo.should raise_error(TypeError)
10
+ end
11
+ end
12
+
13
+ describe "AAlib.init" do
14
+ before :each do
15
+ @hp = AAlib::HardwareParams.new
16
+ end
17
+
18
+ # AAlib.init checks its arguments in violation of duck typing, but incorrect
19
+ # arguments can lead to segmentation faults if AAlib calls the C-library
20
+ # functions with incorrect arguments.
21
+
22
+ it "raises TypeError with wrong driver" do
23
+ clo = lambda { AAlib.init(Object.new) }
24
+ clo.should raise_error(TypeError)
25
+ end
26
+
27
+ it "raises TypeError with wrong hardware_params" do
28
+ clo = lambda { AAlib.init(AAlib.memory_driver, Object.new) }
29
+ clo.should raise_error(TypeError)
30
+ end
31
+
32
+ it "returns an AAlib::Context with memory driver" do
33
+ AAlib.init(AAlib.memory_driver, @hp).should be_an_instance_of(AAlib::Context)
34
+ end
35
+
36
+ it "returns an AAlib::Context with memory driver and no hardware_params" do
37
+ AAlib.init(AAlib.memory_driver).should be_an_instance_of(AAlib::Context)
38
+ end
39
+
40
+ it "returns an AAlib::Context when called with save driver" do
41
+ opts = AAlib::SaveData.new("test.txt", "text")
42
+ context = AAlib.init(AAlib.save_driver, @hp, opts)
43
+ context.should be_an_instance_of(AAlib::Context)
44
+ end
45
+
46
+ it "raises ArgumentError when called with save driver and no driver_opts" do
47
+ clo = lambda { AAlib.init(AAlib.save_driver, @hp, nil) }
48
+ clo.should raise_error(ArgumentError)
49
+ end
50
+
51
+ it "raises TypeError when called with save driver and wrong driver_opts" do
52
+ opts = AAlib::SaveFormat.find("text")
53
+ clo = lambda { AAlib.init(AAlib.save_driver, @hp, opts) }
54
+ clo.should raise_error(TypeError)
55
+ end
56
+ end
57
+
58
+ # describe "AAlib.color_from_rgb" do
59
+ # it "converts an RGB color to greyscale" do
60
+ # AAlib.color_from_rgb(0, 0, 0).should == 0
61
+ # AAlib.color_from_rgb(255, 255, 255).should == 255
62
+ # AAlib.color_from_rgb(255, 0, 0).should == 29
63
+ # AAlib.color_from_rgb(0, 255, 0).should == 58
64
+ # AAlib.color_from_rgb(0, 0, 255).should == 10
65
+ # AAlib.color_from_rgb(47, 57, 98).should == 22
66
+ # end
67
+ #
68
+ # it "behaves stupidly when args are larger than 255" do
69
+ # AAlib.color_from_rgb(256, 0, 0).should == 30
70
+ # end
71
+ # end
72
+
73
+ describe "AAlib.help" do
74
+ it "returns a large help string" do
75
+ AAlib.help.should be_an_instance_of(String)
76
+ end
77
+ end
78
+
79
+ describe "AAlib.formats" do
80
+ it "returns an array of formats" do
81
+ AAlib.formats.should be_an_instance_of(Array)
82
+ end
83
+
84
+ it "returns an array of AAlib::SaveFormat objects" do
85
+ AAlib.formats.each do |driver|
86
+ driver.should be_an_instance_of(AAlib::SaveFormat)
87
+ end
88
+ end
89
+ end
90
+
91
+ describe "AAlib.drivers" do
92
+ it "returns an array of drivers" do
93
+ AAlib.drivers.should be_an_instance_of(Array)
94
+ end
95
+
96
+ it "returns an array of AAlib::Driver objects" do
97
+ AAlib.drivers.each do |driver|
98
+ driver.should be_an_instance_of(AAlib::Driver)
99
+ end
100
+ end
101
+ end
102
+
103
+ describe "AAlib.save_driver" do
104
+ it "returns the driver to use for saving to disk" do
105
+ driver = AAlib.save_driver
106
+ driver.should be_an_instance_of(AAlib::Driver)
107
+ driver.shortname.should == 'save'
108
+ driver.name.should == 'Special driver for saving to files'
109
+ end
110
+ end
111
+
112
+ describe "AAlib.memory_driver" do
113
+ it "returns the driver to use for an in-memory context" do
114
+ driver = AAlib.memory_driver
115
+ driver.should be_an_instance_of(AAlib::Driver)
116
+ driver.shortname.should == 'mem'
117
+ # FIXME: may depend on AA-lib version?
118
+ driver.name.should == 'Dummy memory driver 1.0'
119
+ end
120
+ end
121
+
122
+ describe "AAlib.parseoptions" do
123
+ before :each do
124
+ @hp = AAlib::HardwareParams.new
125
+ @rp = AAlib::RenderParams.new
126
+ end
127
+
128
+ it "parses commandline options, setting values in RenderParams" do
129
+ argv = %w{-gamma 2.0 -contrast 5 -bright 100}
130
+ AAlib.parseoptions(@hp, @rp, argv).should be_true
131
+ @rp.gamma.should be_close(2.0, 0.01)
132
+ @rp.contrast.should == 5
133
+ @rp.bright.should == 100
134
+ end
135
+
136
+ it "parses commandline options, setting values in HardwareParams" do
137
+ argv = %w{-width 101 -height 57}
138
+ AAlib.parseoptions(@hp, @rp, argv).should be_true
139
+ @hp.width.should == 101
140
+ @hp.height.should == 57
141
+ end
142
+
143
+ it "leaves untouched other options" do
144
+ argv = %w{--verbose -gamma 2.0 -contrast 5 -bright 100 -other-option 70}
145
+ AAlib.parseoptions(@hp, @rp, argv).should be_true
146
+ argv.should == %w{--verbose -other-option 70}
147
+ end
148
+
149
+ it "uses ARGV by default" do
150
+ ARGV.replace %w{--verbose -gamma 2.0 -contrast 5 -bright 100 -other-option 70}
151
+ AAlib.parseoptions(@hp, @rp).should be_true
152
+ ARGV.should == %w{--verbose -other-option 70}
153
+
154
+ @rp.gamma.should be_close(2.0, 0.01)
155
+ @rp.contrast.should == 5
156
+ @rp.bright.should == 100
157
+ end
158
+
159
+ it "raises TypeError with wrong hardware_params" do
160
+ clo = lambda { AAlib.parseoptions(Object.new, @rp) }
161
+ clo.should raise_error(TypeError)
162
+ end
163
+
164
+ it "raises TypeError with wrong hardware_params" do
165
+ clo = lambda { AAlib.parseoptions(@hp, Object.new) }
166
+ clo.should raise_error(TypeError)
167
+ end
168
+ end
@@ -0,0 +1,25 @@
1
+ . . ........-.:.::::::::;:;;;=;=======+++++|+|+|||||||||i|iiiiiiilllllIIIvIv
2
+ . . .........:-::-:::::::;;;;=;=======++++++|+|+|||+|||||ii|iiiiiillllllIIvIvvv
3
+ . .........-:-:.::::::::;;;;=========++++|+||+||||+|||||ii|iiiiiilllllIIvIvvvvvv
4
+ ........:.::.::::::::;;;=;========++|+|+|+|+||||||||iiiiiiiiillllllIIvvIvvvvvvnv
5
+ ....--::-::::::::;;;=;=========++|+++|+|+|||||||||iii|iiiillllllIvvvvvvvvvvnvnnn
6
+ .--:-:-:::::::;;;=;=;======+++|+|=|||+|||||||||i|iiiiilillllIIvIvvIvvvvvnnnnnnnn
7
+ ::-::::::::;;;=;========+++|+|++||+||||||||i|iiiiiiillllIIIvIvvvvvvvvnnnnnnnnooo
8
+ :-::::::;;;=;========++|++|++|+|||||||||iiiiiiiillllllIIvIvvvvvvvnvnnnnnnnnoo222
9
+ :::::;;;=;========++|+++|+|||||||||||i|iiiiiilllllIIvIvvvvvvvvnvnnnnnnnooo22222S
10
+ ;:;=;=;=======+++|+++|||+|||||||||iiiiiiiiillllIIvIvvvvvvvvnvnnnnnnooo2o2222S2S2
11
+ ;=;========+++++|=|||+|+|||||||iiiiiiiilllIlIIvIvvvvvvvvnvnnnnnnoooo2222S2SS2XSX
12
+ ========+++|+|+|+||+||| nnnnoooo2o2S22S2S2SSSSSX
13
+ =====+++|+|++|+|||||||| AA-lib: the ascii-art library noooo2oSoS22SS2XSSXSXXXX
14
+ =++++|+|++|+|||||||||ii oo2222SoS2SSS2XXSXXXXXXZ
15
+ +++|+|+|+|||||||||iiiiiiiililIlIIvIvvvvvvvvvnnnnnnnnooo22222S2SSSSSSSXXXXXXXZZZU
16
+ |+|+|+||||||||i|iiiiiiiilllIIIvIvvvvvvvvvnnnnnnnooo2o22S2S2X2XS2XSXXXXXXZZZZ#Z#Z
17
+ |+|||||||||iiiiiiiiillllIIIvIvvvvvvvvnvnnnnnnoooo222S2S2S2SSSSSXXXXXXZZZZZ#ZUZ##
18
+ ||||||||iiiiiiiillllllIvIvvvvvvvvvnnnnnnnoooo2o2S22S2S2SSSXSXXXXXXZZZZZ#Z#Z#####
19
+ |||||iiiiiiiiilllllIvIvIvvvvvvvnnnnnnnnooo2222S22SS2XSSXXXXXXXXZZZZUZ#UZ########
20
+ i|iiiiiiiiilllllIIvvvvvvvvvnvnnnnnnnooo22222S2S2XS2XXSXXXXXXZZZUZ#Z#UUU########m
21
+ iiiiiiiilllIlIvvvvvvvvvvnnnnnnnnoooo2222S2SS2SSSSSXXXXXXXZZZUZ#Z#Z#########mmmBB
22
+ iiiillllIIIvvvIvvvvvvnvnnnnnnooo2o2S22S2S2X2XXSXXXXXXZZZZ#Z#Z#Z#########mmmBBWmW
23
+ llllllIIvIvvvvvvvvnnnnnnnnooo2oSoS22S2SSSSXXXXXXXXZZZZUZ#Z#Z#########mmmBBWBWmWW
24
+ lllIvIvvvvvvvvvnnnnnnnnoo2o222SoS2SSSSSSXXXXXXXZZZZ#Z#Z#U########mmmBmWBWBWmWWWW
25
+ vIvvIvvvvvvnvnnnnnnnooo2222S2S2SSSSSSXXXXXXXZZZZ#Z#Z#U########mmmBBBWBWmWWBWWWWW
@@ -0,0 +1,25 @@
1
+ . . ........-.:.::::::::;:;;;=;=======+++++|+|+|||||||||i|iiiiiiilllllIIIvIv
2
+ . . .........:-::-:::::::;;;;=;=======++++++|+|+|||+|||||ii|iiiiiillllllIIvIvvv
3
+ . .........-:-:.::::::::;;;;=========++++|+||+||||+|||||ii|iiiiiilllllIIvIvvvvvv
4
+ ........:.::.::::::::;;;=;========++|+|+|+|+||||||||iiiiiiiiillllllIIvvIvvvvvvnv
5
+ ....--::-::::::::;;;=;=========++|+++|+|+|||||||||iii|iiiillllllIvvvvvvvvvvnvnnn
6
+ .--:-:-:::::::;;;=;=;======+++|+|=|||+|||||||||i|iiiiilillllIIvIvvIvvvvvnnnnnnnn
7
+ ::-::::::::;;;=;========+++|+|++||+||||||||i|iiiiiiillllIIIvIvvvvvvvvnnnnnnnnooo
8
+ :-::::::;;;=;========++|++|++|+|||||||||iiiiiiiillllllIIvIvvvvvvvnvnnnnnnnnoo222
9
+ :::::;;;=;========++|+++|+|||||||||||i|iiiiiilllllIIvIvvvvvvvvnvnnnnnnnooo22222S
10
+ ;:;=;=;=======+++|+++|||+|||||||||iiiiiiiiillllIIvIvvvvvvvvnvnnnnnnooo2o2222S2S2
11
+ ;=;========+++++|=|||+|+|||||||iiiiiiiilllIlIIvIvvvvvvvvnvnnnnnnoooo2222S2SS2XSX
12
+ ========+++|+|+|+||+||| nnnnoooo2o2S22S2S2SSSSSX
13
+ =====+++|+|++|+|||||||| AA-lib: the ascii-art library noooo2oSoS22SS2XSSXSXXXX
14
+ =++++|+|++|+|||||||||ii oo2222SoS2SSS2XXSXXXXXXZ
15
+ +++|+|+|+|||||||||iiiiiiiililIlIIvIvvvvvvvvvnnnnnnnnooo22222S2SSSSSSSXXXXXXXZZZU
16
+ |+|+|+||||||||i|iiiiiiiilllIIIvIvvvvvvvvvnnnnnnnooo2o22S2S2X2XS2XSXXXXXXZZZZ#Z#Z
17
+ |+|||||||||iiiiiiiiillllIIIvIvvvvvvvvnvnnnnnnoooo222S2S2S2SSSSSXXXXXXZZZZZ#ZUZ##
18
+ ||||||||iiiiiiiillllllIvIvvvvvvvvvnnnnnnnoooo2o2S22S2S2SSSXSXXXXXXZZZZZ#Z#Z#####
19
+ |||||iiiiiiiiilllllIvIvIvvvvvvvnnnnnnnnooo2222S22SS2XSSXXXXXXXXZZZZUZ#UZ########
20
+ i|iiiiiiiiilllllIIvvvvvvvvvnvnnnnnnnooo22222S2S2XS2XXSXXXXXXZZZUZ#Z#UUU########m
21
+ iiiiiiiilllIlIvvvvvvvvvvnnnnnnnnoooo2222S2SS2SSSSSXXXXXXXZZZUZ#Z#Z#########mmmBB
22
+ iiiillllIIIvvvIvvvvvvnvnnnnnnooo2o2S22S2S2X2XXSXXXXXXZZZZ#Z#Z#Z#########mmmBBWmW
23
+ llllllIIvIvvvvvvvvnnnnnnnnooo2oSoS22S2SSSSXXXXXXXXZZZZUZ#Z#Z#########mmmBBWBWmWW
24
+ lllIvIvvvvvvvvvnnnnnnnnoo2o222SoS2SSSSSSXXXXXXXZZZZ#Z#Z#U########mmmBmWBWBWmWWWW
25
+ vIvvIvvvvvvnvnnnnnnnooo2222S2S2SSSSSSXXXXXXXZZZZ#Z#Z#U########mmmBBBWBWmWWBWWWWW