今年は iPad, Kindle が発売され電子書籍の年になりそうですね。そこで、自炊系小説ビュワー「吾輩の小説」の紹介です。

「吾輩の小説」は青空文庫系アプリとは違い、自分で購入した最新の小説を読む事ができます。村山春樹だって涼宮ハルヒだって読めちゃいます。あなたの家の本棚に眠る懐かしい小説を読み返す事だってできます。

まずは、書籍の追加方法の説明です。書籍画面から追加ボタンを押して「書籍の追加」画面を表示させます。書籍はzipファイルにまとめてWEBにアップしておきそのURLを指定します。デフォルトで「人間失格」のURLが入っているので試しにこちらを取得してみてください。

小説を読むときは「書籍」画面から追加した小説名をタップします。「整形表示モード」を使えば、文字をこんなに大きく表示して快適に読めます(画像のページは開発者が「涼宮ハルヒの消失」の中で一番好きなシーンです)

そして、高速化されたアプリの起動時間です。信号待ちやエレベーターの待ち短い時間でも小説が読めるように、前回読んでいた部分を高速に表示します。開発者自身、毎日「吾輩の小説」を持ち出して、信号待ち時間で読んでテストしてるのでばっちりです。
読書中に気になった所はメモで残せます。ミステリー物など登場人物が多い小説はとっても便利です(画像は「かまいたちの夜」で有名な我孫子武丸による「殺戮にいたる病」この小説で登場人物をメモったところ死亡者リストになってしまいました)

そして、その日の読書が終わったらコメント付きの読書記録を twitter に残せます。

開発者自身が小学生の頃から「はてしない物語」「小説ドラゴンクエスト」「ロードス島戦記」など千冊程度の小説を読んできた小説大好きっ子です。小説が大好きな iPhone ユーザーは是非使ってみてください。
※「涼宮ハルヒの消失」は角川書店の出版物です
※「かまいたちの夜」はチュンソフトの発売物です
※「殺戮にいたる病」は講談社の出版物です
※「はてしない物語」は岩波書店の出版物です
※「小説ドラゴンクエスト」はエニックスの出版物です
※「ロードス島戦記」は角川書店の出版物です
iPhone 上の描画システムとして、弊社ではこれまで UIKit、OpenGL を使ってきました。 UIKit を使ったのは最初のアプリだけで、パフォーマンス上の理由からそれ以降のアプリはすべて OpenGL で描画しています。
しかし先週から開始した次のアプリの開発では、Quartz と Core Animation を使ってみています。簡単な時計アプリになる予定で、これらのフレームワークでどこまでできるかの試作段階といったところです。その中で 1 つ、詰まった点があったので紹介します。
それはというと、時計の針を描画するところまではよかったのですが、その角度を変えながらアニメーションさせるところで行き詰まったのです。CALayer の「アニメーション可能なプロパティ」 animatable properties (不透明度やサイズなどが含まれます) ならば、値を変えるだけで自動的にアニメーション implicit animation が実行されます。時計の針の角度のような、自分で定義したプロパティでもそれをするにはどうすれば?
Google 検索に 3 時間お付き合いいただいた結果、iPhone OS 3.0 以降なら、CALayer の needsDisplayForKey: クラスメソッドで「変更されたら再描画が必要になるプロパティ」を指定できること、actionForKey: メソッドで「変更されたら実行するアクション (アニメーション)」を指定できることが分かりました。ここまでは簡単に調べられたのですが、時計の針をアニメーションさせるにはもう 1 つ注意すべきことがありました。こちらは後で紹介します。
それより何よりソースコードを見てみましょう。
はじめに、時計の針用のレイヤーを作るコードです:
@implementation ClockView
- (void)awakeFromNib {
[self setupLayers];
[self start];
}
- (void)start {
// tick メソッドを呼び出すタイマーを作成
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0
target:self
selector:@selector(tick)
userInfo:nil
repeats:YES];
self.animationTimer = timer;
}
- (void)setupLayers {
/*
各針用のレイヤーを追加。
angle を変化させてアニメーションする。
*/
{
HandLayer *hand = [[HandLayer alloc] init];
hand.frame = self.frame;
self.secondHand = hand;
[hand release];
[self.layer addSublayer:self.secondHand];
}
// ...
}
- (void) tick {
// 各針に新しい角度を通知
}
ここまでは大したことはありませんね。次がこの記事の心臓、アニメーション部分です:
@interface HandLayer : CALayer {
}
@property CGFloat angle;
@end
@implementation HandLayer
@dynamic angle;
+ (BOOL)needsDisplayForKey:(NSString*)aKey {
// angle が変わったら再描画がいるよ
if ([aKey isEqualToString:@"angle"]) {
return YES;
} else {
return [super needsDisplayForKey:aKey];
}
}
- (id)actionForKey:(NSString *) aKey {
if ([aKey isEqualToString:@"angle"]) {
// angle 用の補間アニメーションを作る
CABasicAnimation *theAnimation = [CABasicAnimation
animationWithKeyPath:aKey];
// 注意: fromValue を設定しないと正しくアニメーションしない
theAnimation.fromValue = [[self presentationLayer] valueForKey:aKey];
return theAnimation;
} else {
return [super actionForKey:aKey];
}
}
- (void)drawInContext:(CGContextRef)context {
// angle プロパティを参照しながら針を描画
}
theAnimation.fromValue を現在表示中の針の角度に設定していることに注意してください。これがドキュメントを探しても見つけられなかったポイントです。解決できたのは、Omni Group のブログ記事 “Animating CALayer content”のおかげでした。その当時は自分で needsDisplayForKey: 周辺の仕組みを実装しないといけなかったんですね。
最後に、動画をどうぞ:
※ 動画中の針のアニメーションは、アニメーションのローカル時間を決める関数 timingFunction にデフォルトの線形タイプではなく kCAMediaTimingFunctionEaseIn を使っています。
参考:
追記:
実際に実行されている補間アニメーションの開始値 fromValue → 終了値 toValue / 増加値 byValue をログに出してみました。
2010-03-13 11:12:41.179 app[14615:207] angle \
<CABasicAnimation: 0x3b02620>, 6.283185 -> (null) / (null)
fromValue が設定されていて、その他は設定されていないのが分かります。終了値が設定されていないのにどうやって補間しているのかは謎です…。
自炊系小説ビューワ「吾輩の小説」で今日も電車で本を読んでいたら、話の中に数学パズルが出てきました。それもそのはず、読んでいたのは森博嗣 による「笑わない数学者」です。
五つのビリヤードの玉を、真珠のネックレスのように、リングにつなげてみるとしよう。玉には、それぞれナンバが書かれている。さて、この五つの玉のうち、幾つ取っても良いが、隣どうしが連続したものしか取れないとしよう。一つでも、二つでも、五つ全部でも良い。しかし、離れているものは取れない。この条件で取った玉のナンバを足し合わせて、1から21までのすべての数ができるようにしたい。さあ、どのナンバの玉を、どのように並べて、ネックレスを作れば良いかな?
うーん、まず1を作るには「1」の玉が必要。次に2を作るには、0+2、1+1の組み合わせがあって、1の玉を2つは入れられないから、「2」の玉も必要だ。と、ここまで考えたところで気付きました。プログラマにはコンピュータという計算の得意な友達がいる!
ということで計算機にパズルを解いてもらいました。言語は、最近はまっているClojureという関数型言語です。
; 名前空間の設定と使うライブラリのインポート
(ns ball-chain
(:use [clojure.contrib.combinatorics :only (combinations permutations)])
(:use clojure.contrib.test-is))
; 玉を定義
(defn balls [] (range 3 15))
; ネックレスの start 番目から length 個だけ取り出して数を合計する関数
(defn take-sum [chain start length]
(reduce + (for [i (range start (+ start length))]
(nth chain
(mod i (count chain)) 1))))
; ネックレスから玉を取り出して
; 合計が number になるようにできるか? を返す関数
(defn yields?
([chain number]
(not (empty? (take 1 (filter #(yields? chain number %) (range 0 5))))))
([chain number start]
(not (empty? (take 1 (filter #(= number (take-sum chain start %)) (range 1 5)))))))
; ネックレスが条件に合うか? を返す関数
(defn correct? [chain]
(every? identity (map #(yields? chain %) (range 1 20))))
; 玉を組み合わせてネックレスを作る関数
(defn all-chains [] (map permutations (map #(concat % (list 1 2)) (combinations (balls) 3))))
; ネックレスの組み合わせの中から、条件に合うものを抽出する関数
(defn answers []
(for [perms (all-chains) :when (not-empty (filter correct? perms))]
(filter correct? perms)))
; yields? のテスト
(deftest test-yields
(is (yields? (list 1 2 3 4 11) 1))
(is (yields? (list 1 2 3 4 11) 2))
(is (yields? (list 1 2 3 4 11) 3))
(is (yields? (list 1 2 3 4 11) 4))
(is (yields? (list 1 2 3 4 11) 11))
)
; テストを実行
(run-tests)
; 答えを1つ出力
(println (take 1 (answers)))
「1と2の玉が必ずある」という条件を組み込んで、多少の高速化をはかっています。ネックレス状なので右回りも左回りも同等の組み合わせになる、といった条件は考慮できていません。
これを Mac OS X で動かすには、たとえば ball-chain.clj というファイルに保存した上で、以下のように実行します。
$ sudo port install clojure clojure-contrib
$ java -classpath .:/opt/local/share/java/clojure/lib/clojure.jar:/opt/local/share/java/clojure/lib/clojure-contrib.jar clojure.main ball-chain.clj --
※ 本来は
だけで実行できるはずなんですが、MacPorts のバグのため、それには少し設定が必要です。詳しくはリンク先を参照してください。
答え? 答えはネタバレなのでここには書きません。どうしても見たい方は↓へどうぞ
。。。
スクリプトの出力結果はこちら:
$ clj ball-chain.clj
Testing ball-chain
Ran 1 tests containing 5 assertions.
0 failures, 0 errors.
(((3 10 2 5 1) (3 1 5 2 10) (5 1 3 10 2) (5 2 10 3 1) (10 3 1 5 2) (10 2 5 1 3) (1 3 10 2 5) (1 5 2 10 3) (2 5 1 3 10) (2 10 3 1 5)))
1 つ目の答え (3 10 2 5 1) が本当に合っているか、見てみましょう。
| 1 |
3 10 2 5 1 |
| 2 |
3 10 2 5 1 |
| 3 |
3 10 2 5 1 |
| 4 |
3 10 2 5 1 |
| 5 |
3 10 2 5 1 |
| 6 |
3 10 2 5 1 |
| 7 |
3 10 2 5 1 |
| 8 |
3 10 2 5 1 |
| 9 |
3 10 2 5 1 |
| 10 |
3 10 2 5 1 |
| 11 |
3 10 2 5 1 |
| 12 |
3 10 2 5 1 |
| 13 |
3 10 2 5 1 |
| 14 |
3 10 2 5 1 |
| 15 |
3 10 2 5 1 |
| 16 |
3 10 2 5 1 |
| 17 |
3 10 2 5 1 |
| 18 |
3 10 2 5 1 |
| 19 |
3 10 2 5 1 |
| 20 |
3 10 2 5 1 |
| 21 |
3 10 2 5 1 |
その他の答えも、右回り・左回りを逆にしたり、さらに開始点をずらしたりすることで 1 つ目の答えと同じネックレスになっていることがわかります。
期待 by mskk – Version 2.0 – 2010/03/08
整形表示モード追加に大感謝です。
まさに求めていた機能です。有難うございました。
欲をいえばファイル名の変更が出来るようになると嬉しいです。
あと中途半端な部分でページ送りされるのでその辺を改善して欲しいです。
今後のアップデートも期待しています。
ver 1.0 をリリースしたのち、iPhoneなどの携帯電話の小さい画面で、さらに文字を見やすくする方法を半月ほど調査&研究していたのですが、やはり整形表示モードのような仕組みを入れるしかないと結論に達して実装しました。今後は整形の精度を上げていきます。
さて、2点ある修正点ですが、まず1つめの「ファイル名の変更」は、開発初期から必要性があると考えていましたので実装します。
2つめの「中途半端な部分でページ送り」は、私がページ送り後にページ送り前の1行を見ながら読まないと内容を把握できないという状態ですので実装は見送らせて頂きます。
ただし、人によって読み方はさまざまですので、まだ決定はしていませんが「吾輩の小説 Pro」という細かい設定が行えるバージョンを出す事を検討しています。
レビューありがとうございました。参考にしながら使いやすいアプリを作っていきます。
iPad/Kindleが発売されて電子書籍市場は今後活性化していくと思います。そして、電子書籍市場への参加の仕方に2つの手法があると思っています。
1つはこちら。今までの印刷業者や書店に置き換わるところを担おうとする方法です。
電子出版はすでに始まっている -- 池田信夫 blog part2
3月1日付で「株式会社アゴラブックス」を設立し、私が代表取締役に就任した。役員兼社員5人の超零細企業だが、4月から電子書籍の刊行を始める予定だ――といっても、設備は何もない。インフラはGoogle Appsで1人年間6000円。システム管理もすべてアウトソースするので、固定費はゼロ。失敗した場合のリスクもほとんどない。
そして、もう1つの可能性を、富士通のScanSnapというスキャナが100万台売れたというところから感じました。
感謝を込めて「ScanSnapシリーズ 100万台突破記念キャンペーン」を開始!
一部の電子書籍ユーザーには自炊とも呼ばれている方法で、自分で書籍をスキャンしてPCやiPhoneなどで読む方法です。前者の電子書籍販売ルートが完璧に構築されれば不必要になると思うので、それまでのつなぎの手法になると思います。
弊社では後者の手法をとっていきます。一時的な需要しかありませんが、前者には無い優れた点として、電子書籍化されていない最新の出版物を楽しめるという非常に大きい利点があるからです。
まずは「吾輩の小説 for iPhone」で、小説をiPhoneで読めるアプリを作りました。
読書記録をつぶやける自炊系の小説ビュワー「我輩の小説」


※整形モードを追加したバージョンは現在申請中です。今週中にリリースされると思います。