Group
Extension

Text-Md2Inao/lib/Text/Md2Inao/Builder/InDesign.pm

package Text::Md2Inao::Builder::InDesign;
use utf8;
use strict;
use warnings;

use parent qw/Text::Md2Inao::Builder/;
use Text::Md2Inao::Builder::DSL;

use Text::Md2Inao::Logger;
use Text::Md2Inao::Util;
use File::ShareDir qw(dist_dir);
use Path::Tiny;
use List::Util qw/max/;

tie my %meta2label, "Tie::IxHash",
    author               => '著者',
    'author(romaji)'     => '著者(ローマ字)',
    supervisor           => '監修',
    'supervisor(romaji)' => '監修(ローマ字)',
    affiliation          => '所属',
    url                  => 'URL',
    mail                 => 'mail',
    github               => 'Github',
    twitter              => 'Twitter',
;

sub _new {
    my $class = shift;
    my $self = $class->SUPER::_new(@_);

    # check the repo's config/ first, and then try to get dist_dir()
    for my $dir('config', eval { dist_dir('Text-Md2Inao') }) {
        if (-d $dir) {
            $self->load_filter_config(path($dir, 'id_filter.json'));
            last;
        }
    }
    return $self;
}

sub prepend_metadata {
    my ($self, $c, $text) = @_;
    if ($c->metadata) {
        my @lines;
        if (my $chapter = $c->metadata->{chapter}) {
            push @lines, sprintf "<ParaStyle:章番号・連載回数>章番号:第%d章", $chapter;
        }

        if (my $serial = $c->metadata->{serial}) {
            push @lines, sprintf "<ParaStyle:章番号・連載回数>連載回数:第%d回", $serial;
        }

       if (my $title = $c->metadata->{title}) {
            push @lines, sprintf "<ParaStyle:タイトル>タイトル:%s", $title;
        }

        if (my $subtitle = $c->metadata->{subtitle}) {
            push @lines, sprintf "<ParaStyle:キャッチ>キャッチ:%s", $subtitle;
        }

        for (keys %meta2label) {
            if (my $value = $c->metadata->{$_}) {
                push @lines, sprintf "<ParaStyle:本文>%s:%s", $meta2label{$_}, $value;
            }
        }
        $text = join "\n", @lines, $text;
    }
    return $text;
}

sub after_filter {
    my ($self, $c, $out) = @_;
    $out = $self->prepend_metadata($c, $out);
    $out = $self->SUPER::after_filter($c, $out);
    chomp $out;
    return <<EOF;
<SJIS-MAC>
$out
EOF
}

sub list_marker {
    my ($style, $i) = @_;

    if ($style eq 'disc') {
        my $code = 0x2776 - 1;
        return sprintf "<CharStyle:丸文字><%x><CharStyle:>", $code + $i;
    }

    if ($style eq 'circle') {
        my $code = 0x2460 - 1;
        return sprintf "<CharStyle:丸文字><%x><CharStyle:>", $code + $i;
    }

    if ($style eq 'square') {
        return sprintf "<cTypeface:B><cFont:A-OTF ゴシックMB101 Pro><cotfcalt:0><cotfl:nalt,7>%d<cTypeface:><cFont:><cotfcalt:><cotfl:>", $i;
    }

    if ($style eq 'alpha') {
        return sprintf "<CharStyle:丸文字><cLigatures:0><cOTFContAlt:0><cOTFeatureList:nalt,3>%s<cLigatures:><cOTFContAlt:><cOTFeatureList:><CharStyle:>", chr($i + 96);
    }
}

sub replace_list_maker {
    my $text = shift;

    # convert
    $text =~ s/\(d(\d+)\)/list_marker('disc', $1)/eg;
    $text =~ s/\(c(\d+)\)/list_marker('circle', $1)/eg;
    $text =~ s/\(s(\d+)\)/list_marker('square', $1)/eg;
    $text =~ s/\(a(\d+)\)/list_marker('alpha', $1)/eg;

    # escape
    $text =~ s/\(\\([dcsa]?\d+)\)/($1)/g;

    return $text;
}

sub escape_html {
    my $html = shift;
    $html =~ s/([<>])/<005C>$1/g;
    return $html;
}

sub fallback_to_escaped_html {
    return escape_html(fallback_to_html(shift));
}

sub blank_line {
    my $c = shift;
    if ($c->blank_style and $c->blank_style eq 'full') {
        return '<ParaStyle:本文>';
    } else {
        $c->in_column ? return '<ParaStyle:コラム半行アキ>' : return '<ParaStyle:半行アキ>'
    }
}

case default => sub {
    my ($c, $h) = @_;
    $h->as_HTML('', '', {});
};

case text => sub {
    my ($c, $text) = @_;
    $text = escape_html($text);

    if ($text =~ m!\(注:! or $c->in_footnote) {
        $text = replace_note_parenthesis($c, $text, '注');
        $text =~ s!◆注/◆!<cstyle:上付き><fnStart:><pstyle:注釈>!g;
        $text =~ s!◆/注◆!<fnEnd:><cstyle:>!g;
    }

    # 各行の余計な先頭空白を取り除く (全角は字下げ指定なので残す)
    $text =~ s/^[ ]+//mg;

    # 改行を取り除く
    $text =~ s/(\n|\r)//g;
    # キャプション
    if ($text =~ s!^●(.+?)::(.+)!●$1\t$2!) {
        $text =~ s!\[(.+)\]$!\n$1!;
    }

    return replace_list_maker $text;
};

case "h1" => sub {
    my ($c, $h) = @_;
    return sprintf "<ParaStyle:大見出し>%s\n", $c->parse_element($h);
};

case "h2" => sub {
    my ($c, $h) = @_;
    return sprintf "<ParaStyle:中見出し>%s\n", $c->parse_element($h);
};

case "h3" => sub {
    my ($c, $h) = @_;
    return sprintf "<ParaStyle:小見出し>%s\n", $c->parse_element($h);
};

case "h4" => sub {
    my ($c, $h) = @_;
    return sprintf "<ParaStyle:コラムタイトル>%s\n", $c->parse_element($h);
};

case "h5" => sub {
    my ($c, $h) = @_;
    return sprintf "<ParaStyle:コラム小見出し>%s\n", $c->parse_element($h);
};

case strong => sub {
    my ($c, $h) = @_;
    return sprintf "<CharStyle:太字>%s<CharStyle:>", $c->parse_element($h);
};

case em => sub {
    my ($c, $h) = @_;
    my $ret;
    $ret .= $c->use_special_italic ? '<CharStyle:イタリック(変形斜体)>' : '<CharStyle:イタリック(変形斜体)>';
    $ret .= $c->parse_element($h);
    $ret .= '<CharStyle:>';
    return $ret;
};

case code => sub {
    my ($c, $h) = @_;
    return sprintf "<CharStyle:コマンド>%s<CharStyle:>", $c->parse_element($h);
};

case p => sub {
    my ($c, $h) = @_;
    my $text = $c->parse_element($h);
    if ($text !~ /^[\s ]+$/) {
        if ($text =~ /^<ParaStyle:キャプション>/) { ## Dirty Hack...
            return $text;
        }

        my $label;
        if ($c->in_column) {
            $label = 'コラム本文';
        } elsif ($c->in_quote_block) {
            $label = '引用';
        } else {
            $label = '本文';
        }
        return sprintf "<ParaStyle:%s>%s\n", $label, $text;
    }
};

case kbd => sub {
    my ($c, $h) = @_;
    sprintf "<cFont:KeyMother>%s<cFont:>" , $c->parse_element($h);
};

case span => sub {
    my ($c, $h) = @_;
    if ($h->attr('class') eq 'red') {
        return sprintf "<CharStyle:赤字>%s<CharStyle:>", $c->parse_element($h);
    }
    elsif ($h->attr('class') eq 'ruby') {
        my $ruby = $h->as_trimmed_text;
        $ruby =~ s!(.+)\((.+)\)!<cr:1><crstr:$2><cmojir:0>$1<cr:><crstr:><cmojir:>!;
        return $ruby;
    }

    ## ここでは inao に変換して、後で自由置換で変換
    elsif ($h->attr('class') eq 'symbol') {
        return sprintf "◆%s◆", $c->parse_element($h);
    }

    else {
        return fallback_to_escaped_html($h);
    }
};

case blockquote => sub {
    my ($c, $h) = @_;
    $c->in_quote_block(1);
    my $blockquote = $c->parse_element($_);
    $c->in_quote_block(0);
    return $blockquote;
};

case div => sub {
    my ($c,  $h) = @_;

    if ($h->attr('class') eq 'column') {
        $c->in_column(1);

        # HTMLとして取得してcolumn自信のdivタグを削除
        my $md = $h->as_HTML('');
        $md =~ s/^<div.+?>//;
        $md =~ s/<\/div>$//;

        my $column = $c->parse_markdown($md);
        $c->in_column(0);
        return $column;
    } else {
        return fallback_to_escaped_html($h);
    }
};

case ul => sub {
    my ($c, $h) = @_;
    my $label = $c->in_column ? 'コラム箇条書き' : '箇条書き';

    if ($c->in_list) {
        my $ret = "\n";
        for ($h->content_list) {
            if ($_->tag eq 'li') {
                $ret .= sprintf "<ParaStyle:%s2階層目>・%s\n", $label, $c->parse_element($_);
            }
        }
        chomp $ret;
        return $ret;
    } else {
        my $ret;
        for my $list ($h->content_list) {
            $c->in_list(1);
            $ret .= sprintf("<ParaStyle:%s>・%s\n", $label, $c->parse_element($list));
            $c->in_list(0);
        }
        chomp $ret;
        my $blank = blank_line($c);
        return <<EOF;
$blank
$ret
EOF
    }
};

case ol => sub {
    my ($c, $h) = @_;
    my $out = '';
    my $label = $c->in_column ? 'コラム箇条書き' : '箇条書き';
    my $style = $h->attr('class') || $c->default_list;
    my $i = 0;
    for my $list ($h->find('li')) {
        $out .= sprintf(
            "<ParaStyle:%s>%s%s\n",
            $label,
            list_marker($style, ++$i),
            $c->parse_element($list)
        );
    }
    chomp $out;
    my $blank = blank_line($c);
    return <<EOF;
$blank
$out
EOF
};

case pre => sub {
    my ($c, $h) = @_;
    $c->in_code_block(1);

    my $code = $h->find('code');
    my $text = $code ? $code->as_text : '';
    $text = escape_html($text);

    my $list_label = 'リスト';
    my $comment_label = 'リストコメント';

    # 「!!! cmd」で始まるコードブロックはコマンドライン(黒背景)
    if ($text =~ /!!!(\s+)?cmd/) {
        $text =~ s/.+?\n//;
        $list_label .= '白文字';
        $comment_label .= '白地黒文字';
    }

    ## リストスタイル
    $text = replace_list_maker($text);

    # 文字数カウント
    my $max = max(map { visual_length($_) } split /\r?\n/, $text);
    if ($text =~ /^●/) {
        if ($max > $c->max_list_length) {
            log warn => "リストは" . $c->max_list_length . "文字まで!(現在${max}使用):\n$text\n\n";
        }
    }
    else {
        if ($max > $c->max_inline_list_length) {
            log warn => "本文埋め込みリストは" . $c->max_inline_list_length . "文字まで!(現在${max}使用):\n$text\n\n";
        }
    }

    # コード内コメント
    if ($text =~ m!\(注:! or $c->in_footnote) {
        $text = replace_note_parenthesis($c, $text, '注');
        $text =~ s!◆注/◆!<CharStyle:$comment_label> !g;
        $text =~ s!◆/注◆! <CharStyle:>!g;
    }

    # コード内強調
    $text =~ s!\*\*(.+?)\*\*!<CharStyle:コマンド太字>$1<CharStyle:>!g;

    # コード内イタリック
    $text =~ s!\___(.+?)\___!<CharStyle:イタリック(変形斜体)>$1<CharStyle:>!g;

    chomp $text;

    $c->in_code_block(0);

    my $has_caption;
    my @lines = map {
        if (m/^●(.+?)::(.+)/) {
            $has_caption = 1;
            sprintf "<ParaStyle:キャプション>%s\t%s", $1, $2;
        }
        else {
            sprintf "<ParaStyle:%s>%s", $list_label, $_;
        }
    } split /\n/, $text;

    my $lines = join "\n", @lines;
    if ($has_caption) {
        return $lines . "\n";
    } else {
        my $blank = blank_line($c);
        return <<EOF;
$blank
$lines
EOF
    }
};

case a => sub {
    my ($c, $h) = @_;
    my $url   = $h->attr('href');
    my $title = $c->parse_element($h);
    if ($url and $title) {
        return sprintf "%s<cstyle:上付き><fnStart:><pstyle:注釈>%s<fnEnd:><cstyle:>", $title, $url;
    } else {
        return fallback_to_escaped_html($h);
    }
};

case img => sub {
    my ($c, $h) = @_;
    $c->{img_number} += 1;

    my $template = <<EOF;
<ParaStyle:キャプション>●図%d\t%s
<ParaStyle:赤字段落>%s
EOF

    return sprintf (
        $template,
        $c->{img_number},
        $h->attr('alt') || $h->attr('title'),
        $h->attr('src')
    );
};

case dl => sub {
    my ($c, $h) = @_;
    my $out = '';
    my $label = $c->in_column ? 'コラム箇条書き' : '箇条書き';
    for ($h->descendants) {
        if ($_->tag eq 'dt') {
            $out .= sprintf "<ParaStyle:%s>・%s\n", $label, $c->parse_element($_);
        } elsif ($_->tag eq 'dd') {
            $out .= sprintf "<ParaStyle:%s説明>%s\n", $label, $c->parse_element($_);
        }
    }
    chomp $out;
    my $blank = blank_line($c);
    return <<EOF;
$blank
$out
EOF
};

case table => sub {
    my ($c, $h) = @_;
    my $out = '';

    my $summary = $h->attr('summary') || '';
    $summary =~ m!(.+?)::(.+)!;
    $out .= sprintf "<ParaStyle:キャプション>%s\t%s\n", $1, $2;

    for my $table ($h->find('tr')) {
        if (my $header = join "\t", map { $c->parse_element($_) } $table->find('th')) {
            $out .= sprintf "<ParaStyle:表見出し行>%s\n", $header;
        }

        if (my $data = join "\t", map { $c->parse_element($_) } $table->find('td')) {
            $out .= sprintf "<ParaStyle:表>%s\n", $data;
        }
    }

    return $out;
};

case hr => sub {
    my ($c, $h) = @_;
    return "<ParaStyle:区切り線>\n"
};

1;


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.