2017年5月18日木曜日

2chのAAをOSに依存せず綺麗に表示するJavascript/CSSの設定方法 (旧版)

(注) この記事の内容は古いです。 昔はブラウザのバグで大変だったねという歴史と、細かなテクニックを覚えておくために残していますが、歴史はどうでもいいから AA をすぐに表示したい人は、こちらをどうぞ

(以下保管用) 2chのAAをOSに依存せず表示するのは結構骨が折れます。 この事に関しては私がまとめるまでもなく以下のサイトで丁寧に説明されており、おそらく現時点で最も詳しいページと思います。


ただ表示テストをしていて気付いたのですが、フォント次第でかなり気になるレベルで印象が変わります。 特に気になったのは「しょぼん」。 フォントがインストールされていれば表示の違いを以下で確認できますが、フォント指定には一考の余地があると思いました。
(´・ω・`)  # MS Pゴシック
(´・ω・`)  # 梅Pゴシック
(´・ω・`)  # IPA モナー Pゴシック
(´・ω・`)  # Monapo
(´・ω・`)  # Mona
(´・ω・`)  # Textar
上記の表示を見ていると、私はMS Pゴシック > 梅Pゴシック > Textar > IPA モナー Pゴシック > Monapo = Monaの順で綺麗と感じます。 慣れ過ぎてしまったせいかも知れませんが、梅Pゴシックはかなり綺麗な表示だと思っています。 IPA モナー Pゴシックは綺麗ですが、少しフォントが細過ぎる気もします。 Textarも綺麗ですが、「・」が丸くなり過ぎて少し違和感があります。 Mona, Monapoは「ω」が小さ過ぎてあまり可愛くなく、違和感も出てしまっています。



(2018-10-31 色々修正): またUbuntuのFirefoxではMS Pゴシックを上書きするようなフォントを利用すると他のサイトにも影響が出てしまうようです。こういった問題がある以上は、あまりMS Pゴシック互換フォントは指定しないほうが良いと思っています。

となるとLinux/Macで使えるAAフォントがないという事態になってしまうのですが、私は代替フォントとして梅Pゴシックを愛用しています。 梅Pゴシックは完全なAAフォントではなく、大量の空白文字を使うと若干ズレる事があるフォントなのですが、大半のAAは綺麗に表示できます。 標準インストールもされていないフォントなのですが、Linuxならapt-getなどで簡単にインストールできる事から、私はこれを指定に入れています。

ただiOS/Androidは梅Pゴシックでも駄目で、これらのOSはフォントのインストール自体が苦行でしかありません。 なのでこれらのOSではWebフォントのTextarを使うのがベストだと思います。



前置きが長くなりましたが、このような事情を踏まえると以下のCSSが良いと思います。 white-spaceは改行や空白をそのまま表示するようにpreを指定し、 word-wrapはデフォルト値ですがブログのテンプレートなどで変更が加わりやすいのでnormalを明示しておくと良いと思います。
(2018-10-31 修正): ただしLinuxでの利用を想定すると、さらに一手間を加える必要があります。 長くなるので上記リンクを参照してください。私はこの仕組みは良くないと思っているのですが、palm84さんがなぜそうなるのかを詳細に説明してくださったので、その記事も参照すると、Linuxのフォント指定の仕組みが詳しくわかります。
pre.aa {
  font-size: 1em;
  line-height: 1.1;
  font-family: 'MS Pゴシック','MS PGothic','梅Pゴシック',Textar,sans-serif;
  white-space: pre;
  word-wrap: normal;
}


CSSの設定はこれで終わりですが、WebフォントのTextarを表示するためには、加えてTextarのWebフォント読込用のスクリプトをダウンロードし、自分のサイト上に展開する必要があります。 これは以下のコードをHTMLの下のほうに登録するだけで済みます。 headに登録しても良いとは思うのですが、初回起動時のレンダリングが相当遅くなるのでbodyの一番下が良いと思います。 初回起動時以外はそれなりの速度でレンダリングしてくれます。
<script type="text/javascript" charset="utf-8" src="/textar-font/webfont.js"></script>

こういった野良Webフォントはあまり良くないので、Google Web FontsにTextarが採用されると安心感も出るのですけどね。 日本語対応が始まりつつあるものの、今のところ未対応のようです。

またTextarのWebフォント読込用のスクリプトの使用法について軽くメモしておきます。 実装上は<p class='textar-aa'>...AA 1行目...<br>...AA 2行目...</p>という記述方式を想定しているようですが、 <pre class='aa'>...AA...</pre>といった記載方法のほうが楽だと思います。


ここまでの設定でAAを綺麗に表示できるようになったと思いますが、 さらにフォントを設定できるボタンを付けておけばフォントにこだわりのある方にも対応でき万全だと思います。例えばこんな感じ。

とんかつ作るよ

.  ∧_∧    ヘ⌒ヽフ ⌒γ
 (`・ω・)  (・ω・ )   )
 / оо━二二二二二フ
 しー-J

2017年5月16日火曜日

JavaScriptのGridライブラリの機能比較とまとめ

JavaScript の Grid ライブラリも最近また進化してきて、比較する必要性が出てきたので、まとめてみました。 このページでの選択基準は、私が使いたかった機能の機能性、ロード速度、スマホ対応、更新頻度です。

機能面で私が必要としているのは、最低限は外部ファイル読み込みとソート機能です。ないものは評価対象外です。 キーボード操作、カラム選択、カラム範囲選択、フィルタリング、列フィルタリングはどうしても欲しい。 カラム選択は項目毎にクリックせず複数選択可能なら◎、選択に手間が掛かるなら○、選択機能がなければ×です。 フィルタリングは全体検索ができるかどうかと使いやすさで評価を多少変えています。 列フィルタリングは数値で範囲検索ができれば◎、一致検索しかできなければ△、機能がなければ×です。APIはあってもUIやサンプルがなければ×にしています。

スマホ対応に関しては、水平スクロール対応と、UI の対応しやすさが重要だと思います。 グリッドは表示幅が広くなりやすいので、水平スクロールができると表示サイズの調整がしやすいです。 スマホ対応の評価は独断と偏見ですが、実装面や機能面で課題があると思ったら評価を下げています。 ライブラリの更新は頻繁に行われるほうがバグ遭遇時に対応しやすいので考慮に入れました。

ライブラリ数がかなり多いので、高機能で良さそうなものだけまとめてみました。


名称 読込
速度
キー
操作
選択 範囲
選択
全体
検索
列検索 水平
移動
スマホ
対応
更新
頻度
備考
Angular UI Grid◎?
ag-grid◎?一部有料
Handsontable (有料)
jQWidgets商用有料
IgniteUI Grid有料
jqGrid, Guriddo◎?商用有料
w2ui Grid (1.4.x)×
w2ui Grid (1.5rc)
Handsontable (無料)××
SlickGrid実装難解
React Data Grid◎?
DataTables××
TreeGridほぼ有料
dhtmlGrid商用有料
dgrid×
jsGrid×
FancyGrid×商用有料
gripple-react◎?×××××
FooTable△?××××商用有料

気付けば良いライブラリが凄く増えてますね。機能要件を重視した分類表なので鵜呑みにはせず、利用用途に応じて選択するのが良いと思います。

参考文献:

追記: 最近は、Tabulator というライブラリも出てきました。

2017年5月5日金曜日

Hugoのページ生成をpartialCachedで高速化

静的サイトジェネレータのHugoに最近、partialCachedという機能が付いていました。 名前の通りpartialでの呼び出しをキャッシュするみたいです。 同じレイアウトを大量のページで使い回す場合にはかなり早くなりそうだと前々から思っていました。

使い方は至ってシンプルで、以下のようにするだけです。
{{ partial "header.html" . }}  # before

# after1 ... サイト全体で内容が統一な時
{{ partialCached "header.html" . }}
# after2 ... セクション内で内容が同一な時
{{ partialCached "header.html" . .Section }}

というのがマニュアルに書かれている事なのですが、実際にはマニュアルだけでは動作がよくわからなかったので色々確認してみました。 その結果、hugo/layouts/_default/single.htmlなどのデフォルト設定が優先されるようです。 ここでのグローバル宣言は他セクションを上書きします。

バグの混入が怖い方は、まず全ページで共通のpartialコンテンツだけキャッシュすると良いと思います。 ここで留めればまずバグは入らないでしょう。 さらに高速化したい人は微妙に表示が異なるようなpartialコンテンツをセクションごとに.Section付きで編集していけば、 バグの混入確率は低く抑えられそうな気がします。

何はともあれ1万ページくらいあるサイトに早速適用してみました。何度も計測している訳ではないのでざっくり計算です。 直前のキャッシュの効き方にも影響されますし、ページ構成にもよるでしょうが、結構高速化している事は確かのようです。
total in 59349 ms  # before
total in 25563 ms  # after

Hugoは元々高速なのにさらに早くなりました。素晴らしい。

もっと早く知っておきたかったmattnさんのGo言語ユーティリティ

Go言語は気付けばmattnさんのライブラリを使ってるので、先回りして便利そうなユーティリティを調査する事にしました。知ってると「めんどいムキー!」となる事がかなり減るかも。なるべく汎用的なものに絞って、よく使ってる順に並べました。いくつか初めて使って、使い始めたものもあります。

gom

Rubyのbundleの代替。ないと生きていけない。最近はdepも出てきましたが、depはGOPATHに依存するのがちょっとなあ。

go-sqlite3

SQLite3を使うための標準。ないと生きていけない。

go-shellwords

シェルのコマンド形式をGoのexec.Command形式に。ないと生きていけない。

go-zglob

RubyのDir.globの代替。ないと生きていけない。

go-runewidth

マルチバイト文字列を分割。ないと生きていけない。

go-options

コマンドラインオプションの解析。シンプルで便利。

go-forlines

標準入力から一行ずつ読み込む。ないと困る時もありそう。

go-pipeline

exec.Commandでパイプやリダイレクトを使う。ないと困る時もありそう。

go-colorable

ログをカラフルに。便利ですが、ロギングライブラリはたくさんあるので何とも。

もっと早く知っていれば人生が楽になれたかも知れないものがチラホラ。

Go言語でスクリプトを書いてみる

Go言語をスクリプトにも利用するための練習として、以前書いたスクリプトをGo言語で書いてみました。エラー処理をかっとばして書けばGoもかなりコンパクトに書けます。Rubyでは49行でしたが、Goでも70行くらいで書けました。色々面倒なところはありますが、何とかなる範囲ではあるかも。

package main

import (
  "fmt"
  "os"
  "os/exec"
  "path/filepath"
  "time"
  "github.com/mattn/go-shellwords"
  "github.com/mattn/go-zglob"
)

func html_minifier(from string, to string) {
  cmd := fmt.Sprintf(`
  sh -c 'html-minifier %v \
    --collapse-boolean-attributes \
    --collapse-whitespace \
    --remove-attribute-quotes \
    --remove-comments \
    --remove-empty-attributes \
    --remove-redundant-attributes \
    --remove-script-type-attributes \
    --remove-style-link-type-attributes \
    --html5 \
    --use-short-doctype \
    --minify-js \
    --minify-css > %v'
  `, from, to)
  args, _ := shellwords.Parse(cmd)
  exec.Command(args[0], args[1:]...).Run()
}

func main() {
  dir := os.Args[1]
  var minifySize, html_minifierSize, originalSize int64
  var minifyTime, html_minifierTime int64

  paths := getPaths(dir, "*.html")

  for _, path := range paths {
    fileinfo, _ := os.Stat(path)
    originalSize += fileinfo.Size()
  }

  for _, path := range paths {
    cmd := fmt.Sprintf("sh -c 'minify %s > %s'", path, path+".min")
    args, _ := shellwords.Parse(cmd)
    t := time.Now().UnixNano()
    exec.Command(args[0], args[1:]...).Run()
    minifyTime += time.Now().UnixNano() - t

    fileinfo, _ := os.Stat(path+".min")
    minifySize += fileinfo.Size()
  }

  for _, path := range paths {
    t := time.Now().UnixNano()
    html_minifier(path, path+".min")
    html_minifierTime += time.Now().UnixNano() - t

    fileinfo, _ := os.Stat(path+".min")
    html_minifierSize += fileinfo.Size()
  }

  fmt.Printf("file num: %v\n", len(paths))
  fmt.Printf("original:      %v\n", originalSize)
  fmt.Printf("Minify:        %v, %v\n", minifySize, time.Duration(minifyTime))
  fmt.Printf("html_minifier: %v, %v\n", html_minifierSize, time.Duration(html_minifierTime))
}
Goで即興スクリプトを書いてみて気になった点は3つほど。(1)RubyのDir.globみたいな再帰検索はこれまで自作していましたが、最近はgithub.com/mattn/go-zglobを利用すると簡単に書けるようになりました。どんどんmattnさんのライブラリで埋まっていく…。

(2)ベンチマークにはtestingという標準ライブラリがあります。時間やメモリを計測するには非常に便利ですが、計測精度を高めるために勝手に連続実行する仕組みになっています。同時に色々な情報を処理しながら計測しようとすると少し困ります。手軽な計測であればtime.Now()で地道に計算するのが楽かなあ。

(3)外部コマンドを実行するにはexec.Command(args)を使いますが、argsには文字列の配列を渡す必要があり面倒です。github.com/mattn/go-shellwordsを利用するとシェル上での実行文字列を配列に変換してくれるのでかなり楽になります。ただしcommand > outのようなコマンド内での出力には対応していないので、sh -c 'command > out'のような形式に変更して実行する必要がありました。

Go言語はスクリプトごとにbuildしていると容量がかなり無駄になりますが、以下の方法でバイナリサイズを小さくすれば気にならない程度には小さくなります。
$ sudo apt-get install upx-ucl
$ go get github.com/pwaller/goupx
$ go build -ldflags '-s' test.go
$ goupx -9 test  # or goupx test

もう少し色々なパターンで記述の容易さを確認してみたいです。

2017年5月2日火曜日

Medium、Tumblr、ブログ、まとめサイトの比較

Mediumというサービスがあるらしいので、さっそく使ってみました。

ざっくり言ってしまうと、Tumblrやブログのように記事を書くためのサービスですが、 TumblrやInstagramのようにユーザや記事のフォローやタイムライン表示ができ、 Gunosyのようなレコメンドで興味のある記事も紹介してくれるサービス、という感じでしょうか。


もう少し真面目に比較してみると、Tumblrはトップページが編集用のDashboardですが、 MediumはInstagramやGunosyのようなタイムラインになっています。 便利なような気もしますが、英語の記事ばかり出てくるので、日本人にはまだ使い勝手がそれほど良くない。 日本人の利用にはしばらく課題が残りそうです。

ブログと違うのは、コンテンツはブログごとに管理するのではなくMediumが管理している点です。 記事の検索もMedium内で行われる事になります。 これがどれくらい便利かはまだよくわからないですが、ユーザが増えれば良い記事が探しやすくなるかも。 なんとなくまとめサイトの海外版のような位置付けになるんじゃないかという気がしています。

記事の書き方はTumblrにかなり近いですが、機能は少なめです。 複雑な事をするのは難しそうで、短文をパラパラ書くためのサービスという印象です。 あまりカスタマイズもできません。 何らか凝った事をするならブログやTumblr、日本のまとめサイトのほうが良いんじゃないかと思います。


今のところは課題のほうが遥かに多い印象を受けますが、レコメンド機能があらかじめ備わっているので、 記事を書く事で自分が読みたい記事が見つかる正のスパイラルが得られるようになるかも。 この辺はGunosyに似てると思いました。

現在のネットでは記事検索はGoogleなどの検索エンジンに頼りきっていますが、 検索エンジン任せでは配信に気付いてもらうのは結構大変です。 Mediumは海外ユーザが結構居るので、そういった方にタイムラインで見てもらえるのは利点かも知れません。 Mediumやまとめサイトではコンテンツをサイト側のドメイン上で管理するので、ドメインパワーが強くなりやすい特性を活かせるかも知れません。


他にも収益化に関しては結構差がありますね。 ブログやTumblrと言えばアフィリエイトですが、Mediumやまとめサイトではコンテンツをサイト側のドメイン上で管理するので、アフィリエイトは使えません。 その代わりMediumでは会員制プランに登録すると、会費からの収益を「重要な」ライターに還元する仕組みになってるようです。 noteのような有料記事路線とも似ていますが、間にサイト管理者が入らないぶんnoteのほうがクリアな仕組みになっていると思います。 どのように収益化したいかは人それぞれだと思いますが、私は仕組みが最もクリアなブログが好きですね。

まだまだ未知数なので、今のところは記事を書きやすいサービスを使えば良いんじゃないかなという気がします。 ただ技術系ブログは今のところBlogger推しかな。

Hugoでmarkdownにscriptを書く

Hugoは非常に便利な静的サイトジェネレータですが、markdownにscriptを書きにくいという問題があります。 例えばhugo/content/foo/test.mdが以下のような場合、
---
layout: hoge
permalink: /foo/test/
---

<div>
  <script>
    setBar("fuga");
  </script>
</div>

hugo/public/foo/test/index.htmlの出力結果は以下になります。つまりスクリプトの中身が勝手にエスケープされてしまいます。これではスクリプトが動作しません。
<html>
  <head>...</head>
  <body>
    ...
    <div>
      <script>
        setBar(&ldquo;fuga&rdquo;);
      </script>
    </div>
  </body>
</html>

この問題はshortcodeを使う事で解決します。まずはhugo/layouts/shortcodes/script.htmlを作成し以下のように記述します。*.htmlの*はshortcodeの呼出名になります。
<script>
{{ .Inner | safeJS }}
</script>

そしてhugo/content/foo/test.mdを以下のように書き換えます。
---
layout: hoge
permalink: /foo/test/
---

<div>
  {{< script >}}
    setBar("fuga");
  {{< /script >}}
</div>

これでscriptタグの中身はHTMLエスケープされなくなりました。後は好きなだけスクリプトを書けます。

2017年5月1日月曜日

HTML最小化ツール tdewolff/minify は凄いかも

HTML最小化は今までhtml-minifierを使っていたのですが、MinifyというライブラリがHugoに採用されるかもという話があり注目しました。

実行時間が非常に短いらしいのでさっそく軽く実行時間をテストしてみます。事前にGo言語を用意し、Minifyを以下のコマンドでインストール。html-minifierのインストール方法は長くなるので公式を参照頂くとして割愛します。
$ go get github.com/tdewolff/minify/cmd/minify

例によって検証用スクリプトを軽く組んでみました。
dir = ARGV[0]

def html_minifier(from, to)
  cmd = <<-CMD
  html-minifier #{from} \
    --collapse-boolean-attributes \
    --collapse-whitespace \
    --remove-attribute-quotes \
    --remove-comments \
    --remove-empty-attributes \
    --remove-redundant-attributes \
    --remove-script-type-attributes \
    --remove-style-link-type-attributes \
    --html5 \
    --use-short-doctype \
    --minify-js \
    --minify-css > #{to}
  CMD
  `#{cmd}`
end


minify_size = html_minifier_size = original_size = 0
minify_time = html_minifier_time = 0

files = Dir.glob("#{dir}/**/*.html").each {|file|
  original_size += File.size(file)

  t = Time.now
  `minify #{file} > #{file}.min`
  t = Time.now - t
  minify_size += File.size("#{file}.min")
  minify_time += t

  t = Time.now
  html_minifier(file, "#{file}.min")
  t = Time.now - t
  html_minifier_size += File.size("#{file}.min")
  html_minifier_time += t
}

puts "file num: #{files.length}"
puts "original:      #{original_size} bytes"
puts "Minify:        #{minify_size} bytes, #{minify_time} sec"
puts "html-minifier: #{html_minifier_size} bytes, #{html_minifier_time} sec"

軽く確認しただけでも凄い結果です。Minifyは非常に高速に動作する上に圧縮率も良いです。検証にはGo-1.8.1 + Minify-2.1.0、Node.js-7.8.0 + html-minifier-3.0.2を使用してます。html-minifierはちょっと古めですがご容赦ください。
$ ruby test.rb test_dir
file num: 93
original:      1716365 bytes
Minify:        1477416 bytes, 1.4351485620000002 sec
html-minifier: 1497892 bytes, 70.05997373700002 sec

これだけ高速に動作するならページ数が多少多くてもMinifyでHTML最小化→gzip/brotliで圧縮配信する価値がありそうです。 HTML最小化→gzip/brotliの効果はこちら。 html-minifier→zopfliをMinify→brotliにしたら半日掛かっていたバッチが30分以下になり、体感でもわかるくらいページ読み込みが早くなりました。