root/platform/tdiary/plugin/tdiarytimes2.rb

Revision 5057, 11.3 kB (checked in by hsbt, 11 months ago)

platform/tdiary/plugin: changed file encoding to UTF-8

Line 
1# tdiarytimes.rb $originalRevision: 1.1 $
2#
3# Copyright (c) 2003 neuichi <neuichi@nmnl.jp>
4# Distributed under the GPL
5#
6# 2003-12-01 circle extention added by Minero Aoki <aamine@loveruby.net>
7# $Id: tdiarytimes2.rb,v 1.2 2007/01/11 02:55:26 tadatadashi Exp $
8#
9# プラグイン配布ページ
10# http://i.loveruby.net/w/tdiarytimes.html
11#
12# 動作条件:
13# ruby-gdが使える環境が必要です。
14#
15# 使い方:
16# このプラグインをプラグインディレクトリに入れ、
17# index.rbと同じディレクトリに、tdiarytimes.pngという名前の
18# サーバが書き込み権限を持っているファイルを作ります。
19#       これで日記に書き込みするごとに、tdiarytimes.pngに
20#       画像を書き込みます。
21#
22# 日記上からこのpngファイルを呼び出すには、
23# tDiray上からプラグインとして
24# <%=tdiarytimes%>
25# として呼び出します。
26# 引数としてimgタグのaltの文字列を指定することも出来ます。
27# <%=tdiarytimes '文字列'%>
28#
29# また、tdiary.confに以下のオプションを書き込むことにより、
30# カスタマイズをすることが出来ます。
31#
32# @options['tdiarytimes.width'] = 400
33# 四角の横幅。デフォルト値400。
34# 実際に出力される画像サイズは、これに+10したサイズ。
35#
36# @options['tdiarytimes.height'] = 20
37# 四角の縦幅。デフォルト値20。
38# 実際に出力される画像サイズは、これに+16したサイズ。
39#
40# @options['tdiarytimes.file'] = 'tdiarytimes.png'
41# 出力する画像ファイル名。デフォルトは'tdiarytimes.png'
42#
43# @options['tdiarytimes.fillcolor'] = '#444444'
44# 四角の色。デフォルトは'#444444'
45#
46# @options['tdiarytimes.linecolor'] = '#ffffff'
47# 縦棒の色。デフォルトは'#ffffff'
48#
49# @options['tdiarytimes.textcolor'] = '#444444'
50# 文字色。デフォルトは'#444444'
51#
52# @options['tdiarytimes.fadeout'] = false
53# フェードアウトするか。デフォルトはfalse。
54# フェードアウトしたいときには true にすればよい。
55#
56# @options['tdiarytimes.fadeoutcolor'] = '#ffffff'
57# フェードアウトするとき、
58# デフォルトではfillcolorへとフェードアウトしていく。
59# ここで色を指定するとその色へとフェードアウトしていく。
60# デフォルトは false
61#
62# @options['tdiarytimes.text'] = 'T D I A R Y T I M E S'
63# 出力する文字。デフォルトは'T D I A R Y T I M E S'。なお半角英数字のみ対応。
64#
65# @options['tdiarytimes.day'] = 30
66# ログを保存する最大日数。デフォルトは30。
67# この場合、30日以上経ったデータは消去され、縦棒として描画されなくなる。
68#
69
70require 'GD'
71
72::GD::Image.module_eval {
73  def tiny_string(text, x, y, color)
74    string(GD::Font::TinyFont, x, y, text, color)
75  end
76
77  def small_string(text, x, y, color)
78    string(GD::Font::SmallFont, x, y, text, color)
79  end
80}
81
82# Ruby 1.6 missing File.read
83unless ::File.respond_to?(:read)
84  def (::File).read(fname)
85    File.open(fname) {|f|
86      return f.read
87    }
88  end
89end
90
91# Ruby 1.6 missing MatchData#captures
92unless ::MatchData.method_defined?(:captures)
93  ::MatchData.module_eval {
94    def captures
95      a = to_a()
96      a.shift
97      a
98    end
99  }
100end
101
102class TDiaryTimes
103  class << TDiaryTimes
104    alias newobj new
105  end
106
107  def TDiaryTimes.new(datadir, options)
108    case options['tdiarytimes.shape']
109    when 'bar', nil
110      c = TDiaryTimesBar
111    when 'circle'
112      c = TDiaryTimesCircle
113    else
114      raise ArgumentError, "unknown tdiarytimes.shape: #{options['tdiarytimes.shape']}"
115    end
116    c.newobj("#{datadir}/tdiarytimes", options)
117  end
118
119  def initialize(dbfile, options)
120    @dbfile     = dbfile
121    @image_file = options['tdiarytimes.file'] || 'tdiarytimes.png'
122    @day        = options['tdiarytimes.day'] || 30
123    @keepdb     = options['tdiarytimes.keepdb']
124  end
125
126  attr_reader :image_file
127
128  def update_image
129    now = Time.now
130    mtimes = (load_database() + [now]).reject {|tm|
131               (now - tm) > (60 * 60 * 24 * @day)
132             }
133    image = create_image(mtimes)
134    File.open(@image_file, 'w') {|png|
135      image.png(png)
136    }
137    save_database(mtimes) unless @keepdb
138  end
139
140  private
141
142  #
143  # database
144  #
145
146  def load_database
147    begin
148      return Marshal.load(File.read(@dbfile))
149    rescue Errno::ENOENT
150      return []
151    end
152  end
153
154  def save_database(content)
155    File.open(@dbfile, 'w') {|f|
156      Marshal.dump(content, f)
157    }
158  end
159
160  #
161  # common paint methods
162  #
163
164  def fadeout_color(srccolor, destcolor, time)
165    par = (Time.now - time).to_f / (60 * 60 * 24 * @day)
166    r, g, b = *zip(parse_rgb(srccolor), parse_rgb(destcolor))\
167        .map {|src, dest| src - (src - dest) * par }.map {|c| c.to_i }
168    sprintf('#%02x%02x%02x', r, g, b)
169  end
170
171  def parse_rgb(str)
172    hex = '[\da-f]'
173    m = /\A\#(#{hex}{2})(#{hex}{2})(#{hex}{2})\z/io.match(str) or
174        raise ArgumentError, "tdiarytimes: not color: #{str.inspect}"
175    m.captures.map {|c| c.hex }
176  end
177
178  def zip(*lists)
179    result = []
180    lists[0].each_index do |idx|
181      result.push lists.map {|lst| lst[idx] }
182    end
183    result
184  end
185end
186
187class TDiaryTimesBar < TDiaryTimes
188  def initialize(dbfile, options)
189    super
190    @text         = options['tdiarytimes.text'] || 'T D I A R Y T I M E S'
191    @width        = options['tdiarytimes.width'].to_i || 400
192    @height       = options['tdiarytimes.height'].to_i || 20
193    @textcolor    = options['tdiarytimes.textcolor'] || '#444444'
194    @fillcolor    = options['tdiarytimes.fillcolor'] || '#444444'
195    @linecolor    = options['tdiarytimes.linecolor'] || '#ffffff'
196    @fadeoutcolor = options['tdiarytimes.fadeoutcolor'] || @fillcolor
197    @fadeoutp     = options['tdiarytimes.fadeout']
198  end
199
200  def html(alt)
201    %Q[<img src="#{h image_file()}"
202            alt="#{h( alt || @text )}"
203            width="#{@width + GAP_W}"
204            height="#{@height + GAP_H}"
205            class="tdiarytimes">].gsub(/\s+/, ' ')
206  end
207
208  private
209
210  GAP_W = 16
211  GAP_H = 16
212
213  def create_image(mtimes)
214    image = GD::Image.new(@width + GAP_W, @height + GAP_H)
215    image.transparent(image.colorAllocate('#fffffe'))
216    image.interlace = true
217
218    image.tiny_string @text, (GAP_W / 2), 0, image.colorAllocate(@textcolor)
219    image.filledRectangle           0 + GAP_W / 2,            0 + GAP_H / 2,
220                          image.width - GAP_W / 2, image.height - GAP_H / 2,
221                          image.colorAllocate(@fillcolor)
222    if @fadeoutp
223      paint_lines_fadeout image, mtimes, @linecolor, @fadeoutcolor
224    else
225      paint_lines         image, mtimes, @linecolor
226    end
227    paint_hours image, image.colorAllocate(@textcolor),
228                (image.width - GAP_W > 160 ? 2 : 4)
229
230    image
231  end
232
233  def paint_lines(image, mtimes, color)
234    gdcolor = image.colorAllocate(color)
235    mtimes.each do |time|
236      line image, time, gdcolor
237    end
238  end
239
240  def paint_lines_fadeout(image, mtimes, linecolor, destcolor)
241    mtimes.each do |time|
242      line image, time,
243           image.colorAllocate(fadeout_color(linecolor, destcolor, time))
244    end
245  end
246
247  def line(image, time, color)
248    x0 = (image.width - GAP_W).to_f * (time.hour * 60 + time.min) / (60 * 24)
249    x = (x0 + (GAP_W / 1.25)).to_i
250    image.line x,            0 + (GAP_H / 2),
251               x, image.height - (GAP_H / 2),
252               color
253  end
254
255  def paint_hours(image, color, stepping)
256    0.step(24, stepping) do |hour|
257      image.tiny_string hour.to_s,
258          (image.width - GAP_W) * (hour.to_f / 24) + (GAP_W / 2) - 4,
259          image.height - (GAP_H / 2),
260          color
261    end
262  end
263end
264
265class TDiaryTimesCircle < TDiaryTimes
266  def initialize(dbfile, options)
267    super
268    #@text   # cannot change now
269    @width        = options['tdiarytimes.width'].to_i || 80
270    @height       = options['tdiarytimes.height'].to_i || 80
271    @textcolor    = options['tdiarytimes.textcolor'] || '#444444'
272    @fillcolor    = options['tdiarytimes.fillcolor'] || '#444444'
273    @linecolor    = options['tdiarytimes.linecolor'] || '#ffffff'
274    @fadeoutcolor = options['tdiarytimes.fadeoutcolor'] || @fillcolor
275    @fadeoutp     = options['tdiarytimes.fadeout']
276  end
277
278  def html(alt)
279    %Q[<img src="#{h image_file()}"
280            alt="#{h( alt || '' )}"
281            width="#{@width}"
282            height="#{@height}"
283            class="tdiarytimes">].gsub(/\s+/, ' ')
284  end
285
286  private
287
288  MIN_DEGREE = 0
289  MAX_DEGREE = 250
290  BAR_WIDTH = 24
291  TRANSCOLOR = '#ffffff'
292
293  def create_image(mtimes)
294    image = GD::Image.new(@width, @height)
295    trans = image.colorAllocate(TRANSCOLOR)
296    image.transparent trans
297    image.interlace = true
298
299    paint_outer_circle image, trans
300    if @fadeoutp
301      paint_lines_fadeout image, mtimes, @linecolor, @fadeoutcolor
302    else
303      paint_lines         image, mtimes, @linecolor
304    end
305    paint_inner_circle image, trans
306    textcolor = image.colorAllocate(@textcolor)
307    image.small_string 'tdiary', @width / 2 + 1, 11, textcolor
308    image.small_string 'times',  @width / 2 + 4, 21, textcolor
309
310    image
311  end
312
313  def paint_outer_circle(image, trans)
314    image.filledArc @width / 2, @height / 2,
315                    @width, @height,
316                    MIN_DEGREE, MAX_DEGREE,
317                    if @fillcolor == TRANSCOLOR
318                        then trans
319                        else image.colorAllocate(@fillcolor)
320                        end,
321                    0
322  end
323
324  def paint_inner_circle(image, trans)
325    image.filledArc @width / 2, @height / 2,
326                    @width - BAR_WIDTH * 2,
327                    (@width - BAR_WIDTH * 2) * (@height.to_f / @width),
328                    0, 360, trans, 0
329  end
330
331  def paint_lines(image, mtimes, color)
332    gdcolor = image.colorAllocate(color)
333    mtimes.each do |time|
334      line image, time, gdcolor
335    end
336  end
337
338  def paint_lines_fadeout(image, mtimes, linecolor, destcolor)
339    mtimes.each do |time|
340      line image, time,
341           image.colorAllocate(fadeout_color(linecolor, destcolor, time))
342    end
343  end
344
345  def line(image, time, color)
346    d0 = (time.hour * 60 + time.min).to_f / (60 * 24)
347    d = MIN_DEGREE + (MAX_DEGREE - MIN_DEGREE) * d0
348    image.line @width / 2, @height / 2,
349               @width  / 2 + degcos(d) * (@width  / 2) * 0.95,
350               @height / 2 + degsin(d) * (@height / 2) * 0.95,
351               color
352  end
353
354  include Math
355
356  def degsin(d)
357    sin(d / (180 / Math::PI))
358  end
359
360  def degcos(d)
361    cos(d / (180 / Math::PI))
362  end
363end
364
365def tdiarytimes(alt = nil)
366  TDiaryTimes.new(@conf.data_path, @options).html(alt)
367end
368
369
370if $0 == __FILE__   # debugging
371  tmp_options_bar = {
372    'tdiarytimes.shape'     => 'bar',
373    'tdiarytimes.textcolor' => '#666666',
374    'tdiarytimes.linecolor' => '#df0000',
375    'tdiarytimes.fillcolor' => '#0f5f0f',
376    'tdiarytimes.fadeout'   => true,
377    'tdiarytimes.keepdb'    => false   # for debug
378  }
379  tmp_options_circle = {
380    'tdiarytimes.shape'     => 'circle',
381    'tdiarytimes.textcolor' => '#000000',
382    'tdiarytimes.linecolor' => '#bfbfbf',
383    'tdiarytimes.fillcolor' => '#2f2f7f',
384    'tdiarytimes.fadeout'   => true,
385    'tdiarytimes.keepdb'    => false   # for debug
386  }
387
388  @mode = 'latest'
389  @conf = Object.new
390  def @conf.data_path
391    '.'
392  end
393  case ARGV[0]
394  when 'bar'
395    @options = tmp_options_bar
396  else
397    @options = tmp_options_circle
398  end
399  TDiaryTimes.new(@conf.data_path, @options).update_image
400  puts tdiarytimes()
401  exit 0
402end
403
404if /append|replace/ =~ @mode
405  TDiaryTimes.new(@conf.data_path, @options).update_image
406end
Note: See TracBrowser for help on using the browser.