2008-02-01 (金)
■ GIMP と ImageMagick の画像縮小アルゴリズム
画像を加工するのに GIMP を使っていたのだけれど、 どうも縮小する時の品質が良くない気がしたので検索してみた。
いつも、縮小アルゴリズム(というか補間に用いるフィルタ)には bicubic を指定していたが、 実際にはその指定は無視されて、平均画素法というのが使われるらしい。
じゃあ、ImageMagick で縮小するとどうだろう? 調べると、-filter オプションでフィルタをいろいろと指定できるようだ。 ここに、フィルタと生成される画像の一覧が載っていた。
デフォルトでは Lanczos が使われるらしいが、 GIMP で縮小したものよりも輪郭がはっきり出ており、細かい部分がぼけていない気がする。 ImageMagick で縮小する方がいいな。
ついでにメモっておくと、具体的にはこんなコマンドで処理できる。 少しくっきりさせるために、sharpen もかけてみた。
$ convert -sharpen 1 original.png sharpen.png $ convert -resize 16x16 sharpen.png icon_16x16.png
複数ファイルをまとめて処理したい場合は mogrify コマンドを使えばよい。
2008-02-04 (月)
■ PukiWiki のページ内リンクをスムーズスクロール化
気分転換にやってみたので途中経過を。 まず、スムーズスクロールを実現する JavaScript を探してきた。
それぞれライセンスは X11 licence、MIT licence なので、安心して使える。
次に、後者のライブラリは name 属性のアンカータグにしか対応していないので、 id 属性に対応させるために以下のように書き換える。
--- smooth-src-comments.js.orig
+++ smooth-src-comments.js
@@ -81,13 +81,11 @@
l.onclick = function(){
Scroller.end(this);
l=this.hash.substr(1);
- a = document.getElementsByTagName('a');
- for (i=0;i<a.length;i++) {
- if(a[i].name == l){
- clearInterval(Scroller.interval);
- Scroller.interval=setInterval('Scroller.scroll('+Scroller.gy(a[i])+')',10);
- }
- }
+ a = document.getElementById(l);
+ if (a) {
+ clearInterval(Scroller.interval);
+ Scroller.interval=setInterval('Scroller.scroll('+Scroller.gy(a)+')',10);
+ }
}
}
}
あとは、スキンのどこかに、
<script type="text/javascript" src="skin/smooth-src-comments.js"></script>
のように書いて JavaScript を埋め込めば、 contentsx プラグインの出力する目次や footnote へのリンクをクリックした時に、 その箇所までスムーズスクロールするようになる。
というところまではできたのだけれど、どうもイマイチ。 前者のライブラリは、最初から最後まで等速でスクロールしてなんだか味気ない。 後者のライブラリは、少し手前の位置でスクロールが止まってしまう事がある。 おそらく計算式がどこか間違っていると思うので、それをデバッグできるといいのだけれど。
(2008/02/09 追記)
さらに修正を施して、きちんと動くようにした。詳細は以下のページにまとめておく。
2008-02-08 (金)
■ position:relative; と overflow:hidden; 絡みの IE のバグ?
また IE のバグっぽい現象に遭遇した。 うまく説明できないが、position: relative; を使う時、 overflow: hidden; を同時に設定しないと動作が変になることがある。 しかも、IE6 では問題ないのに IE7 でだけおかしくなる。 具体的には、こんな現象に悩まされた。
<div id="ajaxtree"> <ul> <li>...</li> <li>...</li> ...... </ul> </div>
のようなツリーメニュー用の HTML があって、この外側に角丸コーナーをつけるために、 この部分をさらに <div class="corner">...</div> のように囲んだ。 この時、
.corner {
position: relative;
}
のようにスタイルシートに設定したところ、 <ul> や <li> タグの部分をクリックしても onclick のイベントが発生しなくなってしまった。 さらに、マウスカーソルの形を変えるスタイルシートの設定も効かなくなり、 感じとしては <ul> や <li> のブロックが何か別の要素の下に隠れてしまったかのようだ。
最初、hasLayout 絡みの現象かな? と思ったので、zoom: 1; と設定してみたが全く効果無し。 試行錯誤の末、#ajaxtree か .corner のところに overflow: hidden; を追加してやると正常に動作するようになった。 なぜだ? 理由がさっぱり分からない。
これ以上調べることは面倒なのでしないけれど、これ、おそらく IE のバグだよなぁ…。 他のブラウザでは問題なく動くわけだし。
2008-02-13 (水)
■ 指定した幅で文字列を丸める mb_strimwidth() 関数
PHP の便利な関数を見つけた。
Smarty の truncate のマルチバイト文字対応版みたいなのが欲しくなり、 mb_truncate なんてのを探してきて改造しようとしていたが、 そんなことをしなくても、この関数を使うだけで済んでしまった。
この関数は、文字数やバイト数をカウントするのではなく、 半角文字=1、全角文字=2 として幅をカウントしてくれる。 別の言い方をすると、半角に換算してn文字分、という文字列を切り出してくれる。 日本語と英語に対応したコードを書くときに便利なので、早速使わせてもらおう。
2008-02-18 (月)
■ ブラウザのブックマークを階層化して整理する
最近、ブックマークはどう整理するのが良いか、という自分なりの考えがようやくまとまってきたので、 それに従ってブックマークをちょこちょこ整理している。 ちなみに RSS リーダーを使う時や、ダウンロードしてきたファイルを整理する時も、 ほぼ同じ方法で仕分けしている。
まず、次のようにプログラミング言語やOSなどで大分類する。
- C++
- Java
- JavaScript
- PHP
- Python
- Ruby
- CSS
- database
- hardware
- linux
- windows
そして、これらのフォルダーの下をさらに細かく階層化していく。 例えば、PHP フォルダーの中は次のような感じで分類している。
- PHP
- application
- blog
- wiki
- extension
- accelerator
- template engine
- framework
- Piece Framework
- Zend Framework
- library
- graph
- syntax highlighter
- template engine
- application
まず、単体で動くアプリケーションなのか、それとも組み込んで使うライブラリなのかといった、 使い方の観点で分類する。 その後、どういった機能を持つものなのか、といった機能面でさらに分類する。 こうやってフォルダーを作っておいて、個々のブックマークをその中に放り込んでいる。
本当は、タグとかコメントも付けて整理したいので、 将来的には、どこかのソーシャルブックマークにまとめ直すかもしれない。 でも、ソーシャルブックマークだと、こういう階層化のような分類がしにくそうなんだよなぁ。
2008-02-20 (水)
■ php.ini の session.hash_bits_per_character の設定を変更してみる
php.ini に session.hash_bits_per_character という設定項目があることを今さらながら知った。 php.ini-dist では 4 に、php.ini-recommended では 5 に設定されている。
; Define how many bits are stored in each character when converting ; the binary hash data to something readable. ; ; 4 bits: 0-9, a-f ; 5 bits: 0-9, a-v ; 6 bits: 0-9, a-z, A-Z, "-", "," session.hash_bits_per_character = 4
つまり標準の設定だと、セッション ID は 0-9, a-f の文字で構成される文字列になる。 一方、これを session.hash_bits_per_character = 5 に設定すると、 使う文字の範囲を 0-9, a-v に拡大することで、セッション ID の文字数を減らすことができる。
ということで、標準の php.ini では、
session.hash_function = 0 session.hash_bits_per_character = 4
のような設定になっていると思うが、もしそうなっていたら、
session.hash_function = 1 session.hash_bits_per_character = 5
と変更するのが良いと思う。 こうすると、
- 128 bits (MD5) / 4 bits = 32
- 160 bits (SHA-1) / 5 bits = 32
のように、セッション ID を 32 文字に保ちつつ、 より安全な SHA-1 をハッシュ関数に使うことができる。
ちなみに session.hash_bits_per_character = 6 にすればさらに文字数を減らせるが、 そうするとセッション ID に A〜Z の文字も含まれるようになる。 通常、セッションは sess_(セッションID) という名前のファイルに保存されるので、 ファイル名の大文字小文字を区別しないようなファイルシステムだと不具合が生じる。 (と、どこかのページに書いてあった気がする。) というわけで、おそらく session.hash_bits_per_character = 5 にしておくのが無難。
2008-02-22 (金)
■ 32 文字の SHA-1 ハッシュ値を返す関数
先日書いたように、 session.hash_bits_per_character = 5 に設定すると、 SHA-1 を用いたセッション ID を 32 文字で表現できる。 これと同じように、sha1() 関数の返り値も 32 文字の文字列にすることを考えてみた。
残念なことに sha1() 関数にそういったオプションは存在しないが、 検索してみると、rsky さんの作った関数が見つかった。
ハッシュに MD5 を使っているアプリケーションをデータベースのテーブル定義等を変更せずに SHA-1 に移行するために 4bit × 40文字でなく、5bit × 32文字のハッシュを返す関数をつくってみた。
function sha1_32($str) { $hex = sha1($str); $hash = ''; $c = array_map('strval', array_merge(range(0, 9), range('a', 'v'))); //$c = array_map('chr', array_merge(range(48, 57), range(97, 118))); for ($i = 0; $i < 40; $i += 5) { $dec = hexdec(substr($hex, $i, 5)); $o1 = floor($dec / 32768); $o2 = floor(($dec % 32768) / 1024); $o3 = floor(($dec % 1024) / 32); $o4 = $dec % 32; $hash .= $c[$o1] . $c[$o2] . $c[$o3] . $c[$o4]; } return $hash; }
でも、これ、ビット演算を使った方が速いんじゃないか? そう思って少し書き換えてみたりしたが、 base_convert() 関数を使うと、もっとすっきり書けることに気が付いた。
function sha1_32($str) {
$hex = sha1($str);
$hash = '';
for ($i = 0; $i < 40; $i += 5) {
$str = substr($hex, $i, 5);
$str = base_convert($str, 16, 32);
if (strlen($str) < 4) {
$str = str_pad($str, 4, '0', STR_PAD_LEFT);
}
$hash .= $str;
}
return $hash;
}
この方が分かりやすいし速い。
(2008/02/23 追記)
SHA-1 ハッシュを 32 進数で表記、という言い方をした方が良かったかな。
(2008/03/01 追記)
わお、rsky さんも同じこと書いてた orz
あと、先日のコード は 16進数を 32進数に変換しているだけなので
$hash .= str_pad(base_convert(substr($hex, $i, 5), 16, 32), 4, '0', STR_PAD_LEFT);の方がスマート。substr() を使わずに一気にやってしまいたいところだけど、大きい数値を base_convert() で処理しようとするとオーバーフローしてしまうようなので 20bit ずつ変換する点はそのまま。
ただし、str_pad() が必要になるのは最上位桁が0の場合だけなので、 先に strlen($str) < 4 という条件で判別してしまう方がほんの少し速いです。
2008-02-23 (土)
■ PHP 製 YAML パーサ Horde/Yaml
PHP で書かれた YAML パーサを見つけた。 Spyc を参考に作り直したものらしい。 BSD ライセンスで、PHP 5.1.0 以上用。 syck などの他の YAML パーサがインストールされていたら、 そちらを優先的に使う機能もある。
- Horde/Yaml
- Mike Naberezny - Horde/Yaml 1.0 Released
- Horde/Yaml 1.0.1 released with PHP object support :: Chuck Hagenbuch :: very.excited.schnauzer
結構良さげなので、Spyc の代わりに使えないかと思い、実験してみた。 まず、こういうラッパークラスを作って、spyc.php5 という名前で保存。
<?php
require_once 'Horde/Yaml.php';
require_once 'Horde/Yaml/Exception.php';
require_once 'Horde/Yaml/Loader.php';
require_once 'Horde/Yaml/Node.php';
class Spyc
{
public static function YAMLLoad($filename)
{
return Horde_Yaml::loadFile($filename);
}
}
そして、spyc-0.2.5.tar.gz に同梱されている test.php5 というスクリプトを実行してみたところ、
Key: 'no time' failed
と、テストに1つだけ失敗してしまった。残念。
…と思ったが、もしかするとこれ、Spyc の方が間違っているのかもしれない。 問題の箇所の YAML はこうなっている。
# A folded block as a mapped value no time: > There isn't any time for your tricks! Do you understand? # A literal block as a mapped value some time: | ......
Spyc だとこういう値が返ってくる。
array(
'no time' => "There isn't any time for your tricks! \nDo you understand?"
)
一方、Horde/Yaml だとこういう値が返ってくる。
array(
'no time' => "There isn't any time for your tricks!\nDo you understand?\n"
)
YAML の細かい仕様は全然知らないのだけれど、 YAML 1.1 の定義 をざっと見た感じでは、 Horde/Yaml の動作の方が正しいんじゃないかなぁ?
2008-02-27 (水)
■ Zend Framework 1.0.4 と 1.5.0 RC1 がリリース
Zend Framework の最新版がリリースされた。 ZF 1.0.4 の方は 1.0 系列の最終リリースのはず。 ZF 1.5.0 RC1 の方は変更点がはっきりしないけれど、README にはこう書かれていた。
NEW FEATURES
- New Zend_Form component with support for AJAX-enabled form elements
- New action and view helpers for automating and facilitating AJAX requests and alternate response formats
- Infocard and OpenID authentication adapters
- Support for complex Lucene searches, including fuzzy, date-range, and wildcard queries
- Support for Lucene 2.1 index file format
- Partial, Placeholder, Action, and Header view helpers for advanced view composition and rendering
- New Zend_Layout component for automating and facilitating site layouts
- UTF-8 support for PDF documents
- New Technorati and SlideShare web services
ENHANCEMENTS AND BUGFIXES
- Zend_Json has been augmented to convert from XML to JSON format
- New Zend_TimeSync component supporting the Network Time Protocol (NTP)
- Improved performance of Zend_Translate with new caching option
- addRoute(), addRoutes(), addConfig(), removeRoute(), removeDefaultRoutes() methods of Zend_Controller_Router_Rewrite now support method chaining
- Yahoo web service supports Yahoo! Site Explorer and video searches
- Database adapter for Firebird/Interbase
- Query modifiers for fetch and find methods in Zend_Db_Table
- 'init' hook to modify initialization behaviour in subclasses Zend_Db_Table, Rowset, and Row
- Support for HTTP CONNECT requests in Zend_Http_Client
- Support for PHP's hash() for read/write control in Zend_Cache
- Zend_Cache_Backend_File may be configured to call ignore_user_abort() to maintain cache data integrity
- Timezone in Zend_Date may be set by locale
- Zend_Cache can now use custom frontend and backend classes
って、よく見たら、前回の Preview Release の時と書いてあることは同じだ。 でも、メーリングリストを読んでいると、 細かい変更や改良がかなり入って API も決定されつつあるようなので、 そろそろ本格的に触り始めてもいいかもしれない。
Zend Framework 1.5 に移行できるよう準備はしておきたいので、 今から ZF 1.0.4 と ZF 1.5.0RC1 と両方インストールして、 今まで書いたコードが両方の環境で動くかどうかテストしてみようと思う。
あぁ、そうそう。 Zend Framework のオンラインマニュアルは、 ずいぶん前から 1.5 系列のものに差し替えられているので注意。 ZF 1.0 系列の使い方を調べたかったら、マニュアルをダウンロードしてから読んだ方がよい。
(追記)
…と思って、ZF 1.0.4 のマニュアルをダウンロードして読んでいたら、これも ZF 1.5 系列のものに置き換わっていた! (ZF 1.0.4 には存在しないコンポーネントの説明が載っていたりする。) わぁ、何考えてんだ! マニュアルと中身が対応していないじゃないか! ということで、1つ前の ZF 1.0.3 用のマニュアルを読む方が安全かもしれない。
■ オープンソース版「Fastladder」が CakePHP に移植されたらしい
すげぇ、感動した!
あー、でも考えてみたら、主要部分は JavaScript なんだろうから、 サーバーサイドの処理の部分だけ PHP に置き換えればいいんだろうな。 でも、面白いなぁ。後で試してみよう。
2008-02-28 (木)
■ Zend Framework 1.5 では setDispatcher() は setControllerDirectory() より前に書くこと
今まで書いたコードが Zend Framework 1.5.0 RC1 で動くかどうかテストしてみたところ、 ほとんどのコードはそのまま動いたが、自作のディスパッチャを使っているコードが動かなかった。 もちろん ZF 1.0.4 なら問題なく動くし、setDispatcher() を使わなければ正常に動く。 でも、自作の dispatcher といっても、 標準の dispatcher を継承してメソッドを追加しているだけなので、処理内容に違いはないはずなのに。
このデバッグにかなり時間を費やしてしまったが、結局、 ブートストラップで Zend_Controller_Front クラスの setControllerDirectory() メソッドを呼んでいたのを、 setDispatcher() メソッドより後ろに移動すると動くことが分かった。 つまり、
$front = Zend_Controller_Front::getInstance();
$front->setControllerDirectory(CONTROLLER_DIR)
->setDispatcher($dispatcher);
の順番だとエラーになるが、
$front = Zend_Controller_Front::getInstance();
$front->setDispatcher($dispatcher)
->setControllerDirectory(CONTROLLER_DIR);
の順番だと動く。 なにこれ? いったい何が起こっているんだ!?
該当箇所のコードを見てみる。
class Zend_Controller_Front
{
public function setDispatcher(Zend_Controller_Dispatcher_Interface $dispatcher)
{
$this->_dispatcher = $dispatcher;
return $this;
}
public function setControllerDirectory($directory, $module = null)
{
$this->getDispatcher()->setControllerDirectory($directory, $module);
return $this;
}
}
うわぁ、分かった。 ZF 1.5 で setControllerDirectory() の実体が Zend_Controller_Front クラスから Dispatcher クラスに移ったので、 controllerDirectory の設定をした後で dispatcher の設定をすると、ディレクトリの設定が無効になってしまうのか。
えーと、これどうしよう? dispatcher のコンストラクタの中で、他に dispatcher のインスタンスが存在しないかどうかチェックし、 存在したらその $_controllerDirectory の値をコピーしてくる? うーん、一応できそうだけれど、そんな変なコードはあまり書きたくない。 setControllerDirectory() を呼ぶのはなるべく最後にする、ということでごまかそうか。
2008-02-29 (金)
■ Pure PHP を用いるコンパイル型テンプレートエンジン phtmlc
PHP そのものをテンプレートエンジンとして使おうとする場合、 HTML エスケープの処理を1つ1つするのが面倒です。 rsky さん作の Speedy を使ったりするとかなり楽になりますが、他の方法がないか考えてみました。
考えたのは、ショートタグの機能をつぶして、
<?=...?> → <?php echo htmlspecialchars(..., ENT_QUOTES) ?>
のように内部で変換されるようにしたらどうか、ということです。 これなら、HTML エスケープして欲しくない場合には、
<?php echo ... ?>
と書けばよいので、仕様としては綺麗にまとまります。 ただ、PHP 自体にパッチをあてたりするのは最終手段にしたいので、 まずは Proof of Concept な実験をしてみることにします。
というわけで、本末転倒なことをしている感がしなくもないですが、 phtml (PHP + HTML) ファイルをコンパイルする phtmlc というテンプレートエンジンを、 テストのために作ってみました。
実際に行った作業は、 60行テンプレートエンジン の置換の処理を行っている関数を、
function convert_string($s) {
$pattern = array(
'/^<\?xml/',
'/<\?=\s*(.*?)[;\s]*\?>/',
);
$replacement = array(
'<<?php ?>?xml',
'<?php echo htmlspecialchars($1, ENT_QUOTES) ?>',
);
return preg_replace($pattern, $replacement, $s);
}
のように書き換えただけです。はっきりいって手抜きです。 それでも一応、これを使って、
- layout.php
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<body>
<h1><?= $title ?></h1>
<div id="maincontent">
<!-- テンプレートの内容 -->
<?php echo $_content ?>
<!-- /テンプレートの内容 -->
</div>
</body>
</html>
- template.php
<table>
<?php foreach ($list as $i=>$item): ?>
<tr bgcolor="<?= $i % 2 ? '#FFCCCC' : '#CCCCFF' ?>">
<td><?= $i ?></td>
<td><?= $item ?></td>
</tr>
<?php endforeach ?>
</table>
のような PHP のテンプレートを処理すると、 HTML エスケープの処理が自動的に行われることを確認できました。
