#                                                         -*- Perl -*-
# Copyright (c) 1999, 2000, 2001  Motoyuki Kasahara
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#

#
# テキストを収めたファイルを生成するクラス (FreePWING::Text, 
# FreePWING::Heading) のための基底仮想クラス。
#
package FreePWING::BaseText;

require 5.005;
require Exporter;
use English;
use FileHandle;
use FreePWING::Reference;
use FreePWING::Tag;
use FreePWING::RefUserChar;
use FreePWING::RefSound;
use FreePWING::CharConv;
use strict;
use integer;

use vars qw(@ISA
	    @EXPORT
	    @EXPORT_OK
	    $block_length
	    $max_indent_level);

@ISA = qw(Exporter);

#
# ブロックの長さ (バイト数)
#
$block_length = 2048;

#
# 最大字下げレベル
#
$max_indent_level = 6;

#
# 最大バッファサイズ
#
my $max_buffer_size = 64 * 1024;

#
# 書式:
#	new()
# メソッドの区分:
# 	public クラスメソッド。
# 説明:
# 	新しいオブジェクトを作る。
# 戻り値:
# 	作成したオブジェクトへのリファレンスを返す。
#
sub new {
    my $type = shift;
    my $new = {
	# 本文ファイルのハンドラ
	'handle' => FileHandle->new(),

	# 本文ファイル名
	'file_name' => '',

	# 出力バッファ
	'buffer' => '',

	# コンテキスト数
	'context_count' => 0,

	# new_context() を無視するかどうか
	'new_context_skip_flag' => 1,
	
	# 現在のコンテキストに対して書き込んだエントリ数
	'entry_count' => 0,

	# これまでに書き込んだエントリ数
	'total_entry_count' => 0,

	# new_entry() を無視するかどうか
	'new_entry_skip_flag' => 1,
	
	# 現在のファイルのオフセット (これまでに書き込んだバイト数)
	'position' => 0,

	# 現在のコンテキストの開始位置のオフセット
	'context_position' => 0,

	# 現在のエントリの開始位置のオフセット
	'entry_position' => 0,

	# 参照情報
	'reference' => FreePWING::Reference->new(),

	# タグ
	'tag' => FreePWING::Tag->new(),

	# 現在のエントリの修飾制御記述子の入れ子状態
	'modifier_stack' => [],

	# 半角外字の定義情報
	'half_user_characters' => FreePWING::RefUserChar->new(),

	# 全角外字の定義情報
	'full_user_characters' => FreePWING::RefUserChar->new(),

	# 音声形式情報 (satomii)
	'sounds' => FreePWING::RefSound->new(),

	# エラーメッセージ
	'error_message' => '',
    };
    return bless($new, $type);
}

#
# 書式:
#	open(file_name, [reference_file_name, [tag_file_name]])
#           file_name
#		テキストファイルの名前。
#           reference_file_name
#		参照情報ファイルの名前。
#           tag_file_name
#		タグファイルの名前。
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	書き込み用のテキストファイルを開く。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub open {
    my $self = shift;
    my ($file_name, $reference_file_name, $tag_file_name) = @ARG;

    #
    # テキストファイルを開く。
    #
    $self->{'file_name'} = $file_name;
    if (!$self->{'handle'}->open($self->{'file_name'}, 'w')) {
	$self->{'error_message'} =
	    "failed to open the file, $ERRNO: " . $self->{'file_name'};
	$self->close_internal();
	return 0;
    }
    binmode($self->{'handle'});

    #
    # 参照情報ファイルを開く。
    #
    if (defined($reference_file_name)
	&& !$self->{'reference'}->open($reference_file_name)) {
	$self->{'error_message'} = $self->{'reference'}->error_message();
	$self->close_internal();
        return 0;
    }

    #
    # タグファイルを開く。
    #
    if (defined($tag_file_name)
	&& !$self->{'tag'}->open($tag_file_name)) {
	$self->{'error_message'} = $self->{'tag'}->error_message();
	$self->close_internal();
        return 0;
    }

    #
    # あらかじめ新しいコンテキストを開始しておく。
    #
    if (!$self->new_context_internal()) {
    	return 0;
    }

    return 1;
}

#
# 書式:
#	close()
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	オブジェクトが開いているテキストファイル群を閉じる。
#	テキストファイルを開いていない場合は、何もしない。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub close {
    my $self = shift;

    if (!$self->{'handle'}->fileno()) {
	return 1;
    }

    #
    # 直前のエントリとコンテキストを終端する。
    #
    if (!$self->add_entry_end()) {
	return 0;
    }
    if (!$self->add_context_end()) {
	return 0;
    }

    #
    # 半端なブロックの後方を "\0" で埋める。
    #
    my $pad_length = $block_length - $self->{'handle'}->tell() % $block_length;
    if ($pad_length < $block_length
	&& !$self->write_data("\0" x $pad_length)) {
	return 0;
    }
	

    #
    # バッファの内容をファイルに書きだす。
    #
    if (!$self->flush_buffer()) {
	return 0;
    }

    #
    # ファイルを閉じる。
    #
    $self->close_internal();

    return 1;
}

#
# 書式:
#	close_internal()
# メソッドの区分:
# 	private インスタンスメソッド。
# 説明:
#       close() の内部処理用メソッド。
#
sub close_internal {
    my $self = shift;

    if ($self->{'handle'}->fileno()) {
	$self->{'handle'}->close();
    }
    $self->{'reference'}->close_internal();
    $self->{'tag'}->close_internal();
}

#
# 書式:
#	set_half_user_characters_in_file(file_name)
#           file_name
#		全角外字定義ファイルの名前。
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	半角外字の定義ファイルを読み込む。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub set_half_user_characters_in_file {
    my $self = shift;
    my ($file_name) = @ARG;

    if (!$self->{'half_user_characters'}
	->add_characters_in_file($file_name)) {
	$self->{'error_message'} =
	    $self->{'half_user_characters'}->error_message();
	$self->close_internal();
        return 0;
    }

    return 1;
}

#
# 書式:
#	set_full_user_characters_in_file(file_name)
#           file_name
#		全角外字定義ファイルの名前。
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	半角外字の定義ファイルを読み込む。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub set_full_user_characters_in_file {
    my $self = shift;
    my ($file_name) = @ARG;

    if (!$self->{'full_user_characters'}
	->add_characters_in_file($file_name)) {
	$self->{'error_message'} =
	    $self->{'full_user_characters'}->error_message();
	$self->close_internal();
        return 0;
    }

    return 1;
}

#
# 書式:
#	set_sounds_in_file(file_name)
#           file_name
#		音声形式ファイルの名前。
# メソッドの区分:
#	public インスタンスメソッド。
# 説明:
#	音声形式ファイルを読み込む。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
# (satomii)
#
sub set_sounds_in_file {
    my $self = shift;
    my ($file_name) = @ARG;

    if (!$self->{'sounds'}->add_sounds_in_file($file_name)) {
	$self->{'error_message'} = $self->{'sounds'}->error_message();
	$self->close_internal();
	return 0;
    }
    return 1;
}

#
# 書式:
#	write_data(data)
#	    data
#		書き込むデータ (文字列)。
# メソッドの区分:
# 	private インスタンスメソッド。
# 説明:
# 	与えられたデータを実際にファイルに書き込む。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub write_data {
    my $self = shift;
    my ($data) = @ARG;

    $self->{'buffer'} .= $data;

    if ((length($self->{'buffer'}) > $max_buffer_size)
	&& !$self->flush_buffer()) {
      return 0;
    }

    $self->{'position'} += length($data);
    return 1;
}

#
# 書式:
#	flush_buffer()
# メソッドの区分:
# 	private インスタンスメソッド。
# 説明:
# 	バッファの内容をファイルに書き込み、バッファをを空にします。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub flush_buffer {
    my $self = shift;

    if (!length($self->{'buffer'})) {
      return 1;
    }

    if (!$self->{'handle'}->print($self->{'buffer'})) {
	$self->{'error_message'} =
	    "failed to write the file, $ERRNO: " . $self->{'file_name'};
	$self->{'buffer'} = '';
	$self->close_internal();
	return 0;
    }

    $self->{'buffer'} = '';
    return 1;
}

#
# 書式:
#	new_context()
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	新たなコンテキストの開始を指示する。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub new_context {
    my $self = shift;
    
    #
    # 最初のコンテキストに対して、明示的に new_context() を呼んだ
    # ときはすぐに返る。
    #
    if (!$self->{'new_entry_skip_flag'}) {
	$self->{'new_context_skip_flag'} = 0;
    }
    if ($self->{'new_context_skip_flag'}) {
	$self->{'new_context_skip_flag'} = 0;
	return 1;
    }
    if (!$self->new_context_internal()) {
	return 0;
    }
    return 1;
}

#
# 書式:
#	new_context_internal()
# メソッドの区分:
# 	private インスタンスメソッド。
# 説明:
# 	新たなエントリの開始を指示する。
# 	new_context() の実体。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub new_context_internal {
    my $self = shift;

    #
    # 直前のエントリとコンテキストの終端記号を書き込む。
    #
    if (0 < $self->{'entry_count'} && !$self->add_entry_end()) {
	return 0;
    }
    if (0 < $self->{'context_count'} && !$self->add_context_end()) {
	return 0;
    }

    $self->{'entry_count'} = 0;
    $self->{'new_entry_count'} = 0;
    $self->{'new_entry_skip_flag'} = 1;
    $self->{'context_count'}++;

    #
    # 新たなコンテキストの開始記号を書き込み、あらかじめ新しいエントリ
    # を開始しておく。
    #
    if (!$self->add_context_start()) {
	return 0;
    }
    $self->{'context_position'} = $self->{'position'};
    if (!$self->new_entry_internal()) {
    	return 0;
    }

    return 1;
}

#
# 書式:
#	add_context_start()
# メソッドの区分:
# 	protected インスタンスメソッド。
# 説明:
# 	新たなコンテキストの開始を示す制御記述子を追加する。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub add_context_start {
    my $self = shift;

    if (!$self->write_data(pack('n', 0x1f02))) {
	return 0;
    }
    return 1;
}

#
# 書式:
#	add_context_end()
# メソッドの区分:
# 	protected インスタンスメソッド。
# 説明:
# 	現在のコンテキストの終端を示す制御記述子を追加する。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub add_context_end {
    my $self = shift;
    
    if ($self->{'modifier_stack'}->[-1] eq 'half-width'
	&& !$self->add_half_width_end()) {
	return 0;
    }
    if (!$self->write_data(pack('n', 0x1f03))) {
	return 0;
    }
    return 1;
}

#
# 書式:
#	new_entry()
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	新たなエントリの開始を指示する。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub new_entry {
    my $self = shift;
    
    #
    # 最初のエントリに対して、明示的に new_entry() を呼んだときは
    # すぐに返る。
    #
    if ($self->{'new_entry_skip_flag'}) {
	$self->{'new_entry_skip_flag'} = 0;
	return 1;
    }
    if (!$self->new_entry_internal()) {
	return 0;
    }
    return 1;
}

#
# 書式:
#	new_entry_internal()
# メソッドの区分:
# 	private インスタンスメソッド。
# 説明:
# 	新たなエントリの開始を指示する。
# 	new_entry() の実体。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub new_entry_internal {
    my $self = shift;

    #
    # 直前のエントリの終端記号を書き込む。
    #
    if (0 < $self->{'entry_count'} && !$self->add_entry_end()) {
	return 0;
    }

    $self->{'entry_position'} = $self->{'position'};

    #
    # エントリの開始記号を書き込む。
    #
    if (!$self->add_entry_start()) {
	return 0;
    }

    $self->{'entry_count'}++;
    $self->{'total_entry_count'}++;
    return 1;
}

#
# 書式:
#	add_entry_start()
# メソッドの区分:
# 	protected インスタンスメソッド。
# 説明:
# 	新たなエントリの開始を示す制御記述子を追加する。
#       (継承したクラスで上書きするためのメソッド)
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub add_entry_start {
    return 1;
}

#
# 書式:
#	add_entry_end()
# メソッドの区分:
# 	protected インスタンスメソッド。
# 説明:
# 	現在のエントリの終端を示す制御記述子を追加する。
#       (継承したクラスで上書きするためのメソッド)
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub add_entry_end {
    my $self = shift;
    
    if ($self->{'modifier_stack'}->[-1] eq 'half-width'
	&& !$self->add_half_width_end()) {
	return 0;
    }
    if (0 < @{$self->{'modifier_stack'}}) {
	$self->{'error_message'} =
	    "modifier not teminated before end of entry";
	$self->close_internal();
	return 0;
    }
    return 1;
}

#
# 書式:
#	add_text(text)
#	    text
#		テキストの文字列。
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	与えられたデータをテキストファイルに追加する。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub add_text {
    my $self = shift;
    my ($text) = @ARG;

    #
    # 改行 ("\r", "\n") は無視。
    #
    $text =~ s/[\r\n]//g;
	#
	# 水平タブ ("\t") は、まず空白に変換。
	# 
    $text =~ s/\t/ /g;

    my $word;
    for (;;) {
	if ($text =~ s/^([\x20-\x7f]+)//) {
	    #
	    # US-ASCII は半角扱い。
	    #
	    $word = $1;
	    if ($self->{'modifier_stack'}->[-1] ne 'half-width'
		&& !$self->add_half_width_start()) {
		return 0;
	    }
	    $word =~ s/(.)/$ascii_to_jisx0208_table->[unpack("C",$1)-0x20]/g;
	    if (!$self->write_data($word)) {
		return 0;
	    }
	} elsif ($text =~ s/^(([\xa1-\xfe]{2})+)//) {
	    #
	    # JIS X 0208 文字はそのまま記録。
	    #
	    $word = $1;
	    if ($self->{'modifier_stack'}->[-1] eq 'half-width'
		&& !$self->add_half_width_end()) {
		return 0;
	    }
	    $word =~ tr/\xa1-\xfe/\x21-\x7e/;
	    if (!$self->write_data($word)) {
		return 0;
	    }
	} elsif ($text =~ s/^((\x8e[\xa1-\xfe])+)//) {
	    #
	    # SS2 を用いた JIS X 0201 カナは JIS X 0208 カナに変換。
	    #
	    $word = $1;
	    if ($self->{'modifier_stack'}->[-1] eq 'half-width'
		&& !$self->add_half_width_end()) {
		return 0;
	    }
	    $word =~ s/\x8e(.)/$jisx0201_to_jisx0208_table->[unpack("C",$1)-0xa0]/g;
	    if (!$self->write_data($word)) {
		return 0;
	    }
	} else {
	    if(length($text)) {
	    $self->{'error_message'} =
		    sprintf("invalid character: \\x%02x", unpack("C", $text));
	    $self->close_internal();
	    return 0;
	    } else {
    $self->{'new_entry_skip_flag'} = 0;
    return 1;
	    }
	}
    }
}

#
# 書式:
#	add_half_user_character(character_name)
#	    character_name
#		外字の名前
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	与えられた半角外字をテキストファイルに追加する。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub add_half_user_character {
    my $self = shift;
    my ($character_name) = @ARG;

    #
    # 外字名から文字番号を引く。外字名が登録されていなければエラー。
    #
    my $character_number;
    $character_number
	= $self->{'half_user_characters'}->character($character_name);
    if (!defined($character_number)) {
	$self->{'error_message'} =
	    $self->{'half_user_characters'}->error_message();
	$self->close_internal();
	return 0;
    }

    #
    # 必要に応じて半角開始指定子を書き込む。
    #
    if ($self->{'modifier_stack'}->[-1] ne 'half-width'
	&& !$self->add_half_width_start()) {
	return 0;
    }

    #
    # 外字の文字番号を書き込む。
    #
    if (!$self->write_data(pack('n', $character_number))) {
	return 0;
    }

    return 1;
}

#
# 書式:
#	add_full_user_character(character_name)
#	    character_name
#		外字の名前
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	与えられた全角外字をテキストファイルに追加する。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub add_full_user_character {
    my $self = shift;
    my ($character_name) = @ARG;

    #
    # 外字名から文字番号を引く。外字名が登録されていなければエラー。
    #
    my $character_number;
    $character_number
	= $self->{'full_user_characters'}->character($character_name);
    if (!defined($character_number)) {
	$self->{'error_message'} =
	    $self->{'full_user_characters'}->error_message();
	$self->close_internal();
	return 0;
    }

    #
    # 必要に応じて半角終了指定子を書き込む。
    #
    if ($self->{'modifier_stack'}->[-1] eq 'half-width'
	&& !$self->add_half_width_end()) {
	return 0;
    }

    #
    # 外字の文字番号を書き込む。
    #
    if (!$self->write_data(pack('n', $character_number))) {
	return 0;
    }

    return 1;
}

#
# 書式:
#	add_entry_tag(tag)
#	    tag
#		タグ名
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	現在のエントリの開始位置にタグを追加する。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub add_entry_tag {
    my $self = shift;
    my ($tag) = @ARG;
 
    $tag =~ s/^/text:/;
    return $self->{'tag'}->add_entry($tag, $self->{'entry_position'});
}

#
# 書式:
#	add_tag(tag)
#	    tag
#		タグ名
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	現在のテキスト位置にタグを追加する。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub add_tag {
    my $self = shift;
    my ($tag) = @ARG;
    
    $tag =~ s/^/text:/;
    return $self->{'tag'}->add_entry($tag, $self->{'position'});
}

######################################################################
# <制御記述子を追加するメソッド群>
#
# 書式:
#	メソッド名(argument)
#	    argument
#		制御記述子の引数。詳しくはメソッド毎に説明。とくに
#		記載がないメソッドは引数を取らない。
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	制御記述子をテキストに追加する。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#

#
# すべての制御開始指定子に共通の処理
#
sub add_modifier_start {
    my $self = shift;
    my ($modifier, $data) = @ARG;

    if ($modifier ne 'half-width'
	&& $self->{'modifier_stack'}->[-1] eq 'half-width'
	&& !$self->add_half_width_end()) {
	return 0;
    }
    if (grep($_ eq $modifier, @{$self->{'modifier_stack'}}) != 0) {
	$self->{'error_message'} = "duplicate $modifier modifier";
	$self->close_internal();
	return 0;
    }
    if (!$self->write_data($data)) {
	return 0;
    }

    $self->{'new_entry_skip_flag'} = 0;
    push(@{$self->{'modifier_stack'}}, $modifier);

    return 1;
}

#
# すべての制御終了指定子に共通の処理
#
sub add_modifier_end {
    my $self = shift;
    my ($modifier, $data) = @ARG;

    if ($modifier ne 'half-width'
	&& $self->{'modifier_stack'}->[-1] eq 'half-width'
	&& !$self->add_half_width_end()) {
	return 0;
    }
    if ($self->{'modifier_stack'}->[-1] ne $modifier) {
	$self->{'error_message'} = "unexpected the end of $modifier modifier";
	$self->close_internal();
	return 0;
    }
    if (!$self->write_data($data)) {
	return 0;
    }

    $self->{'new_entry_skip_flag'} = 0;
    pop(@{$self->{'modifier_stack'}});

    return 1;
}

#
# 検索キー
#
sub add_keyword_start {
    my $self = shift;

    if (!$self->add_modifier_start('keyword', pack('nn', 0x1f41, 0x0100))) {
	return 0;
    }
    return 1;
}

#
# 検索キー終了
#
sub add_keyword_end {
    my $self = shift;

    if (!$self->add_modifier_end('keyword', pack('n', 0x1f61))) {
	return 0;
    }
    return 1;
}

#
# 別項目参照
#
sub add_reference_start {
    my $self = shift;

    if (!$self->add_modifier_start('reference', pack('n', 0x1f42))) {
	return 0;
    }
    return 1;
}

#
# 項目参照終了
# 参照先の位置を示すタグ名を引数で指定。
#
sub add_reference_end {
    my $self = shift;
    my ($target_position) = @ARG;

    $target_position =~ s/^/text:/;
    if (!$self->add_modifier_end('reference', pack('nNn', 0x1f62, 0, 0))) {
	return 0;
    }
    if (!$self->{'reference'}
	->add_tag_entry($self->{'position'} - 6, $target_position)) {
	$self->{'error_message'} = $self->{'reference'}->error_message();
	$self->close_internal();
	return 0;
    }
    return 1;
}

#
# カラー図版参照 (BMP 版)
# カラー図版名を引数で指定。
#
sub add_color_graphic_start {
    my $self = shift;
    my ($picture_name) = @ARG;

    $picture_name =~ s/^/cgraph:/;
    if (!$self->add_modifier_start('color-graphic',
				   pack('n3N3n', 0x1f4d, 0x0009, 0x0001,
					0, 0, 0, 0))) {
	return 0;
    }
    if (!$self->{'reference'}
	->add_tag_entry($self->{'position'} - 6, $picture_name)) {
	$self->{'error_message'} = $self->{'reference'}->error_message();
	$self->close_internal();
	return 0;
    }
    return 1;
}

#
# カラー図版参照 (JPG 版)
# カラー図版名を引数で指定。
#
sub add_jpeg_graphic_start {
    my $self = shift;
    my ($picture_name) = @ARG;

    $picture_name =~ s/^/cgraph:/;
    if (!$self->add_modifier_start('color-graphic',
				   pack('n3N3n', 0x1f4d, 0x1209, 0x0001,
					0, 0, 0, 0))) {
	return 0;
    }

    if (!$self->{'reference'}
	->add_tag_entry($self->{'position'} - 6, $picture_name)) {
	$self->{'error_message'} = $self->{'reference'}->error_message();
	$self->close_internal();
	return 0;
    }
    return 1;
}

#
# カラー図版参照終了 (BMP 版)
#
sub add_color_graphic_end {
    my $self = shift;

    if (!$self->add_modifier_end('color-graphic', pack('n', 0x1f6d))) {
	return 0;
    }
    return 1;
}

#
# カラー図版参照終了 (JPG 版)
#
sub add_jpeg_graphic_end {
    my $self = shift;

    if (!$self->add_modifier_end('color-graphic', pack('n', 0x1f6d))) {
	return 0;
    }
    return 1;
}

#
# 音声参照
# 音声データの名前を引数で指定。
#
sub add_sound_start {
    my $self = shift;
    my ($sound_name) = @ARG;

    # 音声形式は FreePWING::RefSound 経由で取得する。(satomii)
    if (!$self->add_modifier_start(
	     'sound',
	     pack('nC2nNnNn', 0x1f4a, 0,
		  $self->{'sounds'}->sound_type($sound_name),
		  $self->{'sounds'}->sound_format($sound_name),
		  0, 0, 0, 0))) {
	return 0;
    }

    # if (!$self->add_modifier_start('sound',
    #				   pack('n3N3', 0x1f4a, 0, 0x0012, 0, 0, 0))) {
    #	return 0;
    # }
    if (!$self->{'reference'}
	->add_tag_entry($self->{'position'} - 12, "sound:$sound_name")) {
	$self->{'error_message'} = $self->{'reference'}->error_message();
	$self->close_internal();
	return 0;
    }
    if (!$self->{'reference'}
	->add_tag_entry($self->{'position'} - 6, "sound-end:$sound_name")) {
	$self->{'error_message'} = $self->{'reference'}->error_message();
	$self->close_internal();
	return 0;
    }
    return 1;
}

#
# 音声参照終了
# 参照先の位置を示すタグ名を引数で指定。
#
sub add_sound_end {
    my $self = shift;

    if (!$self->add_modifier_end('sound', pack('n', 0x1f6a))) {
	return 0;
    }
    return 1;
}

#
# 半角開始指定
#
sub add_half_width_start {
    my $self = shift;

    if (!$self->add_modifier_start('half-width', pack('n', 0x1f04))) {
	return 0;
    }
    return 1;
}

#
# 半角終了指定
#
sub add_half_width_end {
    my $self = shift;

    if (!$self->add_modifier_end('half-width', pack('n', 0x1f05))) {
	return 0;
    }
    return 1;
}

#
# 下添え字開始指定
#
sub add_subscript_start {
    my $self = shift;

    if (!$self->add_modifier_start('subscript', pack('n', 0x1f06))) {
	return 0;
    }
    return 1;
}

#
# 下添え字終了指定
#
sub add_subscript_end {
    my $self = shift;

    if (!$self->add_modifier_end('subscript', pack('n', 0x1f07))) {
	return 0;
    }
    return 1;
}

#
# 字下げ指定
# 字下げ量 (2 〜 $max_indent_level) を引数で指定。
#
sub add_indent_level {
    my $self = shift;
    my ($level) = @ARG;

    if ($level < 2 || $max_indent_level < $level) {
	$self->{'error_message'} = "invalid indent level: $level";
	$self->close_internal();
	return 0;
    }
    if (!$self->write_data(pack('nn', 0x1f09, $level))) {
	return 0;
    }
    $self->{'new_entry_skip_flag'} = 0;
    return 1;
}

#
# 改行
#
sub add_newline {
    my $self = shift;

    if ($self->{'modifier_stack'}->[-1] eq 'half-width'
	&& !$self->add_half_width_end()) {
	return 0;
    }
    if (0 < @{$self->{'modifier_stack'}}) {
	$self->{'error_message'} = "modifier not teminated before newline";
	$self->close_internal();
	return 0;
    }
    if (!$self->write_data(pack('n', 0x1f0a))) {
	return 0;
    }
    $self->{'new_entry_skip_flag'} = 0;
    return 1;
}

#
# 上添え字開始指定
#
sub add_superscript_start {
    my $self = shift;

    if (!$self->add_modifier_start('superscript', pack('n', 0x1f0e))) {
	return 0;
    }
    return 1;
}

#
# 上添え字終了指定
#
sub add_superscript_end {
    my $self = shift;

    if (!$self->add_modifier_end('superscript', pack('n', 0x1f0f))) {
	return 0;
    }
    return 1;
}

#
# 分割禁止開始指定
#
sub add_nowrap_start {
    my $self = shift;

    if (!$self->add_modifier_start('nowrap', pack('n', 0x1f10))) {
	return 0;
    }
    return 1;
}

#
# 分割禁止終了指定
#
sub add_nowrap_end {
    my $self = shift;

    if (!$self->add_modifier_end('nowrap', pack('n', 0x1f11))) {
	return 0;
    }
    return 1;
}

#
# 強調開始指定
#
sub add_emphasis_start {
    my $self = shift;

    if (!$self->add_modifier_start('emphasis', pack('n', 0x1f12))) {
	return 0;
    }
    return 1;
}

#
# 強調終了指定
#
sub add_emphasis_end {
    my $self = shift;

    if (!$self->add_modifier_end('emphasis', pack('n', 0x1f13))) {
	return 0;
    }
    return 1;
}

#
# 別フォント開始指定
#
sub add_font_start {
    my $self = shift;
    my ($font_name) = @ARG;
    my $parameter;

    if ($font_name eq 'italic') {
	$parameter = 1;
    } elsif ($font_name eq 'bold') {
	$parameter = 3;
    } else {
	$self->{'error_message'} = "unknown font name $font_name";
	$self->close_internal();
	return 0;
    }
    if (!$self->add_modifier_start('font', pack('nn', 0x1fe0, $parameter))) {
	return 0;
    }
    return 1;
}

#
# 別フォント終了指定
#
sub add_font_end {
    my $self = shift;

    if (!$self->add_modifier_end('font', pack('n', 0x1fe1))) {
	return 0;
    }
    return 1;
}

######################################################################
# <インスタンス変数の値を返すメソッド群>
#
# 書式:
#	インスタンス変数名()
# メソッドの区分:
# 	public インスタンスメソッド。
# 戻り値:
#	インスタンス変数の値を返す。
#
sub file_name {
    my $self = shift;
    return $self->{'file_name'};
}

sub reference_file_name {
    my $self = shift;
    return $self->{'reference'}->file_name();
}

sub tag_file_name {
    my $self = shift;
    return $self->{'tag'}->file_name();
}

sub half_user_character {
    my $self = shift;
    my ($name) = @ARG;
    return $self->{'half_user_characters'}->character($name);
}

sub full_user_character {
    my $self = shift;
    my ($name) = @ARG;
    return $self->{'full_user_characters'}->character($name);
}

sub context_position {
    my $self = shift;
    return $self->{'context_position'};
}

sub entry_position {
    my $self = shift;
    return $self->{'entry_position'};
}

sub context_count {
    my $self = shift;
    return $self->{'context_count'};
}

sub entry_count {
    my $self = shift;
    return $self->{'entry_count'};
}

sub total_entry_count {
    my $self = shift;
    return $self->{'total_entry_count'};
}

sub error_message {
    my $self = shift;
    return $self->{'error_message'};
}

1;
