hishidaの開発blog

EBシリーズ(EBPocket,EBWin,EBMac,EBStudio),KWIC Finder,xdoc2txt,読書尚友の開発者ブログ

iOS 11 Beta

WWDCAppleからiPad proやMacbookなどの新製品とiOS11 が発表され、即日iOS Dev Center から、Xcode 9 betaと iOS11 betaがダウンロードできるようになった。早速iOS11 betaをダウンロードして検証した。iPad mini2 と、最近中古で購入したiPhone 5sにiOS11 betaを入れてEBPocketの動作確認をしたところ、特に問題もなく正常動作した。予想通り32ビットアプリは動作しなくなっており、古い小学館大辞泉はついにアウト。
Xcode 9 のほうは一筋縄でいかず、コンパイルを通すのにかなり苦労しそうだ。当面はXcode 8.xでAppStoreに提出できるはずだが、1年以内にはXcode 9 対応も行わないと、将来アプリの更新ができなくなってしまう。

読書尚友 2.0.0

読書尚友に大きめの改良を行なったので、メジャーバージョンアップとみなして2.0.0とした。

  • OPDSカタログライブラリ
  • PDF(目次対応)
  • Unicodeテキストのサロゲートペア
  • 内蔵フォントを源ノ明朝と花園明朝B(Unicode拡張領域用)に変更


フォントをIPA明朝から源ノ明朝に変えたので、電子書籍の文字が非常に美しくなった。またLatin文字もNoto Serifベースなので、英文でも同じフォントで対応できるのもいい。花園明朝Bを搭載したのは、サロゲートペアに対応したのでUnicode CJK Ext.B,C,D,Eを表示させるためである。ただし、フォントを2つ内蔵したので、apkのサイズが肥大化してしまい(約60MB)、残り領域が少ないとインストールできない可能性がある。Unicode拡張領域を使用する文書は滅多にないので、オプションでダウンロードさせる仕組みにしたほうがいいかもしれない。(追記:2.0.1で花園明朝Bはオプションでダウンロードにした)
PDFは目次に対応し、正式版とした。ただ階層目次の開閉には対応していないので、フラットに全目次が表示される。このあたりはまだ工夫の余地がある。

android用OPDSクライアント公開

前回のブログ( 読書尚友のPDFサポート 他 - hishidaのblog )で検討中と書いたandroid用OPDSクライアントがそこそこ動くようになったので、Google Playで公開した。とりあえずカタログのナビゲーション機能を実装してみたが、次の段階ではOpenSearchに対応してみたい。
https://play.google.com/store/apps/details?id=info.ebstudio.opdsviewer

初期状態では次のOPDSカタログを登録している。

Project Gutenberg http://m.gutenberg.org/?format=opds
Feedbooks http://www.feedbooks.com/publicdomain/catalog.atom
BookServer(Internet Archive) http://bookserver.archive.org/catalog/
Manybooks http://manybooks.net/opds/
Smashwords https://smashwords.com/atom
青空文庫 OPDS http://aozora.textlive.net/catalog.opds
台湾 中華電子佛典協會 漢文大蔵経 http://www.cbeta.org/opds/
達人出版会 http://tatsu-zine.com/catalogs.opds
O'Reilly Japan https://www.oreilly.co.jp/ebook/catalogs.opds

これ以外に、有名どころで O’Reilly Media(https://opds.oreilly.com/opds/)も動作を確認したが、リンク先が購買しかないので、初期登録からは外した。
日本の出版サイトでは技術評論社がOPDSを提供しているが(http://gihyo.jp/dp/catalogs.opds)、いろいろと技術的な問題があって初期登録からは外すことにした。何が問題かというと、feedの作りが悪いのか、Abdroidの標準のXmlPullParserではパースで文法エラーになる。SAX Parserだと読み込みに成功した。パーサーによる相性問題が他にもあるかもしれないので、設定でXmlPullParserとSAX Parserを選択できるようにしてみた。
また、技術評論社のOPDSには新刊(http://gihyo.jp/dp/new.opds)と既刊書(http://gihyo.jp/dp/all.opds)があり、既刊書の方のOPDSが、1600冊以上のentryを一度に返してくる作りになっている。スマートフォンでは長考状態になって全く実用に耐えない。件数が多い場合には100件ずつぐらいページ単位で返すような設計にすべきだと思う。

海外の有名な読書リーダー(Moon+ Reader、Aldiko等)では大抵はOPDSブラウズ機能を内蔵している。日本ではOPDS自体が普及していないこともあり、青空文庫専用ビューアが普及している。
正直OPDSクライアントのニーズがあるかどうかわからないが、個人的にはOPDSの勉強にはなった。
読書尚友にもライブラリ機能としてOPDSブラウズを組み込むことを予定している。

読書尚友のPDFサポート 他

PDF対応について

「読書尚友のPDF対応を開発中」という記事を2015年8月に載せてから、1年半も経ってしまった。
2015-08-26 - hishidaのblog
おさらいすると、Android用のPDF用ライブラリはmuPDFをはじめGPLのものが多く、ソースの公開義務が生じるので、なかなか使用しづらい。このため、Android 用の書籍ビューアの中には、PDFビューアの部分だけプラグインにしているものもある(MHE Novel Viewer、Perfect Viewer等)。
その後、Android 5.0 Lollipop からPdfRendererという機能が標準で備わったが、これはページ毎にImageViewにビットマップで表示するもので、拡大縮小したときにスケールに合わせてレンダリングしなおさないと文字がギザギザになる等、あまり使いやすくない。またLollipop以上でないと使用できないので、kitkat以下を切り捨てることになり、制限がきつい。PdfRendererを使用した読書尚友のPDF対応は2015年時点でできていたのだが、上記の理由で公開していなかった。
ところが最近、Apache2.0ライセンスのPDFライブラリで使用できそうなものを見つけた。
com.github.barteksc.pdfviewer.PDFView
GitHub - barteksc/AndroidPdfViewer: Android view for displaying PDFs rendered with PdfiumAndroid
スワイプによる拡大縮小、ダブルタップによる拡大もできるし、アンチエイリアスも効くので、PDFの表示 だけで編集をしないのなら、十分に実用になる。何より、Android3.0以降で使用できるので、大半のユーザが恩恵を得られる。
というわけで、読書尚友1.54でPDFビューアを実装してみた。apkのサイズが10kb→28kbと大きくなってしまったが、それほど問題にならないと思う。今の所、有料版だけの機能だが、free版にも入れるかどうかは今後検討したい。
この後の作業としては、PDF関連の機能の追加 (目次とメタ情報の表示機能)を予定している。

書籍一覧のカード型UIへの変更とブクログサポート

他に最近の改良としては、書籍一覧のUIをCardViewを使用してカード型にした(Androidのアプリの流行としては数年遅れだが)。RecyclerViewの導入も試してみたが、ViewHolderモデルのListViewと速度的な差はないので、結局ListViewのままとした。RecyclerViewでないとできないようなUI(スワイプによる削除や項目の移動など)が必要なければ、無理にRecyclerViewに変える必要はないと思う。

また、メニューボタンにブクログへのリンクを追加してみた。独自の感想投稿機能を作るよりも、青空文庫で正式に使用されている感想投稿サイトをサポートしたほうがいいと判断した。

OPDSの検討開始

次の段階として、OPDSをサポートしてみようかと思っている。
JEPA|日本電子出版協会 OPDSとは?
OPDSを実装しておけば、Project Gutenbergなども接続できるようになる。そうなると英文のePubの表示をきちんとしないといけないが(せめて英文の行端揃えは必須)、時間をかければできていくのではないかと思う。
また、有志による青空文庫のOPDSも稼働しているようだ。
20 | 2月 | 2011 | 潮流工房
継続してサポートされる保証はないが、とても有意義な試みだ。
OPDSクライアントとして単独のアプリにするか、読書尚友に組み込むかはこれから検討する。

読書尚友とEBPocket for Androidをsplit-screenに対応させた

Android7.0 Nougatからsplit-screenの機能が加わっているが、読書尚友とEBPocket for Androidをsplit-screenに対応させてみた。
といっても日常的に使用しているZenfone 3 laserにはまだAndroid7.0アップデートが来ないので、エミュレータでの動作確認になる。
(あまりアップデートが遅れるようだと、初めからAndroid7.0が搭載された格安SIMフリーのnova liteあたりに買い換えたほうがいいかもしれない)
実はAndroid 7.0 に対応していないアプリでもsplit-screenは使用できるが、"app may not work with split-screen."というメッセージが表示されてしまう。

Android 7.0 Split-screen対応

マルチ ウィンドウのサポート  |  Android Developers
Split-screenにする要件は、

  • 画面のサイズが動的に変更されても画面のパーツが正常に表示されること。読書尚友もEBPocketも画面の回転に対応しているので、これはクリアしている。
  • targetSdkVersionを24(Android7.0)以上にする
  • manifestsでapplicationかactivityに、android:resizeableActivity="true"を記述する。

エミュレータでの実行結果は次の通り。


縦横でEBPokcetのレイアウトが変わっていることがわかる。読書尚友で単語を選択してEBPocketでクリップボード検索で辞書を引くこともできる。これはEBシリーズ全体でやりたかったことのゴールに近い。

さてここで一つ問題があり、targetSdkVersionをAPI24(Android7.0)以上にするということは、API23(Android 6.0)で導入された新しいパーミッションの考え方に対応しないといけないということ。
どちらかというと、こちらの作業のほうが大変だった。

Android 6.0 パーミッション対応

実行時のパーミッション リクエスト  |  Android Developers
Android 5.x以前のパーミッションの考えかたは、アプリのインストール時に一括で許可を与えるものだったが、Android6.0からは、パーミッションを使用するときに個別に許可・不許可できるようになった。
例えば、「カメラは許可するが位置情報の使用は許可しない」とかを選択できるようになった。
読書尚友、EBPocketの場合は、WRITE_EXTERNAL_STORAGEのパーミッションが必要になる。

パーミッションがあるかどうかを確認し、ない場合は要求するコードは次の通り。

//	権限があるかどうか確認
int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);

if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
	// Should we show an explanation?
	if (ActivityCompat.shouldShowRequestPermissionRationale(this,
			Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
		// 説明が必要な場合。EBPocketの場合はパーミッションが必須なので要求
		ActivityCompat.requestPermissions(this, new String{
				Manifest.permission.WRITE_EXTERNAL_STORAGE
		}, REQCODE_PERMISSION);
	} else {
		// 説明が不要な場合。パーミッションを要求する
		ActivityCompat.requestPermissions(this, new String{
				Manifest.permission.WRITE_EXTERNAL_STORAGE
		}, REQCODE_PERMISSION);
	}
	return;
}

パーミッションの要求の結果はコールバックされる。

/**
 *
 * @param requestCode
 * @param permissions
 * @param grantResults
 */
@Override
public void onRequestPermissionsResult(int requestCode, String permissions, int grantResults) {
	switch (requestCode) {
		case REQCODE_PERMISSION: {
			if (grantResults.length > 0
					&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
				// パーミッションの取得に成功した。
				// パーミッションが必要な処理をここに書く

			} else {
				// パーミッションの取得に失敗した

			}
		}
	}
}

C配列にObjective-Cのオブジェクトを保存するのは危険?

やっと安定したと思っていたEBPocket for iOSだが、複合検索で外字を選択すると異常終了するという報告をいただいた。
http://ebstudio.info/wforum_ebppc/hatenacamera.cgi?mode=allread&no=2686&page=0
エミュレータで調べたところ、32bit OSでは問題が起きず、64bit OSだけ異常終了するらしい。NSStringの文字列がいつの間にかautoreleaseされて不正参照になっていることまでわかったが、原因がわかるまでしばらく時間がかかった。
結論的には、C配列にNSString*のオブジェクト参照を入れていたためだった。

問題の個所はこんな感じで、NSString*の配列m_keywordをクラス変数として静的に確保していた。

#define	MAX_CPLX_GRP	10
@interface ComplexSearchViewController : UIViewController 
{
	//	省略

	NSString*m_keyword[MAX_CPLX_GRP];
}

クラスオブジェクトの生存中は、クラス変数のm_keywordに代入した文字列も生存するものだとなんとなく思いこんでいた。
(Objective-Cのメモリ管理の基本であるalloc/init/retain/releaseなどについては、一応理解しています)
だがよく考えてみれば、m_keyword
は単なるC配列なので、代入してもNSStringの参照カウントは増えない。これではどこかのタイミング(関数の出口など)でautoreleaseされるのは当然だ。
そこで次のようにC配列をやめてNSMutableArrayにしてみた。NSMutableArrayなら代入すれば参照カウントがインクリメントされてautoreleaseされなくなるのではないか。
NSMutableArrayでは C配列と同様に 変数名[添字] の形で代入や参照が書けるので、ソースの修正は最小限にできる(本当はreplaceObjectAtIndex:withObject:みたいな長ったらしい名前のメソッドがある。[]はいわゆるシンタックスシュガー)。

@interface ComplexSearchViewController : UIViewController 
{
	//	省略

	NSMutableArray*m_keyword;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        // Custom initialization
        m_keyword = [[NSMutableArray alloc]initWithCapacity:MAX_CPLX_GRP];
    }
    return self;
}

- (void)dealloc {
	//	省略
    [m_keyword release];
	
    [super dealloc];
}

案の定、これで落ちなくなった。万歳。
Objective-Cはメモリ管理に気を使う。
今はiOS向けの新規案件は、より進化した言語であるSwiftの利用が増えているのではないだろうか。私もこれから何かiOS向けに書くとすれば、Swiftを選ぶ。読書尚友のiOS版をSwiftで作ってみてもいいかもしれない。

読書尚友に底本出版社別リストを追加

青空文庫のHPでは、1万冊を超える作品を探すためのインデックスとして、作家別(作家名の五十音順)、作品別(作品名の五十音順)の総合インデックスと、分野別リスト(日本十進分類)が提供されている。
大体の青空文庫ビューアアプリでは、作家別・作品別のインデックスはあるが、分野別のインデックスを実装しているものは少ない。読書尚友を開発するとき、分野別リストを実現することは目標の一つだった。
読書尚友では、これに加えて、公開年別リストも提供している。青空文庫では毎年1月1日に、その年著作権が切れる著者の作品を一斉に公開する慣例があり、毎年の年初の公開作品を見たい場合があるので、年月日別のインデックスがあれば便利ではないかと思ったためだ。また、青空文庫に最初に登録された作品は何だったかなど、青空文庫の歴史を調べたいときにも役立つ。
今回さらに、新たな試みとして、底本出版社別リストを追加した。たとえば、底本出版社別リスト→「作品社」(底本出版社)→「日本の名随筆10 山」(底本)→「槍ヶ岳第三回登山」のような読み方ができる。




実は「えあ草紙」というビューアに、特別企画ページとして「日本の名随筆、花の名随筆」や「推理作家一覧」などがあるのがヒントになった。面白いと思ったが、テーマ別だとメンテナンス作業がネックになる。底本出版社別ならメンテナンス不要で、青空文庫の収録冊数が増えるほど有益性が増すと思う。
一つの問題は、作品や作家のようにID化されていないので、同じ底本でも表記が異なるとインデックスが分かれてしまうこと(例:「山と渓谷社」と「山と溪谷社」、「学芸書林」と「學藝書林」、半角空白と全角空白の混在等)。全角空白を半角化するなど正規化を試みているが、完全にはいかない。
まだ発展途上だが、新しい青空文庫の読み方を一つ提案できたと思う。