読者です 読者をやめる 読者になる 読者になる

pixiv小説縦書き機能 開発の裏側 ~横のものを縦にする~

はじめましてこんにちは。pixivでアルバイトをしているhakatashiです。

f:id:hakatashi:20150615182417p:plain

さる6月10日、パソコン版pixiv小説にて縦書き表示機能がリリースされました。この開発のあらかたを担当したので、今回の縦書き機能開発における裏側を紹介いたします。

構想

縦書き機能開発にあたり、設計段階からその大部分を一任されました。小説機能開発において自分の中に絶えず理念として存在していたのは、ユーザーに最高の読書体験を提供することです。縦書きによって得られる利益を最大化し、快適な閲覧を支援するために、以下のような構想を置きました。

  • 縦書き横書きの組版の差異における違和感を可能な限り軽減すること。
  • スクロールとページングを融合した、柔軟で快適な閲覧インターフェイスを提供すること。

この2点について詳しく解説します。

縦組版

まず、ウェブブラウザで縦書き表示を実現するにあたり、どのような手法をとるかという問題がありました。

ウェブブラウザは本来横書き表示を想定して実装されています。現代では縦書き含む縦方向の組版はほとんど日本でしか用いられない*1こともあって、ブラウザの縦書き対応は遅々として進まず、2015年6月現在でも標準化には至っていません

そのような経緯もあり、半ば強引なやり方で縦書きを実現する有名なライブラリが複数知られています。株式会社CMONOS様が開発した竹取JSは、CSSのTransforms Moduleを最大限に活用したライブラリで、横書きの組版を全体に90度回転させ、さらに内部の各文字を逆方向に90度回転させて正立させるという手法をとっています。Watanabe Masaki様が開発したnehan.jsは文字列を行に細分して1文字ごとに改行を打つ手法をとっており、高い互換性が期待されます。

どちらも優れたライブラリですが、pixiv小説に採用するには無視できないハードルがありました。竹取JSは細かな表示の調整をするのに複雑に入り組んだコードを修正する必要があるためメンテナンスコストが高く、nehan.jsは行ごとに要素を区切るため、pixiv小説の機能の一つである「文字サイズ切り替え」などとの相性が悪いと見えました。

翻ってブラウザの縦書き対応の話ですが、実のところCSSにおける縦書き対応はW3Cにおける日本人などの活躍によりすでに骨子が固まりつつあり、すでにChromeやSafariなどの主要ブラウザは先行実装を開始しています。おりしもFirefoxは今月30日リリース予定のFirefox 39にて縦書きがサポートされる予定となっており、またつねづね互換性について悩まされるIEについても、歴史的に縦書きブラウザのパイオニアとなっている経緯があり、ほとんどのバージョンで満足に縦書きを使用することができます。

これらの事情により、この2015年、ブラウザ上で縦書きを実装するのに大袈裟なハックは必要ないと判断しました。惜しむらくはFirefoxで縦書きがデフォルトで使用できるようになるまで少なくとも2~3ヶ月待つ必要があるということですが、標準化への敬意を示すためにも、今回はネイティブ実装に頼った開発を行うことになりました。2015年、今年は縦書きの年と言っても過言ではないでしょう。

さてその実装ですが、蓋を開けてみれば、むべなるかな、さまざまなバグの温床となっています。縦方向の組版における描画規則は、たとえばUnicode Technical Report #50に定められていますが、これを採用した実装はほとんど存在しません。致命的なのは半角文字の扱いです。pixivにはすでに半角カギ括弧や半角句読点を採用している小説も多数投稿されているため、これらの小説は正しく表示されなくなってしまいます。これでは最高どころか最低の読書体験です。

f:id:hakatashi:20150615155753p:plain

上図: 半角カギ括弧(U+FF62, U+FF63)を用いた場合(左)と全角カギ括弧(U+300C, U+300D)を用いた場合(右)。UTR50ではどちらもR, Tr(倒立文字)として定められている。(Windows 8.1/Google Chrome 44.0.2403.39/Yu Mincho)

この他にも、横書き時に綺麗に表示される文字も、組版の問題でそのまま縦書きにすると汚く見える場合があります。

f:id:hakatashi:20150615161530p:plain

上図: 横書きでそれなりに読める文章(左)と、それをそのまま縦書きにしたもの(右)。

これらの問題は本来レンダラやCSSによって統一的に制御されるべきですが、2015年6月現在、CSSの縦中横の仕様はほとんど定まっておらず、またpixiv小説の「縦書きと横書きを動的に切り替える」という仕様からも、これらの完全な対応は難しいでしょう。

そこで、今回の縦書き表示機能では、このような縦書きで見にくいいくつかの文字について、ユーザーに「おせっかい」を焼いて適切に文字を変換する強力な機能、コードネーム「おせっかい機能」を実装しました。これにより、上の文章は以下のように表示されます。

f:id:hakatashi:20150615162855p:plain

この変換機能には文字の前後の文脈をチェックして変換するかしないか判断する機能がついているので、例えば同じエックスでも英文中のエックスがベタで組まれることはありません。実装には綿密な調査を要しました。これらの例のほかにも、計100種程度の文字が自動で変換されます。

スクロールとページの融合

実際のpixivの小説閲覧画面をご覧いただくとわかりますが、左右に伸びる小説の中を移動するのに、左右のボタンを押す「ページめくり」とマウスホイールによる「スクロール」の2つの移動方法が用意されています。

これは縦書きを実装する上で最もこだわった部分です。そもそも紙の書籍における読書体験は縦書きでページめくりを基本としていることもあって、縦書きとスクロールとの相性は必ずしも良いとはいえず、「すべからく電子書籍はスクロールを廃しページめくりを採用すべし」との意見もまま聞くことです。しかし同時に、ウェブページ一般における閲覧との一貫性を保つこと、マウス操作での操作からスムーズに移行できることなどからスクロールも重要な要素であると考えていました。

そこで、そもそもスクロールとページめくりは互いに背反するものではないと考えました。そして完成したのが今の実装です。左右にスクロールする画面の中、両脇のボタンでおよそ1画面ほどスクロールします。

マウスホイールによる左右スクロール、及びブラウザ標準のスクロールバーの変更はInuYaksa様jQuery.nicescroll改造したものを使用して実現しています(Thank you!)。

さて、ここで重大な問題が起こります。スクロールを自由に行えるようにしたことで、ページめくりをした際に行の中途半端な位置でスクロールが止まってしまうことがあります。この状態で愚直に1画面ぶん横にスクロールすると、その行の内容はページをめくる前後でぶつ切れになってしまいます。短い人生なりに少なからず紙の本を読んできましたが、そんなページはついぞ見たことがありません。

f:id:hakatashi:20150615165925p:plain

これをぴったりきっちりスクロールするにはどうすればよいでしょう? 現在のDOMには文字の位置を取得するAPIがありません。行高からページに収まる行数を計算する? 挿絵や見出しにぶつかった時に詰まりますし、そもそもページの端できっちり行が始まっている保証はありません。すべての段落の位置を取得してページ境にかぶる位置を探す? pixiv小説の最大文字数は30万文字です(2015年6月現在)。ページをめくるたびにすべての段落の位置を計算していては実装として重すぎます。

この問題を解決するため、jQuery.howmuchreadというjQueryプラグインを独自に開発しました。このプラグインは、指定した要素中に含まれるテキストをシリアライズしたのち、スクロールしている位置やページ端がかぶる位置を二分探索というアルゴリズムによって1文字単位で正確に導き出します。30万文字の小説でも、わずか18回の探索によって確実にスクロール位置の算出が可能です。このプラグインは、現在オープンソースで公開されています

このプラグインにより、現在の縦書き版pixiv小説では、完全に表示されていない最初の文字がちょうど表示される位置にスクロールするという仕様になっています。スクロールとページめくりを快適に融合させる上で、これはどうしても実現したかった機能です。

おわりに

今回、プロダクト開発でありながら同時にオープンソース開発に携わることができたのは、個人的に非常に満足できたことです。時代への迎合という点でもそうですが、普段からオープンソースプロダクトにお世話になっている身からしても、こういった開発体制は今後も続けていきたいと考えています。

そして、繰り返しになりますが、2015年は縦書きの年です! 縦書きで文字組みするためのリソースや実装はすでに揃いつつあります。この機会にみなさんも縦書きの世界に触れてみてはいかがでしょうか。

最後に、アルバイトの身でも得意な分野での開発ができるpixivでみなさんも働いてみませんか? アルバイトも募集中です!

*1:歴史的には中国・モンゴル・満州でも用いられましたが、現在ではほとんど使われません。