| 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 | |
|---|
| 70 | require '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 |
|---|
| 83 | unless ::File.respond_to?(:read) |
|---|
| 84 | def (::File).read(fname) |
|---|
| 85 | File.open(fname) {|f| |
|---|
| 86 | return f.read |
|---|
| 87 | } |
|---|
| 88 | end |
|---|
| 89 | end |
|---|
| 90 | |
|---|
| 91 | # Ruby 1.6 missing MatchData#captures |
|---|
| 92 | unless ::MatchData.method_defined?(:captures) |
|---|
| 93 | ::MatchData.module_eval { |
|---|
| 94 | def captures |
|---|
| 95 | a = to_a() |
|---|
| 96 | a.shift |
|---|
| 97 | a |
|---|
| 98 | end |
|---|
| 99 | } |
|---|
| 100 | end |
|---|
| 101 | |
|---|
| 102 | class 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 |
|---|
| 185 | end |
|---|
| 186 | |
|---|
| 187 | class 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 |
|---|
| 263 | end |
|---|
| 264 | |
|---|
| 265 | class 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 |
|---|
| 363 | end |
|---|
| 364 | |
|---|
| 365 | def tdiarytimes(alt = nil) |
|---|
| 366 | TDiaryTimes.new(@conf.data_path, @options).html(alt) |
|---|
| 367 | end |
|---|
| 368 | |
|---|
| 369 | |
|---|
| 370 | if $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 |
|---|
| 402 | end |
|---|
| 403 | |
|---|
| 404 | if /append|replace/ =~ @mode |
|---|
| 405 | TDiaryTimes.new(@conf.data_path, @options).update_image |
|---|
| 406 | end |
|---|