network_executive 0.0.4 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. data/README.md +53 -24
  2. data/app/assets/fonts/network_executive/league_gothic.css +13 -0
  3. data/app/assets/fonts/network_executive/league_gothic.svg +148 -0
  4. data/app/assets/fonts/network_executive/league_gothic.ttf +0 -0
  5. data/app/assets/fonts/network_executive/league_gothic.woff +0 -0
  6. data/app/assets/javascripts/network_executive/components/iframe_notifier.coffee +14 -0
  7. data/app/assets/javascripts/network_executive/components/namespace.coffee +1 -0
  8. data/app/assets/javascripts/network_executive/components/osd.coffee +65 -0
  9. data/app/assets/javascripts/network_executive/components/photo_player.coffee +55 -0
  10. data/app/assets/javascripts/network_executive/components/postman.coffee +23 -0
  11. data/app/assets/javascripts/network_executive/components/set_top_box.coffee +81 -0
  12. data/app/assets/javascripts/network_executive/components/tweet_player.coffee +95 -0
  13. data/app/assets/javascripts/network_executive/components/you_tube_player.coffee +85 -0
  14. data/app/assets/javascripts/network_executive/slideshow.coffee +6 -0
  15. data/app/assets/javascripts/network_executive/twitter.coffee +7 -0
  16. data/app/assets/javascripts/network_executive/you_tube.coffee +8 -0
  17. data/app/assets/javascripts/network_executive.coffee +10 -0
  18. data/app/assets/stylesheets/network_executive/{gui_components.css → components/gui_components.css} +0 -0
  19. data/app/assets/stylesheets/network_executive/{normalize.css → components/normalize.css} +0 -0
  20. data/app/assets/stylesheets/network_executive/{osd.css → components/osd.css} +6 -34
  21. data/app/assets/stylesheets/network_executive/components/osd_guide.css +43 -0
  22. data/app/assets/stylesheets/network_executive/components/photo_player.css +61 -0
  23. data/app/assets/stylesheets/network_executive/{smpte.css → components/smpte.css} +0 -0
  24. data/app/assets/stylesheets/network_executive/components/tweet_player.css +60 -0
  25. data/app/assets/stylesheets/network_executive/components/you_tube_player.css +12 -0
  26. data/app/assets/stylesheets/network_executive/off_air.css +5 -0
  27. data/app/assets/stylesheets/network_executive/slideshow.css +4 -0
  28. data/app/assets/stylesheets/network_executive/twitter.css +5 -0
  29. data/app/assets/stylesheets/network_executive/you_tube.css +3 -0
  30. data/app/assets/stylesheets/{network_executive/application.css → network_executive.css} +5 -5
  31. data/app/controllers/network_executive/components_controller.rb +4 -0
  32. data/app/controllers/network_executive/guide_controller.rb +41 -0
  33. data/app/controllers/network_executive/programs_controller.rb +9 -0
  34. data/app/helpers/network_executive/network_helper.rb +6 -0
  35. data/app/models/network_executive/channel.rb +15 -14
  36. data/app/models/network_executive/guide.rb +65 -0
  37. data/app/models/network_executive/program.rb +69 -5
  38. data/app/models/network_executive/viewer.rb +4 -0
  39. data/app/programs/network_executive/off_air.rb +18 -0
  40. data/app/views/network_executive/components/slideshow.html.erb +20 -0
  41. data/app/views/network_executive/components/twitter.html.erb +16 -0
  42. data/app/views/network_executive/components/you_tube.html.erb +13 -0
  43. data/app/views/network_executive/guide/index.html.erb +30 -0
  44. data/app/views/network_executive/network/index.html.erb +3 -3
  45. data/app/views/network_executive/programs/off_air.html.erb +13 -0
  46. data/config/routes.rb +7 -1
  47. data/lib/network_executive/channel_schedule.rb +65 -0
  48. data/lib/network_executive/components/photo_player.rb +48 -0
  49. data/lib/network_executive/components/tweet_player.rb +48 -0
  50. data/lib/network_executive/components/you_tube_player.rb +15 -0
  51. data/lib/network_executive/components.rb +3 -0
  52. data/lib/network_executive/engine.rb +29 -3
  53. data/lib/network_executive/off_air_schedule.rb +18 -0
  54. data/lib/network_executive/producer.rb +3 -3
  55. data/lib/network_executive/program_schedule.rb +96 -0
  56. data/lib/network_executive/program_schedule_proxy.rb +55 -0
  57. data/lib/network_executive/scheduled_program.rb +26 -0
  58. data/lib/network_executive/scheduling.rb +15 -21
  59. data/lib/network_executive/time_calculations.rb +10 -0
  60. data/lib/network_executive/version.rb +1 -1
  61. data/lib/network_executive.rb +2 -1
  62. data/network_executive.gemspec +5 -0
  63. data/spec/models/channel_spec.rb +30 -12
  64. data/spec/models/{lineup_spec.rb → guide_spec.rb} +28 -6
  65. data/spec/models/program_spec.rb +46 -3
  66. data/spec/models/viewer_spec.rb +9 -1
  67. data/spec/programs/off_air_spec.rb +9 -0
  68. data/spec/spec_helper.rb +3 -2
  69. data/spec/unit/channel_schedule_spec.rb +67 -0
  70. data/spec/unit/components/photo_player_spec.rb +76 -0
  71. data/spec/unit/components/tweet_player_spec.rb +68 -0
  72. data/spec/unit/components/you_tube_player_spec.rb +17 -0
  73. data/spec/unit/off_air_schedule_spec.rb +28 -0
  74. data/spec/unit/producer_spec.rb +2 -4
  75. data/spec/unit/program_schedule_proxy_spec.rb +102 -0
  76. data/spec/unit/program_schedule_spec.rb +191 -0
  77. data/spec/unit/scheduled_program_spec.rb +57 -0
  78. data/spec/unit/scheduling_spec.rb +61 -72
  79. data/vendor/assets/javascripts/bigtext.js +264 -0
  80. metadata +185 -44
  81. data/app/assets/javascripts/application.js +0 -13
  82. data/app/assets/javascripts/network_executive/osd.js +0 -62
  83. data/app/assets/javascripts/network_executive/set_top_box.js +0 -45
  84. data/app/controllers/network_executive/lineup_controller.rb +0 -11
  85. data/app/models/network_executive/channel_schedule.rb +0 -13
  86. data/app/models/network_executive/lineup.rb +0 -74
  87. data/app/models/network_executive/lineup_range.rb +0 -34
  88. data/app/models/network_executive/program_schedule.rb +0 -148
  89. data/app/views/network_executive/lineup/index.html.erb +0 -26
  90. data/spec/models/channel_schedule_spec.rb +0 -29
  91. data/spec/models/lineup_range_spec.rb +0 -65
  92. data/spec/models/program_schedule_spec.rb +0 -399
@@ -0,0 +1,191 @@
1
+ describe NetworkExecutive::ProgramSchedule do
2
+
3
+ it 'should define an Occurrence structure' do
4
+ occ = described_class::Occurrence.new
5
+
6
+ occ.should respond_to(:start_time)
7
+ occ.should respond_to(:duration)
8
+ occ.should respond_to(:end_time)
9
+ end
10
+
11
+ describe '#initialize' do
12
+ context 'with an unknown program name' do
13
+ before do
14
+ NetworkExecutive::Program.stub( :find_by_name ).and_return nil
15
+ end
16
+
17
+ it 'should raise a ProgramNameError' do
18
+ expect{ described_class.new( 'null_prog' ) }.to raise_error NetworkExecutive::ProgramNameError
19
+ end
20
+ end
21
+
22
+ context 'with a known program name' do
23
+ let(:known_program) { double('program') }
24
+ let(:duration) { nil }
25
+
26
+ before do
27
+ NetworkExecutive::Program.stub( :find_by_name ).and_return known_program
28
+ end
29
+
30
+ subject { described_class.new( 'known', duration:duration ) }
31
+
32
+ it { should respond_to(:start_time) }
33
+ its(:program) { should == known_program }
34
+
35
+ context 'and no duration specified' do
36
+ its(:duration) { should == 24.hours }
37
+ end
38
+
39
+ context 'and a duration specified' do
40
+ let(:duration) { 35.minutes }
41
+
42
+ its(:duration) { should == 35.minutes }
43
+ end
44
+
45
+ end
46
+ end
47
+
48
+ describe '#proxy' do
49
+ before do
50
+ NetworkExecutive::Program.stub( :find_by_name ).and_return true
51
+ end
52
+
53
+ subject { described_class.new('p').proxy }
54
+
55
+ it 'should have a schedule proxy' do
56
+ subject.should be_a NetworkExecutive::ProgramScheduleProxy
57
+ end
58
+
59
+ it 'should execute the block' do
60
+ canary = 'alive'
61
+
62
+ described_class.new('p'){ canary = 'dead' }.proxy
63
+
64
+ subject
65
+
66
+ canary.should == 'dead'
67
+ end
68
+ end
69
+
70
+ describe '#start_time=' do
71
+ before do
72
+ NetworkExecutive::Program.stub( :find_by_name ).and_return true
73
+ end
74
+
75
+ it 'should reset the proxy' do
76
+ time = Time.now
77
+
78
+ NetworkExecutive::ProgramScheduleProxy.should_receive(:new).with( time, anything )
79
+
80
+ described_class.new('p').start_time = time
81
+ end
82
+ end
83
+
84
+ describe '#duration=' do
85
+ before do
86
+ NetworkExecutive::Program.stub( :find_by_name ).and_return true
87
+ end
88
+
89
+ it 'should reset the proxy' do
90
+ time = 10.minutes
91
+
92
+ NetworkExecutive::ProgramScheduleProxy.should_receive(:new).with( anything, time )
93
+
94
+ described_class.new('p').duration = time
95
+ end
96
+ end
97
+
98
+ describe '#occurs_at?' do
99
+ before do
100
+ NetworkExecutive::Program.stub( :find_by_name ).and_return true
101
+ end
102
+
103
+ subject { described_class.new('p').occurs_at? Time.now }
104
+
105
+ it 'should delegate to its proxy' do
106
+ NetworkExecutive::ProgramScheduleProxy.any_instance.should_receive( :occurs_at? )
107
+
108
+ subject
109
+ end
110
+ end
111
+
112
+ describe '#play' do
113
+ let(:program) { double('program') }
114
+
115
+ before do
116
+ NetworkExecutive::Program.stub( :find_by_name ).and_return program
117
+ end
118
+
119
+ subject { described_class.new('p').play }
120
+
121
+ it 'should delegate to its proxy' do
122
+ program.should_receive( :play )
123
+
124
+ subject do
125
+ # ...
126
+ end
127
+ end
128
+ end
129
+
130
+ describe '#update' do
131
+ let(:program) { double('program') }
132
+
133
+ before do
134
+ NetworkExecutive::Program.stub( :find_by_name ).and_return program
135
+ end
136
+
137
+ subject { described_class.new('p').update }
138
+
139
+ it 'should delegate to its proxy' do
140
+ program.should_receive( :update )
141
+
142
+ subject do
143
+ # ...
144
+ end
145
+ end
146
+ end
147
+
148
+ describe '#whats_on?' do
149
+ before do
150
+ Timecop.freeze
151
+
152
+ NetworkExecutive::Program.stub( :find_by_name ).and_return double('program')
153
+ end
154
+
155
+ subject { described_class.new( 'p').whats_on? }
156
+
157
+ it 'should indicate if the schedule has anything occuring at the given time' do
158
+ NetworkExecutive::ProgramScheduleProxy.any_instance.should_receive( :occurring_at? ).with Time.now
159
+
160
+ subject
161
+ end
162
+ end
163
+
164
+ describe '#occurrence_at' do
165
+ before do
166
+ NetworkExecutive::Program.stub( :find_by_name ).and_return true
167
+
168
+ NetworkExecutive::ProgramScheduleProxy
169
+ .any_instance
170
+ .stub(:occurrences_between)
171
+ .and_return [ Time.now.end_of_day ]
172
+ end
173
+
174
+ let(:time) { Time.now.end_of_day }
175
+
176
+ subject do
177
+ described_class.new('p', start_time:time, duration:1.minute).occurrence_at time
178
+ end
179
+
180
+ it 'should build an Occurrence' do
181
+ start_time = time.dup
182
+ duration = 1.minute - 1
183
+ end_time = start_time + duration
184
+
185
+ subject.start_time.should eq start_time
186
+ subject.duration.should eq duration
187
+ subject.end_time.should eq end_time
188
+ end
189
+ end
190
+
191
+ end
@@ -0,0 +1,57 @@
1
+ require 'ostruct'
2
+
3
+ describe NetworkExecutive::ScheduledProgram do
4
+
5
+ it { should respond_to(:program) }
6
+ it { should respond_to(:occurrence) }
7
+ it { should respond_to(:remainder) }
8
+ it { should respond_to(:portion) }
9
+
10
+ describe '#display_name' do
11
+ let(:program) { double('program', display_name:'foo') }
12
+
13
+ subject { described_class.new(program).display_name }
14
+
15
+ it { should == 'foo' }
16
+ end
17
+
18
+ describe '#+' do
19
+ context 'with a mismatched program' do
20
+ it 'should raise an ArgumentError' do
21
+ expect { subject + double('program') }.to raise_error ArgumentError
22
+ end
23
+ end
24
+
25
+ context 'with the same program type' do
26
+ let(:end_time) { Time.now }
27
+ let(:program_a) { OpenStruct.new( duration:59.seconds ) }
28
+ let(:occurrence) { OpenStruct.new( duration:59.seconds, end_time:end_time ) }
29
+ let(:program_b) { OpenStruct.new( duration:59.seconds ) }
30
+
31
+ subject do
32
+ described_class.new( program_a, occurrence ) + program_b
33
+ end
34
+
35
+ before do
36
+ program_a.stub(:occurrence).and_return double('occurrence').as_null_object
37
+ end
38
+
39
+ it 'should extend its duration with that of the other program' do
40
+ subject.program.duration.should == 119.seconds
41
+ end
42
+
43
+ it 'should extend its occurrences duration with that of the other program' do
44
+ subject.occurrence.duration.should == 119.seconds
45
+ end
46
+
47
+ it 'should extend its occurrences end time with that of the other program' do
48
+ subject.occurrence.end_time.should == end_time + 1.minute
49
+ end
50
+
51
+ it 'should return itself' do
52
+ subject.should be_a NetworkExecutive::ScheduledProgram
53
+ end
54
+ end
55
+ end
56
+
57
+ end
@@ -1,106 +1,95 @@
1
1
  describe NetworkExecutive::Scheduling do
2
2
 
3
- describe '.every' do
4
- it 'should schedule the program' do
5
- channel = Class.new( NetworkExecutive::Channel ) do
6
- include NetworkExecutive::Scheduling
7
-
8
- every :day, play:'my_show'
9
- end
10
-
11
- channel.schedule.first.should be_a NetworkExecutive::ProgramSchedule
3
+ let(:klass) do
4
+ Class.new do
5
+ include NetworkExecutive::Scheduling
12
6
  end
13
7
  end
14
8
 
15
- describe 'schedule' do
16
- it 'should be a ChannelSchedule' do
17
- channel = Class.new( NetworkExecutive::Channel ) do
18
- include NetworkExecutive::Scheduling
19
- end
20
-
21
- channel.schedule.should be_a NetworkExecutive::ChannelSchedule
22
- end
9
+ before do
10
+ stub_const 'MyChannel', klass
23
11
  end
24
12
 
25
- describe '#whats_on?' do
26
- let(:channel) do
27
- Class.new( NetworkExecutive::Channel ) do
28
- include NetworkExecutive::Scheduling
13
+ subject { MyChannel.new }
14
+
15
+ describe '.schedule' do
16
+ context 'with a block' do
17
+ it 'should add a program' do
18
+ params = [
19
+ 'program',
20
+ kind_of(Hash)
21
+ ]
22
+
23
+ NetworkExecutive::ChannelSchedule.any_instance.should_receive( :add ).with( *params )
24
+
25
+ MyChannel.schedule( 'program' ) do
26
+ # ...
27
+ end
29
28
  end
30
29
  end
31
30
 
32
- context 'with nothing scheduled' do
33
- it 'should return nil' do
34
- channel.new.whats_on?.should be_nil
31
+ context 'without a block' do
32
+ it 'should be a ChannelSchedule' do
33
+ MyChannel.schedule.should be_a NetworkExecutive::ChannelSchedule
35
34
  end
36
35
  end
36
+ end
37
37
 
38
- it 'should return the most appropriate program for a given time' do
39
- program_c = double('program c', include?: true)
40
-
41
- channel.schedule.add double('program a', include?: false)
42
- channel.schedule.add double('program b', include?: true)
43
- channel.schedule.add program_c
44
- channel.schedule.add double('program d', include?: false)
45
-
46
- channel.new.whats_on?.should == program_c
38
+ describe '#schedule' do
39
+ it 'should be a ChannelSchedule' do
40
+ subject.schedule.should be_a NetworkExecutive::ChannelSchedule
47
41
  end
42
+ end
48
43
 
49
- it 'should return all programs scheduled for a range of times' do
50
- program_b = double 'program b'
51
- program_b.stub(:include?).and_return true, false
52
-
53
- program_c = double 'program c'
54
- program_c.stub(:include?).and_return false, true
55
-
56
- channel.schedule.add double('program a', include?: false)
57
- channel.schedule.add program_b
58
- channel.schedule.add program_c
59
- channel.schedule.add double('program d', include?: false)
44
+ describe '#whats_on?' do
45
+ before { Timecop.freeze }
60
46
 
61
- shows = channel.new.whats_on?( 1.hour.ago, 1.hour.from_now )
47
+ it 'should return what is on right now' do
48
+ subject.should_receive( :whats_on_at? ).with( Time.now.change(sec:0) )
62
49
 
63
- shows.first[:program].should be program_b
64
- shows.last[:program].should be program_c
50
+ subject.whats_on?
65
51
  end
52
+ end
66
53
 
67
- it 'should yield a block when provided a stop_time' do
68
- program_a = double('program a', include?: true)
69
- channel.schedule.add program_a
54
+ describe '#whats_on_at?' do
55
+ context 'with nothing scheduled' do
56
+ before do
57
+ NetworkExecutive::ChannelSchedule.any_instance.stub(:find).and_return nil
58
+ end
70
59
 
71
- channel.new.whats_on?( 1.hour.ago, 1.hour.from_now) do |program|
72
- program.should be program_a
60
+ it 'should return OffAir' do
61
+ subject.whats_on_at?( Time.now ).should be_a NetworkExecutive::OffAirSchedule
73
62
  end
74
63
  end
75
- end
76
64
 
77
- describe '#with_showtimes_between' do
78
- let(:klass) do
79
- Class.new( NetworkExecutive::Channel ) do
80
- include NetworkExecutive::Scheduling
65
+ context 'with something scheduled' do
66
+ let(:prog_1) { double('unscheduled', whats_on?: false) }
67
+ let(:prog_2) { double('scheduled', whats_on?: true) }
68
+ let(:prog_3) { double('unscheduled', whats_on?: false) }
81
69
 
82
- every :day, play:'my_show'
70
+ before do
71
+ subject.schedule << prog_1
72
+ subject.schedule << prog_2
73
+ subject.schedule << prog_3
83
74
  end
84
- end
85
-
86
- let(:channel) { klass.new }
87
75
 
88
- subject do
89
- program_a = double 'program_a', include?: true
76
+ it 'should return the scheduled program' do
77
+ subject.whats_on_at?( Time.now ).should == prog_2
78
+ end
79
+ end
80
+ end
90
81
 
91
- channel.schedule.add program_a
82
+ describe '#whats_on_between?' do
83
+ it 'should ask the ChannelSchedule what is on between two dates' do
84
+ date_a = Time.now
85
+ date_b = date_a + 1.5.hours
92
86
 
93
- range = [ 1.hour.ago, 1.hour.from_now ]
87
+ args = [ date_a, date_b, nil ]
94
88
 
95
- channel.with_showtimes_between( *range ) do |showtime, program|
96
- 'block retval'
97
- end
98
- end
89
+ NetworkExecutive::ChannelSchedule.any_instance.should_receive(:whats_on_between?).with( *args )
99
90
 
100
- it 'should contain the blocks return value' do
101
- subject.all?{ |x| x == 'block retval' }.should be_true
91
+ subject.whats_on_between? date_a, date_b
102
92
  end
103
-
104
93
  end
105
94
 
106
95
  end
@@ -0,0 +1,264 @@
1
+ ;(function(window, $)
2
+ {
3
+ var counter = 0,
4
+ $headCache = $('head'),
5
+ oldBigText = window.BigText,
6
+ oldjQueryMethod = $.fn.bigtext,
7
+ BigText = {
8
+ DEFAULT_MIN_FONT_SIZE_PX: null,
9
+ DEFAULT_MAX_FONT_SIZE_PX: 528,
10
+ GLOBAL_STYLE_ID: 'bigtext-style',
11
+ STYLE_ID: 'bigtext-id',
12
+ LINE_CLASS_PREFIX: 'bigtext-line',
13
+ EXEMPT_CLASS: 'bigtext-exempt',
14
+ DEFAULT_CHILD_SELECTOR: '> div',
15
+ childSelectors: {
16
+ div: '> div',
17
+ ol: '> li',
18
+ ul: '> li'
19
+ },
20
+ noConflict: function(restore)
21
+ {
22
+ if(restore) {
23
+ $.fn.bigtext = oldjQueryMethod;
24
+ window.BigText = oldBigText;
25
+ }
26
+ return BigText;
27
+ },
28
+ init: function()
29
+ {
30
+ if(!$('#'+BigText.GLOBAL_STYLE_ID).length) {
31
+ $headCache.append(BigText.generateStyleTag(BigText.GLOBAL_STYLE_ID, ['.bigtext * { white-space: nowrap; }',
32
+ '.bigtext .' + BigText.EXEMPT_CLASS + ', .bigtext .' + BigText.EXEMPT_CLASS + ' * { white-space: normal; }']));
33
+ }
34
+ },
35
+ bindResize: function(eventName, resizeFunction)
36
+ {
37
+ if($.throttle) {
38
+ // https://github.com/cowboy/jquery-throttle-debounce
39
+ $(window).unbind(eventName).bind(eventName, $.throttle(100, resizeFunction));
40
+ } else {
41
+ if($.fn.smartresize) {
42
+ // https://github.com/lrbabe/jquery-smartresize/
43
+ eventName = 'smartresize.' + eventName;
44
+ }
45
+ $(window).unbind(eventName).bind(eventName, resizeFunction);
46
+ }
47
+ },
48
+ getStyleId: function(id)
49
+ {
50
+ return BigText.STYLE_ID + '-' + id;
51
+ },
52
+ generateStyleTag: function(id, css)
53
+ {
54
+ return $('<style>' + css.join('\n') + '</style>').attr('id', id);
55
+ },
56
+ clearCss: function(id)
57
+ {
58
+ var styleId = BigText.getStyleId(id);
59
+ $('#' + styleId).remove();
60
+ },
61
+ generateCss: function(id, linesFontSizes, lineWordSpacings, minFontSizes)
62
+ {
63
+ var css = [];
64
+
65
+ BigText.clearCss(id);
66
+
67
+ for(var j=0, k=linesFontSizes.length; j<k; j++) {
68
+ css.push('#' + id + ' .' + BigText.LINE_CLASS_PREFIX + j + ' {' +
69
+ (minFontSizes[j] ? ' white-space: normal;' : '') +
70
+ (linesFontSizes[j] ? ' font-size: ' + linesFontSizes[j] + 'px;' : '') +
71
+ (lineWordSpacings[j] ? ' word-spacing: ' + lineWordSpacings[j] + 'px;' : '') +
72
+ '}');
73
+ }
74
+
75
+ return BigText.generateStyleTag(BigText.getStyleId(id), css);
76
+ },
77
+ jQueryMethod: function(options)
78
+ {
79
+ BigText.init();
80
+
81
+ options = $.extend({
82
+ minfontsize: BigText.DEFAULT_MIN_FONT_SIZE_PX,
83
+ maxfontsize: BigText.DEFAULT_MAX_FONT_SIZE_PX,
84
+ childSelector: '',
85
+ resize: true
86
+ }, options || {});
87
+
88
+ return this.each(function()
89
+ {
90
+ var $t = $(this).addClass('bigtext'),
91
+ childSelector = options.childSelector ||
92
+ BigText.childSelectors[this.tagName.toLowerCase()] ||
93
+ BigText.DEFAULT_CHILD_SELECTOR,
94
+ maxWidth = $t.width(),
95
+ id = $t.attr('id');
96
+
97
+ if(!id) {
98
+ id = 'bigtext-id' + (counter++);
99
+ $t.attr('id', id);
100
+ }
101
+
102
+ if(options.resize) {
103
+ BigText.bindResize('resize.bigtext-event-' + id, function()
104
+ {
105
+ BigText.jQueryMethod.call($('#' + id), options);
106
+ });
107
+ }
108
+
109
+ BigText.clearCss(id);
110
+
111
+ $t.find(childSelector).addClass(function(lineNumber, className)
112
+ {
113
+ // remove existing line classes.
114
+ return [className.replace(new RegExp('\\b' + BigText.LINE_CLASS_PREFIX + '\\d+\\b'), ''),
115
+ BigText.LINE_CLASS_PREFIX + lineNumber].join(' ');
116
+ });
117
+
118
+ var sizes = calculateSizes($t, childSelector, maxWidth, options.maxfontsize, options.minfontsize);
119
+ $headCache.append(BigText.generateCss(id, sizes.fontSizes, sizes.wordSpacings, sizes.minFontSizes));
120
+ });
121
+ }
122
+ };
123
+
124
+ function testLineDimensions($line, maxWidth, property, size, interval, units)
125
+ {
126
+ var width;
127
+ $line.css(property, size + units);
128
+
129
+ width = $line.width();
130
+
131
+ if(width >= maxWidth) {
132
+ $line.css(property, '');
133
+
134
+ if(width == maxWidth) {
135
+ return {
136
+ match: 'exact',
137
+ size: parseFloat((parseFloat(size) - 0.1).toFixed(3))
138
+ };
139
+ }
140
+
141
+ return {
142
+ match: 'estimate',
143
+ size: parseFloat((parseFloat(size) - interval).toFixed(3))
144
+ };
145
+ }
146
+
147
+ return false;
148
+ }
149
+
150
+ function calculateSizes($t, childSelector, maxWidth, maxFontSize, minFontSize)
151
+ {
152
+ var $c = $t.clone(true)
153
+ .addClass('bigtext-cloned')
154
+ .css({
155
+ fontFamily: $t.css('font-family'),
156
+ 'min-width': parseInt(maxWidth, 10),
157
+ width: 'auto',
158
+ position: 'absolute',
159
+ left: -9999,
160
+ top: -9999
161
+ }).appendTo(document.body);
162
+
163
+ // font-size isn't the only thing we can modify, we can also mess with:
164
+ // word-spacing and letter-spacing.
165
+ // Note: webkit does not respect subpixel letter-spacing or word-spacing,
166
+ // nor does it respect hundredths of a font-size em.
167
+ var fontSizes = [],
168
+ wordSpacings = [],
169
+ minFontSizes = [],
170
+ ratios = [];
171
+
172
+ $c.find(childSelector).css({
173
+ 'float': 'left',
174
+ 'clear': 'left'
175
+ }).each(function(lineNumber) {
176
+ var $line = $(this),
177
+ intervals = [4,1,.4,.1],
178
+ lineMax;
179
+
180
+ if($line.hasClass(BigText.EXEMPT_CLASS)) {
181
+ fontSizes.push(null);
182
+ ratios.push(null);
183
+ minFontSizes.push(false);
184
+ return;
185
+ }
186
+
187
+ // TODO we can cache this ratio?
188
+ var autoGuessSubtraction = 20, // px
189
+ currentFontSize = parseFloat($line.css('font-size')),
190
+ lineWidth = $line.width(),
191
+ ratio = (lineWidth / currentFontSize).toFixed(6),
192
+ newFontSize = parseFloat(((maxWidth - autoGuessSubtraction) / ratio).toFixed(3));
193
+
194
+ outer: for(var m=0, n=intervals.length; m<n; m++) {
195
+ inner: for(var j=1, k=4; j<=k; j++) {
196
+ if(newFontSize + j*intervals[m] > maxFontSize) {
197
+ newFontSize = maxFontSize;
198
+ break outer;
199
+ }
200
+
201
+ lineMax = testLineDimensions($line, maxWidth, 'font-size', newFontSize + j*intervals[m], intervals[m], 'px');
202
+ if(lineMax !== false) {
203
+ newFontSize = lineMax.size;
204
+
205
+ if(lineMax.match == 'exact') {
206
+ break outer;
207
+ }
208
+ break inner;
209
+ }
210
+ }
211
+ }
212
+
213
+ ratios.push(maxWidth / newFontSize);
214
+
215
+ if(newFontSize > maxFontSize) {
216
+ fontSizes.push(maxFontSize);
217
+ minFontSizes.push(false);
218
+ } else if(!!minFontSize && newFontSize < minFontSize) {
219
+ fontSizes.push(minFontSize);
220
+ minFontSizes.push(true);
221
+ } else {
222
+ fontSizes.push(newFontSize);
223
+ minFontSizes.push(false);
224
+ }
225
+ }).each(function(lineNumber) {
226
+ var $line = $(this),
227
+ wordSpacing = 0,
228
+ interval = 1,
229
+ maxWordSpacing;
230
+
231
+ if($line.hasClass(BigText.EXEMPT_CLASS)) {
232
+ wordSpacings.push(null);
233
+ return;
234
+ }
235
+
236
+ // must re-use font-size, even though it was removed above.
237
+ $line.css('font-size', fontSizes[lineNumber] + 'px');
238
+
239
+ for(var m=1, n=5; m<n; m+=interval) {
240
+ maxWordSpacing = testLineDimensions($line, maxWidth, 'word-spacing', m, interval, 'px');
241
+ if(maxWordSpacing !== false) {
242
+ wordSpacing = maxWordSpacing.size;
243
+ break;
244
+ }
245
+ }
246
+
247
+ $line.css('font-size', '');
248
+ wordSpacings.push(wordSpacing);
249
+ }).removeAttr('style');
250
+
251
+ $c.remove();
252
+
253
+ return {
254
+ fontSizes: fontSizes,
255
+ wordSpacings: wordSpacings,
256
+ ratios: ratios,
257
+ minFontSizes: minFontSizes
258
+ };
259
+ }
260
+
261
+ $.fn.bigtext = BigText.jQueryMethod;
262
+ window.BigText = BigText;
263
+
264
+ })(this, jQuery);