| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Unicode::CharWidth; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 2 |  |  | 2 |  | 61763 | use 5.010; | 
|  | 2 |  |  |  |  | 8 |  | 
| 4 | 2 |  |  | 2 |  | 10 | use strict; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 42 |  | 
| 5 | 2 |  |  | 2 |  | 9 | use warnings; | 
|  | 2 |  |  |  |  | 7 |  | 
|  | 2 |  |  |  |  | 96 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | =head1 NAME | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | Unicode::CharWidth - Character Width properties | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | =head1 VERSION | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | Version 1.05 | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | =cut | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | our $VERSION = '1.05'; | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | # the names of the character classes we'll define | 
| 20 |  |  |  |  |  |  | # we arrange them so, that in an array of 4 elements the mbwidth value | 
| 21 |  |  |  |  |  |  | # indexes the corresponding element, -1 being equivalent to 3 | 
| 22 |  |  |  |  |  |  |  | 
| 23 | 2 |  |  | 2 |  | 10 | use Carp; | 
|  | 2 |  |  |  |  | 2 |  | 
|  | 2 |  |  |  |  | 208 |  | 
| 24 |  |  |  |  |  |  | our @CARP_NOT = qw(utf8); # otherwise we see errors from unicode_heavy.pl | 
| 25 |  |  |  |  |  |  |  | 
| 26 | 2 |  |  |  |  | 198 | use constant CLASS_NAMES => ( | 
| 27 |  |  |  |  |  |  | 'InZerowidth',   # mbwidth ==  0 | 
| 28 |  |  |  |  |  |  | 'InSinglewidth', # mbwidth ==  1 | 
| 29 |  |  |  |  |  |  | 'InDoublewidth', # mbwidth ==  2 | 
| 30 |  |  |  |  |  |  | 'InNowidth',     # mbwidth == -1 | 
| 31 | 2 |  |  | 2 |  | 10 | ); | 
|  | 2 |  |  |  |  | 4 |  | 
| 32 |  |  |  |  |  |  |  | 
| 33 | 2 |  |  | 2 |  | 11 | use constant WIDTH_VALUES => (0 .. 2, -1); # order corresponds to CLASS_NAMES | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 108 |  | 
| 34 | 2 |  |  | 2 |  | 9 | use constant STD_QUICKSTART => 'UCW_startup'; | 
|  | 2 |  |  |  |  | 8 |  | 
|  | 2 |  |  |  |  | 498 |  | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | sub import { | 
| 37 | 2 |  |  | 2 |  | 21 | my $class = shift; | 
| 38 | 2 |  |  |  |  | 4 | my ($arg) = @_; | 
| 39 | 2 | 50 | 33 |  |  | 13 | if ( $arg and $arg eq '-gen' ) { | 
| 40 | 0 |  |  |  |  | 0 | _gen_and_save_proptab(_startup_path()); | 
| 41 | 0 |  |  |  |  | 0 | carp 'Exiting'; | 
| 42 | 0 |  |  |  |  | 0 | exit 0; # so no useful program runs with this option | 
| 43 |  |  |  |  |  |  | } | 
| 44 | 2 |  |  |  |  | 7 | _compile_functions(); | 
| 45 | 2 |  |  |  |  | 6 | @_ = ($class); | 
| 46 | 2 |  |  |  |  | 13 | require Exporter; | 
| 47 | 2 |  | 50 |  |  | 210 | goto(Exporter->can('import') or die q(Exporter can't import?)); | 
| 48 |  |  |  |  |  |  | } | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | our @EXPORT = CLASS_NAMES; | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | # compile the four exported functions | 
| 53 |  |  |  |  |  |  | sub _compile_functions { | 
| 54 | 2 |  |  | 2 |  | 7 | my $tabs = _get_proptab(_startup_path()); | 
| 55 | 2 |  |  |  |  | 8 | for my $name ( CLASS_NAMES ) { | 
| 56 | 8 |  |  |  |  | 14 | my $tab = $tabs->{$name}; | 
| 57 | 2 |  |  | 2 |  | 11 | no strict 'refs'; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 171 |  | 
| 58 |  |  |  |  |  |  | # avoid 'redefined' warnings | 
| 59 | 8 | 50 |  | 7 |  | 98 | *$name = sub { $tab } unless __PACKAGE__->can($name); | 
|  | 7 |  |  |  |  | 13194 |  | 
| 60 |  |  |  |  |  |  | } | 
| 61 |  |  |  |  |  |  | } | 
| 62 |  |  |  |  |  |  |  | 
| 63 | 2 |  |  | 2 |  | 1569 | use Dir::Self; | 
|  | 2 |  |  |  |  | 816 |  | 
|  | 2 |  |  |  |  | 12 |  | 
| 64 | 2 |  |  | 2 |  | 1595 | use File::Spec::Functions (); | 
|  | 2 |  |  |  |  | 2204 |  | 
|  | 2 |  |  |  |  | 476 |  | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | sub _startup_path { | 
| 67 | 4 |  |  | 4 |  | 13 | File::Spec::Functions::catfile( | 
| 68 |  |  |  |  |  |  | __DIR__, STD_QUICKSTART() | 
| 69 |  |  |  |  |  |  | ) | 
| 70 |  |  |  |  |  |  | } | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | sub _get_proptab { | 
| 73 | 2 |  |  | 2 |  | 107 | my $file = _startup_path(); | 
| 74 | 2 | 50 |  |  |  | 54 | _read_startup($file) || croak( | 
| 75 |  |  |  |  |  |  | "Missing $file in distribution " . __PACKAGE__ | 
| 76 |  |  |  |  |  |  | ) | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | sub _gen_and_save_proptab { | 
| 80 | 0 | 0 |  | 0 |  | 0 | unless ( _effective_locale() =~ /\.UTF-8$/ ) { | 
| 81 | 0 |  |  |  |  | 0 | croak "Generation must be under a UTF-8 locale" | 
| 82 |  |  |  |  |  |  | } | 
| 83 | 0 |  |  |  |  | 0 | _write_startup(_gen_proptab(), _startup_path()); | 
| 84 |  |  |  |  |  |  | } | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | sub _effective_locale { | 
| 87 | 0 | 0 | 0 | 0 |  | 0 | $ENV{LC_CTYPE} || $ENV{LANG} || $ENV{LC_ALL} || '' | 
|  |  |  | 0 |  |  |  |  | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  |  | 
| 90 | 2 |  |  | 2 |  | 13 | use constant MAX_UNICODE => 0x10FFFF; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 623 |  | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | sub _gen_proptab { | 
| 93 | 0 |  |  | 0 |  | 0 | require Text::CharWidth; | 
| 94 | 0 |  |  |  |  | 0 | my @proptab; # we'll make it a hash later (_reform_proptab) | 
| 95 |  |  |  |  |  |  | # make room for as many elements as we have class names | 
| 96 |  |  |  |  |  |  | # so index -1 is index 3 (InNowidth) | 
| 97 | 0 |  |  |  |  | 0 | $#proptab = $#{ [CLASS_NAMES] }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 98 | 0 |  |  |  |  | 0 | my $last_width = 99; # won't occur | 
| 99 | 0 |  |  |  |  | 0 | for my $code ( 0 .. MAX_UNICODE ) { | 
| 100 | 0 |  |  |  |  | 0 | my $width = Text::CharWidth::mbwidth(chr $code); | 
| 101 | 0 | 0 |  |  |  | 0 | if ( $width == $last_width ) { | 
| 102 |  |  |  |  |  |  | # continue current interval | 
| 103 | 0 |  |  |  |  | 0 | $proptab[$width]->[-1]->[1] = $code; | 
| 104 |  |  |  |  |  |  | } else { | 
| 105 |  |  |  |  |  |  | # start new interval (pair) for current length | 
| 106 | 0 |  |  |  |  | 0 | push @{ $proptab[$width] }, [$code, $code]; | 
|  | 0 |  |  |  |  | 0 |  | 
| 107 |  |  |  |  |  |  | } | 
| 108 | 0 |  |  |  |  | 0 | $last_width = $width; | 
| 109 |  |  |  |  |  |  | } | 
| 110 | 0 |  |  |  |  | 0 | _reform_proptab(@proptab) # make a hash of strings, keyed by class name | 
| 111 |  |  |  |  |  |  | } | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | sub _reform_proptab { | 
| 114 | 0 |  |  | 0 |  | 0 | my @proptab = @_; | 
| 115 | 0 |  |  |  |  | 0 | for my $tab ( @proptab ) { | 
| 116 | 0 |  |  |  |  | 0 | $tab = join "\n", map _one_or_two(@$_), @$tab; | 
| 117 |  |  |  |  |  |  | } | 
| 118 | 0 |  |  |  |  | 0 | my %proptab; | 
| 119 | 0 |  |  |  |  | 0 | @proptab{CLASS_NAMES()} = @proptab; | 
| 120 | 0 |  |  |  |  | 0 | \ %proptab | 
| 121 |  |  |  |  |  |  | } | 
| 122 |  |  |  |  |  |  |  | 
| 123 | 2 |  |  | 2 |  | 11 | use constant CODEPOINT_FMT => '%04X'; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 219 |  | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | sub _one_or_two { | 
| 126 | 0 |  |  | 0 |  | 0 | my ($from, $to) = @_; | 
| 127 | 0 |  |  |  |  | 0 | my $fmt = CODEPOINT_FMT; # print only first element if second is equal | 
| 128 | 0 | 0 |  |  |  | 0 | $fmt .= " $fmt" if $from != $to; # ... or both elements | 
| 129 | 0 |  |  |  |  | 0 | sprintf $fmt, $from, $to | 
| 130 |  |  |  |  |  |  | } | 
| 131 |  |  |  |  |  |  |  | 
| 132 | 2 |  |  | 2 |  | 3927 | use Storable (); | 
|  | 2 |  |  |  |  | 8510 |  | 
|  | 2 |  |  |  |  | 639 |  | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | sub _read_startup { | 
| 135 | 2 |  |  | 2 |  | 5 | my ($file) = @_; | 
| 136 | 2 | 50 |  |  |  | 4 | my $tab = eval { Storable::retrieve($file) } or croak( | 
|  | 2 |  |  |  |  | 6 |  | 
| 137 |  |  |  |  |  |  | _strip_error($@) | 
| 138 |  |  |  |  |  |  | ); | 
| 139 | 2 | 50 |  |  |  | 236 | unless ( _validate_proptab($tab) ) { | 
| 140 | 0 |  |  |  |  | 0 | croak("File '$file' wasn't created by " . __PACKAGE__); | 
| 141 |  |  |  |  |  |  | } | 
| 142 | 2 |  |  |  |  | 9 | $tab; | 
| 143 |  |  |  |  |  |  | } | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | sub _write_startup { | 
| 146 | 0 |  |  | 0 |  | 0 | my ($proptab, $file) = @_; | 
| 147 |  |  |  |  |  |  | # only write validated $proptab | 
| 148 | 0 | 0 |  |  |  | 0 | die "Failing our own validation" unless _validate_proptab($proptab); | 
| 149 | 0 | 0 |  |  |  | 0 | if ( eval { Storable::nstore($proptab, $file); 1 } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 150 | 0 |  |  |  |  | 0 | carp "Created startup file $file"; | 
| 151 |  |  |  |  |  |  | } else { | 
| 152 |  |  |  |  |  |  | # remove file/line from message and re-croak | 
| 153 | 0 |  |  |  |  | 0 | croak _strip_error($@); | 
| 154 |  |  |  |  |  |  | } | 
| 155 |  |  |  |  |  |  | return # nothing in particular, no-one cares | 
| 156 | 0 |  |  |  |  | 0 | } | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | sub _strip_error { | 
| 159 | 0 |  |  | 0 |  | 0 | my ($error) = @_; | 
| 160 | 0 |  |  |  |  | 0 | $error =~ s/at .* line \d+.*//s; | 
| 161 | 0 |  |  |  |  | 0 | ucfirst $error | 
| 162 |  |  |  |  |  |  | } | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | $@ =~ s/at .* line \d+.*//s; | 
| 165 | 2 |  |  | 2 |  | 24 | use List::Util (); | 
|  | 2 |  |  |  |  | 10 |  | 
|  | 2 |  |  |  |  | 431 |  | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | sub _validate_proptab { | 
| 168 | 2 |  |  | 2 |  | 4 | my ($tab) = @_; | 
| 169 | 2 |  |  |  |  | 4 | my $ncn = @{ [CLASS_NAMES] }; # number of class names | 
|  | 2 |  |  |  |  | 5 |  | 
| 170 |  |  |  |  |  |  | ref $tab eq 'HASH' and | 
| 171 | 8 |  |  |  |  | 31 | $ncn == grep { exists $tab->{$_} } CLASS_NAMES and | 
| 172 | 8 |  |  |  |  | 27 | $ncn == grep { defined $tab->{$_} } CLASS_NAMES and | 
| 173 | 2 | 50 | 33 |  |  | 12 | $ncn == grep { $tab->{$_} =~ /^[[:xdigit:]\s]*$/ } CLASS_NAMES | 
|  | 8 |  | 33 |  |  | 198 |  | 
| 174 |  |  |  |  |  |  | } | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  | __PACKAGE__ | 
| 177 |  |  |  |  |  |  | __END__ |