Perl で utf8 文字列を byte サイズで split する

utf8 な文字列を特定のバイトサイズで切り分ける処理って Perl でどう書けば一番良いのかを長いこと考えてた(あまり困ってはなかった)んだけど、UTF-8文字列をバイト数でカットした時の末尾の処理 見たら簡単に書けた。

#!/usr/bin/perl

use strict;
use warnings;
use Encode qw( is_utf8 decode _utf8_on );
require bytes;

my $utf8 =
    decode( 'euc-jp',
    "この日本語テキストは euc-jp で書かれていますが utf8 に変換されます" );

my @splited = byte_split( $utf8, 12 );

binmode STDOUT, ':utf8';
printf "utf8 string = %s\n", $utf8;

for my $str (@splited) {
    print $str, "\n";
    printf "\tutf8 length = %s\n", length $str;
    printf "\tbyte length = %s\n", bytes::length($str);
}

sub byte_split {
    my $str         = shift;
    my $byte_length = shift;

    return unless is_utf8($str);
    return unless $byte_length;
    return unless $byte_length > 2;
    my @result;
    my @strings = unpack "C*", $str;
    while (@strings) {
        my @spliced = splice @strings, 0, $byte_length;
        my( $round, $remainder ) = round_utf8( pack "C*", @spliced );
        _utf8_on($round);
        push @result, $round;

        if ($remainder) {
            my @remainder_list = unpack "C*", $remainder;
            @strings = ( @remainder_list, @strings );
        }
    }
    return @result;
}

sub round_utf8 {
    my $str = shift;
    my $remainder;

    if ( $str =~ /[\x00-\x7F]$/ ) {
        return $str;
    }
    if ( $str =~ s/([\xC0-\xFD])$// ) {
        $remainder = $1;
    }
    if ( $str =~ s/([\xE0-\xFD][\x80-\xBF])$// ) {
        $remainder = $1;
    }
    if ( $str =~ s/([\xF0-\xFD][\x80-\xBF]{2})$// ) {
        $remainder = $1;
    }
    # $str =~ s/[\xF8-\xFD][\x80-\xBF]{3}$//;  #4バイト余った場合
    # $str =~ s/[\xFC-\xFD][\x80-\xBF]{4}$//;  #5バイト余った場合
    return ( $str, $remainder );
}

実行するとこんな感じ

% perl test.pl
utf8 string = この日本語テキストは euc-jp で書かれていますが utf8 に変換されます
この日本
        utf8 length = 4
        byte length = 12
語テキス
        utf8 length = 4
        byte length = 12
トは euc-j
        utf8 length = 8
        byte length = 12
p で書か
        utf8 length = 5
        byte length = 11
れていま
        utf8 length = 4
        byte length = 12
すが utf8
        utf8 length = 8
        byte length = 12
に変換さ
        utf8 length = 4
        byte length = 12
れます
        utf8 length = 3
        byte length = 9

スクリプトEUC-JP で書いたので中で utf8 に変換しています。unpack, pack を使っているので処理も速い(と思う)。Lingua::JA::Jtruncate は EUC, sjis, jis しか対象にしてないんだよなあ。他に CPAN module で似た処理をするやつってあるんですかね。