Perl/Tk で縦書きビューワー『ぽちたて』を作ってみた

縦書きビューワー『ぽちたて』は Perl/Tk版から Gtk2-Perl版へ移行しました。このページの記述は古いバージョンの物です。

↓最新版はこちら↓

ここには、ぽちが PerlPerl/Tkモジュール とLingua::JA::Foldモジュールを使って作ったテキストファイル縦書き形式ビューワー『ぽちたて』を置いています。

青空昆布の開発を引き継ぎましたxjp2 に刺激を受けて、万年Perl初心者のぽちが無謀にも自分で縦書きビューワーを作ってみました。扱える入出力日本語文字コードが UTF-8 だけなので事実上 Linux/UNIX向けな上に、 UTF-8 だけでなく Shift JISEUC-JPJIS で書かれたテキストファイルに対応し、Windows の ActivePerl でも利用出来ます。とは言うものの、最低でもフォントの縦書きグリフの表示が可能になるまではあくまでα版ですのでご注意を!

IPAフォント使用時のぽちたて画面サムネイル

縦書き画面は出版社別文庫の1Pに入る最大文字数!を参考に、一般的な文庫本の 1ページ分を表示するように設定してあります。

機能

UTF-8、Shift JIS、EUC-JP、JIS で書かれたテキストファイルを受け取って縦書き表示します。

動かすのに必要ないろいろ

ぽちたてを動かすには Perl本体と、Perlモジュールの Perl/Tk と Lingua::JA::Fold が必要になります。モジュールはディストリビューションでパッケージングされたものがあるならそれを使うなり、無ければ Perl本体に入っている CPANという名前のモジュールを使って CPAN から (モジュール名とサイトの名前が同じなのでヤヤコシイ) インストールするなりしてみて下さい。

使い方

ファイルに実行権限を与えた後でターミナルエミュレータから、

$ ./pochitate.pl [テキストファイル]

または、コマンドプロンプトから、

C:¥> perl pochitate.pl [テキストファイル]

という感じで pochitate.pl の引き数にテキストファイルを渡して実行してあげて下さい。

ぽちたて画面右下の「<」をクリックするかスペースキーで次のページへ、「>」をクリックするか 'p'キーで前のページへ移動します。栞を挟む場合は、「栞」をクリックするか 'b'キーで栞ファイル pochitate_shiori.txt にファイル名とページを記録します。栞ファイルがある時に引き数を与えずに実行すると読み掛けのページから表示します。

$ ./pochitate.pl

または、

C:¥> perl pochitate.pl

終了する場合は「×」をクリックするか 'q'キーを押して下さい。

現在のリリース

  • ぽちたて 0.0.3 - 2009.09.17 栞機能の追加とバグフィックス
  • ぽちたて 0.0.2 - 2009.09.15 キーボードからの操作と UTF-8 だけでなく、Shift JIS、EUC-JP、JIS での入力に対応
  • ぽちたて 0.0.1 - 2009.09.12 初めてのリリース

ごめんなさい

2009年 9月16日 23時50分から 2009年 9月17日 9時にかけてリリース予定のバージョンでは無いものをぽちたて 0.0.3 として公開していました。その間にぽちたてを拾って行った方はお手数ですけども、もう一度改めて持って行って下さい。

本当に申し訳ありません。

 

To Do

  • 縦書きグリフの表示
 

既知の問題

  • 栞で復帰すると栞を挟んだページより前に戻れない

コード

  • pochitate003.pl
#!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Encode;
use Encode::Guess qw/euc-jp shiftjis iso-2022-jp/;
use Tk;
use Lingua::JA::Fold qw(fold);
use open IN  => ":bytes"; # 入力される文字コードが混在している可能性があるので

# 設定いろいろ ( IPAフォント使用時に合わせてあります)
my $font_family            = 'IPA明朝';  # テキスト表示画面のフォント名
my $font_size              = 11;         # テキスト表示画面のフォントサイズ
my $font_style             = 'normal';   # テキスト表示画面のフォントスタイル
my $font_color             = '#000000';  # テキスト表示画面のフォントカラー
my $background_color       = '#e0ffff';  # テキスト表示画面の背景色
my $text_width             = 523;        # テキスト表示開始部の幅
my $text_height            = 28;         # テキスト表示開始部の高さ
my $line_horizontal_margin = 26;         # 各行間の横の余裕
my $chr_vertical_margin    = 15;         # 各文字間の縦の余裕
my $fold_length            = 42;         # 文庫本 1ページの一般的な一行の文字数
my $page_lines             = 18;         # 文庫本 1ページの一般的な行数
my $counting_font_family   = 'IPAゴシック'; # ページ数表示部のフォント名
my $counting_font_style    = 'bold';     # ページ数表示部のフォントスタイル
my $shiori_file            = 'pochitate_shiori.txt'; # 栞ファイルの場所

my @chr_ids;             # ページ内各文字の ID の配列
my @offsets = (0);       # 既読ページのテキストデータ読み出し後のオフセット配列
my $page = 0;            # 現在表示しているページ数
my $shiori_page;         # 栞を挟んだページ
my $filename;            # 読み込むテキストファイル名

# 引き数が無くても栞があったら自動復帰
unless ($filename = $ARGV[0]){
    if (-e $shiori_file){
        open my $sh,"<", $shiori_file
            or die "Couldn't open $shiori_file for reading: $!\n";
        $filename = <$sh>;
        close $sh;
        chomp $filename;
        ($filename, $offsets[0], $shiori_page) = split ' ', $filename;
        ($ARGV[0], $page) = ($filename, $shiori_page);
    } else {
        die "There is no argument.\n";
    }
}

# メインウィンドウの準備
my $top = MainWindow->new( -title => 'ぽちたて 0.0.3' );

# フレームの準備
my $frame_top = $top->Frame();
my $frame_bottom = $top->Frame();

# 主画面となるキャンバスウィジェットの配置
$frame_top = $top->Canvas( width => 603, height => 671 );
$frame_top->create( 'rectangle', 1, 1, 602, 670, -fill => $background_color );

# ページ数表示部、ページ移動ボタンの配置
# (「<」で次ページ、「>」で前ページ、「×」で終了)
$frame_bottom->Label( -textvariable => \$page,
          -font => [ $counting_font_family, 12, $counting_font_style ] )
          ->pack( -side => 'left' );
$frame_bottom->Button( -text => '<',
          -command => [ \&next_tategaki, \@chr_ids, \@offsets, \$page ],
          -font => [ $font_family, 4, $font_style ] )->pack( -side => 'left' );
$frame_bottom->Button( -text => '>',
          -command => [ \&prev_tategaki, \@chr_ids, \@offsets, \$page ],
          -font => [ $font_family, 4, $font_style ] )->pack( -side => 'left' );
$frame_bottom->Button( -text => '栞',
          -command => \&shiori,
          -font => [ $font_family, 10, $font_style ] )->pack( -side => 'left' );
$frame_bottom->Button( -text => '×', -command => \&exit,
          -font => [ $font_family, 4, $font_style ] )->pack( -side => 'left' );

$top->bind('<space>' => \&next_ward); # キー操作 ' ' で次ページ
$top->bind('<p>' => \&prev_ward);     # キー操作 'p' で前ページ
$top->bind('<b>' => \&shiori);        # キー操作 'b' で栞を挟む
$top->bind('<q>' => \&exit);          # キー操作 'q' で終了

# フレーム内の配置
$frame_top->pack();
$frame_bottom->pack( -side => 'right' );

# テキストファイルからの読み込みと文字コード判定・折り返し処理
my $strs;
for (@ARGV){
    open IN, $_ or die "Couldn't open IN for reading: $!\n";
    my $content .= join '', <IN>;
    close IN;
    my $guess = guess_encoding($content);
    ref $guess or die "Couldn't guess: $guess\n";
    open IN, $_ or die "Couldn't open IN for reading: $!\n";
    while (<IN>){
        $_ = $guess->decode($_);
        $strs .= fold( 'text' => $_, 'length' => $fold_length,
                       'mode' => 'traditional' );
    }
    close IN;
}

open my $fh, "<", \$strs or die 'Couldn\'t open $fh for reading: ', "$!\n";
seek $fh, $offsets[0], 0;
&next_tategaki(\@chr_ids, \@offsets, \$page);

MainLoop();

# 次ページ表示
sub next_tategaki{
    my ( $chr_ids_ref, $offsets_ref, $page_ref) = @_; 
    return if eof $fh;
    for (@$chr_ids_ref){
        $frame_top->delete( $_ );
    }
    @$chr_ids_ref = ();
    &write_tategaki($chr_ids_ref);
    push @$offsets_ref, tell $fh or die "Couldn't tell the file offset: $!\n";
    $$page_ref++;
    undef;
}

# 前ページ表示
sub prev_tategaki{
    my ( $chr_ids_ref, $offsets_ref, $page_ref) = @_;
    return if $#offsets < 0 || defined $shiori_page && $shiori_page > $page - 2;
    for (@$chr_ids_ref){
        $frame_top->delete( $_ );
    }
    @$chr_ids_ref = ();
    pop @$offsets_ref if $#offsets > 1;
    seek $fh, $offsets[-2], 0;
    &write_tategaki($chr_ids_ref);
    $$page_ref-- if $$page_ref > 1;
    undef;
}

# 縦書き描画
sub write_tategaki{
    my $chr_ids_ref = shift;
    my $lines;
    my $line_width = $text_width;
    while (<$fh>){
        $_ = decode_utf8($_);
        my $chr_height = $text_height;
        my @string = split(//, $_);
        for (@string){
            my $id = $frame_top->create( 'text', $line_width, $chr_height,
                          -fill => $font_color, -text => $_,
                          -font => [ $font_family, $font_size, $font_style ] );
            push @$chr_ids_ref, $id;
            $chr_height += $chr_vertical_margin;
        }
        $line_width -= $line_horizontal_margin;
        $lines++;
        last if ($lines > $page_lines - 1);
    }
    undef;
}

# 栞を挟む
sub shiori{
    open my $sh, ">", $shiori_file
        or die "Couldn't open $shiori_file for writing: $!\n";
    print $sh $filename, ' ', $offsets[-2], ' ' , $page - 1, "\n";
    close $sh or die "Couldn't close $shiori_file: $!\n";
    undef;
}

sub next_ward{
    &next_tategaki(\@chr_ids, \@offsets, \$page);
    undef;
}

sub prev_ward{
    &prev_tategaki(\@chr_ids, \@offsets, \$page);
    undef;
}

# Copyright (c) 2009, 犬山ぽち丸 
# このコードは、Perl自体と同じライセンスで配布します。
# Copyright (c) 2009, Pochimaru Inuyama. All rights reserved.
# This code is distributed under the same terms as Perl itself.

bzip2zip に圧縮した物も置いておきます。

  • pochitate003.pl.bz2 ( 2634 bytes sha256sum: fb7f10d9bd6337385435553756e378aa46b11407ed3cf125928351a42e5eb9d5 *pochitate003.pl.bz2 )
  • pochitate003.zip ( 2729 bytes sha256sum: 768419deb089a86defb0e5a9d5955b1ea0e428fa138da9338c16b2608e6b7cbe *pochitate003.zip )

謝辞

ぽちたて作りにあたって多くの方々の励ましの言葉や助言、力添えをいただいています。ぽちたてがより良いものになっていくのはこうした方々のお蔭です。本当にありがとうございます。

今後もちまちま作り続けようと思っていますので、ぽちたてを使ってみてくれた奇特な方がいらっしゃいましたら感想やバグレポートなどを下記メールアドレスまでお願いします。

動作確認環境 : Perl 5.10.0 on Debian GNU/Linux 5.0 Lenny

戻る


Last updated : 2010/06/30
Author : 犬山ぽち丸 / INUYAMA Pochimaru / Pochimaru Inuyama
E-mail : pochi@hoshinoumi.net
Key ID : 4A1B5E85
Key fingerprint : 4605 4D40 6154 20C1 5592 3E54 5A37 FEE9 4A1B 5E85

正当な XHTML 1.1 です 正当な CSS です