anti scroll

ブラウザと小説の新しい関係を模索する

アンカーリンクと、リンク先のプレビューに対応しました

アンカーリンクと、リンク先のプレビューに対応しました。

アンカーリンクとは

id属性を付けた要素に対して、<a href="#そのidの値">...</a>のように宣言したリンクのことを、アンカーリンクといいます。

リンクのhref属性の値が #(シャープ)から始まっていることに注意してください。

こうして作成したリンクは、

  1. クリックすると、リンク先の要素が記述されたページに移動できます。
  2. マウスを乗せると、リンク先の要素がプレビューで確認できます。

記述例1

<blockquote id="bq1">「強い者が勝つのではない。勝った者が強いのだ」</blockquote>
[page-break]
この<a href="#bq1">発言</a>は、ドイツの皇帝と呼ばれた「ベッケンバウアー」によるものです。

f:id:convertical:20210220100100p:plain
マウスを乗せるとアンカーリンク先のプレビューが表示される

ここではblockquoteタグに使っていますが、imgタグなどに使っても便利だと思います。

記述例2

他にも、先頭ページに空のaタグをid="top"のような属性で宣言しておいて、最終ページから「先頭ページに戻る」みたいなリンクを貼ることもできます。

<a id="top"></a>
冒頭の文章
[page-break]
最終ページの文章
<a href="#top">トップページに戻る</a>

この場合、リンク先の中身は空なので、マウスを乗せてもプレビューは表示されません。

記述例3

アンカーリンクを利用して、ちょっとしたゲームブックを作成することもできます。

その他

VirtualBox(v6.1)に付属のVBoxGuestAdditions-5.2.44にて、Centos8が起動しなくなった件について(解決)

原因はGuestAdditionが作成するvboxsf.koが、/lib/modules/4.18.0-193.28.1.el8_2.x86_64/misc/にコピーされていなかったからのようです。

解決方法

まず、最新カーネルイメージは正常起動せず、emergency modeで起動するため、一つ前の世代のイメージからOSを起動します。

一応、以下に手順。

  • VirtualBoxのUIからOSを通常起動します。
  • Grubカーネルイメージを選択する画面で、F12を押します。準備してないと遅れるので注意。
  • 並んでいる項目の一番上のカーネルイメージが最新ですが、それは起動しないので、上から二番目のやつを選択します。
  • たぶん普通に起動するので、あとはsshで入るなりして、以下の作業をしましょう。
cd /opt/VBoxGuestAdditions-5.2.44/src/
sudo make
sudo make install # ここでvboxsf.koがコピーされていない
sudo cp vboxsf.ko /lib/modules/4.18.0-193.28.1.el8_2.x86_64/misc/

# 念の為、再度、最新のカーネルイメージをインストール
sudo dnf -y reinstall kernel-core.x86_64

これで最新のカーネルイメージが起動するようになりました。

参考:ubuntuをWindowsOS上で動作させる - Guest Additionsインストールのエラーvboxsfがmodprobeできない対処

各種機能を改善しました

縦書き文庫の各種機能を改善しました。

作品一覧にてタグが表示されるように

作品の一覧にて、タグが表示されるようになりました。

f:id:convertical:20201113125300p:plain
タグの表示

これによって、同じ系統の作品にアクセスしやすくなりました。

シリーズに追加できる作品数を20から50に増やしました

大菩薩峠(41作品)のように大きなシリーズもあるので、シリーズあたりの作品数を20から50に拡張しました。

ゲームパッドの操作を記述する言語「combo-script」と、その再生アプリ「combo-player」を作りました

きっかけ

作ったきっかけは、最近買ったウイイレ2020の操作が、ちっとも頭に入らなかったからです。。。

一応ゲーム内にマニュアルがあるんですけど、文字とか矢印で説明されても、ピンと来ないんですよね。。。

そこで思いついたのが「ゲームの操作をぱぱっと記述したら、パッド上の動きをアニメーションを再生しつつ、操作ログも出してくれる」みたいなものがあったら、理解が捗るかなあ〜?ということです。

というわけで作ったのが、combo-scriptと、combo-playerです。

例えばcombo-scriptで昇龍拳を記述して、combo-playerで再生するとこんな感じになります。

f:id:convertical:20200811165349g:plain
昇龍拳

その他、色々なコマンドについては、デモサイトを見てください。

tategakibunko.github.io

上のデモサイトでは、ウイイレの他に、スト5とか鉄拳のコマンドとかも追加しておきました。

メニューの「Games」から選択できます。

コマンド部分は編集可能になっているので、色々と書き換えて遊んでみてください。

f:id:convertical:20200811165922p:plain
コマンド部分(編集可能)

combo-script

combo-scriptは、ゲーム操作を記述するための言語です。こんな風に記述します。

コナミコマンド

up, up, down, down, left, right, left, right, A, B

昇龍拳十字キー操作で記述)

right, down, (right, down), punch

(right, down)は右と左を同時押し(つまり右下)という意味です。

一般にボタンの同時押しは(X, Y, Z...)のように記述します。

昇龍拳(スティック操作で記述)

setL(0), moveL(0, 90), rotateL(90, 45), punch

これは左スティックを0度(真右)に方向け、そこから真下に動かし、更にそこから45度(右斜め下)の位置まで回転させ、最後にパンチボタンを押す、という意味です。

setL, moveL, rotateLの引数は、傾ける先の角度ですが、この角度は時計回りであることに注意してください。時計回りなので、90度は真上ではなく、真下です。

ウイイレ、コントロールシュート

R2 { square }

上の構文はHolding Syntaxと言って、X { Y, Z, ... } などと書くと「Xの操作をしながらY,Z」という意味になります。

なので上のコマンドは

  • R2を押しながら□ボタン
  • 終わったらR2を離す

という意味になります。

combo-script(言語仕様)

詳しくは公式サイトを見てほしいのですが、大雑把に説明すると、

  • ボタンの押下、押しっぱなし、同時押し、各種スティック操作などを抽象化した言語です。
  • 特別なプログラミング知識がいらないので、誰でもコマンド部分を記述できます。
  • 矛盾した操作はコンパイル時にエラーが出ます(押し込んでいる最中のボタンを再び押すとか、左と右を同時に押すとか、色々)。
  • 押下についてはpushDown, pushUpを個別に扱う方法もあります。
  • プラグインで拡張することができます。
  • 定義済みの関数(rotateLとかmoveLとか色々)以外のシンボル名は、ユーザー定義のボタン名、もしくはユーザー定義のプラグイン関数として解釈されます。
  • 例えばpunchと書いたら、ユーザー定義のpunchボタンがあって、それを押して離す、という一連の動作として解釈されます。つまり、punchという名前のボタンをどう解釈するかは、combo-scriptを組み込む実装者に任されます。
  • 関数も同様です。例えばfoo(1,2)と書いた場合、そういう定義済みの関数はないので、ユーザー定義のプラグイン関数とみなされます。そして、実際の動作はcombo-scriptを組み込む実装者に任されます。
  • つまりボタン名も関数名も、それを解釈するのはプレイヤー側の仕事で、combo-script側は名前にルールや制限を設けません。コンパイラ側が判断するのは、それがボタンか、関数か、そしてそれらの動作は、どういう文脈で押されたか、ということだけです(通常押し、同時押し、押しっぱなしなど)。
  • というわけで、例えばパッド右側にある4つのボタンなどは、プレステ風にtriangle/circle/cross/squareと定義してもいいし、Xbox風にY,B,A,Xと定義してもいいです。
  • ただしボタン名を数字から始めることはできません。つまりbutton1はいいけど、1buttonは駄目。
  • ちなみに、combo-scriptは大文字と小文字を区別しません。だからupと書いてもUPと書いても同じです。これは関数も同じ。
  • あとボタン名については英数じゃなくてもいいです。例えば日本語で強パンチとか書いても問題ありません。

コンパイルの仕方とか、それをどうやって自分のプレイヤーアプリに組み込むか、みたいなことは公式サイトを確認してください。

感想

  • ウイイレ2020は難しい。
  • 昇龍拳のコマンドを初めて知った。
  • 鉄拳のコマンドを思い出すことが出来た。

先頭ページに「登場人物の一覧」が表示されるようになりました

作品に登場人物を登録した場合、先頭ページに「登場人物の一覧」が表示されるようになりました。

f:id:convertical:20200705102918p:plain
登場人物の一覧

市販の書籍でも、だいたいこんな感じで冒頭に表示されてますよね。

登場人物の挿絵については、小説本文の下側に「登場人物」のタブメニューがあって、そこをクリックすると挿絵付きで一覧が表示されます。

f:id:convertical:20200705102945p:plain
挿絵付きの登場人物一覧

人物をクリックすると、こんな感じで詳細情報が表示されます。

f:id:convertical:20200705103223p:plain
人物の詳細

jingoo 1.4.0 をリリースしました

jingoo 1.4.0をリリースしました。

github.com

新しくなった点

  • Jg_templateモジュールを部分的に改良したJg_template2というモジュールが追加されました。

サンプル

これまでJg_templateモジュールは、次のようにmodels連想リストを与えていました。

open Jingoo
open Jg_types

let result = Jg_template.from_string "{{ msg }}" ~models:[
 ("msg", Tstr "hello, world!");
]

Jg_template2モジュールからは、models名前を与えたら値を返す関数を渡します。

open Jingoo
open Jg_types

let alist = [("msg", Tstr "hello, world!")]
let result = Jg_template2.from_string "{{ msg }}" ~models:(fun key ->
  try List.assoc key alist with Not_found -> Tnull
)

これの何が良いのかというと、string -> tvalueという型の関数にさえなっていれば、データソースは何でも良くなることです。

例えばデータソースを(連想配列より高速な)Hashtblにすることもできます。

open Jingoo
open Jg_types

let htbl = Hashtbl.create 1 in
let () = Hashtbl.add htbl "msg" (Tstr "hello, world!") in
let result = Jg_template2.from_string "{{ msg }}" ~models:(fun key ->
  try Hashtbl.find htbl key with Not_found -> Tnull
)

データソースなしで、単なる関数にしてしまうと、さらに高速です。

open Jingoo
open Jg_types

let result = Jg_template2.from_string "{{ msg }}" ~models:(function
 | "msg" -> Tstr "hello, world!"
 | _ -> Tnull
)

関数にすることで、乱数値みたいなものを扱うこともできます。

open Jingoo
open Jg_types

let result = Jg_template2.from_string "{{ msg }}, {{ rand }}" ~models:(function
 | "msg" -> Tstr "hello, world!"
 | "rand" -> Tint (Random.int 100) (* 0 ~ 100 の値をランダムに返す *)
 | _ -> Tnull
)

将来的にはこっちをメインのAPIとして使用したいのですが、旧版のAPIで既に広く使用されてしまっているので、今回はモジュールを別名で分けることで対応しました。

参考

In Jingoo.Jg_template, provide a function for the getting variables · Issue #112 · tategakibunko/jingoo · GitHub

シリーズ作品において続きの作品へのリンクが本文の最後に表示されるようになりました

Togetterの次の記事に「探す作業が嫌だ」みたいなことが書かれていて「そりゃごもっとも…」と思ったので、表題の件を実装しました。

少年ジャンプ+副編集長が大学1年生から「漫画アプリのUIについて物申したい!」というDMが来て実際に会って考えが整理されて意義深い時間になったという話 - Togetter

これまでは、シリーズを設定している作品について、続きを読む際に、いったんシリーズ作品のリンクへ飛んでから、自分で続きを探さないといけませんでした。

これからは続きがある場合は、本文の最後にリンクが表示されます。

f:id:convertical:20200615125638p:plain
続きがある場合は、リンクを表示する

当たり前にそうあるべきことが、これまで出来ていなかったことが、ちょっと恥ずかしいです。

nehan7をリリース

nehan6からnehan7にバージョンアップしました。

インストール

npm install --save nehan

変更点

  • 組版速度が約20%向上しました。
  • コードサイズが約10%削減されました。
  • nehan.cssが不要になりました。
  • 行内に置換要素、画像、ルビ、圏点傍点、複数サイズの文字などを同時に含むような場合でも、ベースラインが正確に計算されるようになりました。
  • テーブルセルに対してvertical-align: middleが効くようになりました。
  • いくつかのパターンにおける文字詰めの表示崩れを修正しました。
  • 行末揃えの処理を指定しても組版速度が落ちなくなりました。
  • PageReaderは廃止されました。今後はPagedHtmlDocumentを使用してください(後述)。

縦書き文庫のビューアーの刷新

縦書き文庫のビューアーも、新バージョンのエンジンに差し替えました。

nehanのコードサイズが減ったぶん、ページの読み込みも(少しだけ)速くなったと思います。

500KByteほど削減されているので、モバイル環境なんかでは、そこそこ効果が大きいのではないでしょうか。

UIについても、ちょっとだけ刷新しました。

これまでは「本文」と「目次・登場人物」の2列で表示していたのですが、今後は本文を一列(ワンカラム)で表示するようにして、「目次・登場人物」は本文下のメニューに移動させました。

これにより、本文は表示領域が広くなり見やすくなった一方、目次や登場人物に対する操作性は悪くなってしまったので、これに関しては今後の課題としておきます。

(本文の横に目次やら人物やらのタブを表示したらいいのかなあ、と考えています)

nehan7のざっくりとした使い方(開発者向け)

だいたいこんな感じで使います。

import { PagedHtmlDocument, CssStyleSheet } from 'nehan';

const src = "<h1>hello, nehan!</h1>";
const doc = new PagedHtmlDocument(src, {
  styleSheets: [
    new CssStyleSheet({
      "body": {
        "writing-mode": "horizontal-tb", // or "vertical-rl"
        // インライン方向のサイズ(横書きならwidth、縦書きならheightに相当)
        "measure": "450px", 
        // ブロック方向のサイズ(横書きならheight、縦書きならwidthに相当)
        "extent": "500px",
        "font-size": "16px",
        "padding": "1em"
      }
    })
  ]
});
const $dst = document.querySelector("#dst"); // 結果を格納するDOM

// 描画を開始(非同期処理)
doc.render({
  onPage: (ctx){
    const page = ctx.caller.getPage(ctx.page.index); // ページを評価
    $dst.appendChild(page.dom); // 評価したページのDOMを注入
    $dst.appendChild(document.createElement("hr")); // 一応、分割線
  },
  onComplete: (ctx){
    console.log(`終わりました(${ctx.time}msec)`);
  }
});

注意事項1(論理プロパティについて)

nehanは論理組版エンジンなので、領域サイズの指定にwidthとかheightなどは使えません。

代わりにmeasureとかextentという論理プロパティで指定します。

measureは横書き(writing-mode:"horizontal-tb")ではwidthのことです。

縦書き(writing-mode:"vertical-rl")ならheightになります。

extentは横書き(writing-mode:"horizontal-tb")ではheightのことです。

縦書き(writing-mode:"vertical-rl")ならwidthになります。

同じく、top, right, bottom, leftといった名前は、nehanではそれぞれbefore, end, after, startと指定します。

例えば横書きのmargin-leftは、nehanではmargin-startです。

before/afterはブロック方向の前後で、start/endはインライン方向の前後、と覚えてください。

注意事項2(段組とページ送りについて)

上のサンプルでは、appendChildを使って、組版結果をどんどん追記する「段組み方式」で表示していますが、ページ送りで表示したいときは、自前で現在表示するページやページ番号などを管理する必要があります。

その場合、起動時には先頭ページだけを表示し、それ以降のページはページ送りをしたタイミングでdoc.getPageを使って動的に対象ページを取得し、画面上のDOMを差し替える、という実装になるでしょう。

ページ送りの場合の実装については、サンプルのbook.tsなどが参考になるかもしれません。