prawn_calendar 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ explicit.pdf
19
+ hugo.pdf
20
+ implicit.pdf
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in prawn_calendar.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Bernhard Weichel
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # PrawnCalendar
2
+
3
+ This gem provides a class to generate calendars with schedules using prawn.
4
+
5
+ * Weekly overview
6
+ * Specifiy hours of a day to show
7
+ * Handle schedules out of the limits of a day
8
+ * Indicate recurring schedules
9
+
10
+ see [Sample output](spec/output/testcalendar.pdf)
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'prawn_calendar'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install prawn_calendar
25
+
26
+ ## Usage
27
+
28
+ PrawnCalendar is a ruby library. See the spec
29
+ how to use it.
30
+
31
+ For the impatient ...
32
+
33
+ ~~~~ruby
34
+ it "implicit creates a calendar" do
35
+ Prawn::Document.generate("implicit.pdf") do
36
+ calendar=PrawnCalendar::WeeklyCalendar.new(self)
37
+ calendar.mk_calendar([20,700], width:500,height:250) do
38
+ cal_entry("2013-05-04T08:00:00+01:00", "2013-05-04T19:00:00", "cc 7as ist ein test, der laufen muss")
39
+
40
+ end
41
+ end
42
+ end
43
+
44
+ ~~~~
45
+
46
+ ## Limitations
47
+
48
+ * It does not handle schedules over multiple days
49
+ * Only generates week calendars
50
+ * cannot colorize calendars
51
+ * no exception handling
52
+
53
+ ## Contributing
54
+
55
+ 1. Fork it
56
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
57
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
58
+ 4. Push to the branch (`git push origin my-new-feature`)
59
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+ require 'rake/clean'
4
+
5
+ desc "Run specs"
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.pattern = "./**/*_spec.rb" # don't need this, it's default.
8
+ t.rspec_opts = ['-fd -fd --out ./testresults/test_results.log -fh --out ./testresults/test_results.html']
9
+ # Put spec opts in a file named .rspec in root
10
+ end
11
+
12
+
13
+ desc "document (yard) all AUTOSAR ruby helpers defined here"
14
+ task :doc do
15
+ sh "yard --markup markdown doc . "
16
+ end
@@ -0,0 +1,3 @@
1
+ module PrawnCalendar
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,399 @@
1
+ require "prawn_calendar/version"
2
+ require 'prawn'
3
+ require 'time'
4
+ require 'date'
5
+
6
+
7
+ module Prawn
8
+ class Document
9
+
10
+ # Defines the grid system for a particular document. Takes the number of
11
+ # rows and columns and the width to use for the gutter as the
12
+ # keys :rows, :columns, :gutter, :row_gutter, :column_gutter
13
+ #
14
+ def define_grid(options = {})
15
+ @grid = Grid.new(self, options)
16
+ @boxes = nil # see
17
+ end
18
+ end
19
+ end
20
+
21
+ module PrawnCalendar
22
+
23
+ #
24
+ # [ class description]
25
+ #
26
+ # @author [author]
27
+ #
28
+ class WeeklyCalendar
29
+ # the Prawn instance xxx
30
+ attr_accessor :pdf
31
+
32
+ # the start of the interval
33
+ attr_accessor :c_date_start
34
+
35
+ # the gutter of calendar annotations
36
+ attr_accessor :c_annotation_gutter
37
+
38
+ # the number of divisions in calender columns
39
+ # this is to adjust the proportion of the first and
40
+ # the subsequent columns
41
+ attr_accessor :c_col_division
42
+
43
+ # the end of the interval
44
+ attr_accessor :c_date_end
45
+
46
+ # An array with labels of the day. e.g. ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]
47
+ attr_accessor :c_days
48
+
49
+ # The fontsize of calendar entries
50
+ attr_accessor :c_entry_fontsize
51
+
52
+ # The gutter of calendar entries
53
+ attr_accessor :c_entry_gutter
54
+
55
+ # The radius of the calendar entries
56
+ attr_accessor :c_entry_radius
57
+
58
+ # The right margin of calendar entries
59
+ attr_accessor :c_entry_right_gutter
60
+
61
+ # The number of Rows shown in the calendar without time
62
+ # These rows are intended for additional comments
63
+ attr_accessor :c_extra_rows
64
+
65
+ # the number of divisions in the first column
66
+ # the one showing the time
67
+ attr_accessor :c_firstcol_division
68
+
69
+ # The font size of the calendar annotations
70
+ attr_accessor :c_fontsize
71
+
72
+ # the number of divisions of the calenar rows.
73
+ # this finally determines the resolution of
74
+ # time shown in the calendar.
75
+ # Defaults to 4 which is (15 minutes)
76
+ #
77
+ attr_accessor :c_row_division
78
+
79
+ # the time where calendar display ends (defaults to 22)
80
+ attr_accessor :c_time_end
81
+
82
+ # the time where calendar display starts (defaults to 8)
83
+ attr_accessor :c_time_start
84
+
85
+
86
+ #
87
+ # This is the write accessor to the attributre c_cate_start.
88
+ # Note that this adjusts the start date such that it
89
+ # comes to a monday and sets the end of the interval
90
+ # to the subseqent sunday
91
+ #
92
+ # @param day [String] Iso 8601 form of the start date.
93
+ #
94
+ # @return [type] [description]
95
+ def c_date_start=(day)
96
+ # round to the beginning of the day.
97
+ d = Date.iso8601(day)
98
+ # note that we start the week on monday, therefore d -(d-1).wday
99
+ # note that Date adds days, while Time adds seconds
100
+ #
101
+ # compute the beginning of the week
102
+ @c_date_start = (d -(d-1).wday).to_time
103
+ # set end to the last second of end date
104
+ @c_date_end = (@c_date_start.to_date + 7).to_time-1
105
+ end
106
+
107
+ #
108
+ # This is the constructor
109
+ # @param pdf [Prawn] The handle to prawn which renders the calendar
110
+ # @param &block [Proc] Code to change the initial configruation of the calendar.
111
+ #
112
+ # @return [type] [description]
113
+ def initialize(pdf, &block)
114
+ @pdf=pdf
115
+
116
+ @c_annotation_gutter = 2
117
+ @c_col_division = 5
118
+ self.c_date_start=(Date.today.iso8601) # default to today.
119
+ @c_days = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]
120
+ @c_entry_fontsize = 7
121
+ @c_entry_gutter = 0
122
+ @c_entry_radius = 2
123
+ @c_entry_right_gutter = 2
124
+ @c_extra_rows = 1
125
+ @c_firstcol_division = 2
126
+ @c_fontsize = 8
127
+ @c_row_division = 4
128
+ @c_time_end = 22
129
+ @c_time_start = 8
130
+ yield(self) if block_given?
131
+ end
132
+
133
+
134
+ #
135
+ # This generates an entry in the calendar gird.
136
+ # Basically it is used for annotations.
137
+ #
138
+ # Note that the coordinates are counter intuitive related
139
+ # to the usual positioning in pdf/prawn. While bounding_box
140
+ # is [horizontal, vertical] this stuff is [vertial, horizontal]
141
+ #
142
+ # @param ll [Array] left point [row, column] in grid coordinates
143
+ # [0,0] is the upper left corner
144
+ # [0,1] is the second field in the first row.
145
+ # @param rr [Array] end point [row, column] in grid coordinates
146
+ #
147
+ # @param text [String] The string to be placed. It allows to use
148
+ # html formatting as far as supported by prawn.
149
+ #
150
+ # @return nil
151
+ def mk_entry(ll, rr, text)
152
+ @pdf.grid(ll, rr).bounding_box do
153
+
154
+ @pdf.stroke_bounds
155
+ excess_text = @pdf.text_box text,
156
+ :at => [@c_annotation_gutter, @pdf.bounds.height - @c_annotation_gutter],
157
+ :width => @pdf.bounds.width - (2 * @c_annotation_gutter),
158
+ :height => @pdf.bounds.height - (2 * @c_annotation_gutter),
159
+ :overflow => :truncate,
160
+ :kerning => true,
161
+ :inline_format => true,
162
+ :size => @c_fontsize
163
+ end
164
+ nil
165
+ end
166
+
167
+
168
+ #
169
+ # This makes a calendar entry based on time.
170
+ #
171
+ # Please note
172
+ #
173
+ # * it is not very robust yet
174
+ # * it only supports single day entries
175
+ # * entries are truncated if they do not fit into the range of hours
176
+ # in this case, the end time is added to the text
177
+ #
178
+ # @param starttime [String] Starttime in iso8601 format
179
+ # @param endtime [String] Endtime in iso8601 format
180
+ # @param text [String] The text of calender entry
181
+ # @param extraargs [Hash] additional arguments
182
+ # :recurring true/false
183
+ #
184
+ # @return nil
185
+ #
186
+ def cal_entry(starttime, endtime, text, extraargs={})
187
+ time_to_start = Time.iso8601(starttime).localtime
188
+ time_to_end = Time.iso8601(endtime).localtime
189
+
190
+ a= ((@c_date_start.to_time .. @c_date_end.to_time).cover?(time_to_start))
191
+ puts "datum ausserhalb des Kalenders #{@c_date_start} < #{time_to_start} < #{@c_date_end} #{text}" if a==false
192
+
193
+ hour_to_start = time_to_start.hour
194
+ hour_to_end = time_to_end.hour
195
+ min_to_start = time_to_start.min / (60/@c_row_division)
196
+ min_to_end = time_to_end.min / (60/@c_row_division)
197
+
198
+ finaltext="<b>#{text}</b>"
199
+
200
+ # handle the case that the entry is out of bounds
201
+ # in this case it is drawn in the extra lines
202
+ # an start/endtime is shown as extra tet
203
+ extratext=nil
204
+
205
+
206
+
207
+ # entry starts too early
208
+ if (hour_to_start == 0) || (hour_to_start) < @c_time_start then
209
+ extratext = true
210
+ hour_to_start = @c_time_start-1
211
+ min_to_start = 0
212
+ end
213
+
214
+ # entry ends too early
215
+ if (hour_to_end == 0) || (hour_to_end) < @c_time_start then
216
+ extratext=true
217
+ hour_to_end= @c_time_start
218
+ min_to_end = 0
219
+ end
220
+
221
+ # entry starts too late
222
+
223
+ if (hour_to_start == 0) || (hour_to_start >= @c_time_end+1) then
224
+ extratext = true
225
+ hour_to_start = @c_time_end + 1
226
+ min_to_start = 0
227
+ end
228
+
229
+ # entry ends too late
230
+ if (hour_to_end == 0) || (hour_to_end >= @c_time_end+1) then
231
+ extratext = true
232
+ hour_to_end = @c_time_end + 2
233
+ min_to_end = 0
234
+ end
235
+
236
+ if extratext then
237
+ finaltext = finaltext + "<br><font size='#{@c_fontsize-2}'>#{time_to_start.strftime('%k.%M')} - #{time_to_end.strftime('%k.%M')}</font>"
238
+ end
239
+
240
+
241
+
242
+
243
+ starttime_s = time_to_start.strftime("%k.%M")
244
+ endtime_s = time_to_end.strftime("%k.%M")
245
+
246
+ text_to_show = "#{starttime_s} - #{endtime_s}<br/>#{text}"
247
+ text_to_show = "#{finaltext}"
248
+
249
+ day = (time_to_start.wday + 6) % 7 # since we start on monday shift it one left
250
+ column = @c_firstcol_division + day * @c_col_division
251
+
252
+ srow = 2 * @c_row_division + (hour_to_start - @c_time_start) * @c_row_division + min_to_start
253
+ erow = 2 * @c_row_division + (hour_to_end - @c_time_start) * @c_row_division -1 + min_to_end
254
+
255
+ cal_entry_raw([srow, column], erow - srow, text_to_show, extraargs[:recurring])
256
+ end
257
+
258
+
259
+ #
260
+ # This creates a raw calendar entry based on grid coordinates
261
+ #
262
+ # @param ll [Array] Left corner, see mk_entry for details.
263
+ # @param length [Integer] The number of grid rows covered by the entry
264
+ # @param text [String] The text of the calendar entry
265
+ #
266
+ # @return nil
267
+ def cal_entry_raw(ll, length, text, recurring=false)
268
+ width=4
269
+ ur=[ll[0]+length, ll[1]+width]
270
+ @pdf.grid(ll,ur).bounding_box do
271
+
272
+ @pdf.fill_color "f0f0f0"
273
+
274
+ @pdf.line_width 0.1
275
+
276
+
277
+ # add one pixel to the borders to keep the entry away from the lines
278
+ @pdf.rounded_rectangle([@c_entry_gutter+1, @pdf.bounds.height - @c_entry_gutter], # startpoint
279
+ @pdf.bounds.width - 2 * @c_entry_gutter - @c_entry_right_gutter -2, # width
280
+ @pdf.bounds.height - 2 * @c_entry_gutter, # height
281
+ @c_entry_radius # radius
282
+ )
283
+ @pdf.fill_and_stroke
284
+
285
+ @pdf.fill_color "000000"
286
+
287
+ if recurring==true then
288
+ @pdf.text_box "(w)",
289
+ :at => [@pdf.bounds.width - 13,8],
290
+ :width => 10,
291
+ :size => 6
292
+ end
293
+
294
+ if recurring==true then
295
+ @pdf.rounded_rectangle([@pdf.bounds.width - 2, @c_entry_gutter], # startpoint
296
+ 2 , # width
297
+ 2 , # height
298
+ @c_entry_radius # radius
299
+ )
300
+ @pdf.fill_and_stroke
301
+ end
302
+
303
+
304
+
305
+ # text is limited to gutter. Therefore gutter needs to be doubled
306
+ # no limit at right and bottom
307
+ excess_text = @pdf.text_box text,
308
+ :at => [@c_entry_gutter +2, @pdf.bounds.height- 2*@c_entry_gutter-1], # need 1 pixel more a the top
309
+ :width => @pdf.bounds.width-4*@c_entry_gutter -2*@c_entry_right_gutter,
310
+ :height => @pdf.bounds.height-4*@c_entry_gutter,
311
+ :overflow => :truncate,
312
+ :kerning => true,
313
+ :inline_format => true,
314
+ :size => @c_entry_fontsize
315
+ end
316
+ nil
317
+ end
318
+
319
+
320
+
321
+ #
322
+ # Creates an empty calendar
323
+ #
324
+ # @param ll [Array] the start point of the calendar
325
+ # values are points, 0,0 is the lower left corner of the calender
326
+ # see Prawn::Document.bounding_box for details
327
+ # @param opts [Hash] [the options, keys: :width, :height]
328
+ # @param &block [Proc] The statements to fill the calendar.
329
+ #
330
+ # @return [type] [description]
331
+ def mk_calendar(ll, opts, &block)
332
+ @pdf.bounding_box(ll, width: opts[:width], height: opts[:height]) do
333
+ @pdf.stroke_bounds
334
+ rows = (2 + @c_time_end - @c_time_start +1 + @c_extra_rows) * @c_row_division
335
+ columns = @c_firstcol_division + @c_days.count * @c_col_division
336
+
337
+ @pdf.define_grid(:columns => columns, :rows => rows, :gutter => 0)
338
+
339
+ # the frames
340
+ @pdf.line_width 1
341
+ mk_entry([0,0],
342
+ [rows - 1, columns-1], "") # outer frame
343
+
344
+ mk_entry([2 * @c_row_division, @c_firstcol_division],
345
+ [rows - 1 , columns-1], "") # inner frame
346
+
347
+ mk_entry([2 * @c_row_division, 0],
348
+ [rows -1, @c_firstcol_division - 1], "") # left frame
349
+
350
+ # the day - line
351
+ row=0;col=@c_firstcol_division
352
+ curday=@c_date_start.to_date
353
+
354
+ @c_days.each do |i|
355
+
356
+ columnhead="#{i} #{curday.strftime('%d.%m.')}"
357
+ mk_entry([row,col], [row+@c_row_division-1, col+@c_col_division-1], columnhead)
358
+ #require 'pry';binding.pry
359
+ curday=curday+1
360
+ col += @c_col_division
361
+ end
362
+
363
+ # the day after line
364
+ row=@c_row_division; col=@c_firstcol_division
365
+ @pdf.line_width 0.75
366
+ @c_days.each do |i|
367
+ mk_entry([row, col], [rows -1, col + @c_col_division - 1], "")
368
+ col+=@c_col_division
369
+ end
370
+
371
+ #the calendar fields
372
+ @pdf.line_width "0.1"
373
+ col=0;row=@c_row_division
374
+
375
+ # the rows
376
+ rowlabels=Array(@c_time_start .. @c_time_end).map{|i| "0#{i}.00"[-5..5]}
377
+
378
+ a=[" ", rowlabels, [].fill(" ", 0, @c_extra_rows)]
379
+ a.flatten.each do |i|
380
+
381
+ # the first column
382
+ # -1 bcause it denotes tha last filled cell, not the next unfilled one
383
+ mk_entry([row,col],[row + @c_row_division - 1, col+@c_firstcol_division - 1], "#{i}")
384
+
385
+ # the other columns
386
+ icol=@c_firstcol_division
387
+ (1..7).each do |i|
388
+ mk_entry([row, icol], [row + @c_row_division -1, icol + @c_col_division - 1], " ")
389
+ icol=icol + @c_col_division
390
+ end
391
+ row += @c_row_division
392
+ end
393
+
394
+ #yield the block
395
+ instance_eval(&block)
396
+ end
397
+ end
398
+ end
399
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'prawn_calendar/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "prawn_calendar"
8
+ spec.version = PrawnCalendar::VERSION
9
+ spec.authors = ["Bernhard Weichel"]
10
+ spec.email = ["github.com@nospam.weichel21.de"]
11
+ spec.description = %q{This gem provides a function to generate calendars and calendar entries.}
12
+ spec.summary = %q{generate calendars with prawn}
13
+ spec.homepage = "https://github.com/bwl21/prawn_calendar"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "prawn" # "~> 1.0.0.rc2"
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency "yard"
26
+ spec.add_development_dependency "redcarpet"
27
+ spec.add_development_dependency "pry"
28
+ end