縦書き ePub

ePub電子書籍フォーマットのひとつである。 私はウェブ上の文章を ePub 形式に変換して電子書籍端末 (Sony Reader) で読むということをよくやっている。 近頃の Windows ではデフォルトのウェブブラウザ (Microsoft Edge) が ePub に対応していて、特に指定しなければ拡張子が epub のファイルはこれで開かれるだろう。 その他、 Adobe Digital Edition というソフトが ePub を読むソフトとしてよく知られている。

ePubhtml5 の規格を基礎にしていて、文章そのものは xhtml5 形式で記述する。 CSS も利用可能だ。 そして縦書き文章にするときはその旨の指定を CSS 書くのだけれど、どうやら Microsoft EdgeAdobe Digital Edition とでは必要な指定が違うようなのだ。

色々といじって試した結果、両者できちんと縦書きにするには CSS に以下のような指定をすれば良いようだということがわかった。

html, body {
 writing-mode: vertical-rl;
 -epub-writing-mode: vertical-rl;
}

この他、ソフトによって極端に動作が重くなってしまうデータ構成なども過去に発見したことがあり、オープンスタンダードなフォーマットと言えどもソフトの都合に合わせる泥臭い作業が必要なのだという実態を感じた。

Document ID: f81a7565c777323b60f34e7f8fbeaa61

区切れ

ある日、母が新聞を見ながら「これがどうしても理解できん。 日本語として成り立ってないようにしか思えん」と言って示したのはテレビ番組表であった。 具体的には 2017 年 7 月 5 日の朝日新聞香川版のテレビ番組表、 OHK テレビの 7 時から 9 時の番組の箇所である。

おじゃMAP‼ 復活
結婚式企画2時間SP
「私、幸せになっても
いいのでしょうか?」
幼い子残し他界した夫
妻の再出発…結婚式を
香取ザキヤマが初司会
&プロデュースで感涙
▽意外と知られてない
穴場⁉香港ディズニー
3万円台で行けちゃう
衝撃のツアー‼◇N天

母は「他界した夫妻が再出発ってどういうこと?」と頭をひねっていた。 もちろんこれは読点を入れれば何の疑問もなく解釈できる。 「他界した夫、妻の再出発」である。

番組表は書ける文字数が少ないせいか無理に詰め込む傾向があり、ときにはこういったことが起こる。 興味深い事例であった。

Document ID: 2f2080a84d17e1d6f8f20a0f6a4fd862

こんな夢をみた「太鼓折半」

こんな夢を見た。 私は辞書をひいていた。 「太鼓(たいこ)折半(せっぱん)」という語を調べようとしていた。

そして見付けた項目には「ひとつひとつは不幸な事例が重なって結果的に利益を得ること」とあった。

目が覚めてから調べてみたが、実際にはそんな言葉は存在しなかった。

私の夢に現れたからには私の脳で生み出された言葉なのだろう。 どういうメカニズムでこういう造語が、しかも合成元の言葉 (太鼓と折半) から全く想像できないような意味で作り出されるのか興味深いことだ。

Document ID: b05792fb9115aad482b40443f5415625

型を隠せるか

C++ ではクラス定義時に private としたメンバには外からはアクセスできない。 そのメンバへの参照やポインタを作って外へ漏らしたりしない限りあくまで内部的なデータである。 (メモリレイアウトを知っていれば無理矢理にアクセスすることは出来なくもないが、それは言語の範囲外の話ということでとりあえず置く。)

そして型についてもまたクラスの private 要素として定義することが出来る。 たとえば

#include <iostream>

class foo {
private:
  class bar {};
public:
  static bar make_bar(void) {
    return bar();
  }
  static void print_bar(bar) {
    std::cout << "bar" << std::endl;
  }
};

という定義を作ったときは foo::bar という型を隠したいという意図がある。 foo::bar という型に外からアクセスできないのだから、 foo::bar 型の変数を foo の外で作ることは出来ない。 foo::make_bar が返した値は捨てるか foo::print_bar が受け取る以外の使い方は出来ない。 …… C++03 までは。

C++11 以降では autodecltypeprivate な型も使える。

int main(void) {
  foo::make_bar(); // OK
  foo::print_bar(foo::make_bar()); // OK
  foo::bar x = foo::make_bar(); // foo::bar は private なのでエラー
  auto x = foo::make_bar(); // C++11 からは auto が使える
  decltype(foo::make_bar()) y = foo::make_bar(); // C++11 からは decltype が使える

  return 0;
}

autodecltype を通じてでも foo::bar という型を外で利用できなくする方法というのはあるだろうか?

Document ID: 10255bcb2ece0cf1d51420c8189eb2a2

Scheme でローカル定数

Common Lisp でローカルな定数を導入する構文を作ろうとする記事を読んだ。

Common Lispでローカル定数の構文 — #:g1

私は以前に似たようなことをやっていたことを思い出した。

以前に作ったマクロ define-constant は最終的に define に展開されるので、そのまま internal define に使っても機能はするのだが、あらためて let 系の形式でローカル定数を定義するマクロを書いてみることにした。

#!r6rs
(import (rnrs))

(define-syntax let-constant
  (lambda(stx)
    (syntax-case stx ()
      ((k ((var form) ...) body body* ...)
       (with-syntax
           (((t ...) (generate-temporaries #'(var ...))))
         #'(let ((t form) ...)
             (let-syntax
                 ((var
                   (make-variable-transformer
                    (lambda(stx2)
                      (syntax-case stx2 ()
                        ((set! v _)
                         (syntax-violation 'set!
                                           "attempt to assign constant"
                                           stx2
                                           #'v))
                        (_ #'t))))) ...)
               body body* ...)))))))

(let-constant ((x 1))
  ;;(set! x 2) ;; ← もし set! を試みたらエラーになる
  (display x)))

例として書いた定数 x は識別子マクロで、展開後には隠れた変数に置き換えられる。 識別子マクロは set! で代入されるときの展開形を特別に定義することも出来て、ここではそういう形になっているときは syntax-violation を使って構文エラーになるように定義した。

Document ID: d5f61355b4e55961f2af8517178d2c53

その心は

技術系の質問サイトや SNS(ソーシャルネットワークサービス) での技術コミュニティを見ている中で、技術的な困難さとは別に応え難いなと思う質問を見ることがある。 それは「A とは B のようなものですよね?」というような質問だ。 既に知っていることに当て嵌めて理解しようとするのが悪いわけではないが、どのくらい近ければ「ようなもの」と言ってしまってよいのかというのは微妙だ。

こういった質問の裏には「A を B と同一視してやってみたら上手くいかなかった」という背景が隠れていることがある。 仮にほとんどの部分で同じものであっても、だからこそ僅かな差の部分が躓きの原因になるので、そういったときに大雑把に「ようなもの」であるというのは何の(なぐさ)めにもならず、話が噛み合わない原因になると思う。

こういった比較のときには「ようなもの」ではなく「A は B と同じように C ですよね?」という風に、ふたつが近いと考える理由を提示するのが望ましいのではないだろうか。 もっとも、そこまで整理して考えることが出来るくらいならわざわざ質問しなくても解決できるかもしれないが。

Document ID: 98fa74bb0b34e6901c6ba4f119a68394

パイプでメモリをストリームにする

メモリ上にあるデータをあたかもファイルのように読みたいというのを以前に考えて、C++ のストリームクラスとして定義したことがある。

しかし、これはもちろん C++ でなければ使えない。 もう少し低水準の API を用いて実現できないかと考えたとき、 Windows API のパイプはほぼファイルと同じインターフェイスで使えることを思い出した。 シークが出来ないという制限はついてしまうが、それを除けば見掛け上はファイルと同じ形で処理できるのではないかと考え、実装してみた。

// memstream.h
HANDLE CreateMemoryFile(const LPVOID data, const DWORD data_size);
// memstream.c
#include <windows.h>
#include <process.h>
#include "memstream.h"

struct writer_args {
  HANDLE hWritePipe;
  LPVOID data;
  DWORD data_size;
};

static const int perround = 1024*64;

static void writer(void* args) {
  struct writer_args* wargs = args;
  char* data = wargs->data;
  DWORD data_size = wargs->data_size;
  HANDLE hWritePipe = wargs->hWritePipe;
  free(wargs);
  DWORD written_size;
  DWORD writing_size;
  
  for(DWORD rest=data_size; rest>0; data+=written_size, rest-=written_size) {
    writing_size = rest<perround ? rest : perround;
    if(!WriteFile(hWritePipe, data, writing_size, &written_size, NULL)) break;
  }

  CloseHandle(hWritePipe);
  _endthread();
}

HANDLE CreateMemoryFile(const LPVOID data, const DWORD data_size) {
  HANDLE rp, wp;
  BOOL result = CreatePipe(&rp, &wp, NULL, 0);
  if(!result) return INVALID_HANDLE_VALUE;
  struct writer_args* wargs = malloc(sizeof(struct writer_args));
  wargs->hWritePipe = wp;
  wargs->data = data;
  wargs->data_size = data_size;
  _beginthread(writer, 0, wargs);
  return rp;
}

これは以下のような要領で使うことが出来る。

#include <windows.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include "memstream.h"

int main(void) {
  char* sample_str="Hello, this is file mapped memory sample";
  HANDLE mfh = CreateMemoryFile(sample_str, strlen(sample_str));
  int fd = _open_osfhandle((int)mfh, _O_RDONLY);
  FILE *fp = _fdopen(fd, "r");
  int ch;
  while((ch=getc(fp)) != EOF) putchar(ch);
  fclose(fp);
  return 0;
}

ウェブ上を検索してみるとスレッドを使わずに一気にパイプに書き込んでしまう方法を紹介しているものもある。 しかし、データ量が充分に小さいときを除いてはパイプのバッファサイズを越えてしまう可能性がある。 そのとき、少しづつでもパイプから読み出してデータを消費するのならばいずれは書込みも完了するのだが、シングルスレッドでは永遠に待つようになってしまう。 CreatePipe API の第四引数でパイプのバッファサイズを指定しても、あくまでヒントとして活用されるに過ぎず、その大きさのバッファを確保することを保証していないということに注意して欲しい。 パイプのバッファサイズ (としてヒントを与えた数値) より少ないデータ量しか書込まないからロックしないとは言い切れず、シングルスレッドで確実にロックしないようにする方法は (MSDN における CreatePipe に関する記述の範囲内では) 読み取れなかった。 そんなわけで、パイプを書き込む側はスレッドにするのが安全であると判断して上記のように実装した次第である。

Document ID: ecd99cc5c21bd06439374c829b044f4b