May 20, 2013

ぽちたて 0.1.8 をリリースしました

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

放置されたまま幾星霜……なんと二年振りの更新になりますが、ぽちたて 0.1.8 をリリースしました。

今月は、長い間 testing だった debian wheezy が stable になったり、perl-5.18.0 が出たりと、ぽちのパソコン環境的に何かと色々ありました。testing時代からずっと wheezy を使って来たので前者はどうという事も無かったのですけども、後者の Perl 5.18.0 では、ぽちたて 0.1.7 がファイルを開こうとすると、

Strings with code points over 0xFF may not be mapped into in-memory file handles

とエラーを吐いて die してしまいます。

perl5180delta を読んでみると、Updated Modules and Pragmata の * PerlIO::scalar has been upgraded to 0.16. にこんな文章が。

The buffer scalar supplied may now only contain code points 0xFF or lower. [perl #109828]

つまり、 PerlIO::scalar はコードポイント 0xFF以下の文字列しか open 出来なくなっちゃってたのでした。ぽちの緩い書き方のせいで内部表現の文字列入りスカラー変数のリファレンスを open していた 0.1.7 がファイルを開けなくなるのも当然です。

というわけで、ぽちたて 0.1.8 ではきちんと中身の文字列を UTF-8 でエンコードしてから open するように修正しました。

ぽちは、「お行儀良く書いてね?」と怒られてたわけです。ちゃんと怒ってくれるなんて素晴らしい!!

Jul 07, 2011

ぽちたて 0.1.7 をリリースしました

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

今夜は七夕なのにぽち地元は天気が悪く、天の川は見えそうになくてちょっと残念ですけども、ぽちたて 0.1.7 をリリースしました。

昨日、CLItwitterクライアントを書いていて、ぽちたてではホームディレクトリを取得するのに環境変数を直接使っていた事に気がつきました。これでは Windows環境だと、環境変数を改めて自分で設定するひと手間が必要になり、そのままぽちたてを動かす事は出来ません。そこで 0.1.7 では、File::HomeDir を使ってもっとポータビリティに配慮してみました。UNIX/Linux環境でぽちたてを使われている場合には、0.1.6 のままでも実用上全く問題ありませんけども、0.1.7 の方がほんの少しだけお行儀が良いかもしれません。

Jun 20, 2011

ぽちたて 0.1.6 をリリースしました

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

今まで、コマンドライン引き数に読み込むファイルを与えて起動した時は、ファイル内容の文字コード判定に失敗したり取り扱えないファイルだったりすると、きちんとエラーを表示して die 出来てたのですけども、ファイル選択ダイアログで読み込むファイルを与えた場合にエラーが発生すると、ファイル選択ダイアログのウィンドウがフリーズしてしまうバグがありました。そんなに変なファイルは読まないだろうとそのままにしていたのですけども、最近はぽち以外にも実際に使って下さってる方がいらっしゃいますので、大慌てできちんとエラー処理を施したぽちたて 0.1.6 をリリースしました。

実際に使って下さってる方がいると、ぽちたて作りにも張り合いが出ます。[犬]ω<)ノ☆

May 03, 2011

ぽちたて 0.1.5 をリリースしました

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

ファイルタイプの判別に File::MMagic を使うようにした、ぽちたて 0.1.5 をリリースしました。

バージョンアップするほどの変更ではないかもしれませんが、とりあえず 0.1.4 よりは安全性は高くなった気がします。

Apr 11, 2011

青空文庫形式テキストのパーサが欲しい!

連日ちまちま作っているぽちたてですけども、単純なテキスト縦書きビューワーとしては、だんだんとそれなりの物になって来ています。昨日も圧縮されたテキストファイルをそのまま読み込めるようにしました。こうなって来ると、青空文庫のテキストを読む時に青空文庫形式テキストをただそのまま素の縦書き表示するだけでなく、青空文庫形式テキストの注記を解釈してルビやレイアウトなどに反映させた縦書き表示をしたくなって来ます。その為に必要になるのが青空文庫形式テキストのパーサです。

ぽちたてを青空文庫ビューワー化させるには、まずこの青空文庫パーサを書かないといけません。ぽちの乏しい知識と泥臭いセンスからすると、安直に Perl正規表現で書く事になると思います。本当は誰か Perl の凄いひとが、AozoraBunko::Parser(仮名)みたいな名前で Perlモジュールを書いてくれたら良いんだけどなあ…。

とりあえず今後の為に、参考になりそうなサイトをメモしておきます。

なんだか、本当にぽちたての青空文庫ビューワー化が出来るのか不安になって来てしまいました。

Apr 10, 2011

ぽちたて 0.1.4 をリリースしました

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

素のテキストファイルだけでなく、gzipbzip2zip形式に圧縮されたテキストファイルも受け取って表示出来るようになった、ぽちたて 0.1.4 をリリースしました。

これでファイルを保管するスペースをだいぶ節約出来るかも?

Apr 05, 2011

ぽちたて 0.1.3 をリリースしました

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

一度栞を挟んだ後で、ぽちたて 0.1.2 を終了せずに再度栞を挟むと、次に起動した時にページ数や読み出し位置がおかしくなる不具合を修正した、ぽちたて 0.1.3 をリリースしました。

何か、いつもいつもリリースした直後にバグが発覚している気がします。リリース前にはもっと変な使い方を試して慎重にデバグしないといけませんね。とほほ…[犬]ω;)。

Apr 04, 2011

ぽちたて 0.1.2 をリリースしました

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

PangoIPAフォント のバージョンアップのお蔭で縦書き表示が以前よりぐっと実用的になったので、ぽちたて 0.1.2 をリリースしました。誰ですか他力本願って言ってるのは?

今回は、従来マウスクリックでのページ送り・ページ戻りがとても面倒臭かったので、キー操作でも出来るようにし、栞機能を見直して、栞で再開した後でも栞を挟んだページよりも前へ戻れるようにしてみました。さほど致命的では無いバグがまだ残っているかもしれませんけども、これで簡易なテキスト縦書きビューワーとしての機能は一応満たせたような気がします。次の目標は青空文庫形式テキストの閲覧機能の実装?

しかし、ぽちたてが Gtk2-perl版になってからは、まだ一人からしか使ってみた感想を聞いた事がありません。未完成な上に事実上ほぼ Linux/UNIX専用だとはいえ、本当にこの世で誰か他に使ってみた人は存在するのでしょうか?まあ、ぽちが使えればそれで構わないんですけどね。

May 25, 2010

ぽちたて 0.1.1 をリリースしました

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

ぽちたて 0.1.0 のリリース直後にビューワーとして致命的な不具合がてんこ盛りで発覚してしまって大慌てのぽち、さっそく大急ぎでもう少しましなぽちたて 0.1.1 をリリースしちゃいます。

今回は、[ファイル]→[開く]による開き直しが出来るようにして、その他のてんこ盛りな不具合を見つけられた範囲で修正してみました。

ノリと勢いでリリースすると本当に駄目ですね…。ぽち、いろいろと大反省中です。

May 20, 2010

ぽちたて 0.1.0 をリリースしました

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

約半年間も放置し続けた挙句、やっとの事でぽちたて 0.1.0 をリリースしました。

このバージョンからは Pango による縦書きを利用する為に、GUIツールキットとして Gtk2-Perl を通して GTK+2 を使っています。ぽちの環境では、Pango での縦書きは正直なところまだまだ実用的な域ではありませんけども、最新の Pango ならきっと素敵な感じになっているだろう事を夢見ています(他力本願)。最新環境の方に試していただけて、どんな感じに表示されるのか教えていただけたら嬉しいです。

Nov 27, 2009

Gtk2-Perl で Pango を使った縦書きビューワーを作ろう(3)

2ch Linux板の「GTK+プログラミング」というスレで、今まで躓いていた部分の解決法を教えて貰えました。事前にテキストを描画したオブジェクトが既にあるのかどうかを確かめて、あるようなら次の描画の為に destroy しちゃうという方法です。

言われてみればなぜ今まで気がつかなかったんだろうという感じのとても真っ当な方法なのですけども、独学の悲しさ、自分ではそこに気がつかないままになってしまっていました。教えてくれた方、本当にありがとうございます。まだまだ細々した部分は後回しにして放置しているのがほとんどの状態ですけども、とりあえずは複数ページに渡るテキストを読み進めたり戻ったりする事が出来るようになりました。

ぽちたて 20091127

#!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Encode;
use Encode::Guess qw/euc-jp shiftjis iso-2022-jp/;        # 日本語文字コード判別
use Lingua::JA::Fold qw/fold/;                            # 日本語禁則折り返し
use open IN  => ":bytes";   # 入力される文字コードが混在している可能性があるので
use Gtk2 qw/-init/;
use Glib qw/TRUE FALSE/;    # TRUE, FALSE で真偽値を扱う
use Cairo;
use Math::Trig qw/pip2/;    # π/2 のラジアン値を使う

my $window_width     = 605;               # ウィンドウの幅
my $window_height    = 693;               # ウィンドウの高さ
my $surface_width    = 600;               # テキスト表示部の幅
my $surface_height   = 661;               # テキスト表示部の高さ
my $font_and_size    = 'IPA明朝 11';      # フォント名とサイズ
my $background_color = '#e0ffff';         # 背景色
my $line_spacing     = 19400;             # 行間隔(1/1024ポイント単位)
my $fold_length      = 42;                # 文庫本 1ページの一般的な一行の文字数
my $page_lines       = 18;                # 文庫本 1ページの一般的な行数
my $shiori_file      = 'pochitate_shiori.txt'; # 栞ファイルの場所

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

# メニューバーを作る為の下準備 
my $entries = [
    [ 'FileMenu', undef, 'ファイル(_F)' ],
    [ 'Open', 'gtk-open', '開く(_O)...', '<ctrl>O', 'ファイルを開く',
        \&text_file_open],
    [ 'Save', 'gtk-save', '栞を挟む(_S)', '<ctrl>S', '栞を挟む', \&shiori ],
    [ 'Quit', 'gtk-quit', '終了(_Q)', '<ctrl>Q', '終了する',
        sub { Gtk2->main_quit; } ],
];
my $menu_info = <<'EOS';
<ui>
    <menubar name='MenuBar'>
        <menu action='FileMenu'> 
            <menuitem action='Open' position='top'/>
            <menuitem action='Save'/>
            <separator/>
            <menuitem action='Quit'/>
        </menu>
    </menubar>
</ui>
EOS

# GTK+ のバージョンを確認
die "This viewer requires GTK+ 2.4.0, but we're compiled for "
    . join '.', Gtk2->GET_VERSION_INFO. "\n"
        unless Gtk2->CHECK_VERSION(2, 4, 0);

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

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

# メインウィンドウの準備
my $window = Gtk2::Window->new('toplevel');
$window->signal_connect('delete_event' => sub { Gtk2->main_quit; });
$window->set_title('ぽちたて 20091127');
$window->set_default_size($window_width, $window_height);

# 垂直ボックスとその第一段の中に水平ボックスを配置
my $vbox = Gtk2::VBox->new(FALSE, 2);
my $hbox = Gtk2::HBox->new(FALSE, 0);
$vbox->pack_start($hbox, FALSE, FALSE, 0);

# メニューバーの作成
my $ui = Gtk2::UIManager->new;
my $accelgroup = $ui->get_accel_group;
$window->add_accel_group($accelgroup);
my $actions = Gtk2::ActionGroup->new('actions');
$actions->add_actions ($entries, undef);
$ui->insert_action_group($actions, 0);
$ui->add_ui_from_string ($menu_info);
my $menubar = $ui->get_widget('/MenuBar');

# ページ移動ボタンの作成
my $button_next = Gtk2::Button->new;
my $icon_next = Gtk2::Image->new_from_stock('gtk-go-back', 'menu');
$button_next->set_image($icon_next);
$button_next->signal_connect('clicked' => \&next_ward);
my $button_prev = Gtk2::Button->new;
my $icon_prev = Gtk2::Image->new_from_stock('gtk-go-forward', 'menu');
$button_prev->set_image($icon_prev);
$button_prev->signal_connect('clicked' => \&prev_ward);

# ページ数表示部の作成
my $page_count = Gtk2::Label->new("- $page -");

# 垂直ボックス第一段の水平ボックスへ各ウィジェットの配置
$hbox->pack_start($menubar, FALSE, FALSE, 0);
$hbox->pack_start($button_next, FALSE, FALSE, 0);
$hbox->pack_start($button_prev, FALSE, FALSE, 0);
$hbox->pack_start($page_count, TRUE, TRUE, 0);

# スクロール付きウィンドウを作成して垂直ボックスの第二段に配置
my $scrolled_window = Gtk2::ScrolledWindow->new(undef, undef);
$scrolled_window->set_policy('automatic', 'automatic');
$vbox->pack_start($scrolled_window, TRUE, TRUE, 0);

open my $fh, '<', \$strs or die "Couldn't open virtual file for reading: $!\n";
seek $fh, $offsets[0], 0;

&next_tategaki(\@offsets, \$page);

$window->add($vbox);
$window->show_all;

Gtk2->main;

sub text_file_open {
    my $dialog = Gtk2::FileChooserDialog->new(
                                              'ファイルを開く',
                                              undef,
                                              'open',
                                              'gtk-cancel' => 'cancel',
                                              'gtk-ok' => 'ok',
                                              );
    $dialog->show_all;

    my $response = $dialog->run;
    if ($response eq 'ok') {
        my $filename = $dialog->get_filename;
        # 得たファイル名のテキストファイルを読み込む。ここに色々書く予定。
    }
    $dialog->destroy;
    undef;
}

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

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

# 縦書き描画
sub write_tategaki {
    # 既にテキストを表示していれば一旦クリア
    my $current_object = $scrolled_window->child;
    $current_object->destroy if $current_object;

    # cairo を使った描画
    my $surface = Cairo::ImageSurface->create('argb32',
                                              $surface_width, $surface_height);

    my $cr = Cairo::Context->create($surface);
    # $cr->set_source_rgb(0,0,1); # 青
    $cr->translate($surface_width, 0);
    $cr->rotate(pip2);
    
    my $layout = Gtk2::Pango::Cairo::create_layout($cr);
    $layout->set_spacing($line_spacing);
    my $context = $layout->get_context;
    $context->set_base_gravity('east');
    my $desc = Gtk2::Pango::FontDescription->from_string($font_and_size);
    $layout->set_font_description($desc);
    my $language = Gtk2::Pango::Language->from_string('ja');
    $context->set_language($language);

    if ($strs){
        my $text;
        my $lines;
        while(<$fh>){
            $text .= $_;
            $lines++;
            last if ($lines == $page_lines);
        }
        $text = decode_utf8($text);
        $layout->set_text($text);
    }
    Gtk2::Pango::Cairo::show_layout($cr, $layout);

    my $drawable = Gtk2::DrawingArea->new;
    $drawable->size($surface_width, $surface_height);
    $drawable->signal_connect(
                              'expose_event' => \&set_surface,
                              $surface,
                              );
    $scrolled_window->add_with_viewport($drawable);
    $drawable->show;
    undef;
}

sub set_surface {
    my ($widget, $event, $surface) = @_;
    my $bg_cr = my $cr = Gtk2::Gdk::Cairo::Context->create($widget->window);

    my $color = Gtk2::Gdk::Color->parse($background_color);
    $bg_cr->set_source_color($color);
    $bg_cr->paint;

    $cr->set_source_surface($surface, 0, 0);
    $cr->paint;
    undef;
}

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

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

sub prev_ward{
    &prev_tategaki(\@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.

Nov 11, 2009

Gtk2-Perl で Pango を使った縦書きビューワーを作ろう(2)

早速行き詰まってしまいました。

問題になってるのは、縦書き表示部分のページ移動による再描画です。ぽちには再描画する前にどうやって以前の表示をクリアしたら良いのかが良くわかりません。これは GTK+2 での Pangocairo の描画の仕組みを詳しく理解出来てないせいなのですけども、Gtk2-Perl版ぽちたては、Perl/Tk版ぽちたてのように簡単に次ページには行けないようです。とほほ[犬]ω;)

ちゃんと動かないのですが、仕方なく途中経過の断片コードを載せておきます。

ぽちたて 20091110

#!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Encode;
use Encode::Guess qw/euc-jp shiftjis iso-2022-jp/;        # 日本語文字コード判別
use Lingua::JA::Fold qw/fold/;                            # 日本語禁則折り返し
use open IN  => ":bytes";   # 入力される文字コードが混在している可能性があるので
use Gtk2 qw/-init/;
use Glib qw/TRUE FALSE/;    # TRUE, FALSE で真偽値を扱う
use Cairo;
use Math::Trig qw/pip2/;    # π/2 のラジアン値を使う

my $window_width     = 605;               # ウィンドウの幅
my $window_height    = 693;               # ウィンドウの高さ
my $surface_width    = 600;               # テキスト表示部の幅
my $surface_height   = 661;               # テキスト表示部の高さ
my $font_and_size    = 'IPA明朝 11';      # フォント名とサイズ
my $background_color = '#e0ffff';         # 背景色
my $line_spacing     = 19400;             # 行間隔(1/1024ポイント単位)
my $fold_length      = 42;                # 文庫本 1ページの一般的な一行の文字数
my $page_lines       = 18;                # 文庫本 1ページの一般的な行数

my @offsets = (0);        # 既読ページのテキストデータ読み出し後のオフセット配列
my $page = 1;             # 現在表示しているページ
my $filename;             # 読み込むテキストファイル名

# メニューバーを作る為の下準備 
my $entries = [
    [ 'FileMenu', undef, 'ファイル(_F)' ],
    [ 'Open', 'gtk-open', '開く(_O)...', '<ctrl>O', 'ファイルを開く',
        \&text_file_open],
    [ 'Save', 'gtk-save', '栞を挟む(_S)', '<ctrl>S', '栞を挟む', \&shiori ],
    [ 'Quit', 'gtk-quit', '終了(_Q)', '<ctrl>Q', '終了する',
        sub { Gtk2->main_quit; } ],
];
my $menu_info = <<'EOS';
<ui>
    <menubar name='MenuBar'>
        <menu action='FileMenu'> 
            <menuitem action='Open' position='top'/>
            <menuitem action='Save'/>
            <separator/>
            <menuitem action='Quit'/>
        </menu>
    </menubar>
</ui>
EOS

# GTK+ のバージョンを確認
die "This viewer requires GTK+ 2.4.0, but we're compiled for "
    . join '.', Gtk2->GET_VERSION_INFO. "\n"
        unless Gtk2->CHECK_VERSION(2, 4, 0);

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

# メインウィンドウの準備
my $window = Gtk2::Window->new('toplevel');
$window->signal_connect('delete_event' => sub { Gtk2->main_quit; });
$window->set_title('ぽちたて 20091110');
$window->set_default_size($window_width, $window_height);

# 垂直ボックスとその第一段の中に水平ボックスを配置
my $vbox = Gtk2::VBox->new(FALSE, 2);
my $hbox = Gtk2::HBox->new(FALSE, 0);
$vbox->pack_start($hbox, FALSE, FALSE, 0);

# メニューバーの作成
my $ui = Gtk2::UIManager->new;
my $accelgroup = $ui->get_accel_group;
$window->add_accel_group($accelgroup);
my $actions = Gtk2::ActionGroup->new('actions');
$actions->add_actions ($entries, undef);
$ui->insert_action_group($actions, 0);
$ui->add_ui_from_string ($menu_info);
my $menubar = $ui->get_widget('/MenuBar');

# ページ移動ボタンの作成
my $button_next = Gtk2::Button->new;
my $icon_next = Gtk2::Image->new_from_stock('gtk-go-back', 'menu');
$button_next->set_image($icon_next);
$button_next->signal_connect('clicked' => \&next_ward);
my $button_prev = Gtk2::Button->new;
my $icon_prev = Gtk2::Image->new_from_stock('gtk-go-forward', 'menu');
$button_prev->set_image($icon_prev);
$button_prev->signal_connect('clicked' => sub { Gtk2->main_quit; });

# ページ数表示部の作成
my $page_count = Gtk2::Label->new("- $page -");

# 垂直ボックス第一段の水平ボックスへ各ウィジェットの配置
$hbox->pack_start($menubar, FALSE, FALSE, 0);
$hbox->pack_start($button_next, FALSE, FALSE, 0);
$hbox->pack_start($button_prev, FALSE, FALSE, 0);
$hbox->pack_start($page_count, TRUE, TRUE, 0);

# スクロール付きウィンドウを作成して垂直ボックスの第二段に配置
my $scrolled_window = Gtk2::ScrolledWindow->new(undef, undef);
$scrolled_window->set_policy('automatic', 'automatic');
$vbox->pack_start($scrolled_window, TRUE, TRUE, 0);

open my $fh, '<', \$strs or die "Couldn't open virtual file for reading: $!\n";
seek $fh, $offsets[0], 0;

&next_tategaki(\@offsets, \$page);

$window->add($vbox);
$window->show_all;

Gtk2->main;

sub text_file_open {
    my $dialog = Gtk2::FileChooserDialog->new(
                                              'ファイルを開く',
                                              undef,
                                              'open',
                                              'gtk-cancel' => 'cancel',
                                              'gtk-ok' => 'ok',
                                              );
    $dialog->show_all;

    my $response = $dialog->run;
    if ($response eq 'ok') {
        my $filename = $dialog->get_filename;
        # 得たファイル名のテキストファイルを読み込む。ここに色々書く予定。
    }
    $dialog->destroy;
}

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

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

# 縦書き描画
# 次ページや前ページへ移動する時に、
# 現在表示しているページをクリアする良い方法がないかなあ?
sub write_tategaki {
    # cairo を使った描画
    my $surface = Cairo::ImageSurface->create('argb32',
                                              $surface_width, $surface_height);

    my $cr = Cairo::Context->create($surface);
# フォントカラーをここで無理矢理指定出来るけど、0-1 の範囲のRGB表記という…。
# color name や hex code からの変換用関数が用意されてないか探してみようっと。
# 無いようなら自分で作る事!
#    $cr->set_source_rgb(0,0,1); # 青
    $cr->translate($surface_width, 0);
    $cr->rotate(pip2);
    
    my $layout = Gtk2::Pango::Cairo::create_layout($cr);
    $layout->set_spacing($line_spacing);
    my $context = $layout->get_context;
    $context->set_base_gravity('east');
    my $desc = Gtk2::Pango::FontDescription->from_string($font_and_size);
    $layout->set_font_description($desc);
    my $language = Gtk2::Pango::Language->from_string('ja');
    $context->set_language($language);

    if ($strs){
        my $text;
        my $lines;
        while(<$fh>){
            $text .= $_;
            $lines++;
            last if ($lines == $page_lines);
        }
        $text = decode_utf8($text);
        $layout->set_text($text);
    }
    Gtk2::Pango::Cairo::show_layout($cr, $layout);

    my $drawable = Gtk2::DrawingArea->new;
    $drawable->size($surface_width, $surface_height);
    $drawable->signal_connect(
                              'expose_event' => \&set_surface,
                              $surface,
                              );
    $scrolled_window->add_with_viewport($drawable);

}

sub set_surface {
    my ($widget, $event, $surface) = @_;
    my $bg_cr = my $cr = Gtk2::Gdk::Cairo::Context->create($widget->window);

    my $color = Gtk2::Gdk::Color->parse($background_color);
    $bg_cr->set_source_color($color);
    $bg_cr->paint;

    $cr->set_source_surface($surface, 0, 0);
    $cr->paint;
    undef;
}

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

Nov 09, 2009

Pango 1.26 の縦書きは良いみたい

TrueTypeフォントについて調べてた頃に、2ch の Linux板にある「Linux上でのフォント総合スレ」というスレを覗いてみた事があるのですけども、それ以来定期的にそのスレをチェックするようになりました。さっきかなり久しぶりに眺めてみると、Pango 1.26 では句読点が中心線揃えの変な表示位置から正しい表示位置になっているというお話。しかも、次期ぽちたてに向けたぽちんちの断片コードまで紹介してくれているではありませんか!さらに挙句の果てには、ぽち断片を使って最新リリースの Pango 1.26 での次期ぽちたて表示を試してみてくれた方までいらっしゃいました。

Pango 1.26 でのぽちたて 20091103表示画面サムネイル

うんうん、debian lenny で Pango 1.20 のぽち環境とは違って、充分実用に耐える品質で縦書きされています。次期ぽちたては、Pango の縦書きを使う為ダケGtk2-Perl版にしたので、この表示結果にはぽちもひと安心というかホッとしたというか、なんだか言葉では言い尽くせない思いです。

「Linux上でのフォント総合スレ 4」の皆様、本当にありがとうございます。お蔭様で、がんばって早く縦書きビューワーとしてひと通りの機能を実装しなきゃ!と、急に元気が湧いて来ました。

Nov 07, 2009

Gtk2-Perl で Pango を使った縦書きビューワーを作ろう(1)

前回の日記では次期ぽちたての縦書き表示部分の画像だけしか置いてなかったのですけども、今回からはちゃんと Perl のコードも書いておくことにします。

ぽちたて 20091103表示画面サムネイル

GTK+2 にならとっても分かり易い入門書まで出てたりしてますが、Gtk2-Perl の日本語文書は少ないので、こんないいかげんな断片でも誰かの助けになればと思って載せています。けっして「ぽちたて作りにちっとも進展がないから、とりあえずこないだのコードを載せちゃえ!」って事じゃないんですからねっ?誰ですか?一番役に立つのは結局本家の Gtk2-Perl - Table of Contents だって言ってるのは!(その通りですけども)

当然ではありますけども、Gtk2-Perl版ぽちたてを動かすには Perl や useされる Perlモジュール群だけでなく、事前に GTK+2 や Pangocairo のライブラリがインストールされていなければなりません。debian lenny では libgtk2-perlパッケージをインストールすると、依存関係で必要なライブラリが一緒にインストールされるのでらくちんです。

ぽちたて 20091103

#!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Encode;
use Encode::Guess qw/euc-jp shiftjis iso-2022-jp/;        # 日本語文字コード判別
use Lingua::JA::Fold qw/fold/;                            # 日本語禁則折り返し
use open IN  => ":bytes";   # 入力される文字コードが混在している可能性があるので
use Gtk2 qw/-init/;
use Glib qw/TRUE FALSE/;    # TRUE, FALSE で真偽値を扱う
use Cairo;
use Math::Trig qw/pip2/;    # π/2 のラジアン値を使う

my $window_width     = 605;               # ウィンドウの幅
my $window_height    = 693;               # ウィンドウの高さ
my $surface_width    = 600;               # テキスト表示部の幅
my $surface_height   = 661;               # テキスト表示部の高さ
my $font_and_size    = 'IPA明朝 11';      # フォント名とサイズ
my $background_color = '#e0ffff';         # 背景色
my $line_spacing     = 19400;             # 行間隔(1/1024ポイント単位)
my $fold_length      = 42;                # 文庫本 1ページの一般的な一行の文字数
my $page_lines       = 18;                # 文庫本 1ページの一般的な行数
my $shiori_file      = 'pochitate_shiori.txt'; # 栞ファイルの場所

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

# メニューバーを作る為の下準備 
my $entries = [
    [ 'FileMenu', undef, 'ファイル(_F)' ],
    [ 'Open', 'gtk-open', '開く(_O)...', '<ctrl>O', 'ファイルを開く',
        sub { Gtk2->main_quit; } ],
    [ 'Save', 'gtk-save', '栞を挟む(_S)', '<ctrl>S', '栞を挟む',
        sub { Gtk2->main_quit; } ],
    [ 'Quit', 'gtk-quit', '終了(_Q)', '<ctrl>Q', '終了する',
        sub { Gtk2->main_quit; } ],
];
my $menu_info = <<'EOS';
<ui>
    <menubar name='MenuBar'>
        <menu action='FileMenu'> 
            <menuitem action='Open' position='top'/>
            <menuitem action='Save'/>
            <separator/>
            <menuitem action='Quit'/>
        </menu>
    </menubar>
</ui>
EOS

# GTK+ のバージョンを確認
die "This viewer requires GTK+ 2.4.0, but we're compiled for "
    . join '.', Gtk2->GET_VERSION_INFO. "\n"
        unless Gtk2->CHECK_VERSION(2, 4, 0);

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

# メインウィンドウの準備
my $window = Gtk2::Window->new('toplevel');
$window->signal_connect('delete_event' => sub { Gtk2->main_quit; });
$window->set_title('ぽちたて 20091103');
$window->set_default_size($window_width, $window_height);

# 垂直ボックスとその第一段の中に水平ボックスを配置
my $vbox = Gtk2::VBox->new(FALSE, 2);
my $hbox = Gtk2::HBox->new(FALSE, 0);
$vbox->pack_start($hbox, FALSE, FALSE, 0);

# メニューバーの作成
my $ui = Gtk2::UIManager->new;
my $accelgroup = $ui->get_accel_group;
$window->add_accel_group($accelgroup);
my $actions = Gtk2::ActionGroup->new('actions');
$actions->add_actions ($entries, undef);
$ui->insert_action_group($actions, 0);
$ui->add_ui_from_string ($menu_info);
my $menubar = $ui->get_widget('/MenuBar');

# ページ移動ボタンの作成
my $button_next = Gtk2::Button->new;
my $icon_next = Gtk2::Image->new_from_stock('gtk-go-back', 'menu');
$button_next->set_image($icon_next);
$button_next->signal_connect('clicked' => sub {Gtk2->main_quit;} );
my $button_prev = Gtk2::Button->new;
my $icon_prev = Gtk2::Image->new_from_stock('gtk-go-forward', 'menu');
$button_prev->set_image($icon_prev);
$button_prev->signal_connect('clicked' => sub {Gtk2->main_quit;} );

# ページ数表示部の作成
my $page_count = Gtk2::Label->new("- $page -");

# 垂直ボックス第一段の水平ボックスへ各ウィジェットの配置
$hbox->pack_start($menubar, FALSE, FALSE, 0);
$hbox->pack_start($button_next, FALSE, FALSE, 0);
$hbox->pack_start($button_prev, FALSE, FALSE, 0);
$hbox->pack_start($page_count, TRUE, TRUE, 0);

# スクロール付きウィンドウを作成して垂直ボックスの第二段に配置
my $scrolled_window = Gtk2::ScrolledWindow->new(undef, undef);
$scrolled_window->set_policy('automatic', 'automatic');
$vbox->pack_start($scrolled_window, TRUE, TRUE, 0);

open my $fh, '<', \$strs or die "Couldn't open virtual file for reading: $!\n";
    seek $fh, $offsets[0], 0;
&write_tategaki;

$window->add($vbox);
$window->show_all;

Gtk2->main;

sub text_file_open {
    my $dialog = Gtk2::FileChooserDialog->new(
                                              'ファイルを開く',
                                              undef,
                                              'open',
                                              'gtk-cancel' => 'cancel',
                                              'gtk-ok' => 'ok',
                                              );
    $dialog->show_all;

    my $response = $dialog->run;
    if ($response eq 'ok') {
        my $filename = $dialog->get_filename;
        # 得たファイル名を縦書き描画サブルーチンへ渡して実行する予定地
    }
    $dialog->destroy;
}

# 縦書き描画
sub write_tategaki {
    # cairo を使った描画
    my $surface = Cairo::ImageSurface->create('argb32',
                                              $surface_width, $surface_height);

    my $cr = Cairo::Context->create($surface);
# フォントカラーをここで無理矢理指定出来るけど、0-1 の範囲の RGB表記という…。
# color name や hex からの変換用関数が用意されてないか探してみようっと。
#    $cr->set_source_rgb(0,0,1); # 青
    $cr->translate($surface_width, 0);
    $cr->rotate(pip2);
    
    my $layout = Gtk2::Pango::Cairo::create_layout($cr);
    $layout->set_spacing($line_spacing);
    my $context = $layout->get_context;
    $context->set_base_gravity('east');
    my $desc = Gtk2::Pango::FontDescription->from_string($font_and_size);
    $layout->set_font_description($desc);
    my $language = Gtk2::Pango::Language->from_string('ja');
    $context->set_language($language);

    if ($strs) {
        my $text;
        $text .= $_ while(<$fh>);
        $text = decode_utf8($text);
        $layout->set_text($text);
    }
    Gtk2::Pango::Cairo::show_layout($cr, $layout);

    my $drawable = Gtk2::DrawingArea->new;
    $drawable->size($surface_width, $surface_height);
    $drawable->signal_connect(
                              'expose_event' => \&set_surface,
                              $surface,
                              );
    $scrolled_window->add_with_viewport($drawable);

}

sub set_surface {
    my ($widget, $event, $surface) = @_;
    my $bg_cr = my $cr = Gtk2::Gdk::Cairo::Context->create($widget->window);

    my $color = Gtk2::Gdk::Color->parse($background_color);
    $bg_cr->set_source_color($color);
    $bg_cr->paint;

    $cr->set_source_surface($surface, 0, 0);
    $cr->paint;
    undef;
}

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

Nov 02, 2009

ぽちたて次期バージョンへのみちのり

ぽちたて 0.1.X表示画面サムネイル

Pango を使った縦書きはこんな残念な感じですけれども、Gtk2-Perlcairo-perl を扱う勉強も兼ねて最初のページを表示するだけのものを即席で作ってみました。後は以前のバージョンで実装した機能をこっちに持って来ればなんとかなりそうかも?メニューバーにページ移動ボタンだけでなく「ファイル(F)」なんて部分があるのは、ダイアログ画面でテキストファイルを指定して読み込む機能も付ける予定だからだったりします。

早くそれなりに恰好を付けて、ぽちたて 0.1.0 として公開出来るレベルになるといいなあ…。

Page 1 of 2: 1 2