File Coverage

blib/lib/Image/Bitmap2Paths.pm
Criterion Covered Total %
statement 438 1452 30.1
branch 151 968 15.6
condition 74 1200 6.1
subroutine 26 45 57.7
pod 1 35 2.8
total 690 3700 18.6


line stmt bran cond sub pod time code
1             package Image::Bitmap2Paths;
2            
3             #use 5.022002;
4 1     1   118496 use strict;
  1         2  
  1         34  
5 1     1   57738 use utf8;
  1         244  
  1         6  
6 1     1   25 use warnings;
  1         6  
  1         36  
7 1     1   10487 use Data::Flow qw(0.09);
  1         2565  
  1         169  
8            
9             require Exporter;
10 1     1   5 use AutoLoader qw(AUTOLOAD);
  1         2  
  1         6  
11            
12             our @ISA = qw(Exporter);
13            
14             # Items to export into callers namespace by default. Note: do not export
15             # names by default without a very good reason. Use EXPORT_OK instead.
16             # Do not simply export all your public functions/methods/constants.
17            
18             # This allows declaration use Image::Bitmap2Paths ':all';
19             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
20             # will save memory.
21             our %EXPORT_TAGS = ( 'all' => [ qw(
22            
23             ) ] );
24            
25             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
26            
27             our @EXPORT = qw(
28            
29             );
30            
31             our $VERSION = '0.01001';
32            
33 1   50 1   160 BEGIN { my $debug = $ENV{DEBUG_BITMAP2PATHS} || 0;
34             # $debug++ while @ARGV and $ARGV[0] eq '-d' and shift;
35 1 50   0 0 31 eval ( $debug ? 'sub dwarn { warn @_, ("@_" =~ /\n$/ ? q() : "\n") }' : 'sub dwarn {1}') ;
  0         0  
36 1         25071 eval "sub debug () { $debug }";
37             }
38             my $extend_tip = 1/3; # Crashes of fontforge; see issues #3239 #3240 #3242
39             my($marked, $marked2);
40            
41 0     0 0 0 sub marks_clear() {$marked = $marked2 = undef}
42 0     0 0 0 sub marks() {($marked, $marked2)}
43            
44             # Preloaded methods go here.
45            
46             # Autoload methods go after =cut, and are processed by the autosplit program.
47            
48             # Follow the approach in Audio::FindChunks
49             my %defaults = (
50             coarse_blobs => 0,
51             );
52            
53             my %mirror_from = ( # May be set separately, otherwise are synonims
54             # min_actual_silence_sec => 'min_silence_sec',
55             );
56            
57             my @recognized = # these default to undef, but accessing them is not fatal
58             qw(width height);
59            
60             my %subelements = (
61             LbRb => [qw(Lb Rb)],
62             stageOne => [qw(offs cnt cntmin near nearmin doublerays)],
63             stage10 => [qw(rays10 longedges10 seenlong10 inLong10 midLong10)],
64             stage20 => [qw(edge20 cntedge20 lastedge20 rays20 longedges20 seenlong20 midLong20 inLong20 Simple)],
65             stage30 => [qw(edge30 cntedge30 lastedge30 blobs30 blob30 skipExtraBlob)],
66             stage40 => [qw(edge40 cntedge40 lastedge40)],
67             stage50 => [qw(edge50 cntedge50 lastedge50 rays50 longedges50 seenlong50 midLong50 inLong50)],
68             stage60 => [qw(edge60 cntedge60 lastedge60)],
69             stage70 => [qw(edge70 cntedge70 lastedge70 longedges70 seenlong70 midLong70 inLong70)],
70             stage80 => [qw(edge80 cntedge80 lastedge80 tailEdge)],
71             stage90 => [qw(edge90 cntedge90 lastedge90)],
72             stageA0 => [qw(strokes nextEdgeBlob entryPointBlob inCalcEdge)],
73             );
74            
75             my %filters = (
76             bitmap => [sub {my $i=shift; [[], (map {['', @$_ ,'']} @$i), []]}, 'minibitmap'],
77             width => [sub {my $i=shift; $#{$i->[1]}-1}, 'bitmap'],
78             height => [sub {my $i=shift; $#$i-1}, 'bitmap'],
79             LbRb => [\&LbRb,, 'bitmap', 'width', 'height'],
80             # Lb => [sub { my $LbRb = shift; $LbRb->[0] }, 'LbRb'], # (Extended) index of the last blank column at start
81             # Rb => [sub { my $LbRb = shift; $LbRb->[1] }, 'LbRb'], # (Extended) index of the first blank column at end
82             stageOne => [\&stageOne, qw(bitmap width height)],
83             stage10 => [\&doRays, qw(bitmap width height offs cnt cntmin near nearmin)],
84             stage20 => [\&do_Simple_and_edges, qw(width height rays10 offs cnt longedges10 seenlong10 inLong10 midLong10)],
85             stage30 => [\&nnn_do_Simple_and_edges, qw(width height offs bitmap edge20 cntedge20 lastedge20)],
86             stage40 => [\&nnn0_do_Simple_and_edges, qw(width height edge30 cntedge30 lastedge30 rays20 inLong10 blob30)],
87             stage50 => [\&nnn1_do_Simple_and_edges,
88             qw(width height edge40 cntedge40 lastedge40 rays20 inLong10 midLong10 seenlong10 longedges10 blob30 offs cnt)],
89             stage60 => [\&scan_degree_rays,
90             qw(width height edge50 cntedge50 lastedge50 rays50 midLong50 offs cnt)],
91             stage70 => [\&nnn3_do_Simple_and_edges,
92             qw(width height edge60 cntedge60 lastedge60 longedges50 seenlong50 midLong50 inLong50 cnt)],
93             stage80 => [\&nnn4_do_Simple_and_edges,
94             qw(width height edge70 cntedge70 lastedge70 rays50 offs cnt)],
95             stage90 => [\&nnn5_do_Simple_and_edges,
96             qw(width height edge80 cntedge80 lastedge80 rays50 offs inLong70 cnt near)],
97             stageA0 => [\&nnn6_do_Simple_and_edges,
98             qw(width height edge90 cntedge90 lastedge90 rays50 offs longedges70 blob30 bitmap skipExtraBlob tailEdge coarse_blobs)],
99             );
100            
101             my %recipes = (
102             map(($_ => {default => $defaults{$_}}), keys %defaults),
103             map(($_ => {filter => [sub {shift}, $mirror_from{$_}]}), keys %mirror_from),
104             map( ($_ => {default => undef}),
105             @recognized),
106             map(($_ => {filter => $filters{$_}}), keys %filters),
107             (map {my $coll = $_; my $e = $subelements{$coll}; # For each subelement, create an entry
108             map {$e->[$_] => do {my $i=$_; {filter => [sub {shift()->[$i]}, $coll]}}} 0..$#$e} keys %subelements),
109             # map(($_ => {prerequisites => ['rms_data']}), 'chunks', 'min', 'max'),
110             );
111            
112             # As in Audio::FindChunks
113             sub new {
114 2     2 0 213623 my $class = shift;
115 2         18 my $s = new Data::Flow \%recipes;
116 2         46 $s->set(@_);
117 2         56 bless \$s, $class;
118             }
119 0     0 0 0 sub set ($$$) { ${$_[0]}->set($_[1],$_[2]); $_[0] }
  0         0  
  0         0  
120 45     45 0 28164 sub get ($$) { ${$_[0]}->get($_[1]) }
  45         183  
121            
122             my $height = 16; # Should be a multiple of 4
123             my @dx = (0,1,1,1,0,-1,-1,-1); # Start from "up", go clockwise
124             my @dy = (-1,-1,0,1,1,1,0,-1); # +-direction is "down"
125            
126             sub LbRb ($$$) {
127 2     2 0 174 my($bm,$width,$height) = (shift, shift, shift);
128 2         6 my($Lb, $Rb) = ($width, 1);
129 2         29 for my $i (1..$height) {
130 10         20 my $P = $bm->[$i];
131 10   100     68 $P->[$_] and $Lb = $_-1, last for 1..$Lb;
132 10   100     68 $P->[$_] and $Rb = $_+1, last for reverse($Rb..$width);
133             } # Rb and Lb are one off from the rightmost and leftmost pixels
134 2         10 [$Lb, $Rb]
135             }
136            
137             sub stageOne ($$$) {
138 2     2 1 774 my($bm,$width,$height) = (shift, shift, shift);
139 2         5 my(@near, @cnt, @offs, @doublerays, @cntmin, @nearmin);
140 2         9 for my $y (1..$height) { # Enumerate neighbors of the pixel, doublearys, directions having neighbors on both sides
141 10         20 for my $x ( 1..$width ) {
142 82 100       221 next unless $bm->[$y][$x];
143 6         12 my($prev, @OFF) = 0;
144 6         30 for my $n (0..7) {
145 48         80 my $dx = $dx[$n];
146 48         80 my $dy = $dy[$n];
147             # warn 'dx' unless defined $dx;
148             # warn 'dy' unless defined $dy;
149 48 100       115 next unless $bm->[$y + $dy][$x+$dx];
150 8         21 $near[$y][$x][$n] = 1;
151 8         17 push @OFF, $n;
152 8 100       29 next unless $bm->[$y + 2*$dy][$x + 2*$dx];
153 4         11 $doublerays[$y][$x]++;
154             }
155 6 50       23 $cntmin[$y][$x] = $cnt[$y][$x] = @OFF if @OFF;
156 6 50       19 $nearmin[$y][$x] = [ @{ $near[$y][$x] } ] if $near[$y][$x]; # deep copy
  6         33  
157 6         20 $offs[$y][$x] = \@OFF;
158             }
159             }
160 2         15 [\@offs, \@cnt, \@cntmin, \@near, \@nearmin, \@doublerays];
161             }
162            
163             ############################################## Stage 10 (two!)
164            
165             # Note that if a fake-curve is continued on the other side, we may prefer this to joining it to the dependent star
166             # In presence of dependencies below, the type of ray below is conditional on the eventual type of the dependency vertex.
167             # So the “name” below is preliminary, and may be changed later to a “derived type”.
168            
169             # Dictionary of ray candidates: Dense (>=7 neighbors at dist 1 or 2) (dot denotes an empty place; d is a dependency: rays must be good)
170             # . . . / |. d . . \ .... ..
171             # doubleray: *-- curve: *-. Fork: *d. ./- fake-curve: *-. d/- rhombus: *d. tail: --*- *- ish; serif --* notch:.-* ..*
172             # .|/ .\ | . \ *. \ \ *. d * d .|\ .|. / .|/ | ./. ./|.
173             # fork4: *d. Near-corner: *. m-joint: || elses-ray: *| / 3fork3: *d. *d. Sharp: *--- /. ..|
174             # .|\ .d-- *d d d .|\ . ..\ \.. \- \
175             # Note that dependent is not a neighbor for diagonal elses-ray (and is not unique) - .d- ...\ \
176             # fork4 and one flavor of fork3 are particular cases of fork! Corner-curve: *.\ 3fork3: *| bend-sharp: --*
177             # Later may put: ignore, Ignore, Tail, 2fork3, Enforced, Arrow/(x-)arrow, Probable-curve: *| Joint???: d*-.
178             # 1Spur, MFork; Rhombus-frce, Zh/K-fake-curve is intended to be ½-of-segment .|-
179             # Btail, 4fork, xFork, °. (Also allow longer shaft on Sharp) |.
180             # Opposite-direction pair tail/doubleray is converted to Tail/MFork if tail has cnt==3 (as on top of “M”).
181             # Likewise for a symmetrized case (as at bottom of “V”): if it is C/C with C and the opposite is
182             # unrecognized/C<1Spur>/C (instead of C).
183            
184             sub inspect_ray ($$$$$$$$$) { # returns: type, curvature or undef (0 for tail), is multiplicity checked, dependents, remove, unignore, actions.
185 6     6 0 18 my($x, $y, $cnt, $cntmin, $px, $pxmin, $near, $nearmin, $dirs, @res) = (shift, shift, shift, shift, shift, shift, shift, shift, shift); # dependent of m-joint should be checked separately
186 6         12 for my $dir (@$dirs) { # dependency: $x, $y, $dir,$dir1,...
187 8         17 my $dx = $dx[$dir];
188 8         16 my $dy = $dy[$dir];
189 8         24 my($N, $Nmin) = ($near->[$y+$dy][$x+$dx], $nearmin->[$y+$dy][$x+$dx]);
190 8         23 my($cNmin, $cN, $cN0min) = ($cntmin->[$y+$dy][$x+$dx], $cnt->[$y+$dy][$x+$dx], $cntmin->[$y][$x]);
191 8 50 0     24 push @res, ['Dense'] and next if 6 < $cNmin;
192 8         19 my($N0, $N0min) = ($near->[$y][$x], $nearmin->[$y][$x]);
193 8 100       20 if ($cNmin <= 1) { # below, if @perp, we automatically are diagonal
194 4 50 50     31 push @res, ['tail', 0, 1, undef, undef, undef, ['t']] and next
      33        
195             if $N0->[($dir+4)%8] or $cN0min <= 2;
196 0 0       0 unless (grep $N0min->[($dir+$_)%8], -1,1) {
197 0         0 my @perp = grep $N0min->[($dir+$_)%8], -2,2;
198 0 0 0     0 push @res, ['1Spur', 0, 1] and next if @perp <= 1; # with @perp==1, allow 2 at 135°: a continuation of perp, and of us
199             }
200             }
201 4 50       11 if ($N->[$dir]) {
202 4   50     26 my $cNN = $cntmin->[$y+2*$dy][$x+2*$dx] || 0;
203 4 0 0     12 push @res, ['Dense'] and next if 6 < $cNN and $Nmin->[$dir]; # Nmin: be most forgiving
      33        
204 4 50       21 push @res, ['doubleray', 0] and next
205             } # Now know no straight continuation; check 2 next neighbors
206 0         0 my($seen_next2, %is_next, $d, $across2);
207 0   0     0 $Nmin->[($dir+$_)%8] and $seen_next2++, $d = $_, $is_next{$_}++ for -1, 1;
208 0         0 my($step, @NEAR) = 2 - ($dir & 0x1); # detect notches in diag directions, and serifs in HV directions; also for forks
209             # by seen_next2: 2: fork[3,4] 1: curve fake-curve diamond sharp fork3 0: tail(ish) notch serif near-corner m-joint elses-ray Corner-curve
210 0 0 0     0 unless ($seen_next2) { # No suitable curved continuation
211 0         0 for my $D (-1, 1) { # Protrusion? ($dir is serif/notch/ maybe-m-joint???)
212 0         0 my $DD = ($dir+$step*$D)%8; # close H-or-V direction
213 0         0 my $dx1 = $dx[$DD];
214 0         0 my $dy1 = $dy[$DD];
215 0 0       0 $across2++ if $nearmin->[$y+$dy1][$x+$dx1][$DD]; # can go 2 steps in the close H-or-V direction
216 0 0 0     0 next if not $N0min->[$DD] or $N0min->[($DD+4)%8]; # Skip if extends on the other side
217 0   0     0 my $extra = $D * ($cNmin <= 3 and $cN >= 3 and not $dir & 0x1 and !!$N->[$DD]); # Bottom join of M - m-joint (above: with dir=2, DD=0)
218 0         0 my $curved = $nearmin->[$y+$dy1][$x+$dx1][($DD + $extra)%8]; # for m-joint: extra=1, and we have sloped perp continuation
219             # ???? next if $other_dir and ($dir & 0x1 or );
220             # x[2]: straight perp continuation for m-joints
221 0         0 push @NEAR, [$D, $extra, $curved, $nearmin->[$y+$dy1][$x+$dx1][$DD], $cntmin->[$y+$dy1][$x+$dx1] < 5]; # if not ($dir & 0x1 and '???'); # found long stem nearby
222             } # At most one element in @NEAR...
223 0         0 my $n0;
224 0 0       0 if (@NEAR) {
225 0         0 $n0 = $NEAR[0][0]; # Avoid autovivication, do only if @NEAR...
226 0 0 0     0 push @res, ['notch', $n0, 1, undef, [$x+$dx, $y+$dy], [$x+$dx, $y+$dy, ($dir+4)%8], # Don't cancel each other: double-notches 02d0
      0        
      0        
      0        
      0        
      0        
227             ['I', $x+$dx, $y+$dy, ($dir+2*$n0)%8, ($dir+3*$n0)%8], ['n', $x+$dx, $y+$dy, ($dir+4)%8]] and next # Force ignoring other neighbors
228             if $NEAR[0][2] and $cNmin <= 3 and $cN >= 3 and $dir & 0x1
229             and not $N0min->[($dir + 2*$n0)%8] and not grep $N0min->[($dir + $_*$n0+4)%8], 1, 2;
230 0 0 0     0 push @res, ['serif', $n0, 1, undef, [$x+$dx, $y+$dy], undef, ['I', $x+$dx, $y+$dy, ($dir+3*$n0)%8], ['E', $x+$dx, $y+$dy, ($dir+4)%8]]
      0        
      0        
231             and next if $NEAR[0][2] and $cNmin <= 2 and not ($dir & 0x1);
232             # warn("m: $x $y $dir\n"),
233 0 0 0     0 push @res, ['m-joint', $n0, 1, ['`', [$x + $dx, $y + $dy, ($dir+4)%8]],
      0        
      0        
      0        
      0        
      0        
      0        
234             undef, undef, ['L', $x, $y, ($dir+2*$n0)%8, -$n0, 1, 1, 2]] and next # check separately???
235             if not ($dir & 0x1) and $NEAR[0][1] and ($NEAR[0][2] xor $NEAR[0][3]) and (not $NEAR[0][3] or $NEAR[0][4]) and $cN0min <= 3;
236             }
237 0         0 @NEAR = grep $_->[2], @NEAR; # only curved!
238 0         0 my($nnn, $DD, @NEAR1, $nnn1); # What remains is Near-corner, elses-ray, Corner-curve, bend-sharp, and diagonal notch
239 0         0 for my $D (-1, 1) { # Allow a neighbor come from a ray of the protrusion
240 0 0       0 next unless $Nmin->[($dir+2*$D)%8];
241 0         0 $nnn++, $DD = $D;
242             } # Here dy goes down!!! vvv
243 0 0 0     0 if ($nnn) { # Have a neighbor near end in perpendicular direction
    0 0        
      0        
      0        
244 0 0 0     0 push @res, ['elses-ray', undef, 0, ['″', [$x+$dx+$dy,$y+$dy-$dx,($dir+2)%8], [$x+$dx-$dy,$y+$dy+$dx,($dir-2)%8]]] and next
245             if $nnn == 2; # ??? Do we NEED to check the opposite dir:
246 0 0 0     0 if (($N0->[($dir+4)%8] or not($dir & 0x1) and $cN0min == 3) # and $N0min->[($dir+4-$DD)%8]) # do not allow bending away from the corner
      0        
      0        
      0        
247             and not $N0min->[($dir+2*$DD)%8]
248             and ($px->[$y + $dy + 2*$DD*$dx][$x + $dx - 2*$DD*$dy] # do not allow bending away from us
249             or not($dir & 0x1) and $cntmin->[$y + $dy + $DD*$dx][$x + $dx - $DD*$dy] <= 3)) {
250             # warn "thisC=$cN0min targC=$cNmin notchC=$cntmin->[$y + $dy + $DD*$dx][$x + $dx - $DD*$dy], and ", grep +($N0min->[($dir+4+$_)%8] ? 0 : 1), 1, -1;
251 0 0 0     0 if( $cN0min == 3 and $cNmin == 2 and not($dir % 2) and $cntmin->[$y + $dy + $DD*$dx][$x + $dx - $DD*$dy] == 2
      0        
      0        
      0        
252             and my @back = grep $N0min->[($dir+4+$_)%8], 1, -1 ) {
253             # $marked++;
254 0 0       0 push @res, ['Btail', $DD, 0, undef, undef, undef,
    0          
255             ['N', $x+$dx, $y+$dy, ($dir+2*$DD)%8], ['N', $x+$dx-$DD*$dy, $y+$dy+$DD*$dx, ($dir-2*$DD)%8],
256             ['I', $x+$dx-$DD*$dy, $y+$dy+$DD*$dx, ($dir+4+$DD)%8],
257             (@back ? ['E', $x, $y, ($dir+4+$back[-1])%8] : ())] and next
258             }
259 0 0       0 push @res, ['Near-corner', $DD, 0, [',', [$x + $dx, $y + $dy, ($dir+2*$DD)%8]], undef, undef,
    0          
260             ($dir & 0x1 ? () # TRY: remove the litation on not bending away
261             : (['I', $x, $y, ($dir+$DD)%8], ['Ef', $x+$dx, $y+$dy, ($dir+4)%8]) )] and next
262             # and $px->[$y + 2*$DD*$dx][$x - 2*$DD*$dy]);
263             }
264 0 0 0     0 push @res, ['Corner-curve', $DD, 1] and next if $dir & 0x1 and not $N0min->[($dir+$DD)%8] and $cNmin <= 2;
      0        
      0        
265 0 0 0     0 push @res, ['arrow', $DD, 0, ['…', [$x+$dx, $y+$dy, ($dir + 4)%8, ($dir + 4 - $DD)%8, ($dir + 4 - 2*$DD)%8]], # On barb going to tip
      0        
      0        
      0        
      0        
      0        
266             undef, undef, ['a', $DD]] and next # remove, unignore, @rest
267             if $cNmin == 3 and $dir & 0x1 and $nnn == 1 and $N0min->[($dir+4)%8]
268             and $Nmin->[($dir + 4 - $DD)%8] and $Nmin->[($dir + 4 - 2*$DD)%8];
269             } elsif (not @NEAR and $cNmin == 2 and $N0min->[($dir+4)%8]
270             and $cN0min <= 3 + ($dir & 0x1) and $cnt->[$y][$x] >= 3 + ($dir & 0x1)) { # bend-sharp?
271 0         0 my $DDD;
272 0         0 for my $D (-1,1) {
273 0 0       0 $DDD=$D, last if $N0min->[($dir+$D*$step)%8];
274             }
275 0 0       0 die "bend-sharp: panic" unless $DDD;
276 0         0 my $dx1 = $dx[($dir+$DDD*$step)%8];
277 0         0 my $dy1 = $dy[($dir+$DDD*$step)%8];
278 0 0 0     0 push @res, ['bend-sharp', $DDD, 1] and next if $cntmin->[$y+$dy1][$x+$dx1] <= 4 - ($dir & 0x1)
      0        
279             and $nearmin->[$y+$dy1][$x+$dx1][($dir+4-$DDD)%8];
280             }
281 0 0 0     0 push @res, ['?'] and next if $dir & 0x1;
282 0         0 for my $D (-1, 1) { # Allow a neighbor come from a ray of the protrusion
283 0 0       0 next unless $Nmin->[($dir+$D)%8];
284 0         0 $nnn1++, $DD = $D;
285             } # Here dy goes down!!! vvv
286 0 0 0     0 push @res, ['notch', $n0, 1, undef, [$x+$dx, $y+$dy]] and next
      0        
      0        
      0        
287             if $cNmin <= 2 and not($dir & 0x1) and $nnn1 and not $N0min->[($dir + $DD + 4)%8]; # Miss double-notch=arrow
288 0 0 0     0 push @res, ['Arrow'] and next # On shaft going to tip
      0        
      0        
289             if $cNmin == 3 and not($dir & 0x1 or $nnn1 or $across2 or $N->[($dir+2)%8] or $N->[($dir-2)%8]);
290 0 0       0 push @res, ['?'] and next;
291             } elsif (2 == $seen_next2) { # Only forks, Zh, K's here... # |. . /
292             my($c, $DDD) = 0; # ./- $c counts dots *-
293             $N0min->[($dir+$step*$_)%8] and $c++, $DDD=$_ for -1, 1; # *. . \
294             if ($c == 1 and not($dir & 0x1)) { # Work around ties between legs of K
295             my $x1 = $x + $dx[($dir+$DDD*$step)%8];
296             my $y1 = $y + $dy[($dir+$DDD*$step)%8];
297             my($NNN, $dir1) = ($near->[$y1][$x1], ($dir-$DDD)%8);
298             if ($NNN->[$dir] and $NNN->[$dir1]) {
299             push @res, ['elses-ray', -$DDD, 0, ['"', [$x1, $y1, $dir, $dir1]]];
300             next;
301             }
302             } elsif ($c == 1) {
303             push @res, ['Probable-curve', $DDD] and next;
304             } elsif ($c == 2 and $dir & 0x1 and $N0min->[($dir+4)%8] and not $N0min->[($dir+3)%8] and not $N0min->[($dir+5)%8]) {# K-joint of Ж; repeat what we do below with K-joint
305             # warn "K-joint: ($x,$y) $dir + $d\n";
306             my @R;
307             for my $d (-1, 1) {
308             my $x1 = $x + $dx[($dir+$d)%8];
309             my $y1 = $y + $dy[($dir+$d)%8];
310             my($NNN, $dir1) = ($near->[$y1][$x1], ($dir+2*$d)%8);
311             push @R, ['Zh-fake-curve', $d] if $NNN->[$dir] and not $NNN->[($dir1+1)%8] and not $NNN->[($dir1-1)%8];
312             }
313             push(@res, @R), next if @R == 1;
314             }
315             push @res, ['?'] and next if $c;
316             # $N->[($dir+2*$_)%8] and $c++ for -1, 1;
317             # push @res, ['fork4'] and next if $c and $c == 2;
318             my $opp = ($dir + 4)%8;
319             my @dep = grep { $_ ne $opp and $Nmin->[$_]} 0..7;
320             push @res, ['Fork', undef, 1, ['°', [$x+$dx, $y+$dy, @dep]]] and next; # join all forks with next2==2
321             } # Now have one secondary ray only, $d-curving: curve fake-curve diamond sharp fork3
322 0         0 my $baddir = ($dir - $step*$d)%8;
323 0         0 my $bad = $N0min->[$baddir]; # on fake-curve only
324 0 0       0 if ( $N0min->[($dir+$d)%8] ) { # Parallelogram - essentially, two curves with the same end (diamond sharp fork3)
325 0 0 0     0 if ($bad and $dir & 0x1 and $N0min->[($dir+4)%8] and not $N0min->[($dir+3)%8] and not $N0min->[($dir+5)%8]) { # may be a K-joint
      0        
      0        
      0        
326 0         0 my $x1 = $x + $dx[($dir+$d)%8];
327 0         0 my $y1 = $y + $dy[($dir+$d)%8];
328 0         0 my($NNN, $dir1) = ($near->[$y1][$x1], ($dir+2*$d)%8);
329 0 0 0     0 if ($NNN->[$dir] and not $NNN->[($dir1+1)%8] and not $NNN->[($dir1-1)%8]) {
      0        
330 0         0 push @res, ['K-fake-curve', $d];
331 0         0 next;
332             }
333             }
334 0 0 0     0 push @res, ['?'] and next if $bad or not ($dir & 0x1) and $N0min->[($dir + 2*$d)%8]; # Check last . on diamond and fork3
      0        
      0        
335 0 0 0     0 if ($dir & 0x1 and $Nmin->[($dir + 2*$d)%8] and not $Nmin->[($dir - 2*$d)%8]) { # Avoid the situation in K
      0        
336 0         0 my $dx1 = $dx[($dir + $d)%8];
337 0         0 my $dy1 = $dy[($dir + $d)%8];
338 0         0 my $last;
339 0 0       0 if ($pxmin->[$y+$dy + 2*$dy1][$x+$dx + 2*$dx1]) { # 1D469 𝑩 ; 1D483 𝒃;
340             # ++$marked,
341 0 0 0     0 push @res, ['Sharp', $d, 0, undef, undef, undef, ['L', $x+$dx, $y+$dy, ($dir+$d)%8, $d, 2, 2, 3, 2+$last], # 2nd 'S' optional now???
    0 0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
342             ['S', $x+$dx, $y+$dy, ($dir+4)%8], ['S', $x+$dx1, $y+$dy1, ($dir+4+$d)%8], # Enforce line at dist=3
343             ($px->[$y + 2*$dy + $dy1][$x + 2*$dx + $dx1]
344             ? (['I1', $x + 2*$dx + $dx1, $y + 2*$dy + $dy1, ($dir+4)%8], ['II', $x + $dx + $dx1, $y + $dy + $dy1, $dir]) : ()),
345             ['T',($dir + $d + 4)%8,$d]] and next
346             if $px->[$y+3*$dy1][$x+3*$dx1] # This is heavily hand-crafted to avoid false positives!
347             and ( $px->[$y + 2*$dy + 2*$dy1][$x + 2*$dx + 2*$dx1] xor $px->[$y + 2*$dy + $dy1][$x + 2*$dx + $dx1] ) # 1F590 🖐
348             and ( not $px->[$y + 2*$dy + $dy1][$x + 2*$dx + $dx1] or $cnt->[$y][$x] < 5 and $cnt->[$y+$dy1][$x+$dx1] < 6) # ऄ ፼
349             and not $px->[$y + $dy + 3*$dy1][$x + $dx + 3*$dx1]
350             and $cntmin->[$y][$x] < 6 # 0994 ঔ 210C ℌ
351             # and ( $cntmin->[$y][$x] + $cntmin->[$y+3*$dy1][$x+3*$dx1] < 10 ) # not needed: 1F5FD 🗽
352             and (not $px->[$y-$dy1][$x-$dx1] or $cntmin->[$y][$x] + $cntmin->[$y-$dy1][$x-$dx1] < 10) # 1D4CC 𝓌
353             and (not $px->[$y-$dy+$dy1][$x-$dx+$dx1] or $cnt->[$y][$x] + $cnt->[$y-$dy+$dy1][$x-$dx+$dx1] < 9) # 11C17 𑰗, 1F38E 🎎
354             and (not $px->[$y+4*$dy1][$x+4*$dx1] or not $px->[$y+5*$dy1][$x+5*$dx1]
355             or $cntmin->[$y][$x] + $cntmin->[$y+4*$dy1][$x+4*$dx1] + $cntmin->[$y+5*$dy1][$x+5*$dx1] < 15) # 1D4C9 𝓉
356             and grep !$px->[$y + $_*$dy1 - $dy][$x + $_*$dx1 - $dx], 2,3,4 # 16B6 ᚶ
357             and ( not $px->[$y + 2*$dy + $dy1][$x + 2*$dx + $dx1] # 114C7 𑓇
358             or ( ( grep $px->[$y + 4*$dy1 + $_*($dy-$dy1)][$x + 4*$dx1 + $_*($dx-$dx1)],0,1 # 1160F 𑘏
359             or $px->[$y - $dy1][$x - $dx1] ) # 1F590 🖐
360             and $cntmin->[$y+3*$dy1][$x+3*$dx1] < 6) # 1D752 𝝒
361             and $cnt->[$y+2*$dy+$dy1][$x+2*$dx+$dx1] > 2) # 1D7C5 𝟅
362             and ($last = !!$px->[$y + 4*$dy1][$x + 4*$dx1] or $px->[$y + 5*$dy1 - $dy][$x + 5*$dx1 - $dx]); # 1D491 𝒑
363             } else {
364 0 0 0     0 push @res, ['Sharp', $d, 0, undef, undef, undef, ['L', $x+$dx, $y+$dy, ($dir+$d)%8, $d, 1, 2, 2+$last], # 2nd 'S' optional now (N)
      0        
      0        
      0        
      0        
      0        
      0        
365             ['S', $x+$dx, $y+$dy, ($dir+4)%8], ['S', $x+$dx1, $y+$dy1, ($dir+4+$d)%8],
366             ['T',($dir + $d + 4)%8,$d]] and next # Enforce line at dist=2
367             if ($last = !!$px->[$y+3*$dy1][$x+3*$dx1] or $px->[$y + 4*$dy1 - $dy][$x + 4*$dx1 - $dx])
368             and $px->[$y + 2*$dy + $dy1][$x + 2*$dx + $dx1] and ($last or !$px->[$y + 3*$dy1 - $dy][$x + 3*$dx1 - $dx])
369             and ($last or $cntmin->[$y+$dy1][$x+$dx1] < 6 # with $last, not beneficial
370             and ($cntmin->[$y][$x] < 6
371             and ($cntmin->[$y][$x] < 5 or $px->[$y-$dy1][$x-$dx1] and $cntmin->[$y-$dy1][$x-$dx1] < 4))); # 1d54d 𝕍
372             }
373             } # Now general catch-all:
374 0         0 my $opp = ($dir + 4)%8;
375 0 0       0 my @dep = grep { $_ ne $opp and $Nmin->[$_]} 0..7;
  0         0  
376             # warn "generic: $x,$y, $dir, $d; (@dep);; $seen_next2;;; ", map $N->[$_] || 0, 0..7;
377 0 0       0 push @res, [($Nmin->[($dir - 2*$d)%8] ? '3fork3' : 'rhombus'), $d, 0, ['´', [$x + $dx, $y + $dy, @dep]]] and next;
    0          
378             }
379             # warn "maybe curve: ($x,$y,$dir): ", $bad||0,"\n";
380 0 0 0     0 push @res, ['curve', $d, undef, undef, undef, undef, ['C', $d]] and next unless $bad;
381 0 0 0     0 if ($N0min->[($dir + 4)%8] and $cN0min <= 4 and $dir & 0x1) { # check for double-arrow joint (21a0, 0239)
      0        
382 0         0 my($mirY, $mirX) = ($y + 2*$dy[$baddir], $x + 2*$dx[$baddir]);
383 0 0 0     0 push @res, ['arrow', -$d, 0, ['…', [$x+$dx, $y+$dy, ($dir + 4)%8, ($dir + 4 + $d)%8, ($dir + 4 + 2*$d)%8]], # On barb going to tip
      0        
      0        
      0        
384             undef, undef, ['a', -$d, 1]] and next # remove, unignore, @rest
385             if $px->[$mirY][$mirX] and $cntmin->[$mirY][$mirX] <= 4 and $cnt->[$mirY][$mirX] >= 4 and $nearmin->[$mirY][$mirX][($dir-2*$d)%8];
386             }
387 0 0       0 push @res, ['fake-curve', $d, 0, ['curve', [$x + $dx[$baddir], $y + $dy[$baddir], ($dir+$d)%8]]] and next;
388             }
389 6         22 return \@res;
390             } # dependent for a parallelogram (HV one) should be submitted only once...
391            
392             sub clear_edge ($$$$) {
393 0     0 0 0 my ($e, $edge, $cntedge, $lastedge) = (shift, shift, shift, shift);
394 0         0 my($x, $y, $dir, $x1, $y1) = @$e;
395             # warn "clear $x, $y, $dir, $x1, $y1";
396 0         0 my $dir1 = ($dir+4)%8;
397 0         0 $edge->[$y][$x][$dir] = 0; $edge->[$y1][$x1][$dir1] = 0;
  0         0  
398 0         0 $cntedge->[$y][$x]--; $cntedge->[$y1][$x1]--;
  0         0  
399 0         0 for my $l ([$x, $y], [$x1, $y1]) {
400 0 0       0 next if $cntedge->[$l->[1]][$l->[0]] != 1;
401 0         0 my $D = -1;
402 0   0     0 $edge->[$l->[1]][$l->[0]][$_] and $D = $_, last for 0..7;
403 0         0 $lastedge->[$l->[1]][$l->[0]] = $D; # Good only for 1-edge pixels
404             }
405             }
406            
407             sub add_edge ($$$$) {
408 4     4 0 12 my($e, $edge, $cntedge, $lastedge) = (shift, shift, shift, shift);
409 4         11 my($x, $y, $dir, $x1, $y1) = @$e;
410             # warn "adding $x, $y, $dir, $x1, $y1";
411 4         8 my $dir1 = ($dir+4)%8;
412 4         11 $edge->[$y][$x][$dir]++; $edge->[$y1][$x1][$dir1]++;
  4         10  
413 4         10 $cntedge->[$y][$x]++; $cntedge->[$y1][$x1]++;
  4         16  
414 4         9 $lastedge->[$y][$x] = $dir; $lastedge->[$y1][$x1] = $dir1;
  4         33  
415             }
416            
417             sub add_longedge ($$$$$) {
418 0     0 0 0 my($e, $longedges, $seenlong, $midLong, $inLong) = (shift, shift, shift, shift, shift);
419 0         0 my($x, $y, $dir, $x1, $y1, $rot) = @$e;
420 0         0 push @$longedges, [$x, $y, $x1,$y1, scalar @$longedges, $dir, $rot];
421 0         0 $seenlong->{$x, $y, $x1,$y1} = $seenlong->{$x1,$y1,$x, $y} = $longedges->[-1];
422 0         0 $midLong->{$x+$x1,$y+$y1}++;
423 0         0 $inLong->{$x1,$y1}++;
424 0         0 $inLong->{$x,$y}++;
425             }
426            
427             sub clear_longedge ($$$$$) {
428 0     0 0 0 my($e, $longedges, $seenlong, $midLong, $inLong) = (shift, shift, shift, shift, shift);
429 0         0 my($x, $y, $x1, $y1, $offset) = @$e;
430 0         0 $longedges->[$offset] = 'erased';
431 0         0 delete $seenlong->{$x,$y,$x1,$y1};
432 0         0 delete $seenlong->{$x1,$y1,$x,$y};
433 0         0 $midLong->{$x+$x1,$y+$y1}--;
434 0         0 $inLong->{$x1,$y1}--;
435 0         0 $inLong->{$x,$y}--;
436             }
437            
438             sub post_inspect_ray ($$$$;$) { # Not finished (and it is not exactly clear for what it is best to check...)
439 0   0 0 0 0 my($x, $y, $dir, $rays, $basetype) = (shift, shift, shift, shift, shift || '');
440 0         0 $rays = $rays->[$y][$x];
441 0 0       0 my $ray = $rays->[$dir] or die "Panic: x=$x, y=$y, dir=$dir - missing ray in post_inspect_ray($basetype)";
442 0 0 0     0 return 1 if $ray->[0] =~ /^m/ and $basetype =~ /^m/;
443 0         0 for my $next (1,-1) {
444 0         0 warn "Checking x=$x, y=$y, dir=$dir in post_inspect_ray($basetype) 1=", $rays->[($dir+1)%8] && $rays->[($dir+1)%8][0],
445             " -1=", $rays->[($dir-1)%8] && $rays->[($dir-1)%8][0], "\n" if debug && $basetype =~ /^m/;
446 0 0 0     0 return if $rays->[($dir+$next)%8] and $rays->[($dir+$next)%8][0] =~ /^[Dr3\WP]/; # 'Probable-curve' put here as an experiment XXX
447             }
448             # warn "Checking2: $ray->[0]\n" if $basetype =~ /^3/;
449             # Putting 'elses-ray' into the allowed list is not a good idea
450             # (although it may help half-way with some, like ɫ, у, and helps with Ѭ); maybe allow the caller to permit it???
451 0         0 $ray->[0] =~ /^[dctB1fKZE]/; # doubleray, curve, (B)tail, 1Spur, [Zh/K-]fake-curve, Enforced with no ?/Dense/rhombus/3fork3 nearby
452             } # True if we want to keep the basetype
453            
454             sub remove_px ($$$$$$) {
455 0     0 0 0 my($x, $y, $cnt, $px, $near, $off) = (shift, shift, shift, shift, shift, shift);
456 0         0 for my $dir ( @{ $off->[$y][$x] } ) {
  0         0  
457 0         0 my $dx = $dx[$dir];
458 0         0 my $dy = $dy[$dir];
459 0         0 $near->[$y+$dy][$x+$dx][($dir+4)%8] = 0;
460 0         0 $cnt->[$y+$dy][$x+$dx]--;
461             }
462 0         0 undef $px->[$y][$x];
463             }
464            
465             sub force_line ($$) { # enforces 1 edge in this direction; inserts 'ignore' in $rem1 dirs at point, and in $rem2 dirs at next pt
466 0     0 0 0 my($how, $rays) = (shift, shift);
467 0         0 my($L, $x, $y, $dir, $rot, $len, @ROTS) = @$how; # $L ==eq== 'L' ignored
468             # warn "In force_line($L, $x, $y, $dir, $rot...)";
469 0         0 my $dx = $dx[$dir];
470 0         0 my $dy = $dy[$dir];
471 0         0 for my $i (0..($len - 1)) {
472 0         0 $rays->[$y+$i*$dy][$x+$i*$dx][$dir] = ['Enforced', 0, 1];
473 0         0 $rays->[$y+($i+1)*$dy][$x+($i+1)*$dx][($dir+4)%8] = ['Enforced', 0, 1];
474             }
475 0 0       0 return unless $rot;
476 0         0 my $dx1 = -$rot*$dy;
477 0         0 my $dy1 = $rot*$dx;
478 0         0 for my $i (0..$#ROTS) {
479 0 0       0 my @rot = ($i ? (2,3,1) : (1,2)); # Supported now: 0,1,2,3 (horizontal/vertical only)
480 0         0 @rot = @rot[0..($ROTS[$i]-1)];
481 0         0 for my $Rot (@rot) {
482 0 0       0 if ($Rot == 3) {
    0          
    0          
483 0         0 $rays->[$y+$i*$dy][$x+$i*$dx][($dir+3*$rot)%8][0] = 'ignore'; # 135° direction ($i > 0)
484 0         0 $rays->[$y+($i-1)*$dy+$dy1][$x+($i-1)*$dx+$dx1][($dir-$rot)%8][0] = 'ignore';
485             } elsif ($Rot == 2) {
486 0         0 $rays->[$y+$i*$dy][$x+$i*$dx][($dir+2*$rot)%8][0] = 'ignore'; # Perpendicular direction on ≥1
487 0         0 $rays->[$y+$i*$dy+$dy1][$x+$i*$dx+$dx1][($dir-2*$rot)%8][0] = 'ignore';
488             } elsif ($Rot == 1) {
489 0         0 $rays->[$y+$i*$dy][$x+$i*$dx][($dir+$rot)%8][0] = 'ignore'; # 45° direction on ≥3
490 0         0 $rays->[$y+($i+1)*$dy+$dy1][$x+($i+1)*$dx+$dx1][($dir-3*$rot)%8][0] = 'ignore';
491 0         0 } else { die "Rot=$Rot" }
492             }
493             }
494             }
495            
496             sub doRays ($$$$$$$$$) {
497 2     2 0 36 my($bm,$width,$height, $offs, $cnt, $cntmin, $near, $nearmin) = (shift, shift, shift, shift, shift, shift, shift, shift, shift);
498 2         6 my(@offs, @cnt, @cntmin, @near, @nearmin);
499 2         6 @offs = @$offs; @cnt = @$cnt; @cntmin = @$cntmin; @near = @$near; @nearmin = @$nearmin;
  2         7  
  2         5  
  2         6  
  2         5  
500 2         43 my @pixelsmin = map [@$_], @$bm; # deep copy
501 2         9 my($ER, @rays, @longedges, %seenlong, %inLong, %midLong) = (['']);
502            
503             DO_RAYS:
504 2         7 for my $ray_round (0,1) { # On the second round, some pixels may be decided to be insignificant, and removed
505 2         7 for my $y (1..$height) { # Inspect angular neighborhods in the directions from $offs
506 10         24 my(@r, @row) = [];
507 10         29 for my $x ( 1..$width ) {
508 82 100 50     300 push @r, [] and next unless my $o = $offs[$y][$x];
509 6         13 my $r = $rays[$y][$x];
510 6 50       14 my @o = grep { !$r->[$_] or $r->[$_][0] =~ /^[D\WP]/ } @$o; # 'Probable-curve' put here as an experiment XXX; Dense
  8         31  
511 6         11 my @rr;
512 6 50       18 @rr = @$r if $r;
513 6         26 @rr[ @o ] = @{ inspect_ray $x, $y, \@cnt, \@cntmin, $bm, \@pixelsmin, \@near, \@nearmin, \@o };
  6         31  
514 6         23 push @r, \@rr;
515             }
516 10         25 $rays[$y] = \@r;
517             }
518 2         6 my(@b_postpone, @g_postpone, @rem_postpone, @un_postpone, @protect, @rhombi, %rhombi, @extra_postpone);
519 2         6 for my $y (1..$height) { # 2nd order inspection: check identified dependencies (all dependencies must match;
520 10         43 for my $x ( 1..$width ) { # every dependency must be good in at least one direction)
521 82         127 for my $dir ( @{$offs[$y][$x]} ) {
  82         204  
522 8   50     24 my $ray = $rays[$y][$x][$dir] || next;
523 8         25 my($keep, $TO, $type, $rot, $checked, $DEP, $remove, $unignore, @rest) = (1, undef, @$ray);
524 8 100 66     82 next unless $DEP or @rest or $remove;
      66        
525 4 50 33     19 my @DEP = (($DEP and not ref $DEP->[0]) ? $DEP : ($DEP ? @$DEP : ()));
    50          
526 4         8 for my $ddep (@DEP) { # Switch to the first "alternative variant" with non-satisfied dependencies
527             # Preferred alternatives are grouped by a new-type $ddep->[0], and list of dependent points/dirs;
528             # the alternative is chosen out of those for which one of points has all dirs “good”,
529 0         0 $keep = 0;
530 0         0 for my $depPt (@$ddep[1..$#$ddep]) { # we are OR-ing over the dependencies: we keep if any one matches
531 0         0 my($KEEP,$X,$Y,@DIR) = (1, @$depPt);
532 0         0 for my $DIR (@DIR) { # we are AND-ing over the directions of a dependence: KEEP if all match
533 0 0       0 $KEEP = 0, last unless post_inspect_ray($X, $Y, $DIR, \@rays, $type); # Can be optimized by merging neighbors???
534             }
535 0 0       0 $keep = 1, last if $KEEP;
536             }
537 0 0 0     0 $TO = $ddep->[0] || '.', last unless $keep;
538             }
539 4 50 33     12 push @rem_postpone, $remove if $remove and $keep;
540 4 50 33     12 push @un_postpone, $unignore if $unignore and $keep;
541 4 50 33     29 push @g_postpone, [$y, $x, $dir, @rest] if @rest and $keep;
542 4 50       12 push @b_postpone, [$y, $x, $dir, $TO] unless $keep;
543 4 50 33     22 if ($keep and $type =~ /^[3r]/) {{ # 3fork3, rhombus
544 0         0 my($dx,$dy) = ($dx[$dir],$dy[$dir]);
  0         0  
545 0 0       0 next unless $rays[$y+$dy][$x+$dx][($dir+4)%8][0] =~ /^([dcfs])/; # part of a curve (maybe falsely fake) doubleray curve fake-curve serif
546 0 0       0 $rhombi{$x,$y,$dir}++ unless $1 eq 's'; # serif
547 0         0 push @rhombi, [$x, $y, $dir, $rot, $dx, $dy, "$1"];
548             }}
549 4 50 33     21 push @g_postpone, [$y, $x, $dir, [$TO]] if !$keep and $TO eq '´'; # 3fork3, rhombus; check for Q-joins
550             }
551             }
552             }
553 2         11 while (my $p = shift @rhombi) {
554 0         0 my($x, $y, $dir, $rot, $dx, $dy, $t) = @$p;
555 0         0 my $dir1 = ($dir+$rot)%8;
556 0         0 my($dx1,$dy1, @opp) = ($dx[$dir1],$dy[$dir1]);
557 0 0       0 unless (@opp = grep $rhombi{$x+$dx+$dx1,$y+$dy+$dy1,($_+4)%8}, $dir, $dir1) {
558 0 0 0     0 $rays[$y][$x][$dir][0] = 'Rhombus-force' if $t eq 's' and $cnt[$y+$dy1][$x+$dx1] <= 4; # As in д, and skip dense
559 0         0 next;
560             }
561 0 0       0 next if $t eq 's'; # Done for in-serif
562             # Should watch so that we do not break 0663,0d96,1ba7 ٣ ඖ ᮧ
563 0 0 0     0 if (!($dir%2) and $cnt[$y][$x] == 3 and ($rays[$y][$x][($dir+4)%8] || $ER)->[0] eq 'tail'
      0        
      0        
      0        
      0        
      0        
      0        
      0        
564             and $cnt[$y+$dy1][$x+$dx1] == 4 and ($rays[$y+$dy1][$x+$dx1][$dir] || $ER)->[0] eq 'doubleray'
565             and ($rays[$y+$dy+$dy1][$x+$dx+$dx1][$dir] || $ER)->[0] =~ /^[dc]/) { # Ддщц doubleray curve
566 0         0 $rays[$y][$x][$dir][0] = 'Rhombus-force';
567 0         0 next;
568             }
569 0 0 0     0 if ($cnt[$y+$dy+$dy1][$x+$dx+$dx1] == 3
      0        
570             and grep +(!($_%2) and ($rays[$y+$dy+$dy1][$x+$dx+$dx1][$_] || $ER)->[0] eq 'tail'), @opp) {
571             # Ддщц - on the other side. Detect which one is better. Should watch so that we do not break 0663,0d96,1ba7
572 0         0 my($good, $other) = ([$dir, $dx, $dy], [$dir1, $dx1, $dy1]); # default
573 0         0 for my $g ($other) { # no need to check for $good!
574 0 0       0 ($good, $other) = ($g, $good) if $rays[$y+$g->[2]][$x+$g->[1]][($g->[0]+4)%8][0] eq 'doubleray';
575             }
576 0 0 0     0 if ($cnt[$y+$good->[2]][$x+$good->[1]] == 4
      0        
577             and $rays[$y+$good->[2]][$x+$good->[1]][($good->[0]+4)%8][0] eq 'doubleray'
578             and $rays[$y][$x][($good->[0]+4)%8][0] =~ /^[dc]/) { # doubleray curve
579 0         0 $rays[$y][$x][$other->[0]][0] = $rays[$y+$other->[2]][$x+$other->[1]][($other->[0]+4)%8][0] = 'ignore';
580 0         0 push @extra_postpone, ['R', $y, $x, $good->[0]]; # may be changed to ' very soon; postpone until this
581 0         0 next;
582             }
583             }
584 0         0 $rays[$y][$x][$dir][0] = '2fork3';
585             add_longedge([$x, $y, $dir, $x+$dx+$dx1, $y+$dy+$dy1, $rot], \@longedges, \%seenlong, \%midLong, \%inLong)
586 0 0       0 unless $seenlong{$x, $y, $x+$dx+$dx1,$y+$dy+$dy1};
587             }
588 2         7 while (my $p = shift @b_postpone) {
589 0         0 my($y, $x, $dir, $what) = @$p;
590 0         0 $rays[$y][$x][$dir][0] = $what;
591             }
592 2         8 while (my $p = shift @un_postpone) {
593 0         0 my($x, $y, $dir) = @$p;
594 0         0 $protect[$y][$x][$dir]++
595             }
596 2         7 while (my $p = shift @g_postpone) {
597 4         12 my($Y, $X, $DIR, @p) = @$p;
598 4         9 for my $pp (@p) {
599             # warn "rays0 $rays[8][5][0] @$pp ", $rays[8][5][0] && "<$rays[8][5][0][0]>";
600             # warn "In g_postpone: (@$p)";
601 4 50       13 if ($pp->[0] =~ /^I((I)|1)?$/) { # (only fix '?' if II, and only in one direction unless I
602 0         0 (undef, my ($x, $y, @pp)) = @$pp;
603 0         0 for my $dir (@pp) {
604 0 0 0     0 next if $protect[$y][$x][$dir] or $2 and $rays[$y][$x][$dir][0] ne '?';
      0        
605 0         0 $rays[$y][$x][$dir][0] = 'ignore';
606 0 0       0 next if $1;
607 0         0 my $dx = $dx[$dir];
608 0         0 my $dy = $dy[$dir];
609 0         0 $rays[$y+$dy][$x+$dx][($dir+4)%8][0] = 'ignore';
610             }
611             next
612 0         0 }
613 4 50       22 force_line($pp, \@rays), next if $pp->[0] eq 'L';
614 4 50       13 if ($pp->[0] =~ /^E([Ef])?(m)?$/) { # Enforce a sane type (only on '?' if EE, on f if Ef)
615 0         0 (my($T, $m), undef, my ($x, $y, $dir)) = ($1, !!$2, @$pp);
616             # warn "t[0]=", ord $rays[$y][$x][$dir][0], "; ", ($rays[$y][$x][$dir][0] =~ /^[\WP]/), "; T=$rays[$y][$x][$dir][0]" if $m;
617 0 0       0 next unless $rays[$y][$x][$dir][0] =~ ($T ? ($T eq 'f' ? qr/^f/ : qr/^[?P]/) : qr/^[\WP]/); # '?' fake-curve Probable-curve
    0          
    0          
618 0         0 $rays[$y][$x][$dir][0] = 'Enforce';
619             # warn " -> t[0]=", ord $rays[$y][$x][$dir][0], "; ", ($rays[$y][$x][$dir][0] =~ /^[\WP]/), "; T=$rays[$y][$x][$dir][0]" if $m;
620             # $marked++ if $m;
621 0         0 next;
622             }
623 4 50       13 if (lc $pp->[0] eq 'n') { # Enforce notch
624 0         0 (undef, my ($x, $y, $dir)) = @$pp;
625 0         0 my $C = 2 + ($pp->[0] eq 'n');
626 0 0 0     0 next unless $cnt[$y][$x] == $C and $rays[$y][$x][$dir][0] eq '?';
627 0         0 $rays[$y][$x][$dir][0] = 'Enforce';
628 0         0 next;
629             }
630 4 50       11 if ($pp->[0] eq 'S') { # Enforce Sharp
631 0         0 (undef, my ($x, $y, $dir)) = @$pp;
632 0 0 0     0 next unless $cnt[$y][$x] == 4 and $rays[$y][$x][$dir][0] =~ /^[?fP´r]/; # '?' Probable-curve fake-curve rhombus '´'
633 0         0 $rays[$y][$x][$dir][0] = 'Enforce';
634 0         0 next;
635             }
636 4 50       11 if ($pp->[0] eq 'T') { # Enforce tip (on M etc.)
637 0         0 (undef, my($DIR, $rot)) = @$pp;
638             # warn "$X,$Y,$DIR";
639 0 0 0     0 next unless $cnt[$Y][$X] == 3 and $rays[$Y][$X][$DIR] and ($rays[$Y][$X][$DIR][0] || '') =~ /^t/; # tail
      0        
      0        
640 0         0 my $x = $X + $dx[$DIR];
641 0         0 my $y = $Y + $dy[$DIR];
642 0         0 my $dir = ($DIR+4)%8;
643 0 0 0     0 next unless $cnt[$y][$x] == 1 and $rays[$y][$x][$dir] and ($rays[$y][$x][$dir][0] || '') =~ /^d/; # doubleray
      0        
      0        
644 0         0 @{$rays[$y][$x][$dir]}[0,1] = ('MFork',-$rot); # was doubleray ____ TM
  0         0  
645 0         0 $rays[$Y][$X][$DIR][0] = 'Tail'; # was tail _/
646 0         0 next;
647             }
648 4 50       14 if ($pp->[0] =~ 'a') { # Check arrow backwards
649 0         0 my $t = $rays[$Y][$X][($DIR+4)%8][0];
650 0         0 push @extra_postpone, ['a', $Y, $X, $DIR, @$pp[1,2], $t];
651 0 0       0 next if $t =~ /^[dctNC]/; # doubleray, curve, tail, Near-corner or Corner-curve
652 0         0 $rays[$Y][$X][$DIR][0] = '…';
653 0         0 next;
654             }
655 4 50       30 if ($pp->[0] =~ 't') { # tail; check cedilla
656 4         11 my $dx = $dx[$DIR];
657 4         8 my $dy = $dy[$DIR];
658 4         11 my $T = $rays[$Y+$dy][$X+$dx][($DIR+4)%8];
659 4 50       25 next unless $T->[0] =~ /^c/;
660 0         0 my $rot = $T->[1];
661 0 0       0 next unless $rays[$Y][$X][($DIR+$rot+4)%8][0] =~ /^d/; # doubleray
662 0         0 my $dx1 = $dx[($DIR+$rot+4)%8];
663 0         0 my $dy1 = $dy[($DIR+$rot+4)%8];
664 0 0       0 next unless $cnt[$Y+$dy1][$X+$dx1] == 4;
665 0 0       0 next unless $rays[$Y+$dy1][$X+$dx1][($DIR+$rot+4)%8][0] =~ /^d/; # doubleray
666 0 0       0 next unless $cnt[$Y+2*$dy1][$X+2*$dx1] == 3; # See 6a81
667             # warn "... <$ER> #=$#{$rays[$Y+$dy1][$X+$dx1]} [@{$rays[$Y+$dy1][$X+$dx1]}] <$rays[$Y+$dy1][$X+$dx1][($DIR+2*$rot+4)%8]> x=", $X+$dx1, ", y=", $Y+$dy, ", dir=", ($DIR+2*$rot+4)%8;
668             # warn($rays->[10][7][6] ? "### <$rays->[10][7][6]> [@{$rays->[10][7][6]}] " . (defined $rays->[10][7][6][0]?'d':'u'):"###### not yet");
669 0 0 0     0 next unless ($rays[$Y+$dy1][$X+$dx1][($DIR+2*$rot+4)%8] || $ER)->[0] =~ /^e/; # elses-ray (opp fake-curve)
670 0 0 0     0 next unless ($rays[$Y+$dy1][$X+$dx1][($DIR+3*$rot+4)%8] || $ER)->[0] =~ /^f/; # fake-curve
671 0         0 my $dx2 = $dx[($DIR+2*$rot+4)%8];
672 0         0 my $dy2 = $dy[($DIR+2*$rot+4)%8];
673 0 0       0 next unless $cnt[$Y+$dy1+$dy2][$X+$dx1+$dy2] == 3;
674 0 0 0     0 next unless ($rays[$Y+$dy1+$dy2][$X+$dx1+$dx2][($DIR+2*$rot)%8] || $ER)->[0] =~ /^f/; # fake-curve
675 0         0 $rays[$Y+$dy1][$X+$dx1][($DIR+$rot+4)%8][0] = $rays[$Y+2*$dy1][$X+2*$dx1][($DIR+$rot)%8][0] = 'ignore';
676 0         0 $rays[$Y+$dy1][$X+$dx1][($DIR+2*$rot+4)%8][0] = 'Enforced';
677 0         0 $rays[$Y+$dy1+$dy2][$X+$dx1+$dx2][($DIR+2*$rot)%8][0] = 'curve';
678 0         0 next;
679             }
680 0 0       0 if ($pp->[0] =~ 'C') { # Check spurious connectors
681 0         0 my $dx = $dx[$DIR];
682 0         0 my $dy = $dy[$DIR];
683 0         0 my $T = $rays[$Y+$dy][$X+$dx][($DIR+4)%8];
684 0 0 0     0 next if $T->[0] !~ /^(c)/i or $midLong{2*$X+$dx,2*$Y+$dy};
685 0         0 my($opp, $good) = ($1);
686             # For curves (in both directions), check that going their intendend continuation (which is long) in opposite direction
687             # has another choice (is a Fork) that this (spurious!) line.
688 0 0       0 if ($opp eq 'c') {
689 0         0 my($seen, $arrows, $deg_corner) = ('', 0);
690 0         0 for my $C ([$X,$Y,($DIR + 4 + $T->[1])%8, $T->[1]], [$X+$dx,$Y+$dy,($DIR+$pp->[1])%8,$pp->[1]]) {
691 0         0 my($XX,$YY,$DD,$R) = @$C;
692 0         0 my $dx1 = $dx[$DD];
693 0         0 my $dy1 = $dy[$DD];
694             # warn "$X, $Y, $DIR; $XX, $YY, $DD, $R; $rays[$YY+$dy][$XX+$dx][($DD+4)%8] $rays[$YY][$XX][$DD]"
695             # unless defined $rays[$YY+$dy][$XX+$dx][($DD+4)%8] and defined $rays[$YY][$XX][$DD];
696             # Combination of 1 (Tail) and F is good (2af7, 0593)
697 0 0 0     0 $good = 1 unless $rays[$YY+$dy1][$XX+$dx1][($DD+4)%8][0] =~ /^([F°])/
      0        
      0        
      0        
      0        
698             and ($1 eq 'F' or $rays[$YY][$XX][($DD-3*$R)%8][0] =~ /^C/ and $deg_corner=1) # see 2aa1, 2af7
699             and $rays[$YY][$XX][$DD][0] =~ /^([cF1d])/ # Inserting d here requires test for 22, and hurts ῳ
700             and not ((my $m1 = $1) eq 'd' and $rays[$YY+$dy1][$XX+$dx1][$DD][0] =~ /^d/);
701 0 0       0 $seen .= $m1 unless $good; # Matchess succeeded!
702 0 0 0     0 $arrows++ if $rays[$YY][$XX][$DD][0] =~ /^d/ # doubleray
      0        
703             and $rays[$YY+$dy1][$XX+$dx1][$DD][0] =~ /^a/
704             and $rays[$YY+$dy1][$XX+$dx1][$DD][1] == -$R; # arrow 21f6 but not 222e
705             }
706 0 0 0     0 $good = 1 if !$good and $seen =~ /F1|1F|(11)/ and ($1 or not $deg_corner);
      0        
      0        
707 0 0       0 $good = 0 if $arrows == 2;
708             } else { # E.g., 2a85
709 0         0 for my $C ([$X,$Y,($DIR + 4 + 2*$T->[1])%8, 1, $T->[1]], [$X+$dx,$Y+$dy,($DIR+$pp->[1])%8, 0, $pp->[1]]) {
710 0         0 my($XX,$YY,$DD,$rev,$R) = @$C;
711 0         0 my $dx1 = $dx[$DD];
712 0         0 my $dy1 = $dy[$DD];
713 0 0       0 if ($rev) { # Went in the direction of 'Corner-curve'
714 0 0 0     0 $good = 1 unless $rays[$YY+$dy1][$XX+$dx1][($DD+4)%8][0] =~ /^C/
      0        
715             and $rays[$YY][$XX][$DD][0] =~ /^c/ and $rays[$YY][$XX][$DD][1] == $R;
716             } else {
717 0 0 0     0 $good = 1 unless $rays[$YY+$dy1][$XX+$dx1][($DD+4)%8][0] =~ /^°/
      0        
      0        
      0        
      0        
718             and ($rays[$YY][$XX][($DD-3*$R)%8] || $ER)->[0] =~ /^([1])/ # cC break a lot of stuff
719             and ($1 ne 'C'
720             or $rays[$YY][$XX][($DD-3*$R)%8][1] == $R)
721             and $rays[$YY][$XX][$DD][0] =~ /^([cd])/;
722             }
723             }
724             }
725 0 0       0 unless ($good) {
726 0         0 $rays[$Y][$X][$DIR][0] = '¢';
727 0         0 $opp =~ tr/cC/¢₡/;
728 0         0 $rays[$Y+$dy][$X+$dx][($DIR+4)%8][0] = $opp;
729             }
730 0         0 next;
731             }
732 0 0       0 if ($pp->[0] eq '´') { # Q-join
733 0         0 my $dx = $dx[$DIR];
734 0         0 my $dy = $dy[$DIR];
735 0         0 my $T = $rays[$Y+$dy][$X+$dx][($DIR+4)%8]; # stop if the opposite ray is already Enforce⸣d:
736 0   0     0 my $B = $T->[1] || 0;
737 0         0 my $dx0 = $dx[($DIR+$B)%8];
738 0         0 my $dy0 = $dy[($DIR+$B)%8];
739 0         0 my $dx1 = $dx[($DIR-$B)%8];
740 0         0 my $dy1 = $dy[($DIR-$B)%8];
741 0         0 my ($extra, @LOOP) = $nearmin[$Y+$dy1][$X+$dx1][($DIR-2*$B)%8];
742 0 0 0     0 if ($T->[0] eq 'Probable-curve') {
    0          
743 0 0 0     0 next unless (not @rem_postpone or $ray_round == 1) # Otherwise: triggered on Ӿ
      0        
      0        
      0        
744             and $rays[$Y-$dy0][$X-$dx0][($DIR+$B)%8][0] eq '´' # The last condition: OK on ɚӿޗ; false positive: ೫.
745             and not ($nearmin[$Y+$dy][$X+$dx][($DIR+2*$B)%8] and $nearmin[$Y-$dy0][$X-$dx0][($DIR+4)%8]);
746             # warn "X=$X, Y=$Y, DIR=$DIR, invROT = $B";
747 0         0 $rays[$Y][$X][$DIR][0] = 'Enforce'; # Was '´'
748 0         0 $rays[$Y+$dy][$X+$dx][($DIR+4)%8][0] = 'Enforce'; # Was 'Probable-curve'
749 0         0 $rays[$Y][$X][($DIR+4+$B)%8][0] = 'Enforce'; # Often is 'f....'
750 0         0 $rays[$Y-$dy0][$X-$dx0][($DIR+$B)%8][0] = 'Enforce'; # Was '´'
751             # $rays[$Y][$X][($DIR+$B+4)%8][0] =~ s/^\W.*/Enforce/ if $cntmin[$Y-$B*$dx1][$X+$B*$dy1] < 3; # length=1; Ȼ
752             # $marked = 1;
753 0 0       0 next unless $rays[$Y+$dy+$dy1][$X+$dx+$dx1][($DIR+4-$B)%8][0] eq '´';
754 0         0 $rays[$Y+$dy][$X+$dx][($DIR-$B)%8][0] =~ s/^\W.*/Enforce/;
755 0         0 $rays[$Y+$dy+$dy1][$X+$dx+$dx1][($DIR+4-$B)%8][0] = 'Enforce'; # Was '´'
756 0 0       0 next unless $cntmin[$Y+$dy1][$X+$dx1] <= 4 + !!$extra;
757 0 0       0 $LOOP[0]++ if $nearmin[$Y][$X][($DIR+4-$B)%8];
758 0 0       0 $LOOP[1]++ if $nearmin[$Y+$dy][$X+$dx][($DIR+$B)%8];
759             } elsif ($T->[0] eq '´' and $B == -$rays[$Y][$X][$DIR][1]) { # part of a convex curve
760             # Allow extra spurs coming in from outside/inside (see `Q´).
761 0         0 my @Ex = ($nearmin[$Y][$X][($DIR+2*$B)%8], $nearmin[$Y+$dy][$X+$dx][($DIR+2*$B)%8]);
762             # warn "[@$T], inC=$cntmin[$Y+$dy1][$X+$dx1], afterTargC=$cntmin[$Y+$dy+$dy1][$X+$dx+$dx1], preC=$cntmin[$Y-$B*$dx1][$X+$B*$dy1]";
763 0 0 0     0 next unless $cntmin[$Y+$dy1][$X+$dx1] <= 4 + !!$extra
      0        
764             and $cntmin[$Y+$dy+$dy1][$X+$dx+$dx1] <= 3 and $cntmin[$Y-$B*$dx1][$X+$B*$dy1] <= 3;
765 0 0 0     0 next unless $cntmin[$Y][$X] <= 3 + !!$Ex[0] and $cntmin[$Y+$dy][$X+$dx] <= 3 + !!$Ex[1];
766 0         0 $rays[$Y][$X][$DIR][0] = 'Enforce';
767 0         0 $rays[$Y+$dy][$X+$dx][($DIR+4)%8][0] = 'Enforce';
768 0         0 $rays[$Y-$B*$dx1][$X+$B*$dy1][($DIR+$B)%8][0] = 'Enforce';
769 0 0       0 $rays[$Y][$X][($DIR+$B+4)%8][0] =~ s/^\W.*/Enforce/ if $cntmin[$Y-$B*$dx1][$X+$B*$dy1] < 3; # length=1; Ȼ
770 0         0 $rays[$Y+$dy+$dy1][$X+$dx+$dx1][($DIR-$B+4)%8][0] = 'Enforce';
771 0 0       0 $rays[$Y+$dy][$X+$dx][($DIR-$B)%8][0] =~ s/^\W.*/Enforce/ if $cntmin[$Y+$dy+$dy1][$X+$dx+$dx1] < 3; # length=1; Ȼ
772 0 0       0 next if $extra;
773             } else {
774 0         0 next;
775             } # Emulate a double-width stroke:
776 0 0       0 for my $semiEdge (($LOOP[0] ? ([$Y, $X, ($DIR-$B)%8], [$Y+$dy1, $X+$dx1, ($DIR+4-$B)%8])
    0          
777             : ([$Y+$dy1, $X+$dx1, ($DIR+4)%8], [$Y+$dy1-$dy, $X+$dx1-$dx, $DIR])),
778             ($LOOP[1] ? ([$Y+$dy, $X+$dx, ($DIR+$B+4)%8], [$Y+$dy1, $X+$dx1, ($DIR+$B)%8])
779             : ([$Y+$dy1, $X+$dx1, $DIR], [$Y+$dy1+$dy, $X+$dx1+$dx, ($DIR+4)%8]))) {
780             # $rays[$semiEdge->[0]][$semiEdge->[1]][$semiEdge->[2]][0] =~ s/^\W.*/Enforce/;
781 0         0 $rays[$semiEdge->[0]][$semiEdge->[1]][$semiEdge->[2]][0] = 'Enforce'; # Otherwise would not be considered simple due to ??
782             }
783 0 0       0 for my $semiEdge (($LOOP[0] ? () : ([$Y, $X, ($DIR-$B)%8], [$Y+$dy1, $X+$dx1, ($DIR+4-$B)%8])),
    0          
784             ($LOOP[1] ? () : ([$Y+$dy, $X+$dx, ($DIR+$B+4)%8], [$Y+$dy1, $X+$dx1, ($DIR+$B)%8]))) {
785 0         0 $rays[$semiEdge->[0]][$semiEdge->[1]][$semiEdge->[2]][0] =~ s/^\w.*/Ignored/
786             # and $marked++;
787             }
788 0         0 next;
789             }
790 0         0 die "Unknown postpone action: <$pp->[0]>"
791             }
792             # $rays[$y][$x][$dir][0] = $what;
793             }
794 2         7 while (my $p = shift @extra_postpone) {
795 0         0 my($type, $Y, $X, $DIR, @p) = @$p;
796 0 0       0 if ($type eq 'a') { # Check for 'a' on the other side of the arrow
797 0         0 my $x = $X + 2*$dx[($DIR+$p[0])%8];
798 0         0 my $y = $Y + 2*$dy[($DIR+$p[0])%8];
799 0         0 my $dir = ($DIR-2*$p[0])%8;
800 0 0 0     0 $rays[$Y][$X][$DIR][0] = ($p[1] ? 'x-arrow' : '…')
    0 0        
801             unless $rays[$y][$x][$dir][0] =~ /^a/ and (not $p[1] or $rays[$y][$x][($dir+4)%8][0] eq $p[2]);
802             }
803 0 0       0 if ($type eq 'R') { # Check for 'a' on the other side of the arrow
804 0         0 $rays[$Y][$X][$DIR][0] = 'Rhombus-force';
805             }
806             }
807 2 50       10 last DO_RAYS unless @rem_postpone;
808 0         0 my @SEEN;
809 0         0 while (my $r = shift @rem_postpone) {
810 0         0 my($x, $y) = @$r;
811 0 0       0 remove_px($x, $y, \@cntmin, \@pixelsmin, \@nearmin, \@offs) unless $SEEN[$y][$x]++;
812             }
813             } # end DO_RAYS
814 2         6 for my $y (1..$height) { # In a pair '?'/'f', change '?' to 'Ignore'
815 10         21 for my $x ( 1..$width ) {
816 82 50       192 next unless my $RAYS = $rays[$y][$x];
817 82 100       184 next unless @$RAYS;
818 6         19 for my $dir (@{$offs[$y][$x]}) {
  6         14  
819 8 50       30 next unless $RAYS->[$dir][0] =~ /^(f)/i; # fake-curve/Fork
820 0 0 0     0 next if (my $code = $1) eq 'F' and $cnt[$y][$x] != 1; # Sharp corners (as in V) may result in fork with |stem|=1
821 0         0 my $x1 = $x + $dx[$dir];
822 0         0 my $y1 = $y + $dy[$dir];
823 0         0 my $dir1 = ($dir+4)%8;
824 0 0 0     0 $rays[$y1][$x1][$dir1][0] =~ s/^[?1P].*/Tail/ and (@{$RAYS->[$dir]}[0,1] = ('MFork',0)), next # symmetric MFork
  0         0  
825             if $code eq 'F'; # '?', 'Probable-curve', '1Spur'
826 0         0 $rays[$y1][$x1][$dir1][0] =~ s/^[?P""].*/Ignore/; # '?', 'Probable-curve', '"'
827             }
828             }
829             }
830 2 50 33     24 die '$ER corrupted' unless @$ER == 1 and $ER->[0] eq '';
831 2         31 [\@rays, \@longedges, \%seenlong, \%inLong, \%midLong];
832             }
833            
834             sub do_Simple_and_edges ($$$$$$$$$) {
835 2     2 0 30 my($ER, $width, $height, $RAYS, $offs, $cnt, $longedges, $seenlong, $inLong, $midLong)
836             = ([''], shift, shift, shift, shift, shift, shift, shift, shift, shift);
837 2         8 my @rays = @$RAYS;
838 2         28 my(@Simple, @simpleray, @edge, @cntedge, @lastedge, @update); # Simple points/rays; decided edges
839 2         8 for my $y (1..$height) { # Identify simple points/rays
840 10         23 for my $x ( 1..$width ) {
841 82 50       226 next unless my $RAYS = $rays[$y][$x];
842 82 100       185 next unless @$RAYS; # Contamination???
843 6 50       12 $Simple[$y][$x] = 1, next unless grep { $RAYS->[$_][0] =~ /^[D\WP]/ } @{$offs->[$y][$x]}; # Dense; junk
  8         41  
  6         15  
844             }
845             }
846 2         6 for my $y (1..$height) { # In a pair '?'/'e' with simple neighbor, change '?' to 'Ignore'
847 10         21 for my $x ( 1..$width ) {
848 82 50       177 next unless my $RAYS = $rays[$y][$x];
849 82 100       181 next unless @$RAYS;
850 6 50       17 next unless $Simple[$y][$x];
851 6         9 for my $dir (@{$offs->[$y][$x]}) {
  6         16  
852 8 50       26 next unless $RAYS->[$dir][0] =~ /^e/;
853 0         0 my $x1 = $x + $dx[$dir];
854 0         0 my $y1 = $y + $dy[$dir];
855 0         0 my $dir1 = ($dir+4)%8;
856 0         0 $rays[$y1][$x1][$dir1][0] =~ s/^[?P""].*/Ignore/; # 'Probable-curve', '?' '"'
857             }
858             }
859             }
860             #### warn "... <@{$rays[7][2][0]||['undef']}> <@{$rays[7][2][1]||['undef']}>";
861 2         6 for my $y (1..$height) { # Identify simple points/rays
862 10         21 for my $x ( 1..$width ) {
863 82 50       176 next unless my $RAYS = $rays[$y][$x];
864 82 100       207 next unless @$RAYS; # Contamination???
865             # next if $Simple[$y][$x];
866             # Recalc simple:
867 6 50       12 $Simple[$y][$x] = 1, next unless grep { $RAYS->[$_][0] =~ /^[D\WP]/ } @{$offs->[$y][$x]}; # Dense; junk; Probable-curve
  8         31  
  6         13  
868             FIND_GOOD:
869 0         0 for my $dir ( @{$offs->[$y][$x]} ) { # For non-simple vertices, find simple directions (it+neighbors non-dense/junk)
  0         0  
870 0 0       0 next if $RAYS->[$dir][0] =~ /^[D\WP]/;
871 0         0 for my $rot (1, -1) { # Skip if closest angular neighbor is bad (Dense/Probable)
872             #### warn "miss dir: (x,y,dir,rot)=($x,$y,$dir,$rot); lst=$#$RAYS, ", grep defined $RAYS->[$_], 0..$#$RAYS unless $RAYS->[($dir+$rot)%8];
873 0 0 0     0 next FIND_GOOD if ($RAYS->[($dir+$rot)%8] || $ER)->[0] =~ /^[D\WP]/ and not $RAYS->[$dir][0] =~ /^[BE]/; # Btail/Enforce are checked already!
      0        
874             }
875 0         0 $simpleray[$y][$x][$dir]++;
876             # warn "Simple RAY at x=$x y=$y dir=$dir\n";
877             }
878             }
879             }
880             # warn "Simple RAY ====\n";
881 2         15 for my $y (1..$height) { # Identify simple edges (should be simple in both directions, and of non-fake types)
882 10         21 for my $x ( 1..$width ) {
883 82 50       179 next unless my $RAYS = $rays[$y][$x];
884 82 100       209 my $ok = $Simple[$y][$x]
885             or my $smpl = $simpleray[$y][$x]; # Not cleared if $ok; we do not care
886 82         143 for my $dir ( @{$offs->[$y][$x]} ) {
  82         197  
887 8 100       22 last if $dir > 3; # Inspect one end only
888 4         7 my $semi_bad = 0;
889 4 0 33     17 next unless $ok or $smpl->[$dir] or 2 >= $cnt->[$y][$x] and $RAYS->[$dir][0] eq '°' and ++$semi_bad; # ° = not confirmed Fork
      0        
      0        
      0        
890 4         11 my $x1 = $x + $dx[$dir];
891 4         6 my $y1 = $y + $dy[$dir];
892 4         10 my $dir1 = ($dir+4)%8;
893 4 0 33     12 next unless $Simple[$y1][$x1] or $simpleray[$y1][$x1][$dir1]
      0        
      0        
      0        
894             or 2 >= $cnt->[$y1][$x1] and $rays[$y1][$x1][$dir1][0] eq '°' and ++$semi_bad < 2;
895             # warn "Candidate for °-ray at x=$x y=$y dir=$dir\n" if $semi_bad;
896             # doubleray, curve, notch, serif, (B)tail, 1Spur, [M]Fork, Enforced, Sharp, m-joint, Near-corner, Corner-curve, bend-sharp,
897             # Tail, A/arrow, Rhombus-force
898             # Omit: [Zh/K-]fake-curve, Ignore, rhombus, i, fork4, elses-ray, 3fork3, 2fork3, 4fork, xFork, Dense, x-arrow, \W-junk
899 4 50       26 next unless 2 - $semi_bad == grep /^[dcnstB1FMESmNCbTAaR]/, $RAYS->[$dir][0], $rays[$y1][$x1][$dir1][0];
900 4         16 add_edge([$x, $y, $dir, $x1, $y1], \@edge, \@cntedge, \@lastedge); # Good only for 1-edge pixels
901             }
902             }
903             }
904 2         5 my %candidates_way_out;
905 2         5 for my $y (1..$height) { # Identify singletons with a valid way out (one d in a group of d,e,f,K)
906 10         21 for my $x ( 1..$width ) {
907 82 100 50     245 next unless $Simple[$y][$x] and ($cntedge[$y][$x] || 0) <= 1; # If already have two edges, do not try to find complicated...
      100        
908             # die "x=$x, y=$y, Simple=$Simple[$y][$x], rays=<@{$rays[$y][$x]}>" unless defined $cnt->[$y][$x];
909 4 50       13 next if $cnt->[$y][$x] > 6; # Give up if too many neighbors
910 4         7 my @Neighbors = @{$offs->[$y][$x]}; # Deep copy
  4         12  
911 4         14 push @Neighbors, shift @Neighbors while $Neighbors[-1] == ($Neighbors[0] + 7)%8; # Rotate to start of a run
912 4         10 my(@res, $bad, @good, @maybe, %forced, $L, @Zh);
913 4         8 my $RAYS = $rays[$y][$x];
914             # Before, we assumed that at most one edge is present
915 4 50       13 $L = $lastedge[$y][$x] if $cntedge[$y][$x]; # Connect only as a curve continuation, and only if it continues back
916 4         10 for my $d ( 0..$#Neighbors ) {
917 4         8 my $dir = $Neighbors[$d];
918 4 50 50     57 (!$bad and ( @Zh == 1 and @good == 1 and push @res, [$Zh[0]]
919             or @good == 1 and push @res, [$good[0]]
920             or @maybe == 1 and push @res, [$maybe[0], 1])), # Finish previous group
921             $bad=0, @good = @maybe = @Zh = () # Start processing a new group
922             if $dir != ($Neighbors[$d-1] + 1)%8; # if $dir is after a gap
923 4 50       19 $bad++, next unless $RAYS->[$dir][0] =~ /^[dKZfIeiFMxR]/; # doubleray,[Zh/K-]fake-curve,Ignore,elses-ray,ignore,[M]Fork,x-arrow,Rhombus-force
924 4   33     19 my $sharp_angle = (defined $L and abs(4 - abs($L-$dir)) >= 2);
925 4 50 33     22 push(@good, $dir), next
926             if $RAYS->[$dir][0] =~ /^[dR]/ and not $sharp_angle; # Pick up doubleray, Rhombus-force
927 4 0 33     22 $forced{$dir}++, push(@maybe, $dir), next
      33        
928             if $RAYS->[$dir][0] =~ /^[FM]/ and defined $L and not $sharp_angle; # [M]Fork; pairs of Fc should end here...
929 4 50       14 $forced{$dir}++, push(@Zh, $dir), next if $RAYS->[$dir][0] =~ /^Z/; # special-case Zh-joint
930 4         8 my $x1 = $x + $dx[$dir];
931 4         38 my $y1 = $y + $dy[$dir];
932 4 50       13 my $rays1 = $rays[$y1][$x1] or next;
933 4 50       13 my $R = $rays1->[($dir+4)%8] or next;
934 4 50 33     16 $forced{$dir}++, push(@good, $dir), next if $RAYS->[$dir][0] =~ /^e/ and $R->[0] =~ /^d/; # reversed doublerays
935 4 50 33     18 $forced{$dir}++, push(@maybe, $dir), next if $RAYS->[$dir][0] =~ /^[fK]/ and $R->[0] =~ /^d/; # reversed doublerays
936             }
937 4 50 33     31 !$bad and ( @Zh == 1 and @good == 1 and push @res, [$Zh[0]]
      33        
      33        
      33        
      50        
      33        
938             or @good == 1 and push @res, [$good[0]]
939             or @maybe == 1 and push @res, [$maybe[0], 1]);
940             # warn "c=$c, x=$x, y=$y ==> ways out @res (out of @Neighbors): ", join '|', map $RAYS->[$_][0], @Neighbors;
941 4 50       11 next if @res > 2; # Do not get too carried away...
942 4 50       13 if ($cntedge[$y][$x]) { # Connect only as a curve continuation, and only if it continues back
943 4         7 @res = grep 1 >= abs(4 - abs($L-$_->[0])), @res; # i.e., almost opposite
944             # Maybe if two are left, chose by good vs maybe?
945 4         8 if (0 and @res > 1) {{ # does not actually change anything...
946             my @r = grep !$_->[1], @res or last;
947             @r < @res or last;
948             @res = @r;
949             }}
950             # warn "filtered: (@res)\n";
951             }
952 4 50       43 $candidates_way_out{$y,$x} = {map {+( $_->[0] => [$forced{$_->[0]}, @$_] )} @res} if @res;
  0         0  
953             }
954             }
955 2         6 for my $y (1..$height) { # Finish identifying singletons with a valid way out (one d in a group of d,e,f,K)
956 10         21 for my $x ( 1..$width ) {
957 82 50       226 next unless my $cand = $candidates_way_out{$y,$x};
958 0         0 my @ways = values %$cand;
959 0         0 my @res;
960             # Before, we assumed that at most one edge is present
961             # my $L = $cntedge[$y][$x] and $lastedge[$y][$x]; # Connect only as a curve continuation, and only if it continues back
962 0 0       0 if ($cntedge[$y][$x]) { # Connect only as a curve continuation, and only if it continues back
963             # Do not connect to something which is not a simple edge — or at least doubleray or curve!
964 0         0 for my $d (@ways) {
965 0         0 my $dir = $d->[1];
966 0 0       0 push(@res, $dir), next if $d->[0]; # forced
967 0         0 my $x1 = $x + $dx[$dir];
968 0         0 my $y1 = $y + $dy[$dir];
969             my $good = ($edge[$y1][$x1][$dir] or $rays[$y1][$x1][$dir][0] =~ /^[dcR]/ # doubleray curve Rhombus-force
970 0   0     0 or $candidates_way_out{$y1,$x1}{$dir});
971 0 0       0 unless ($good) {
972 0         0 my $x2 = $x + 2*$dx[$dir];
973 0         0 my $y2 = $y + 2*$dy[$dir];
974 0         0 my $dir2 = ($dir+4)%8;
975 0         0 $good = $candidates_way_out{$y2,$x2}{$dir2};
976             }
977 0 0       0 push @res, $dir if $good;
978             }
979 0 0       0 last if @res > 1;
980             } else {
981 0         0 @res = map $_->[1], @ways;
982             }
983             # warn "filtered:: (@res)\n";
984 0         0 push @update, [$x,$y,@res];
985             }
986             }
987 2         5 my %updated;
988 2         8 while (my $u = shift @update) {
989 0         0 my($x, $y, @res) = @$u;
990 0         0 for my $dir (@res) {
991 0         0 my $x1 = $x + $dx[$dir];
992 0         0 my $y1 = $y + $dy[$dir];
993 0         0 my $dir1 = ($dir+4)%8;
994 0 0 0     0 next if $updated{$x,$y,$dir}++ or $updated{$x1,$y1,$dir1}++;
995 0         0 add_edge([$x, $y, $dir, $x1, $y1], \@edge, \@cntedge, \@lastedge);
996             #warn "Update: $x,$y --> $x1,$y1\n";
997             }
998             }
999 2         6 for my $e (@$longedges) { # If a prefered way is found elsewhere, replace longedge by the prefered way
1000 0 0 0     0 next if not ref $e and $e eq 'erased';
1001 0         0 my($x, $y, $x1,$y1, $offset, $dir, $rot) = @$e;
1002 0         0 my $dir0 = ($dir+$rot)%8;
1003 0         0 my @atBEG = grep $edge[$y][$x][$_], $dir, $dir0;
1004 0         0 my @atEND = grep $edge[$y1][$x1][($_+4)%8], $dir, $dir0;
1005 0 0 0     0 next unless @atBEG or @atEND;
1006 0         0 my @add; # Had a longedge since couldn’t choose 1 of 2 ways around a rhombus; looks like something made a preference…
1007 0 0 0     0 unless (@atBEG and @atEND) { # If have a joiner on both sides, may just drop the longedge altogether
1008 0         0 my @have = (@atBEG, @atEND); # actually, one of them
1009 0 0       0 next if @have == 2; # XXX It is not clear what to add, so do not drop! ???
1010 0   0     0 my $DIR = ($dir + (($have[0] == $dir) && $rot))%8; # Add $dir+$rot on the OTHER side.
1011 0         0 my($dx,$dy) = ($dx[$DIR],$dy[$DIR]);
1012 0 0       0 if (@atEND) {
1013 0         0 @add = [$x, $y, $DIR, $x + $dx, $y + $dy];
1014             } else {
1015 0         0 @add = [$x1, $y1, ($DIR+4)%8, $x1 - $dx, $y1 - $dy];
1016             }
1017             }
1018 0         0 add_edge($_, \@edge, \@cntedge, \@lastedge) for @add;
1019 0         0 clear_longedge([$x, $y, $x1, $y1, $offset], $longedges, $seenlong, $midLong, $inLong);
1020             }
1021 2 50 33     18 die '$ER corrupted' if $ER and (@$ER != 1 or $ER->[0] ne '');
      33        
1022 2         43 [\@edge, \@cntedge, \@lastedge, \@rays, $longedges, $seenlong, $midLong, $inLong, \@Simple];
1023             }
1024            
1025             sub find_blobs ($$$$$$;$$) {
1026 4     4 0 16 my($blob, $width, $height, $pixels, $cntedge, $offs, $lastedge, $skip, $c) = (shift, shift, shift, shift, shift, shift, shift, shift, 0);
1027 4         13 $blob->[0] = [];
1028 4         12 for my $y (1..$height) {
1029 20         49 $blob->[$y] = [];
1030 20         41 for my $x ( 1..$width ) {
1031 164 100       373 next unless $pixels->[$y][$x];
1032 12 50       30 $blob->[$y][$x] = 1, $c++ unless $cntedge->[$y][$x];
1033             }
1034             }
1035 4         11 push @$blob, [];
1036 4         8 my @doblob;
1037 4 100       11 if ($lastedge) { # Add "better consider the same as blob" non-blob pixels
1038 2         5 for my $y (1..$height) {
1039 10         18 for my $x ( 1..$width ) {
1040 82 100 100     404 next if $blob->[$y][$x] or ($cntedge->[$y][$x] || 0) != 1;
      66        
1041 4         11 my $D = ($lastedge->[$y][$x] + 4)%8;
1042 4 50       17 next unless $blob->[$y + $dy[$D]][$x + $dx[$D]];
1043 0         0 my($C, $CC);
1044 0 0       0 for my $rot ( 1, -1, ($D % 2 ? (): (-2,2)) ) { # 22b6 ⊶
1045 0 0       0 $CC++, last if $blob->[$y + $dy[($D+$rot)%8]][$x + $dx[($D+$rot)%8]];
1046             }
1047 0         0 for my $dir ( @{$offs->[$y][$x]} ) {
  0         0  
1048 0 0 0     0 $C++, last if $dir != $D and $blob->[$y + $dy[$dir]][$x + $dx[$dir]];
1049             }
1050 0 0 0     0 push(@doblob, [$y,$x]), $c++, ($CC or $marked++) if $C and not $skip->{$y,$x};
      0        
1051             }
1052             }
1053             }
1054 4         9 $blob->[$_->[0]][$_->[1]]++ for @doblob;
1055 4         12 for my $y (1..$height) { # Replace 1 by 1 + count of neighbor blobs
1056 20 50       45 next unless $blob->[$y];
1057 20         36 for my $x ( 1..$width ) {
1058 164 50       376 next unless $blob->[$y][$x];
1059 0         0 for my $dir ( @{$offs->[$y][$x]} ) {
  0         0  
1060 0 0       0 $blob->[$y][$x]++ if $blob->[$y + $dy[$dir]][$x + $dx[$dir]];
1061             }
1062             }
1063             }
1064 4         13 $c;
1065             }
1066            
1067             sub nnn_do_Simple_and_edges ($$$$$$$) {
1068 2     2 0 28 my($width, $height, $offs, $pixels, $edge, $cntedge,,$lastedge)
1069             = (shift, shift, shift, shift, shift, shift, shift);
1070 2         7 my($do_more, @blob, @clearEdge, %suspectShaft, %skipExtraBlob) = 1;
1071 2         9 my $blobs = find_blobs(\@blob, $width, $height, $pixels, $cntedge, $offs);
1072 2   33     8 while ($blobs and $do_more) { # clear edges with two “noisy” surroundings
1073 0         0 for my $y (0..$#$edge) {
1074 0 0       0 next unless $edge->[$y];
1075 0         0 for my $x ( 0..$#{ $edge->[$y] } ) {
  0         0  
1076 0 0       0 next unless $edge->[$y][$x];
1077 0         0 for my $dir ( 0..3 ) { # Do only once per edge
1078 0 0       0 next unless $edge->[$y][$x][$dir ];
1079 0         0 my $x1 = $x + (my $dx = $dx[$dir]);
1080 0         0 my $y1 = $y + (my $dy = $dy[$dir]);
1081 0 0       0 if ($dir % 2) {
1082 0         0 my(@CC, $CC, $clear);
1083 0         0 for my $rot ( -1, 1 ) { # Three big, bad blobs on the same side of an edge
1084 0         0 my($dx1, $dy1) = ($rot*$dy, -$rot*$dx);
1085 0 0       0 my($dx2, $dy2, $c, $C) = (($dy==$dy1 ? (0, $dy1) : ($dx1, 0)), 0, 0); # dot product with $dxy
1086             # Go in the natural order of 3 neighbors (projection on $dxy):
1087 0 0       0 my @DD = (($dy==$dy1 ? ([$y, $x+$dx1], [$y+$dy1,$x]) : ([$y+$dy1,$x], [$y, $x+$dx1])), [$y1+$dy2,$x1+$dx2]);
1088 0         0 for my $DD (0..2) {
1089 0         0 my $D = $DD[$DD];
1090 0 0 0     0 $CC[$DD]++, $C++ if ($blob[$D->[0]][$D->[1]] || 0) >= 3 - ($DD==1); # More forgiving for middle; 0909
1091 0 0       0 $c++ if $pixels->[$D->[0]][$D->[1]];
1092             }
1093 0 0 0     0 $clear++ and last if $c == 3 and $C >= 2;
      0        
1094 0         0 $CC += $C
1095             } # This gives reasonable (?) results
1096             # warn "$c: diag blob? x,y=$x,$y,$dir $clear CC=$CC <@CC>" if $CC >= 2;
1097 0 0 0     0 push @clearEdge, [$x, $y, $dir, $x+$dx, $y+$dy] and last if $clear or $CC >= 3 and $CC[1] and ($CC[0] or $CC[2]);
      0        
      0        
      0        
      0        
1098             # if ($blob[$y][$x+$dx1] || 0) >= 3 and ($blob[$y+$dy1][$x] || 0) >= 3 and ($blob[$y1+$dy2][$x1+$dx2] || 0) >= 3;
1099             } else {
1100 0         0 my($tot, $done, %neigh, $lastN, $lastR, $lastDx, $lastDy) = (0);
1101 0         0 for my $rot ( -1, 1 ) { # Two big, bad blobs on the same side of an edge
1102 0         0 my($dx1, $dy1) = (-$rot*$dy, $rot*$dx);
1103 0   0     0 $neigh{$rot} = [($blob[$y+$dy1][$x+$dx1] || 0) >= 3, ($blob[$y1+$dy1][$x1+$dx1] || 0) >= 3];
      0        
1104 0   0     0 $neigh{$rot}[$_] and ++$tot and ($lastN, $lastR, $lastDx, $lastDy) = ($_, $rot, $dx1, $dy1) for 0, 1;
      0        
1105 0 0 0     0 ++$done and push @clearEdge, [$x, $y, $dir, $x+$dx, $y+$dy] and last if $neigh{$rot}[0] and $neigh{$rot}[1];
      0        
      0        
1106             }
1107 0 0 0     0 if (!$done and $cntedge->[$y][$x] == 1 and $cntedge->[$y1][$x1] == 1) { # Detect bold arrow tips/barbs
      0        
1108 0 0 0     0 ++$done and push @clearEdge, [$x, $y, $dir, $x+$dx, $y+$dy] if grep 2 == $neigh{1}[$_] + $neigh{-1}[$_], 0, 1;
1109 0 0 0     0 if (!$done and $tot == 1) { # fake serifs near blobs
1110 0         0 my($X, $Y, $D) = ($x, $y);
1111 0 0       0 if ($lastN) {
1112 0         0 ($x, $y, $D) = ($x1, $y1, $dir);
1113             } else {
1114 0         0 ($dx, $dy, $D, $lastR) = (-$dx, -$dy, ($dir+4)%8, -$lastR);
1115             }
1116 0 0 0     0 if (($blob[$y+$dy][$x+$dx] || 0) >= 3 and ($blob[$y+2*$lastDy][$x+2*$lastDx] || 0) >= 3
      0        
      0        
      0        
      0        
      0        
1117             and ($blob[$y+$dy+$lastDy][$x+$dx+$lastDx] || 0) >= 3 and not $blob[$y+$dy-$lastDy][$x+$dx-$lastDx]) {
1118 0         0 push @clearEdge, [$X, $Y, $dir, $x1, $y1];
1119             # warn sprintf "barb: $X, $Y, $dir, $x1, $y1 (%d %d %d) $lastN $lastR", $y+2*$lastDy,$x+2*$lastDx,($D+3*$lastR)%8;
1120 0         0 $suspectShaft{$y+2*$lastDy,$x+2*$lastDx,($D+3*$lastR)%8}++;
1121             }
1122             }
1123             }
1124             }
1125             }
1126             }
1127             }
1128             # warn "remove: @$_[0..2]" for @clearEdge;
1129             # @clearEdge = ();
1130 0         0 $do_more = @clearEdge;
1131 0         0 clear_edge($_,$edge,$cntedge,$lastedge) for @clearEdge; # [$x, $y, $dir, $x1, $y1]
1132 0         0 @clearEdge = ();
1133             # my $rep;
1134 0         0 for my $K (keys %suspectShaft) {
1135 0 0       0 last if $suspectShaft{$K} != 2;
1136 0         0 my($y, $x, $dir) = split /$;/o, $K;
1137             # warn "rep shafts" unless $rep++;
1138             # warn "shaft ($c) $x $y, $dir <$K> $suspectShaft{$K}";
1139 0         0 my($x1, $y1) = ($x+$dx[$dir], $y+$dy[$dir]);
1140 0 0 0     0 last if ($cntedge->[$y1][$x1] || 0) != 1 or $lastedge->[$y1][$x1] != $dir;
      0        
1141 0         0 add_edge([$x, $y, $dir, $x1, $y1], $edge, $cntedge, $lastedge);
1142 0         0 $do_more++;
1143 0         0 $skipExtraBlob{$y,$x}++;
1144             }
1145 0         0 %suspectShaft = ();
1146 0         0 $blobs = find_blobs(\@blob, $width, $height, $pixels, $cntedge, $offs);
1147             # warn "blobs: $blobs ($do_more edges removed)";
1148             }
1149 2         15 [$edge, $cntedge, $lastedge, $blobs, \@blob, \%skipExtraBlob];
1150             }
1151            
1152             sub nnn0_do_Simple_and_edges ($$$$$$$$) {
1153 2     2 0 27 my($width, $height, $edge, $cntedge,,$lastedge, $rays, $inLong, $blob)
1154             = (shift, shift, shift, shift, shift, shift, shift, shift);
1155 2         5 my @edgeAdd;
1156 2         8 for my $y (1..$height) {
1157 10         21 for my $x ( 1..$width ) {
1158 82 100 100     345 next unless 1 == ($cntedge->[$y][$x] || 0) and not $inLong->{$x,$y};
      66        
1159 4         10 my $dir = $lastedge->[$y][$x];
1160 4         10 my $x1 = $x - (my $dx = $dx[$dir]);
1161 4         9 my $y1 = $y - (my $dy = $dy[$dir]);
1162 4 50       15 next if $inLong->{$x1,$y1};
1163 4 100       11 if ($dir < 4) { # symmetric operations
1164 2 50 50     13 push @edgeAdd, [$x1, $y1, $dir, $x, $y]
      33        
1165             if 1 == ($cntedge->[$y1][$x1] || 0) and $lastedge->[$y1][$x1] == ($dir+4)%8; # end-to-end edges
1166             }
1167 4         8 my $r;
1168 4 0 50     29 push @edgeAdd, [$x1, $y1, $dir, $x, $y] # Not good for 210a 2274 fffd
      0        
      33        
      33        
      0        
      0        
      0        
      33        
1169             if 1 == ($blob->[$y1][$x1] || 0) # blob singleton
1170             or !($dir & 0x1) and 1 == ($cntedge->[$y1][$x1] || 0) # 04fa
1171             and 2 == abs(($lastedge->[$y1][$x1] - $dir)%8 - 4) # perpendicular
1172             and $rays->[$y][$x][$dir][0] =~ /^([tBdcN])/ # (B)tail doubleray curve Near-corner
1173             and ($1 ne 'c' or ($r = $rays->[$y][$x][$dir][1] # curve's curving (04fe)
1174             and not $rays->[$y][$x][($dir+4+$r)%8]));
1175             }
1176             }
1177             # warn("adding @$_"),
1178 2         4 add_edge($_, $edge, $cntedge, $lastedge) for @edgeAdd;
1179 2         11 [$edge, $cntedge, $lastedge];
1180             }
1181            
1182             sub calc_Blobby ($$$$$$) { # (Re-)Count neighbors in blobs
1183 10     10 0 29 my ($height, $width, $cntedge, $offs, $cntBlobby, $lastBlobby) = (shift, shift, shift, shift, shift, shift);
1184 10         29 @$cntBlobby = ();
1185 10         25 for my $y (1..$height) {
1186 50         94 for my $x ( 1..$width ) {
1187 410         657 my ($c,$l);
1188 410         654 for my $dir (@{$offs->[$y][$x]}) {
  410         861  
1189 40         114 my $x1 = $x + $dx[$dir];
1190 40         67 my $y1 = $y + $dy[$dir];
1191 40 50       119 $c++, $l=$dir unless $cntedge->[$y1][$x1];
1192             }
1193 410         825 $cntBlobby->[$y][$x] = $c;
1194 410         838 $lastBlobby->[$y][$x] = $l;
1195             }
1196             }
1197             }
1198            
1199             sub nnn1_do_Simple_and_edges ($$$$$$$$$$$$$) {
1200 2     2 0 172 my($width, $height, $edge, $cntedge,,$lastedge, $rays, $inLong, $midLong, $seenlong, $longedges, $blob, $offs, $cnt)
1201             = (shift, shift, shift, shift, shift, shift, shift, shift, shift, shift, shift, shift, shift);
1202 2         5 my @edgeAdd;
1203 2         5 my(@questEdges,@dblCoordEdges,@cntBlobby,@lastBlobby,@outType,@outCont,@ignore,@toClear,%toClear2,@to4fork,@maybe3Fr);
1204 2         10 calc_Blobby($height, $width, $cntedge, $offs, \@cntBlobby, \@lastBlobby);
1205 2         7 for my $y (1..$height) { # Detect more rhombi (pairs 3↔P, 3↔I, 3→´)
1206 10         21 for my $x ( 1..$width ) {
1207 82 50       180 next unless my $RAYS = $rays->[$y][$x];
1208 82 100       202 next unless @$RAYS;
1209 6         24 for my $dir (@{$offs->[$y][$x]}) {
  6         23  
1210 8 50       27 if ($RAYS->[$dir][0] =~ /^3/) {
1211 0         0 my $d = ($dir + (my $rot = $RAYS->[$dir][1]))%8;
1212 0         0 my $x1 = $x + $dx[$dir] + $dx[$d];
1213 0         0 my $y1 = $y + $dy[$dir] + $dy[$d];
1214             # warn "$char: 3 vs ";
1215             # $marked++ if
1216 0 0       0 next unless $rays->[$y1][$x1][($d+4)%8][0] =~ /^([PI´Fr])/; # Probably-curve; Ignore(d); '´' does not actually appear Fork, rhombus
1217 0         0 my $Opp = $1;
1218             # $marked = ($1 eq "´"), next;
1219 0 0       0 if ($cnt->[$y1][$x1] > 4) { # XXXX tmp!!!!!!
1220 0   0     0 my $blobs = !$cntedge->[$y1][$x1] + ($cntBlobby[$y1][$x1] || 0); # may step outside???
1221 0 0       0 next unless $blobs < 2;
1222             # Detect ⧣ ⧤ ⧥ (also 㗬); Assume horizontal/vertical $dir
1223 0         0 my($D,$cntE) = (($dir - $RAYS->[$dir][1])%8);
1224 0 0 0     0 next unless grep !$cnt->[$y + $_*(2*$dy[$dir] - $dy[$d])][$x + $_*(2*$dx[$dir] - $dx[$d])], -1, 1, 2
      0        
1225             or grep !$cnt->[$y1 + $_*(2*$dy[$dir] - $dy[$d])][$x1 + $_*(2*$dx[$dir] - $dx[$d])], -1, 1
1226             or grep($cntE += !!$rays->[$y + $_*(2*$dy[$dir] - $dy[$d])][$x + $_*(2*$dx[$dir] - $dx[$d])][$D], -1, 0, 1),
1227             grep($cntE += !!$rays->[$y1 + $_*(2*$dy[$dir] - $dy[$d])][$x1 + $_*(2*$dx[$dir] - $dx[$d])][$D], -1, 0),
1228             $cntE < 2; # 㩄, 㗬: 2
1229             # $marked++
1230             # , warn "edgeTarg=$cntedge->[$y1][$x1]; blobTarg=$cntBlobby[$y1][$x1] (last=$lastBlobby[$y1][$x1]); $x,$y,$dir"
1231             }
1232 0         0 my $converted;
1233 0 0 0     0 next if $Opp =~ /^[Fr]/ and $edge->[$y1][$x1][($d+4)%8]; # May improve??? XXXX 0468 Ѩ 114E ᅎ
1234 0 0 0     0 if ($Opp =~ /^[Fr]/ and $rays->[$y + $dy[$dir]][$x + $dx[$dir]][($dir + (2+($dir%2))*$rot)%8]
      0        
1235             and $rays->[$y + $dy[$dir]][$x + $dx[$dir]][($dir + (2+($dir%2))*$rot)%8][0] =~ /^¢/ ) { # ₨ 㚌
1236 0         0 push @edgeAdd, [$x + $dx[$dir],$y + $dy[$dir],$d,$x1,$y1];
1237             next
1238 0         0 }
1239 0 0 0     0 if (($cntedge->[$y1][$x1] || 0) == 1 and ($cntedge->[$y][$x] || 0) == 1
      0        
      0        
      0        
      0        
1240             and $edge->[$y1][$x1][$dir] and $edge->[$y][$x][($dir-$RAYS->[$dir][1]+4)%8]) { # tilde: ≁; enforce curve
1241 0         0 my $x2 = $x + $dx[$dir];
1242 0         0 my $y2 = $y + $dy[$dir];
1243             # push(@maybe3Fr, [$x,$y,$dir,$x1,$y1,$d]), next if $Opp =~ /^[Fr]/;
1244 0         0 push @edgeAdd, [$x,$y,$dir,$x2,$y2], [$x2,$y2,$d,$x1,$y1];
1245             # $marked++,
1246 0         0 $converted++, next;
1247             }
1248             # push(@maybe3Fr, [$x,$y,$dir,$x1,$y1,$d]), next if $Opp =~ /^[Fr]/;
1249 0 0 0     0 if ($rays->[$y1][$x1][($dir+4)%8][0] =~ /^F/ and $rays->[$y+$dy[$d]][$x+$dx[$d]][$dir][0] =~ /^d/) { # Fork doubleray
1250             # $marked++, next;
1251 0         0 push @ignore, $rays->[$y1][$x1][($dir+4)%8], $rays->[$y+$dy[$d]][$x+$dx[$d]][$dir];
1252 0         0 push @toClear, [$x1, $y1, ($dir+4)%8, $x+$dx[$d], $y+$dy[$d]];
1253             }
1254             # Detect when there are extra dd-edges to remove (near 3)
1255 0         0 for my $Side ([-1, qr/^(?:f|([cd]))/], [1, qr/^\?/, 1]) { # fake-curve '?' curve doubleray
1256 0         0 my $D = ($dir + $Side->[0]*$RAYS->[$dir][1])%8;
1257 0         0 my $x2 = $x + $dx[$D];
1258 0         0 my $y2 = $y + $dy[$D];
1259 0         0 my $rays2 = $rays->[$y2][$x2];
1260 0 0 0     0 next unless $rays2->[($D+4)%8] and $rays2->[($D+4)%8][0] =~ /^d/
      0        
      0        
1261             and $RAYS->[$D] and $RAYS->[$D][0] =~ /^(?:(d)|\?)/; # doubleray '?'
1262 0 0       0 if ($1) {
1263             # warn "r2=$rays2->[$D][0]; side=$Side->[1]; allow2=$Side->[2]";
1264 0 0 0     0 next unless $rays2->[$D] and $rays2->[$D][0] =~ $Side->[1];
1265 0 0       0 if ($1) { # Only ੴ for the choice $d; for $D many: ⱔ, etc; not good for ㉽ 䉸 {
1266             # warn "N=$cntedge->[$y2][$x2]; s=$cntedge->[$y][$x]";
1267 0 0 0     0 next unless ($cntedge->[$y2][$x2] || 0) == 3 and ($cntedge->[$y][$x] || 0) == 2
      0        
      0        
      0        
      0        
1268             and $edge->[$y2][$x2][($d-2*$RAYS->[$dir][1])%8] and grep $edge->[$y][$x][($_+4)%8], $d, $D;
1269             # $marked++;
1270             }
1271 0 0       0 } else { next unless not $rays2->[$D]
1272             # and ++$marked
1273             }
1274             # $marked = 1;
1275 0   0     0 my $cont = ($Side->[2] and $rays->[$y + 2*$dy[$D]][$x + 2*$dx[$D]][($D+4)%8]);
1276 0 0       0 push @ignore, $RAYS->[$D], $rays2->[($D+4)%8], ( $cont ? $cont : () );
1277 0 0       0 push @toClear, [$x, $y, $D, $x2, $y2] if $edge->[$y][$x][$D];
1278             }
1279             # 3↔P may be repeated opposite to 3↔P or 3↔I: see C481 쒁, F91B 亂. We remove extra edges for duplicates too.
1280 0 0       0 push @to4fork, [$x, $y, $dir, $x1, $y1, $RAYS->[$dir][1]] unless $converted;
1281             }
1282             }
1283             }
1284             }
1285 2         5 $_->[0] = 'ignore' for @ignore;
1286 2         6 for my $e (@toClear) {
1287 0         0 my($x, $y, $dir, $x1, $y1) = @$e;
1288 0 0       0 clear_edge($e,$edge,$cntedge,$lastedge) unless $toClear2{$x+$x1,$y+$y1}++; # [$x, $y, $dir, $x1, $y1]
1289             }
1290 2         4 for my $e (@to4fork) {
1291 0         0 my($x, $y, $dir, $x1, $y1, $rot) = @$e;
1292 0 0       0 next if $seenlong->{$x, $y, $x1, $y1};
1293 0         0 $rays->[$y][$x][$dir][0] = '4fork';
1294 0         0 my $D = ($dir + $rot + 4)%8;
1295 0         0 $rays->[$y1][$x1][$D][0] = 'xFork';
1296 0         0 add_longedge($e, $longedges, $seenlong, $midLong, $inLong);
1297             }
1298 2         6 add_edge($_, $edge, $cntedge, $lastedge) for @edgeAdd;
1299 2         11 calc_Blobby($height, $width, $cntedge, $offs, \@cntBlobby, \@lastBlobby); # In fact, this has no practical effect on the next block
1300 2         5 @edgeAdd = ();
1301 2         6 for my $y (1..$height) { # Upgrade suitable pairs ´´ to edges
1302 10         19 for my $x ( 1..$width ) {
1303 82 50       180 next unless my $RAYS = $rays->[$y][$x];
1304 82 100       189 next unless @$RAYS; # Contamination???
1305 6         10 for my $dir (grep { $RAYS->[$_][0] =~ /^[´]/ } @{$offs->[$y][$x]}) { # '´'
  8         29  
  6         37  
1306 0 0       0 next if $dir > 3; # symmetric
1307 0         0 my $x1 = $x + $dx[$dir];
1308 0         0 my $y1 = $y + $dy[$dir];
1309 0         0 my $dir1 = ($dir+4)%8;
1310 0 0       0 next unless $rays->[$y1][$x1][$dir1][0] =~ /^([´])/; # '´'
1311 0 0       0 next if $cnt->[$y][$x] + $cnt->[$y1][$x1] > 8; # 04FE Ӿ
1312 0 0 0     0 next if grep +($cntBlobby[$_->[1]][$_->[0]] || 0) > 3, [$x,$y], [$x1,$y1];
1313 0         0 my $cX = grep !$cntedge->[$_->[1]][$_->[0]], [$x,$y], [$x1,$y1];
1314 0 0 0     0 next if grep !$cntedge->[$_->[1]][$_->[0]] && ($cntBlobby[$_->[1]][$_->[0]] || 0) > 1 + ($cX == 2), [$x,$y], [$x1,$y1];
1315 0   0     0 my $cXX = grep !$cntedge->[$_->[1]][$_->[0]] && ($cntBlobby[$_->[1]][$_->[0]] || 0) > ($cX == 2), [$x,$y], [$x1,$y1];
1316 0         0 my $rot = $RAYS->[$dir][1];
1317 0 0       0 next unless $rays->[$y1][$x1][$dir1][1] == -$rot;
1318 0         0 my $d = ($dir+$rot)%8;
1319 0         0 my $in = $rays->[$y+$dy[$d]][$x+$dx[$d]];
1320 0 0 0     0 next if 2 == $cXX and not ($in and !$cntedge->[$y+$dy[$d]][$x+$dx[$d]] and $cnt->[$y+$dy[$d]][$x+$dx[$d]] < 6); # 0904 ऄ
      0        
1321 0         0 push @edgeAdd, [$x,$y,$dir,$x1,$y1];
1322 0         0 for my $opp (0, 1) {
1323 0 0       0 my($x,$y,$dir,$rot) = ($opp ? ($x1,$y1,$dir1,-$rot) : ($x,$y,$dir,$rot));
1324 0         0 my $out = $RAYS->[($dir-2*$rot)%8]; # Now: the next condition works for ≥1 end
1325 0 0       0 next if $cnt->[$y][$x] - !!$in - !!$out > 2; # Now can create the neighbor edges
1326 0         0 my $D = ($dir-$rot+4)%8;
1327 0 0 0     0 push @edgeAdd, [$x,$y,$D,$x+$dx[$D],$y+$dy[$D]] if $rays->[$y][$x][$D] and not $edge->[$y][$x][$D];
1328             }
1329             # $marked++;
1330             }}}
1331 2         5 add_edge($_, $edge, $cntedge, $lastedge) for @edgeAdd;
1332 2         8 calc_Blobby($height, $width, $cntedge, $offs, \@cntBlobby, \@lastBlobby); # In fact, this has no practical effect on the next block
1333 2         5 @edgeAdd = ();
1334 2         6 for my $qRound (0,1) {
1335             # warn($rays->[7][2][7] ? "### <$rays->[7][2][7]> [@{$rays->[7][2][7]}] " . (defined $rays->[7][2][7][0]?'d':'u'):"###### not yet");
1336 4         9 for my $y (1..$height) { # Upgrade suitable pairs ?c ?d ?° to edges
1337 20         36 for my $x ( 1..$width ) {
1338 164 50       363 next unless my $RAYS = $rays->[$y][$x];
1339 164 100       367 next unless @$RAYS; # Contamination???
1340 12         21 for my $dir (grep { $RAYS->[$_][0] =~ /^[?]/ } @{$offs->[$y][$x]}) {
  16         46  
  12         27  
1341 0         0 my $x1 = $x + $dx[$dir];
1342 0         0 my $y1 = $y + $dy[$dir];
1343 0         0 my $dir1 = ($dir+4)%8;
1344 0 0 0     0 next unless $rays->[$y1][$x1][$dir1][0] =~ /^([dc°])/ # 'doubleray', 'curve', disabled-Fork
1345             and not $edge->[$y][$x][$dir];
1346             # warn "0 $1 [$y][$x][$dir] [$y1][$x1][$dir1]\n";
1347 0         0 my($Opp, $inBlob) = "$1";
1348             # $marked++;
1349             CHECK_BLOB:
1350 0         0 for my $DD (1, -1) { # abort if near a blob
1351 0 0       0 my @Shear = ($dir % 2 ? (-1,1) : 0);
1352 0         0 for my $shear (@Shear) {
1353 0         0 my $D = (2+$shear)*$DD;
1354 0         0 my($dir2, $badD, $bad0, $blobby, $smallBlobby) = (($dir+$D)%8, 0, 0, 0, 0);
1355 0         0 for my $P ([$x,$y], [$x1,$y1]) {
1356 0         0 $bad0 += !$cntedge->[$P->[1]][$P->[0]];
1357 0 0       0 next unless $rays->[$P->[1]][$P->[0]][$dir2];
1358 0         0 my $x2 = $P->[0] + $dx[$dir2];
1359 0         0 my $y2 = $P->[1] + $dy[$dir2];
1360             # $badD++ if $cntmin[$y2][$x2] and $cntmin[$y2][$x2] > 5 and not $cntedge->[$y2][$x2]; # Less strict than for `Dense´
1361             # $badD += !!$blob[$y2][$x2]; # Less strict than for `Dense´
1362             # $bad0 += !!$blob[$P->[1]][$P->[0]];
1363 0         0 $badD += !$cntedge->[$y2][$x2];
1364 0         0 my $back = $rays->[$P->[1]][$P->[0]][($dir2+4)%8];
1365 0 0 0     0 $blobby++ if (!$cntedge->[$P->[1]][$P->[0]] or ($cnt->[$P->[1]][$P->[0]] + !$back) > 5) # इ
      0        
      0        
1366             and (!$cntedge->[$y2][$x2] and (($cntBlobby[$y2][$x2]||0) > 2 # ध:4; ሯ:3; ऄ इ: 2
1367             or $cntBlobby[$y2][$x2] and $cnt->[$y2][$x2] > 5) # 㜰
1368             or ($cntBlobby[$y2][$x2]||0) > 2 and $cnt->[$y2][$x2] > 5); # ⓲
1369 0 0 0     0 $smallBlobby++ if !$cntedge->[$P->[1]][$P->[0]] and !$cntedge->[$y2][$x2] and $cnt->[$y2][$x2] < 5; # ᎍ
      0        
1370             }
1371             # warn "[$x, $y]->$dir: rot=$D: edge:$bad0, near:$badD, blobby:$blobby sm:$smallBlobby (",$cntedge->[$y][$x]||0, " ", $cntedge->[$y1][$x1]||0,")" if not $qRound and $Opp eq 'c';
1372 0         0 my $bad00 = ($bad0 >= 2 - !$shear); # be stricter on diagonal lines
1373 0 0 0     0 $inBlob++, last CHECK_BLOB if $bad00 and $badD > 1 # ᢜ
      0        
      0        
      0        
1374             or $blobby or $smallBlobby and $badD >= 2;
1375             }
1376             }
1377 0 0 0     0 next if $inBlob and $qRound;
1378             # warn "1 $Opp [$y][$x][$dir] [$y1][$x1][$dir1] inblob=",!!$inBlob,"\n";
1379 0 0 0     0 next if grep +($rays->[$y][$x][($dir+$_)%8] and $rays->[$y][$x][($dir+$_)%8][0] =~ /^A/), 2, -2; # Arrow
1380 0 0       0 if ($qRound) {
1381             # warn "--> $Opp [$y][$x][$dir] [$y1][$x1][$dir1] [",join(',', map !!$questEdges[$y][$x][($dir+$_)%8], -1,1),"] [",join(',', map !!$questEdges[$y1][$x1][($dir1+$_)%8], -1,1),"]\n";
1382             next if grep +($questEdges[$y1][$x1][($dir1+$_)%8] or $questEdges[$y][$x][($dir+$_)%8]), 1, -1
1383 0 0 0     0 or $dblCoordEdges[$y+$y1][$x+$x1] > 1 or $midLong->{$x+$x1,$y+$y1};
      0        
      0        
1384             # Check nearby double-edges
1385 0         0 my(@e2, $e2);
1386 0         0 for my $D (1,-1) {
1387 0         0 push @e2, scalar grep $midLong->{$x+$x1+$D*$dx[($dir+$_)%8],$y+$y1+$D*$dy[($dir+$_)%8]}, -1, 1;
1388 0         0 $e2 += $e2[-1];
1389             }
1390             # $marked++ if $e2;
1391 0         0 if (0 and $e2) { # DOES NOT APPEAR (at least with 2-2 type only)
1392             for my $Pt ([$x1,$y1,0],[$x,$y,1]) { # The far end
1393             next unless $e2[$Pt->[2]] and $cnt->[$Pt->[1]][$Pt->[0]] > 4;
1394             my $blobs = !$cntedge->[$Pt->[1]][$Pt->[0]] + ($cntBlobby[$Pt->[1]][$Pt->[0]] || 0); # may step outside???
1395             # $marked++ unless $blobs < 2;
1396             }
1397             }
1398 0         0 my $compete;
1399 0         0 for my $D (-1, 1) {
1400 0 0       0 next unless $outType[$y][$x][($dir+2*$D)%8];
1401 0 0 0     0 $compete++ unless $edge->[$y1][$x1][($dir+$D)%8] and not $outCont[$y][$x][($dir+2*$D)%8][1+$D];
1402             }
1403 0 0       0 next if $compete;
1404             # warn "d->[$y][$x][$dir]" and
1405 0         0 (debug and $rays->[$y1][$x1][$dir1][0] =~ s/^d/ⓓ/),
1406             # $marked++; # if $Opp eq 'd'; # For doubleray, too many (???) false positives now
1407             push @edgeAdd, [$x,$y,$dir,$x1,$y1]; # unless $Opp eq 'd'; # For doubleray, too many (???) false positives now
1408             } else {
1409             # warn "$Opp [$y][$x][$dir] [$y1][$x1][$dir1]\n";
1410 0         0 $questEdges[$y1][$x1][$dir1]++;
1411 0         0 $questEdges[$y][$x][$dir]++;
1412 0         0 $dblCoordEdges[$y+$y1][$x+$x1]++; # Mark (doubled) midpoint
1413 0         0 $outType[$y][$x][$dir] = $Opp;
1414 0         0 $outCont[$y][$x][$dir][1+$_] = $edge->[$y1][$x1][($dir-$_)%8] for 1, -1;
1415             }
1416             }
1417             }
1418             }
1419             }
1420 2         6 add_edge($_, $edge, $cntedge, $lastedge) for @edgeAdd;
1421 2         25 [$edge, $cntedge, $lastedge, $rays, $longedges, $seenlong, $midLong, $inLong];
1422             }
1423            
1424             sub scan_degree_rays ($$$$$$$$$) {
1425 2     2 0 66 my($width, $height, $edge, $cntedge,,$lastedge, $rays, $midLong, $offs, $cnt)
1426             = (shift, shift, shift, shift, shift, shift, shift, shift, shift);
1427 2         6 my($cntBlobby, $lastBlobby) = ([], []);
1428 2         44 calc_Blobby($height, $width, $cntedge, $offs, $cntBlobby, $lastBlobby);
1429 2         5 my(@todoDegree,%candDegree);
1430 2         6 for my $y (1..$height) { # Detect candidates °c °f °d
1431 10         22 for my $x ( 1..$width ) {
1432 82 50       180 next unless my $RAYS = $rays->[$y][$x];
1433 82 100       202 next unless @$RAYS;
1434 6         10 for my $dir (@{$offs->[$y][$x]}) {
  6         15  
1435 8 50       40 if ($RAYS->[$dir][0] =~ /^°/) { # '°'
1436 0         0 my $x1 = $x + $dx[$dir];
1437 0         0 my $y1 = $y + $dy[$dir];
1438 0         0 my($dir1, @rot) = ($dir+4)%8;
1439             my $goodNearLong = sub ($$$$$$) { # returns false if the edge is a bad candidate
1440 0     0   0 my($x,$y,$dir,$rot,$x1,$y1)=(shift,shift,shift,shift,shift,shift);
1441 0         0 my $x2 = $x1 + $dx[($dir+$rot)%8];
1442 0         0 my $y2 = $y1 + $dy[($dir+$rot)%8];
1443 0         0 my $r = $rays->[$y2][$x2][($dir+4)%8];
1444 0 0 0     0 return 1 unless $r and $r->[0] =~ /^[4x2]/; # longEdge is going in the inspected direction
1445 0 0       0 return 1 unless grep $edge->[$y2][$x2][($dir+$rot*$_)%8], -1..2; # XXX Is this needed??? Good way out of long
1446 0 0 0     0 return 1 if 1 == ($cntedge->[$y1][$x1] || 0) and grep $edge->[$y1][$x1][($dir-$rot*$_)%8], 0, 1, 2; # Good way out
      0        
1447 0         0 return;
1448 0         0 };
1449 0         0 my(@cont, $Opp);
1450             my $goodCont = sub ($$$$$$) { # returns ROTATION if the edge has a good continuation, undef/empty otherwise
1451 0     0   0 my($x,$y,$dir,$x1,$y1,$dir1)=(shift,shift,shift,shift,shift,shift);
1452             return @rot && $rot[0] if
1453             @rot = grep $edge->[$y] [$x] [($dir1+$_)%8], 0,-1, 1 # has a way out (doing exactly 1 is worse)
1454 0 0 0     0 or @rot = grep $midLong->{2*$x+$dx[$dir1]+$dx[($dir1+$_)%8],2*$y+$dy[$dir1]+$dy[($dir1+$_)%8]}, -1, 1
      0        
      0        
      0        
      0        
1455             # not beneficial on diagonal lines:
1456             or !($dir%2) and (2 == grep $edge->[$y][$x][($dir+$_)%8], -2, 2 # ends on a stroke ⇽ 🉡
1457             or !$cntedge->[$y][$x] and 2 == grep $edge->[2*$y-$y1][2*$x-$x1][($dir+$_)%8], -2, 2
1458             and !grep $edge->[2*$y-$y1][2*$x-$x1][($dir+$_)%8], 1, 0, -1
1459             and push @cont, [$x,$y,$dir1,2*$x-$x1,2*$y-$y1]); # a stroke at dist=1
1460 0 0 0     0 if (!$cntedge->[$y][$x] and 1 == ($cntBlobby->[$y][$x] || 0)) {{ # ⾘ XXXX but easier???
      0        
1461 0         0 my $D = $lastBlobby->[$y][$x];
  0         0  
1462 0 0       0 last unless @rot = (grep $D == ($dir1 + $_)%8, -1, 0, 1);
1463 0         0 my $x2 = $x + $dx[$D];
1464 0         0 my $y2 = $y + $dy[$D];
1465             # Not beneficial on 𐃶; CONT is not beneficial on ⪵. Do not CONT if $midLong???
1466             push @cont, [$x,$y,$D,$x2,$y2] and return $rot[0]
1467 0 0 0     0 if 1 == ($cntBlobby->[$y2][$x2] || 0) and (!$midLong->{$x+$x2,$y+$y2} # we will be “connected” to Pt2 anyway
      0        
      0        
      0        
1468             or $cnt->[$y2][$x2] == 3); # Apparently, always true with midLong!
1469             }}
1470 0         0 return undef;
1471 0         0 };
1472             my $goodConts = sub () { # returns ROTATION if the edge has a good continuation, undef/empty otherwise
1473 0     0   0 my($x,$y,$dir,$x1,$y1,$dir1) = ($x,$y,$dir,$x1,$y1,$dir1);
1474 0         0 my(@out, $rot) = ($goodCont->($x,$y,$dir,$x1,$y1,$dir1), $goodCont->($x1,$y1,$dir1,$x,$y,$dir));
1475 0 0 0     0 return @out unless 1 == (($rot, my $junk) = grep defined, @out) and $rot; # 1 way out found, not straight
1476             # Now try to punch through at slope 2 or ½ at the other end.
1477 0         0 my($try) = grep !$out[$_], 0, 1; # Have exactly one defined; it is not 0
1478 0 0       0 ($x,$y,$dir,$x1,$y1,$dir1) = ($x1,$y1,$dir1,$x,$y,$dir) unless $out[0];
1479 0         0 my $D = ($dir+$rot)%8;
1480 0         0 my $x2 = $x1 + $dx[$D];
1481 0         0 my $y2 = $y1 + $dy[$D];
1482             # warn "($x,$y,$dir,$x1,$y1,$dir1) $out[0],$out[1]: $x2,$y2,$D [$edge->[$y2][$x2][$dir],$edge->[$y2][$x2][($dir+4)%8]]";
1483 0 0 0     0 push @cont, [$x1,$y1,$D,$x2,$y2] and $out[$try] = $rot if ($edge->[$y2][$x2] and $edge->[$y2][$x2][$dir]
      0        
      0        
1484             and not grep $edge->[$y2][$x2][($dir+$_)%8], 4); # Having 3,5 here is not beneficial.
1485             @out
1486 0         0 }; # Below: °e is not beneficial, °C does not appear
  0         0  
1487             #warn "$x,$y,$dir ($edge->[$y] [$x] [($dir+$_)%8], ";
1488 0 0 0     0 next unless not $edge->[$y][$x][$dir] and $rays->[$y1][$x1][$dir1][0] =~ /^([cdfF])/ # doubleray (fake-)curve Fork
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
1489             # and not ((grep $edge->[$y] [$x] [($dir1+$_)%8], -1, 0, 1 # has a way out (doing exactly 1 is worse)
1490             # or grep $midLong{2*$x+$dx[$dir1]+$dx[($dir1+$_)%8],2*$y+$dy[$dir1]+$dy[($dir1+$_)%8]}, -1, 1)
1491             # and (grep $edge->[$y1][$x1][($dir+$_)%8], -1, 0, 1
1492             # or grep $midLong{2*$x1+$dx[$dir]+$dx[($dir+$_)%8],2*$y1+$dy[$dir]+$dy[($dir+$_)%8]}, -1, 1))
1493             # and 2 == grep defined, (@out = $goodConts->(\$cont,\$cont1))
1494             and $Opp = $1
1495             and not grep $edge->[$y] [$x] [($dir+$_)%8], -1, 1 # no nearby edges
1496             and not grep $edge->[$y1][$x1][($dir1+$_)%8], -1, 1 # XXX Չ ڼ
1497             # and (warn("10 $x,$y,$dir (2+!$cntedge->[$y1][$x1] > $cntBlobby->[$y][$x])"),1)
1498             and 2 + !$cntedge->[$y1][$x1] > ($cntBlobby->[$y][$x] || 0) # not near blobs (Not beneficial at all).
1499             # and (warn("20 $x,$y,$dir (2+!$cntedge->[$y][$x] > $cntBlobby->[$y1][$x1])"),1)
1500             and 2 + !$cntedge->[$y][$x] > ($cntBlobby->[$y1][$x1] || 0) # (not counting the other side of this edge!)
1501             # and (warn("30 $x,$y,$dir"),1)
1502             and not grep $rays->[$y][$x][($dir+$_)%8] && (($rays->[$y][$x][($dir+$_)%8][0] || '') =~ /^[4x2]/) # not near long edges
1503             && !$goodNearLong->($x,$y,$dir,$_,$x1,$y1), -1, 1 # 4fork does not appear
1504             # and (warn("40 $x,$y,$dir"),1)
1505             and not grep $rays->[$y1][$x1][($dir1+$_)%8] && (($rays->[$y1][$x1][($dir1+$_)%8][0] || '') =~ /^[4x2]/)
1506             && !$goodNearLong->($x1,$y1,$dir1,$_,$x,$y), -1, 1; # 2fork3 xFork (!3fork2!!!)
1507             # and (warn("50 $x,$y,$dir"),1);
1508 0         0 my @out = $goodConts->();
1509 0         0 push @todoDegree, [$x,$y,$dir,$x1,$y1,$dir1,$Opp,@out,@cont];
1510 0         0 $candDegree{$x,$y,$dir} = $candDegree{$x1,$y1,$dir1} = $Opp;
1511             }}}}
1512             # warn($edge->[8][7][5] ? "### <$edge->[8][7][5]>" : "###### not yet ($#todoDegree [@{$todoDegree[0]||[]}] [@{$todoDegree[1]||[]}] [@{$todoDegree[2]||[]}])");
1513 2         7 for my $cand (@todoDegree) { # °f-candidates are not good as continuations; some candidates which work as continuation would not be revived
1514 0         0 my($x,$y,$dir,$x1,$y1,$dir1,$Opp,$out,$out1,@cont,$c) = @$cand; # below: 3: U+10054 (OK); 2: 𝔉 (not OK)
1515 0 0 0     0 $out++ if not defined $out and $c = grep 'f' ne ($candDegree{$x,$y,($dir1+$_)%8} || 'f'), -1, 0, 1 and $c != 2;
      0        
      0        
1516 0 0 0     0 $out1++ if not defined $out1 and $c = grep 'f' ne ($candDegree{$x1,$y1,($dir+$_)%8} || 'f'), -1, 0, 1 and $c != 2;
      0        
      0        
1517 0 0 0     0 next unless defined $out and defined $out1;
1518             # $marked++;
1519 0   0     0 $edge->[$_->[1]][$_->[0]][$_->[2]] or add_edge($_, $edge, $cntedge, $lastedge) for [$x,$y,$dir,$x1,$y1], @cont;
1520             # warn "($x,$y,$dir,$x1,$y1,$dir1,$Opp)";
1521             }
1522             # warn($edge->[8][7][5] ? "### <$edge->[8][7][5]>" : "###### not yet ($#todoDegree [@{$todoDegree[0]||[]}] [@{$todoDegree[1]||[]}] [@{$todoDegree[2]||[]}])");
1523 2         22 [$edge, $cntedge, $lastedge];
1524             }
1525            
1526             sub nnn3_do_Simple_and_edges ($$$$$$$$$$) {
1527 2     2 0 47 my($width, $height, $edge, $cntedge,,$lastedge, $longedges, $seenlong, $midLong, $inLong, $cnt)
1528             = (shift, shift, shift, shift, shift, shift, shift, shift, shift, shift);
1529 2         6 for my $e (@$longedges) { # De-longedge if there is a loners nearby. Probably, it would be better to do earlier;
1530 0 0 0     0 next if not ref $e and $e eq 'erased'; # however, this would break tuneups which historically came first.
1531 0         0 my($x, $y, $x1,$y1, $offset, $dir, $rot) = @$e;
1532 0         0 my $dir0 = ($dir+$rot)%8;
1533 0         0 my @atBEG = grep $edge->[$y][$x][$_], $dir, $dir0;
1534 0         0 my @atEND = grep $edge->[$y1][$x1][($_+4)%8], $dir, $dir0;
1535             # next unless @atBEG or @atEND;
1536 0         0 my @add; # Had a longedge since couldn’t choose 1 of 2 ways around a rhombus; looks like something made a preference…
1537 0 0 0     0 if (not (@atBEG or @atEND)) { # Check for loner singletons on one side
1538 0         0 my @DIR = grep 3 == $cnt->[$y+$dy[$_]][$x+$dx[$_]], $dir, $dir0;
1539 0 0       0 next unless 1 == @DIR;
1540 0         0 my($dx,$dy) = ($dx[$DIR[0]],$dy[$DIR[0]]);
1541             # $marked++;
1542             # next;
1543 0         0 @add = ([$x, $y, $DIR[0], $x + $dx, $y + $dy], [$x1, $y1, ($dir + $dir0 - $DIR[0] + 4)%8, $x + $dx, $y + $dy]);
1544             }
1545 0         0 add_edge($_, $edge, $cntedge, $lastedge) for @add;
1546 0         0 clear_longedge([$x, $y, $x1, $y1, $offset], $longedges, $seenlong, $midLong, $inLong);
1547             }
1548             # warn($edge->[8][7][5] ? "### <$edge->[8][7][5]>" : "###### not yet");
1549 2         11 [$edge, $cntedge, $lastedge, $longedges, $seenlong, $midLong, $inLong];
1550             }
1551            
1552             sub nnn4_do_Simple_and_edges ($$$$$$$$) {
1553 2     2 0 96 my($width, $height, $edge, $cntedge,,$lastedge, $rays, $offs, $cnt)
1554             = (shift, shift, shift, shift, shift, shift, shift, shift);
1555 2         6 my(@edgeAdd, $tailEdge);
1556 2         9 for my $y (0..$#$edge) { # Force the edges near tips
1557 11 100       28 next unless $edge->[$y];
1558 6         11 for my $x ( 0..$#{ $edge->[$y] } ) {
  6         14  
1559 39         65 for my $dir ( 0..$#{ $edge->[$y][$x] } ) {
  39         94  
1560 40 50 66     123 next unless $edge->[$y][$x][$dir] and $rays->[$y][$x][$dir][0] eq 'Tail'; # don't include in the end/nextEdge, special-case later
1561 0         0 my $X = $x + $dx[$dir];
1562 0         0 my $Y = $y + $dy[$dir];
1563 0         0 my $DIR = ($dir+4)%8;
1564 0 0       0 next unless $rays->[$Y][$X][$DIR][0] eq 'MFork';
1565             # next if grep !$edge->[$y][$x][($dir+$_)%8], 3,5; # A branch of a fork may be non-recognized
1566 0         0 $tailEdge->{$x,$y} = [$x, $y, $dir, my $rot = $rays->[$Y][$X][$DIR][1]];
1567             # next unless $edge->[$y][$x] and $tailEdge->{$x,$y};
1568             # warn "tail @($x,$y)";
1569             # my $dir = $tailEdge->{$x,$y}[2];
1570 0 0 0     0 next unless $cnt->[$y][$x] == 3 and $cntedge->[$y][$x] < 3;
1571             # warn "tail \@($x,$y,$dir)";
1572 0         0 my @bends;
1573 0         0 for my $d (grep $_ != $dir, @{$offs->[$y][$x]}) {
  0         0  
1574             next if # $edge->[$y][$x][$d] or
1575 0 0 0     0 ($cntedge->[$y+$dy[$d]][$x+$dx[$d]] || 0) != 1 + !!$edge->[$y][$x][$d];
1576 0 0 0     0 my ($l) = $edge->[$y][$x][$d] ? grep((($_-$d+4)%8 and $edge->[$y+$dy[$d]][$x+$dx[$d]][$_]), 0..7)
      0        
1577             : ($lastedge->[$y+$dy[$d]][$x+$dx[$d]] || 0);
1578 0         0 my $b = ($l - $d + 4)%8 - 4;
1579             # warn "tail \@($x,$y,$dir): $d, $b";
1580 0 0       0 next if 1 < abs $b;
1581 0         0 my $d0 = ($d - $dir)%8 - 4;
1582 0 0 0     0 next if $b and grep $edge->[$y+$dy[$d]+$dy[$l]][$x+$dx[$d]+$dx[$l]][($l + $_*$d0)%8], 1, 2; # ཹ ᰑ ᶒ 1D06C 𝁬 11184 𑆄; but: 㨓
1583 0         0 push @bends, [$d, $b, $d0*$b, $d0];
1584             # warn "bends: ($d0,$b) \@($x,$y,$dir)";
1585             }
1586 0 0 0     0 if ( @bends == 2 and 2 == grep $_->[3], @bends and 1 == (my @O = grep $_->[2] == 1, @bends)
      0        
      0        
1587             and !grep $_->[2] == -1, @bends ) { # connect the two continuations; ¤ µ; not good: ᰑ ᶒ 㨓 11184 𑆄
1588 0         0 my $d = $O[0][0];
1589 0         0 my $D = ($d + $O[0][1] + 4)%8;
1590 0         0 push @edgeAdd, [$x+$dx[$d], $y+$dy[$d], $D, $x+$dx[$d]+$dx[$D], $y+$dy[$d]+$dy[$D]];
1591             # $marked++;
1592             } else { # extend extendable
1593 0         0 for my $B (@bends) {
1594 0         0 my $d = $B->[0];
1595 0 0       0 push @edgeAdd, [$x, $y, $d, $x+$dx[$d], $y+$dy[$d]] unless $edge->[$y][$x][$d];
1596             }
1597             }
1598             }
1599             }
1600             }
1601 2         5 add_edge($_, $edge, $cntedge, $lastedge) for @edgeAdd;
1602             # warn($edge->[8][7][5] ? "### <$edge->[8][7][5]>" : "###### not yet");
1603 2         15 [$edge, $cntedge, $lastedge, $tailEdge];
1604             }
1605            
1606             sub nnn5_do_Simple_and_edges ($$$$$$$$$$) {
1607 2     2 0 62 my($width, $height, $edge, $cntedge,,$lastedge, $rays, $offs, $inLong, $cnt, $near)
1608             = ( shift, shift, shift, shift, shift, shift, shift, shift, shift, shift);
1609             # warn "... reached ($#$near)";
1610 2         8 my($cntBlobby, $lastBlobby, @edgeAdd) = ([], []);
1611 2         10 calc_Blobby($height, $width, $cntedge, $offs, $cntBlobby, $lastBlobby); # In fact, seems like may use the older version???
1612 2         6 for my $y (1..$height) { # Last round of: Identify singletons with a valid way out (one d in a group of d,e,f,K)
1613 10         22 for my $x ( 1..$width ) {
1614             next unless $cnt->[$y][$x] # If already have two edges, do not try to find complicated...
1615 82 100 50     270 and ($cntedge->[$y][$x] || 0) <= 1 and !$inLong->{$x,$y};
      100        
      66        
1616             # next if not $Simple[$y][$x] and $cntedge->[$y][$x];
1617             # warn "... reached ($x,$y) c=$cnt->[$y][$x] cB=", $cntBlobby->[$y][$x];
1618 4 50 50     22 next if $cnt->[$y][$x] + ($cntBlobby->[$y][$x] || 0) > 6; # Give up if too many neighbors (count bad neighbors as 2)
1619             # Before, we assumed that at most one edge is present
1620 4 50       13 my $L = $cntedge->[$y][$x] ? $lastedge->[$y][$x] : 100; # Connect only as a curve continuation, and only if it continues back
1621 4 50       23 next if 1 + ($L!=100) > (my @Neighbors = @{$offs->[$y][$x]});
  4         19  
1622             # next if grep $_ == ($L-1)%8, @Neighbors;
1623 0         0 @Neighbors = grep $rays->[$y][$x][$_][0] !~ /^([i¢₡])/, @Neighbors; # , warn "($x,$y,$L)" ignore, ¢urve, ₡urve
1624 0         0 push @Neighbors, shift @Neighbors while $Neighbors[-1] == ($Neighbors[0] + 7)%8; # Rotate to start of a run
1625 0         0 my $e = 0;
1626 0   0     0 $e++ while $e < $#Neighbors and $Neighbors[$e+1] == ($Neighbors[$e] + 1)%8;
1627             # warn "($x,$y) $e [@Neighbors] <$cntedge->[$y][$x]>";
1628 0         0 my $premark;
1629             # warn "... reached";
1630 0 0       0 if (!$cntedge->[$y][$x]) {
1631             # warn "... reached e=$e #N=$#Neighbors ($x,$y)";
1632 0 0 0     0 next unless $e == $#Neighbors and $e == 2 and not grep !$cntedge->[$y+$dy[$_]][$x+$dx[$_]], @Neighbors;
      0        
1633             # warn "... reached e=$e #N=$#Neighbors";
1634             } else {
1635 0         0 my $e1 = $e++;
1636 0   0     0 $e++ while $e < $#Neighbors and $Neighbors[$e+1] == ($Neighbors[$e] + 1)%8; # find second run
1637 0 0       0 next unless $e == $#Neighbors; # Now: have exactly 2 groups
1638 0 0       0 if (grep $_ == $L, @Neighbors[0..$e1]) {
1639 0         0 splice @Neighbors, 0, $e1 + 1;
1640             } else {
1641 0         0 splice @Neighbors, $e1 + 1, @Neighbors - $e1 - 1;
1642             } # Now only the non-entry group remains
1643 0 0 0     0 next if @Neighbors > 3
      0        
      0        
1644             or grep !$cntedge->[$y+$dy[$_]][$x+$dx[$_]], @Neighbors # See ֍
1645             and (@Neighbors > 1 or grep $near->[$y+$dy[$Neighbors[0]]][$x+$dx[$Neighbors[0]]][($Neighbors[0]+$_)%8], 2,3,-2,-3);
1646             # $premark++ if @Neighbors == 1 and grep !$cntedge->[$y+$dy[$_]][$x+$dx[$_]], @Neighbors;
1647             }
1648             # warn "... reached";
1649             # $marked++ if grep $rays->[$y][$x][$_][0] =~ /^([i¢₡])/, @Neighbors; # , warn "($x,$y,$L)" ignore, ¢urve, ₡urve
1650 0         0 my @cont = grep $edge->[$y+$dy[$_]][$x+$dx[$_]][$_], @Neighbors;
1651 0         0 my $mid = $Neighbors[int(@Neighbors/2)];
1652             # warn(" ($x,$y,$L): <@cont> <@Neighbors>");
1653 0 0       0 if (@cont >= 2) { # Use only if 2-neighbors are in this 45° sector
    0          
1654 0 0 0     0 next if @cont > 2 or @Neighbors > 2 or grep $cnt->[$y+$dy[$_]][$x+$dx[$_]] > 4, @Neighbors
      0        
      0        
      0        
1655             or $L < 8 and grep 1 < abs(($L-$_)%8 - 4), @cont;
1656             # next if !$cntedge->[$y][$x];
1657             } elsif (@cont) { # Use only if perp to a stroke, or continues incoming
1658             # warn "... reached";
1659 0 0       0 if (@Neighbors == 3) { # The only case compatible with no-incoming-edge
    0          
    0          
1660 0 0 0     0 next if $mid%2 or grep !$edge->[$y+$dy[$mid]][$x+$dx[$mid]][($mid+$_)%8], 2, -2; # Now: we are next to a stroke
1661 0 0 0     0 next unless $mid == $cont[0] and ($L > 7 or 2 > abs(($L-$cont[0])%8 - 4)) or ($cont[0] + 4)%8 == $L;
      0        
      0        
1662             } elsif (@Neighbors == 2) { # Use only if extends incoming, or is close, and incoming can't go straight
1663 0         0 my $ang = abs(($cont[0] - $L)%8 - 4);
1664 0 0 0     0 next if $ang and ($ang > 1 or $near->[$y][$x][($L+4)%8]); # “Being close” is not beneficial for ƈ ۜ
      0        
1665             # } elsif (@Neighbors == 1 and $mid%2) { # OK, just use @cont
1666             # next unless grep !$edge->[$y+$dy[$mid]][$x+$dx[$mid]][($mid+$_)%8], 2, -2; # ֍ but not ą
1667             } elsif (@Neighbors == 1) { # OK, just use @cont
1668 0 0 0     0 next if $L < 8 and 1 < abs(($L-$cont[0])%8 - 4);
1669             }
1670             } else {
1671             # warn "... reached";
1672 0 0       0 if (@Neighbors == 3) { # The only case compatible with no-incoming-edge
    0          
    0          
1673             # warn "... reached";
1674 0 0 0     0 next if $mid%2 or grep !$edge->[$y+$dy[$mid]][$x+$dx[$mid]][($mid+$_)%8], 2, -2; # Now: we are next to a stroke
1675 0 0       0 @cont = ($L > 7 ? $mid : grep $_ == ($L+4)%8, @Neighbors);
1676             # next unless $mid == $cont[0] or ($cont[0] + 4)%8 == $L;
1677             } elsif (@Neighbors == 2) { # Use only if extends incoming
1678             # warn "... reached";
1679 0         0 @cont = grep $_ == ($L+4)%8, @Neighbors;
1680             # next unless ($cont[0] + 4)%8 == $L;
1681             } elsif (@Neighbors == 1) {
1682             # warn "... reached";
1683 0 0       0 if ($mid%2) { # ֍ but not ą; what about γ, צ???
1684             # warn "... reached";
1685 0         0 my @NN = grep $_ != ($mid+4)%8, @{$offs->[$y+$dy[$mid]][$x+$dx[$mid]]};
  0         0  
1686 0         0 my @ed = grep $near->[$y+$dy[$mid]][$x+$dx[$mid]][$_], map +($mid + $_)%8, 2, -2;
1687 0 0       0 next if grep !$edge->[$y+$dy[$mid]][$x+$dx[$mid]][$_], @ed; # The present perpendicular directions must be edges
1688             # $marked++ if @ed and ($L > 7 or abs(($L-$mid)%8 - 4) < 2);
1689             # warn "... reached ($#$near,$#{$near->[$y+$dy[$mid]]}) near0=[@$near] near1=[@{$near->[$y+$dy[$mid]]}], x=", $x+$dx[$mid], ", y=", $y+$dy[$mid];
1690             # warn "... reached NN=[@NN] mid=$mid ed=(@ed) L=$L CNT=$cnt->[$y][$x] near=[@{$near->[$y+$dy[$mid]][$x+$dx[$mid]]}]";
1691 0 0 0     0 next if grep abs(($_+4-$mid)%8 - 4) > 1, @NN
      0        
1692             and not (@ed and ($L > 7 or abs(($L-$mid)%8 - 4) < 2) and $cnt->[$y][$x] < 3);
1693             # warn "... reached";
1694             } else {
1695             # warn "... reached";
1696             # $marked++ if grep $edge->[$y+$dy[$mid]][$x+$dx[$mid]][($mid+$_)%8], 2, -2 # Allow one edge (Ԃ) if there is no neighbor in other direction
1697             # and not grep +($near->[$y+$dy[$mid]][$x+$dx[$mid]][($mid+$_)%8] and not $edge->[$y+$dy[$mid]][$x+$dx[$mid]][($mid+$_)%8]), 2, -2;
1698 0 0 0     0 next if grep !$edge->[$y+$dy[$mid]][$x+$dx[$mid]][($mid+$_)%8], 2, -2
      0        
1699             and not ( grep $edge->[$y+$dy[$mid]][$x+$dx[$mid]][($mid+$_)%8], 2, -2 # Allow one edge (Ԃ) if there is no neighbor in other direction
1700             and not grep +($near->[$y+$dy[$mid]][$x+$dx[$mid]][($mid+$_)%8]
1701             and not $edge->[$y+$dy[$mid]][$x+$dx[$mid]][($mid+$_)%8]), 2, -2 );
1702             }
1703             # warn "... reached";
1704             # next if ($mid%2) or grep !$edge->[$y+$dy[$mid]][$x+$dx[$mid]][($mid+$_)%8], 2, -2; # ֍ but not ą
1705 0 0 0     0 @cont = @Neighbors if $L < 8 and 2 > abs(($L-$Neighbors[0])%8 - 4);
1706             }
1707             }
1708             # $marked++ if $premark;
1709             # $marked++
1710             # , next
1711             # if @cont and not $Simple[$y][$x] and $cntedge->[$y][$x];
1712             # next;
1713             # warn(": ($x,$y,$_,$x+$dx[$_],$y+$dy[$_])"),
1714 0         0 push @edgeAdd, [$x,$y,$_,$x+$dx[$_],$y+$dy[$_]] for @cont;
1715             }
1716             }
1717             # warn($edge->[12][3][5] ? "### <$edge->[12][3][5]>" : "###### not yet"); # ($#todoDegree [@{$todoDegree[0]||[]}] [@{$todoDegree[1]||[]}] [@{$todoDegree[2]||[]}])
1718             # warn($edge->[8][7][5] ? "### <$edge->[8][7][5]>" : "###### not yet #=$#edgeAdd [@{$edgeAdd[0]||['N/A']}]");
1719 2         4 add_edge($_, $edge, $cntedge, $lastedge) for @edgeAdd;
1720             # warn($edge->[12][3][5] ? "### <$edge->[12][3][5]>" : "###### not yet"); # ($#todoDegree [@{$todoDegree[0]||[]}] [@{$todoDegree[1]||[]}] [@{$todoDegree[2]||[]}])
1721             # warn($edge->[8][7][5] ? "### <$edge->[8][7][5]>" : "###### not yet");
1722 2         20 [$edge, $cntedge, $lastedge];
1723             }
1724            
1725             # How to recognize rasterization of 1px-wide line?
1726             # Going along a line, there are only two types of delta (neighbors, one
1727             # diagonal, one coordinate).
1728             # For slopes <= 1/2 diagonal delta cannot come in pairs; for slopes above 1/2
1729             # horizontal cannot come in pairs. Hence one gets stretches of one type
1730             # of delta, separated by single deltas of the other type.
1731            
1732             # Be more specific: which stretches may appear? Use continuous fractions!
1733            
1734             # May assume slope M >= 1, take intersection of the line with the vertical grid
1735             # line. Make a path between two copies of the line shifted +- 1/2
1736             # horizontally; color squares with centers inside the path.
1737             # Hence the diagonal-UR delta appears after a square with center (A + .5,B + .5)
1738             # if y-coordinate of the intersection with x = A+1 is between B+0.5 and B+1.5.
1739            
1740             # Hence stretches are determined by closest integers to Mn + b, n in Z. Hence
1741             # they are related if M' = +-M + M0 with integer M0. Hence may reduce to
1742             # 0 < M <= 0.5. Hence stretches are of (max) 2 lengths (differing by 1);
1743             # one of the lengths appears single, the other comes in groups (2-stretches).
1744            
1745             # Which 2-stretches may appear? Boundaries are determined by when the line
1746             # intersects y = n + 0.5 with n in Z. Now exchanging x and y and doing M=1/M
1747             # reduces to the previous step.
1748            
1749             # How deep one may go on 24x24 grid? The shortest non-constant stretch is 1,2;
1750             # such 3-stretch gives the shortest 2-stretch 2,1,1; this gives the shortest
1751             # stretch 1,1,2,1,2,1,2 which is -/-/--/-/--/-/-- which is 19-long. Hence
1752             # 3-stretches may appear... On the other hand, it may be interpreted as a part
1753             # of 2,1 repeated indefinitely (prepend -); is avoided by prepending /...
1754            
1755             # xxx
1756             # xx
1757             # xxx
1758             # xx
1759             # xxx
1760             # xx
1761             # xx
1762             # x
1763            
1764             # This also can take into account that the line may be cut into interval
1765             # somewhere inside a stretch...
1766            
1767             # These transformation may also define "the best" b in y=Mx+b. When we
1768             # reduce to 0 < M <= 0.5 with constant stretches (pattern ----/ repeated),
1769             # the best line passes through middles of /-deltas.
1770            
1771             # On the next layer: if 2-stretches are constant (so stretches are n,m with
1772             # m single, and n coming in groups of N), the line passes through the middle
1773             # of m-stretches.
1774            
1775            
1776             # http://www.sourcecodebrowser.com/autotrace/0.31.1/pxl-outline_8h.html
1777             # http://tug.org/texinfohtml/fontu.html#Limn
1778             # http://stuff.mit.edu/afs/athena/astaff/project/tex/fontutil/fontutils-0.6/limn/fit.c
1779            
1780             # ??? After we found "long strokes", remove them, but keep pixels which
1781             # on both vertical (or horizontal; or diag?) sides have "remaining pixels".
1782             # Try to find strokes in remaining+kept pixels... -- works for "#"
1783            
1784             # ??? Try to find long vert/hor strokes by brute force. Exclude those who
1785             # have too many pixels on neighboring lines. -- works for "$".
1786             # Considering striked-snakes (such as $): k neighbors for 2k-1 is not "too many"
1787            
1788             # Currency ¤ is tricky... -- too many "extendable" lines.
1789            
1790             # Ec E 3/4 Note that ^ in 4 is genuine one, but v is fake...
1791             # > c
1792             # < /
1793             # xx
1794             # Ex x
1795             # x *
1796             # x CC
1797             # / x f
1798             # c Lxx
1799             # E v
1800            
1801             # Input encodes a sequence of rectangles made of grid squares; rectangles share UR/LL corners:
1802             # □ is encoded as 2,4,4,3,1 (all positive)
1803             # □□□
1804             # □□□□
1805             # □□□□
1806             # □□
1807             # We want to find a line which rasterizes to these squares, i.e., intersect the (“red”) vertical disector of every square.
1808             #
1809             # It is the same as intersecting a (“green”) horizontal line of length=1 centered at the shared corners,
1810             # plus intersecting the red lines of the leftmost and the rightmost square. Suppose that non-on-edge rectanles
1811             # are only of two sizes, s and s+1. Swapping x and y axis, and subtracting y'=y-sx moves the green lines to a
1812             # collection of red lines of a new configuration of rectanges. This gives a step of recursion. Above configuration is moved to
1813             # □□
1814             # □
1815             # □
1816             # On this picture, the “old” red lines (“pink”, two at edges) become sloped lines with slope -s, with horizontal projection 1
1817             # (ending on horizontal grid lines, below the center of first square, and below the center of the last square.
1818            
1819             # The right end of the left pink line is below the new-red line of the leftmost new square; hence it is below any fitting
1820             # line. One must only check that the left end of the left pink line is above the fitting line. If this left end is on level
1821             # (or above) the top of the left square, everything is OK.
1822            
1823             # If it is on the level or below the bottom of the left square, then draw a new green line: horizontal line of lenght 1 going right
1824             # from the left end of the pink like. Obviously, the fitting line intersects the pink line iff it intersects the new green line.
1825            
1826             # There are several cases when we may exclude the new green line being below the bottom of the left square:
1827             # • if all rectangles are actually squares, one could replace s by s+1 above, and have one rectangle instead; exclude this;
1828             # • if there is one rectangle longer than 2 (or two of length 2) the slope of the fitting line is < 1, so intersection with
1829             # such green line is impossible;
1830             # • If there is one rectangle of length 2, and the rest are squares, the green line may be 1 unit below the bottom (and it
1831             # is unique; this may be repeated on both ends).
1832            
1833             # Only two cases remain: the rectangles consist of 1 square (total), and that with a pink line which may be either forgotten,
1834             # or replaced by an “additional” green line. (The additional green lines have no associated red lines, so on the NEXT step of induction
1835             # they would give no pink lines.)
1836            
1837             # In the first (“trivial”) case, the preceding step is of two rectangles with no added green lines.
1838            
1839             # So induction step: We start with n rectangles with k≤2 added green lines; coordinate change gives rectangles of total length
1840             # n-1+k with 2-k pink lines. A pink line is either forgotten, or impossible, or gives a unique solution, or is convertible to
1841             # a green line. So either we exclude a configuration, or find a unique solution or a trivial case, or get rectangles with n-1+k
1842             # squares and ≤2-k added green lines. The only cases when the number of squares did not decrease is:
1843             # All rectangles at start are squares except one of length 2; we had 2 added green lines.
1844             # But then on the next step we have no added green lines, so the next step is the trivial one.
1845            
1846             # (To avoid the trivial step [which is tricky] we ensure that we call recursively, there are at least two squares.
1847             # This means at least two green intervals on the previous stage.)
1848             # Provided that this case is handled in the caller, the additional green lines appear when the length of the start/end rectangle
1849             # is s+1; if it is above s+1, this is an impossible situation.
1850            
1851             # In particular, every case is reduced to a “unique solution” one, or the “trivial” one. The last one is equivalent to
1852             # having 3 equidistant paralle lines with an an interval [AB] on the middle one (the preimage [AB] of the last red line), and
1853             # opposite to each other rays XA' and YB' on the other two lines. The fitting line must intersect all 3 of them.
1854             # Oone of ray may be the whole line). It is easy to see that this is equivalent to the line intersecting intervals [XA], [AB]
1855             # and [BY]. If the quadrilateral XAYB is not convex, it may be decreased (so that X,A,B,Y are 3 vertices of a △, and a point
1856             # on a side. If it is convex, then intersecting [AB] is a corollary of other two. ???
1857            
1858             # Possibly unknown squares: the 2nd and 3rd row “share” a x-coordinate; assume that intersecting a red line in any of them is OK.
1859             # □ This is equivalent to having the red line of double length, which is equivalent to
1860             # □□□ the green line of double length at this position (assuming it is not at edge).
1861             # □□□□ Hence this allows the induction step as well.
1862             # □□□□□ (Encode ignoring the extra square at second line, with second line marked as: $extended->{1}=1.)
1863             # □□
1864            
1865             # Returns empty or a,b,db of the line y=ax+b-db which rasterizes to the rectangles of widths @$CC (connected UR ↔ LL corners);
1866             # The other two arguments as as above. LL corner of the leftmost of bottom squares is at 0,0.
1867             sub encodes_line ($;$$$); # Refused degenerated cases when there is a unique solution — with choices in rasterization
1868             sub encodes_line ($;$$$) { # %$extended should not have negative keys
1869 0   0 0 0 0 my ($CC, $green_at_left, $green_at_right, $extended) = (shift, shift, shift, shift||{}); # Every elt encodes delta between cells
1870             # A horrible mess of special-cases before we can recognize "runs", and flip axes...
1871             # warn "Got: [@$CC], $green_at_left, $green_at_right";
1872             # Banal case: one rectangle
1873 0 0 0     0 return 0, 1/2 if 1 >= @$CC and not ($green_at_left or $green_at_right); # No greens at all; one rectangle
      0        
1874 0         0 my(@jumps, $left_red, $right_red, %seen) = @$CC; # jumps between greens
1875 0 0       0 if ($green_at_left) { $left_red = $CC->[0] - 1 }
  0         0  
1876 0         0 else { shift @jumps }
1877 0 0       0 if ($green_at_right) { $right_red = $CC->[-1] - 1 }
  0         0  
1878 0         0 else { pop @jumps }
1879             # Need to exclude a trivial case
1880 0 0       0 unless (@jumps) { # Only one green
1881 0 0 0     0 if ($green_at_left or $green_at_right) { # Maximize some metric (∑distances to mid-red points)
1882 0         0 my $sl = 3/(4*$CC->[0] - 1); # ==> intersects right at height ≈¾.
1883 0 0       0 return ($green_at_left ? ($sl, $sl/4) : ($sl, 1/4 - $sl/2)) # Cut the green interval in the same proportion
1884             } else { # Two rectangles; likewise: if ratio of lengths is t≤1, use ¾(1+t²)/(1+t³)
1885 0         0 my $addHalf = 0;
1886 0 0 0     0 if ($extended->{0} and $CC->[0] < $CC->[1] - 1) {
    0 0        
1887 0         0 $CC = [$CC->[0] + 1, $CC->[1] - 1];
1888             } elsif ($extended->{0} and $CC->[0] == $CC->[1] - 1) {
1889 0         0 $addHalf = 1;
1890             }
1891 0 0       0 if ($CC->[0] + $addHalf == $CC->[1]) { # Go through the center of symmetry, with intersection of edge red lines as above:
    0          
1892 0         0 my $sl = 3/(4*$CC->[0] + 2*$addHalf - 2);
1893 0         0 return($sl, 1/4 - $sl/2);
1894             } elsif ($CC->[0] < $CC->[1]) { # One strategy is to continue periodically, then make a best fit; this joints
1895             # midpoints of rectangles. If differ by one, this breaks the green line 1:3 (with 1 on the side of longer rectangle).
1896             # On the other hand, to avoid close-to pathological rasterizations, we should divide the green line in the middle!
1897             # The best derasterization of 1+2 cuts the red lines at heights ¾,¼,¾ (the last ¾ makes it also good-in-L²-norm).
1898             # (It is also better since there are two ways to treat it: one can consider the main direction to be horizontal,
1899             # or to be diagonal. This “best” approximation is the same in these two approaches.)
1900 0         0 my $t = $CC->[0] / ($CC->[1] - 1); # Break green as 1:3, but use the slope as above with t-correction
1901 0         0 my $sl = 3*(1+$t*$t)/(1+$t*$t*$t)/(4*$CC->[1] - 2);
1902 0         0 return($sl, 1 - $sl*$CC->[0]);
1903             } else { # Likewise
1904 0         0 my $t = $CC->[1] / ($CC->[0] - 1); # If differ by 1, the distances to red lines are ¼, ¾, ⁵⁄₄,... exactly as ½,3/3 for equal lengths
1905 0         0 my $sl = 3*(1+$t*$t)/(1+$t*$t*$t)/(4*$CC->[0] - 2);
1906 0         0 return($sl, 1 - $sl*$CC->[0]);
1907             }
1908             }
1909             }
1910             # Up to this moment, always successfully return; below, unsuccessful returns indented (the only successful is the last one):
1911 0         0 my %jump_pre_ext = map { ($_ + !!$green_at_left, $extended->{$_}) } keys %$extended; # shift keys of extended to be pre-jump
  0         0  
1912 0         0 my $tot_jumps = 0;
1913 0         0 $tot_jumps += $_ for @jumps;
1914 0         0 my($slope_min, $slope_max) = (($tot_jumps - !!$jump_pre_ext{0})/@jumps, ($tot_jumps + !!$jump_pre_ext{@jumps})/@jumps);
1915 0 0       0 if (int($slope_min) != int $slope_max) { # differ by ≤1 unless @jumps = 1
    0          
1916             # There is a chance that after shear transformation with slope = int $slope_max, we have both increasing and decreasing paths.
1917             # But then there is also a horizontal path (yes!); choose it.
1918 0         0 my($H, @ok) = (0, (1) x (1+!!$jump_pre_ext{0})); # Cur min-height; OK-Height of horizontal line (now = before the jump № 0)
1919 0         0 for my $j (0..$#jumps) {
1920 0         0 $H += ( $jumps[$j] - int $slope_max );
1921 0         0 my $add = $jump_pre_ext{$j + 1};
1922 0 0       0 @ok = grep { $H <= $_ and $H + !!$add >= $_ } @ok or
  0 0       0  
1923             return;
1924             }
1925 0         0 die "Bug: need to fix the constant term for the shear transform"; # XXX ??? And axes flip! Check start/end segments too!
1926 0   0     0 my $tot = (grep $_, @ok) || 0; # □□□□□□□□□□□□□ Example. (after shear transform)
1927 0         0 return int $slope_max, $tot/@ok; # □□□□ □ □□□ @ok = 1, $tot = 1
1928             } elsif (%$extended) { # In general: may always look for non-decreasing path (after flip+shear).
1929             # Find rightmost consecutive run on each row (of those joining to the preceeding row)
1930 0         0 my($H, @cur) = (0, (1) x (1+!!$jump_pre_ext{0})); # Cur min-height; OK-Height of horizontal line (now = before the jump № 0)
1931 0         0 my(@starts) = ((0) x (1+!!$jump_pre_ext{0}));
1932 0         0 for my $j (0..$#jumps) {
1933 0         0 $H += ( $jumps[$j] - int $slope_max );
1934 0         0 my $add = !!$jump_pre_ext{$j + 1};
1935             # if one of cur is above $H + $add, remove the run at this height
1936 0 0       0 return if $cur[0] > $H + $add;
1937 0 0 0     0 pop @starts, pop @cur if $#cur and $cur[1] > $H + $add;
1938 0 0       0 @cur = grep { $H <= $_ and $H + $add >= $_ } @cur or
  0 0       0  
1939             return;
1940             }
1941             }
1942 0         0 my($U,$D,@jU,@jD,%seenU,%seenD) = (0,0); # jumps, seen: modifiable up/down
1943 0         0 $#jU = $#jD = @jumps + 5; # Make small negative indices access unreachable elts
1944 0   0     0 $extended->[$_] and $jD[$_ + !!$green_at_left]++ and $jU[$_ + !!$green_at_left - 1]++ for keys %$extended; # may access -1
      0        
1945 0         0 for my $n (0..$#jumps) {
1946 0         0 $seen {$jumps[$n]}++;
1947 0 0       0 $seenU{$jumps[$n]}++ if $jU[$n];
1948 0 0       0 $seenD{$jumps[$n]}++ if $jD[$n];
1949             }
1950 0         0 $seen{$_}++ for @jumps;
1951             # warn("many keys\n"),
1952 0 0       0 return if 2 + !!$U + !!$D < keys %seen; # There should be at most 2 different jumps (after correction UP/DOWN)
1953 0         0 my @JUMPS = sort {$a <=> $b} keys %seen;
  0         0  
1954             # print("jumps=@JUMPS\n"),
1955 0         0 my($min, $max) = @JUMPS[0,-1];
1956             # warn("jumps=@JUMPS\n"),
1957 0 0 0     0 return if @JUMPS > 1 and $max - $min > 1 + !!$U + !!$D; # If two different jumps, must differ by 1
1958 0   0     0 my($min_can_U, $max_can_D) = ($seen{$min} == ($seenU{$min} || 0), $seen{$max} == ($seenD{$max} || 0));
      0        
1959 0 0       0 if ($min_can_U) { # Cannot correct if two mins are adjacent
1960 0 0       0 $min_can_U = 0 if grep { $jumps[$_] == $min and $jumps[$_+1] == $min } 0..($#jumps-1);
  0 0       0  
1961             } # min and max cannot conflict!
1962 0 0       0 if ($max_can_D) { # Cannot correct if two mins are adjacent
1963 0 0       0 $max_can_D = 0 if grep { $jumps[$_] == $max and $jumps[$_+1] == $max } 0..($#jumps-1);
  0 0       0  
1964             }
1965             # warn("edge too long, jumps=@JUMPS\n"),
1966 0 0 0     0 return if not $green_at_left and (my $l_O = $CC->[0] - $min - 1) > 0 # left end too long (with $green_at_left already done)
      0        
      0        
1967             or not $green_at_right and (my $r_O = $CC->[-1] - $min - 1) > 0; # right end too long
1968             # Now may do the induction step (the trivial case @JUMPS == 1 and $min = 1 is already excluded)
1969 0         0 my @rect = 1;
1970 0         0 for my $j (@jumps) {
1971 0 0       0 $rect[-1]++, next if $j == $min;
1972 0         0 push @rect, 1;
1973             } # Found new rectangles
1974 0 0 0     0 return unless my($sl, $sh) = encodes_line \@rect, (!$green_at_left and !$l_O), (!$green_at_right and !$r_O);
      0        
1975             # print "sub-slant=$sl <-- (@rect), ", (!$green_at_left and !$l_O), ", ", (!$green_at_right and !$r_O), "\n";
1976 0         0 $sh += $sl*0.5; # Recalc so that the origin is at bottom of the leftmost new-red (=old-green) line
1977 0         0 $sl += $min; # Undo the shear transformation; now we are in x-y-exchanged coordinate system; origin = bot. of 1st green
1978             # If we had green_at_left, the first of old green intervals is (in x-y-exchanged coordinate system) vertical, centered at (0,0)
1979             # Otherwise it was centered at (1,$CC->[0])
1980 0 0       0 my($X,$Y) = ($green_at_left ? (0,0) : (1, $CC->[0]));
1981 0         0 $Y -= 0.5;
1982 0         0 $sh += $Y - $X*$sl; # Now $sh is w.r.t. the unshifed x-y-exchanged coordinate system
1983 0         0 return (1/$sl, -$sh/$sl); # Finally, exchange the axes back
1984             }
1985            
1986             sub stroke_2_line ($) {
1987 0     0 0 0 my($s, %seen, %dup) = shift; # $s->[$i][0] is dir∈(0..7);
1988 0         0 my @d = map $_->[0], @$s;
1989 0         0 $seen{$_}++ for @d;
1990 0 0       0 2 >= (my @D = keys %seen) or return;
1991 0 0       0 if (@D == 1) {
1992 0         0 my($dx,$dy) = ($dx[$d[0]], $dy[$d[0]]);
1993 0         0 return [0,0, @d * $dx, @d * $dy, 0,0, 2*$D[0]]; # move-beg, vector, move-end, 2*dir
1994             } # now @D == 2;
1995 0   0     0 $d[$_-1] == $d[$_] and $dup{$d[$_]}++ for 1..$#d;
1996             # warn "@d --> dup ", join(' ', %dup), "\n";
1997 0 0       0 1 >= (my @DD = keys %dup) or return;
1998 0 0       0 my $dup = @DD ? $DD[0] : $d[$d[0] % 2]; # if @DD is empty, two dirs alternate; assume that the odd one is a separator; ==>
1999 0         0 my $dir = $D[0] + $D[1];
2000 0         0 my $sep = $dir - $dup; # directional-independence — there is no guarantie that y=x-y preserves the best fit
2001 0 0 0     0 $dir += 8 if $dir == 7 and !($D[0] * $D[1]);
2002             # Do not “optimize” horizontal/vertical lines of len>2 with one diag stroke at the end (excluding tips):
2003 0 0 0     0 if ($sep % 2 and $seen{$sep} and $seen{$sep} <= 2 and @d > 2 and $seen{$sep} == (my @eSEP = grep $d[$_] == $sep, 0, -1)) {
      0        
      0        
      0        
2004 0         0 @eSEP = grep !$s->[$_][5], @eSEP;
2005 0 0       0 return if @eSEP <= 1;
2006             }
2007             # return if $sep % 2 and $seen{$sep} == 1 and @d > 2 and grep $_ == $sep, @d[0,-1];
2008 0         0 my($i,$col,@col) = (0,1);
2009 0         0 while ($i < @d) {
2010 0 0       0 if ($d[$i] == $sep) {
2011 0         0 push @col, $col;
2012 0         0 $col = 1;
2013             } else {
2014 0         0 $col++;
2015             }
2016 0         0 $i++;
2017             }
2018 0         0 push @col, $col;
2019             # warn "Scan of col: @col\n";
2020 0 0       0 my($slope, $offset) = encodes_line \@col or return;
2021 0         0 $offset -= 0.5 - $slope*0.5; # Recalc offset to be w.r.t. the center of the first square
2022             #### warn "slope=$slope; offset=$offset of @col; dup=$dup, sep=$sep [in = @d]\n";
2023             # “Reflection” below moves squares on diagonal to a horizontal sequence of squares; it preserves the square centered at (½,½)
2024             # Offsets w.r.t. this center are inverted
2025 0 0       0 ($dup, $sep, $slope, $offset) = ($sep, $dup, 1-$slope, -$offset) if $dup % 2; # goes more diagonally than horizontally/vertically
2026             # Now $dup is horizontal/vertical, and $sep is diagonal
2027 0         0 my($dx,$dy) = ($dx[$dup], $dy[$dup]);
2028 0         0 my($dx1,$dy1) = ($dx[$sep]-$dx, $dy[$sep]-$dy); # “orthogonal” direction
2029 0         0 my $C = grep $_ == $sep, @d; # Naive move in “orthogonal” direction
2030 0         0 my $lineC = @d * $slope + $offset;
2031 0         0 my $end_off = $lineC - $C;
2032 0         0 return [$dx1 * $offset, $dy1 * $offset, @d * $dx + $lineC * $dx1, @d * $dy + $lineC * $dy1, # vectors of start_offset, end_coord,
2033             $dx1 * $end_off, $dy1 * $end_off, $dir]; # end_off
2034             }
2035            
2036             # Break a “smooth” stroke into convex parts, straight lines, and snakes-not-convertable-to-straight-lines
2037             sub stroke_subdivide ($) { # We suppose it is known that this is not suitable for calculated lines, but rotates at most by ±1
2038 0     0 0 0 my ($edges, $last_snake, $last_r, @runs, @turns, @t_pos, @parts) = (shift, -1, 0, 1); # @runs starts with the first REAL elt, 1
2039 0         0 $turns[0] = $t_pos[0] = 0; # Turns are at VERTICES, so they are shifted w.r.t. edges by -½: this 0 means -½ w.r.t. edge nuns
2040 0         0 for my $i (1..$#$edges) {
2041 0 0       0 next unless my $r = ($edges->[$i][0] - $edges->[$i-1][0])%8; # One of 0, 1, 7
2042 0   0     0 $last_r ||= $r;
2043 0         0 push @turns, $r; # 135° corners: direction
2044 0         0 push @t_pos, $i; # same: ordinal of edge which is after the turn
2045 0 0       0 next if $r == $last_r;
2046 0         0 push @runs, $#turns; # Which corner starts a new convex sequence of corners (including sequences of length=1 in snakes!)
2047 0         0 $last_r = $r;
2048             } # a run is in turns, from $runs[$j] inclusive to $runs[$j+1] exclusive; has the same direction of turns
2049 0         0 push(@turns, 0); push(@t_pos, scalar @$edges); # These are not REAL, and not included in @runs due to end-exclusion
  0         0  
2050 0         0 push @runs, $#turns; # runs are turns between consecutive elts of @runs, begin-inclusive, end-exclusive (REAL elts only!)
2051 0         0 for my $j (1..$#runs) {
2052 0 0       0 if ($runs[$j] - $runs[$j-1] > 1) { # a convex run: ≥2 corners, so cannot be a part of a snake
    0          
2053 0 0 0     0 $parts[-1][1][1] = $runs[$j-2] if $parts[-1] and $parts[-1][1]; # previous part is a snake (unterminated yet); terminate
2054 0         0 push @parts, [[$runs[$j-1], $runs[$j]-1]]; # parts are both-ends-inclusive
2055             } elsif (++$last_snake != $j) { # start of a new snake (ends are dealed with on hext non-snake)
2056 0         0 push @parts, [undef, [$runs[$j-1]]]; # Which turn starts a new snake; the termination slot is bogus so far
2057 0         0 $last_snake = $j;
2058             }
2059             }
2060             # $snakes[-1][1] = @turns if $finish_snake;
2061 0 0       0 $parts[-1][1][1] = $#turns - 1 if $parts[-1][1]; # parts are both-ends-inclusive; include the last REAL element
2062             #my @T = map { $t_pos[$_] . ($turns[$_] > 6 ? '-' : ($turns[$_] ? '+' : '')) } 0..$#turns;
2063             #warn "turns=(@T), runs=(@runs), snake-parts ", (map 0+!!$_->[1], @parts),
2064             # ", lines (turn#) ", (map {"$_->[1][0]..$_->[1][1], "} grep $_->[1], @parts),
2065             # ", lines (edge) ", (map {($t_pos[$_->[1][0]]-1)."..$t_pos[$_->[1][1]], "} grep $_->[1], @parts), "\n";
2066 0         0 my @parts_edges;
2067             # @parts_edges = map [$t_pos[$_->[-1][0]] - 1, $t_pos[$_->[-1][0]]], @parts; # start: edge before corner
2068 0         0 for my $part (@parts) {
2069 0 0       0 if ($part->[0]) {
2070 0         0 my $mid = $part->[0][1] != $#t_pos; # Avoid accessing out-of-bound value (will be overwritten later anyway)
2071 0         0 push @parts_edges, [ $t_pos[$part->[0][0] - 1], $t_pos[$part->[0][1] + $mid] - $mid ]; # both sides inclusive
2072             } else {
2073 0         0 push @parts_edges, [ $t_pos[$part->[1][0]] - 1, $t_pos[$part->[1][1]] ]; # overlaps by 1 the neighbors
2074             }
2075             }
2076             # for my $i (0..$#parts) {
2077             # my $part = $parts[$i];
2078             # if ($part->[0]) {
2079             # my $b = $t_pos[$part->[0][0] - !!$i];
2080             # my $mid = $i != $#parts;
2081             # my $e = $t_pos[$part->[0][1] + $mid] - $mid;
2082             # push @parts_edges, [ $b, $e ]; # both sides inclusive
2083             # } else {
2084             # push @parts_edges, [ $t_pos[$part->[1][0]] - 1, $t_pos[$part->[1][1]] ]; # overlaps by 1 the neighbors
2085             # }
2086             # }
2087             #warn "predivide 0..$#$edges: ", join(' ', map $_->[0], @$edges), " => ", (map "$_->[0]...$_->[1]" . (!!$_->[2] && ':L') . " ", @parts_edges), "\n";
2088             # The logic above breaks for first/last segments:
2089 0         0 $parts_edges[0][0] = 0 ;#if $parts[0][1]; # incorporate the full preceding segment into the snake,
2090 0         0 $parts_edges[-1][1] = $#$edges ;#if $parts[-1][1]; # and not just one edge of it
2091             #warn "predivide 0..$#$edges: ", join(' ', map $_->[0], @$edges), " => ", (map "$parts_edges[$_]->[0]...$parts_edges[$_]->[1]" . (!!$parts[$_]->[1] && ':S') . " ", 0..$#parts_edges), "\n";
2092             #warn "parts_edges=$#parts_edges, edges=$#$edges; @ -1: b: $parts_edges[-1][0]; e: $parts_edges[-1][1]\n";
2093             #warn "parts_edges=$#parts_edges, edges=$#$edges; @ 2: b: $parts_edges[2][0]; e: $parts_edges[2][1]\n" if $#parts_edges==2;
2094 0         0 my($J, $donext, @out) = (0, 1); # up to $J-1 are written to @out
2095             #
2096             # We split the sequence of directions into snakes and convex parts. (May be overlapping where they join.)
2097             # Currently, we use this info in a very rudimentary way: we try to convert a snake to a line (with the overlap edge, or not);
2098             # if cannot, we join the "unrecognized" parts together.
2099             #
2100 0         0 for my $j (0..$#parts) { # Linearize (sub)snakes; remove overlap between snakes and ???
2101 0 0       0 next unless $donext++; # May skip the convex part after an unrecognized snake
2102 0         0 my $part = $parts[$j];
2103             #warn " fixing... j=$j"; # part=<@$part>";
2104             #warn(" $J <-- $j"),
2105 0 0       0 $out[$J++] = $parts_edges[$j], next unless my $snake = $part->[1]; # convex: at start, or after convex/recognized-line
2106 0         0 my($b,$e) = @{ $parts_edges[$j] };
  0         0  
2107 0         0 my @S = @$edges[$b..$e]; # Try first extension by 1 on both sides
2108 0         0 my($line) = stroke_2_line \@S;
2109             #warn "to line: #$j (end: parts=$#parts, parts_edges=$#parts_edges, edges=$#$edges; b: $b; e: $e; OK: ", 0+!!$line,"\n";
2110             ##warn "parts_edges=$#parts_edges, edges=$#$edges; @ 2: b: $parts_edges[2][0]; e: $parts_edges[2][1]\n" if $#parts_edges==2 and $j==2;
2111 0 0       0 if ($line) {
2112             #warn " longline: j=$j J=$J\n";
2113 0         0 $parts_edges[$j][2] = $line;
2114 0 0       0 $J and $parts_edges[$J-1][1]--; # A convex run contains at least 3 edges, so we will not annihilate it completely
2115 0 0       0 $j == $#parts or $parts_edges[$j+1][0]++;
2116 0         0 $out[$J++] = $parts_edges[$j];
2117             next
2118 0         0 } # Now last resort: try to shorten to min possible; since stroke_2_line() failed, we know that @S is long enough…
2119 0         0 my($b1, $e1) = ($b + 1, $e - 1);
2120 0 0       0 $b1 = $b unless $b;
2121 0 0       0 $e1 = $e if $e == $#$edges; # Does not make sense to shorten the snake at start/end of the stroke
2122 0 0 0     0 @out = @parts_edges, next if $b == $b1 and $e == $e1; # stroke is a wholesale snake
2123 0         0 @S = @$edges[$b1..$e1]; # Now try shortened by 1 on both sides
2124 0 0       0 unless (($line) = stroke_2_line \@S) {
2125             #warn " no-short-line: j=$j J=$J end->", ($j || 1 ? '' : '(omitted)' ), $parts_edges[$j + ($j!=$#parts)][1], "\n";
2126 0 0       0 $out[$J++] = $parts_edges[$j] unless $J; # create a previous part, if none
2127 0         0 $donext = 0;
2128 0         0 $out[$J-1][1] = $parts_edges[$j + ($j!=$#parts)][1]; # Extend the preceding part
2129 0         0 next;
2130             }
2131             #warn "shortened: #$j; b: $b --> $b1; e: $e -> $e1\n";
2132             #warn " short-line: j=$j J=$J\n";
2133 0 0       0 if ($b == $b1) {
2134 0 0       0 $J and $parts_edges[$J-1][1]--;
2135             } else {
2136 0         0 $parts_edges[$j][0]++
2137             }
2138 0 0       0 if ($e == $e1) {
2139 0 0       0 $j == $#parts or $parts_edges[$j+1][0]++;
2140             } else {
2141 0         0 $parts_edges[$j][1]--
2142             }
2143 0         0 $parts_edges[$j][2] = $line;
2144 0         0 $out[$J++] = $parts_edges[$j];
2145             }
2146 0         0 @out;
2147             }
2148            
2149             sub crosses_line ($$;$$) { # segL = [stX,stY,eX,eY,del_eX, del_eY]; segL = [stX,stY,eX,eY] eq = (X-stX)*(eY-stY) - (Y-stY)*(eX-stX)
2150 0   0 0 0 0 my($seg,$segL,$opp, $expand, $endEq, $stEq) = (shift,shift,shift,shift||0); # del_eX, del_eY is in the same coordinate system as segL; the rest is shifted
2151 0         0 my($stX,$stY,$eX,$eY,$DeX,$DeY) = @$segL;
2152             # warn "opp=",!!$opp,"\tseg=[@$seg], line=($stX,$stY,$eX,$eY,$DeX,$DeY)";
2153 0         0 my $dF = ($seg->[0] - $seg->[2])*($eY-$stY) - ($seg->[1] - $seg->[3])*($eX-$stX);
2154 0 0       0 if ($opp) {
2155 0         0 $stEq = ($seg->[0] - $DeX)*($eY-$stY) - ($seg->[1] - $DeY)*($eX-$stX); # [0,1] is w.r.t. logical end
2156 0         0 $endEq = $stEq - $dF;
2157             } else {
2158 0         0 $endEq = ($seg->[4] - $stX)*($eY-$stY) - ($seg->[5] - $stY)*($eX-$stX); # [4,5] is w.r.t. start
2159 0         0 $stEq = $endEq + $dF;
2160             }
2161             # warn("st=$stEq end=$endEq"),
2162 0 0       0 return unless $endEq*$stEq < 0;
2163 0         0 my $frac = $endEq/($endEq - $stEq);
2164 0 0       0 $frac = 1 - $frac if $opp;
2165 0         0 my $new = $frac*(1+$expand);
2166 0 0       0 $new = ($frac + 1)/2 if $new > ($frac + 1)/2; # Do not expand pathologically
2167 0         0 $frac = $new;
2168 0 0       0 $frac = 1 - $frac if $opp;
2169 0         0 my $out = [($seg->[0]*$frac + $seg->[2]*(1-$frac)), ($seg->[1]*$frac + $seg->[3]*(1-$frac))]; # in coordinates of seg
2170             # warn "Out=[@$out] st=$stEq end=$endEq";
2171 0         0 $out
2172             }
2173            
2174             sub stroke_2_strokes ($$$) {
2175             # The 1st version should not be applied to smooth closed loops: we assume that 0 is a corner
2176 2     2 0 6 my($s, $calc_hash, $closed) = (shift, shift, shift); # $s->[$i][0] is dir (0..7);
2177 2         11 my @d = map $_->[0], @$s;
2178             # warn "stroke: @d\n"; # start is before the segment with dir = $dir[start]
2179 2         8 my($prev_corner, @corners, @calc) = (0, [0]); # corners, at index: 0=start; optional: 1=calc_line, 2=start_moved, 3=end_moved
2180 2         7 for my $i (0..$#d) { # Between $corners[-1] (inclusive) and $prev_corner (exclusive) there is a region without calculated segments
2181 4 100 66     23 if ($i == $#d or abs(($d[$i+1] - $d[$i])%8 - 4) <= 2) { # found a corner at $i+1 (max+1 is AT_END)
2182 2         9 my(@SS, @parts) = @$s[$prev_corner..$i];
2183 2 50       10 if (@SS <= 1) {
    50          
    0          
2184 0         0 @parts = [$prev_corner, $i];
2185             } elsif (2 == @SS) { # Do not convert to a line — most of the time is not beneficial
2186 2         9 @parts = ([$prev_corner, $i - 1], [$i - 1, $i]);
2187             } elsif (my($Line) = stroke_2_line \@SS) {
2188 0         0 @parts = [$prev_corner, $i, $Line];
2189             } else {
2190 0         0 @parts = stroke_subdivide(\@SS);
2191             #warn "subdivide 0..$#SS: ", join(' ', map $_->[0], @SS), " => ", (map "$_->[0]...$_->[1]" . (!!$_->[2] && ':L') . " ", @parts), " [$s->[0][1], $s->[0][2]] --> [$s->[-1][3], $s->[-1][4]]\n";
2192 0         0 $_->[0] += $prev_corner, $_->[1] += $prev_corner for @parts;
2193             # @parts = [$prev_corner, $i];
2194             ## $prev_corner = $i+1;
2195             ## next; # Not found
2196             } # Invariant: between corners, has either 1-edge segments, or a calculated line
2197 2         5 for my $part (@parts) { # Invariant: $prev_corner >= $corners[-1][0] (this is a candidate for the next corner)
2198             #warn " prev=$prev_corner line=<@{$part->[2] || []}> part[1]=$part->[1] last=$corners[-1][0]";
2199 4 50       40 $prev_corner = $part->[1] + 1, next unless my $line = $part->[2];
2200             # Now we found a calculated segment (at least 2 edges)
2201 0 0       0 push @corners, [$prev_corner] if $prev_corner != $corners[-1][0]; # create a new unrecognized chunk
2202 0   0     0 @{$corners[-1]}[1,2,3] = ($line, !($line->[0] == 0 and $line->[1] == 0), !($line->[4] == 0 and $line->[5] == 0));
  0   0     0  
2203 0         0 push @corners, [$prev_corner = $part->[1]+1]; # start new segment
2204 0 0       0 next if $part->[0] == $part->[1];
2205 0         0 for my $S (@$s[$part->[0]..$part->[1]]) {
2206 0         0 $calc_hash->{$S->[1],$S->[2]}{$S->[0]}++; # x,y,d
2207 0         0 my $d1 = ($S->[0] + 4)%8;
2208 0         0 $calc_hash->{$S->[3],$S->[4]}{$d1}++; # x1,y1,d1
2209             }
2210             }
2211             }
2212             }
2213 2 50       10 push @corners, [$#d+1] unless $corners[-1][0] == $#d+1; # end last segment
2214             # Fixing involves inserting new segments (making new corners); to avoid changing indices, do it back to front:
2215 2         7 for my $i (reverse(0..$#corners-1)) { # Try to fix misplaced joints (currently, only on unrecognized/calculated joins
2216             # warn "doing segment=$i; [", (join ', ', map +(ref() ? "[@$_]" : "$_"), @{$corners[$i]}), "]\n";
2217 2         5 unless (0 and $corners[$i][1]) {
2218 2   0     18 my $move_start = (($i or $closed) and $corners[$i-1][3]) && [@{$corners[$i-1][1]}[4,5]];
2219 2   50     9 my $move_end = $corners[$i+1][2] && [@{$corners[$i+1][1]}[0,1]];
2220             #warn "Fixing segment=$i: start=$move_start end=$move_end\n";
2221 2 50       7 if ($corners[$i][1]) { # Move start on straight-line segments, and move start and end on runs of 1-edge segments
2222             # No need to move start on the first segment if either (A) non-closed curve, or (B) last run is made of 1-edge segments.
2223 0 0 0     0 next unless ($i or $closed) and $corners[$i-1][1]; # If previous is a run of 1-edge segments, it would be fixed there
      0        
2224 0 0       0 my $my_move_start = $corners[$i][2] ? [@{$corners[$i][1]}[0,1]] : [0,0];
  0         0  
2225 0   0     0 $move_start ||= [0,0];
2226 0 0       0 next unless grep $move_start->[$_] != $my_move_start->[$_], 0, 1; # Just an optimization; the code below is more robust
2227             # Segments intersect iff ends of each one are on opposite sides of the line of other one.
2228 0 0       0 my $cross_prev = crosses_line($corners[$i-1][1], $corners[$i][1], !'opp', $extend_tip) or next;
2229 0 0       0 my $cross_our = crosses_line($corners[$i][1], $corners[$i-1][1], 'opp', $extend_tip) or next;
2230             # warn "Fixing... prev=[@$cross_prev] (@{$corners[$i-1][1]}[2,3]) our=[@$cross_our] (@{$corners[$i][1]}[0,1])";
2231             # Try one: just cut off at the intersection
2232 0         0 my @prev;
2233 0         0 $prev[$_] = ($corners[$i-1][1][4+$_] += $cross_prev->[$_] - $corners[$i-1][1][2+$_]) for 0, 1;
2234 0         0 $corners[$i-1][1][2+$_] = $cross_prev->[$_] for 0, 1;
2235 0         0 $corners[$i][1][$_] = $cross_our->[$_] for 0, 1;
2236 0         0 $corners[$i-1][3] = 0; # No longer have a mismatch
2237 0         0 $corners[$i][2] = 0; # No longer have a mismatch
2238 0         0 my @targ = map +($move_start->[$_] + $my_move_start->[$_])/2, 0, 1;
2239 0         0 my($r,$r1) = map $corners[$i-$_][1][6], 0, 1;
2240 0         0 $r1 = ($r1 + 8)%16;
2241 0         0 my $rot = ($r + $r1 + 16*(abs($r - $r1) >= 8))%32;
2242 0         0 splice @corners, $i, 0, [$corners[$i][0], [@prev, @targ, @targ, ($rot + 16)%32/2, 1]],
2243             [$corners[$i][0], [@targ, @$cross_our, @$cross_our, $rot/2, 1]];
2244             # $marked++;
2245 0         0 next;
2246             }
2247 2 50 33     13 next unless $move_start or $move_end;
2248             #warn "Fixing segment=$i: start=$move_start end=$move_end\n";
2249 0         0 my $len = $corners[$i+1][0] - $corners[$i][0];
2250 0 0       0 my @do = (($move_start ? 0 : ()), ($move_end ? $len-1 : ()));
    0          
2251 0 0 0     0 $#do = 0 if @do == 2 and not $do[1]; # len = 1
2252 0         0 my $kill = (@do == $len);
2253 0         0 for my $seg (reverse @do) { # reverse: as above
2254 0 0 0     0 my $start = ($move_start and not $seg) ? $move_start : [0,0];
2255 0 0 0     0 my $end = ($move_end and $seg == $len-1) ? $move_end : [0,0];
2256 0         0 my $dir = $d[$corners[$i][0] + $seg]; # direction before correction (1 after 2*$dir means approx)
2257 0 0       0 warn "i=$i, seg=$seg d=<@d> #d=$#d corners[$i]=<@{$corners[$i]}> #corners=$#corners\n\tcorners=<",
  0         0  
2258             join('> <', map "@$_", @corners), '>' unless defined $dir;
2259 0         0 my $line = [$start->[0], $start->[1], $end->[0] + $dx[$dir], $end->[1] + $dy[$dir], $end->[0], $end->[1], 2*$dir, 1];
2260 0 0       0 if ($seg) {
2261 0         0 $corners[$i+1][2] = 0; # No longer have a mismatch
2262 0         0 splice @corners, $i+1, 0, [$corners[$i+1][0] - 1, $line]; # with no mismatches
2263             } else {
2264 0         0 my $pos = $corners[$i][0];
2265 0 0       0 $corners[$i][0]++, splice @corners, $i, 0, [] unless $kill;
2266 0         0 @{$corners[$i]} = ($pos, $line);
  0         0  
2267 0 0       0 $corners[$i-1][3] = 0 if $i; # No longer have a mismatch
2268 0 0       0 $corners[$i+1][2] = 0 if $corners[$i+1]; # No longer have a mismatch
2269             }
2270             # warn "Fixed segment=$i: start=[@$move_start] end=[@$move_end]\n" if $move_start and $move_end;
2271             }
2272             }
2273             }
2274 2         7 for my $i (reverse(0..$#corners-1)) {
2275             # warn "doing segment=$i; [", (join ', ', map +(ref() ? "[@$_]" : "$_"), @{$corners[$i]}), "]\n";
2276             }
2277 2         5 my @breaks = 0; # Meaning: $corner[$break] starts a new sub-stroke
2278 2         6 for my $i (0..$#corners-2) { # check for mismatch at end
2279 0 0 0     0 push @breaks, $i+1 if $corners[$i][3] or $corners[$i+1][2];
2280             }
2281 2         10 \@corners, \@breaks, \@calc;
2282             }
2283            
2284             sub traverse_boundary($$$$$) { # The blob is on our right
2285 0     0 0 0 my ($x, $y, $dir, $blob, $nextEdge, $c) = (shift, shift, shift, shift, shift, 1);
2286             # warn "Enter traverse_boundary()\n";
2287 0         0 while (1) { # Greedy algorithm: we always go left if we can
2288             # warn "... x=$x, y=$y, d=$dir\n";
2289 0         0 my $dir1 = ($dir - 2) %8;
2290 0         0 my $dx = $dx[$dir];
2291 0         0 my $dy = $dy[$dir];
2292 0         0 my $dx1 = $dx[$dir1];
2293 0         0 my $dy1 = $dy[$dir1];
2294 0         0 my($x1, $y1) = ($x+$dx+$dx1, $y+$dy+$dy1);
2295 0 0       0 if ($blob->[$y1][$x1]) { # Turn Left (already precalculated)
    0          
2296             } elsif ($blob->[$y + $dy][$x + $dx]) { # Continue
2297 0         0 $x1 = $x + $dx; $y1 = $y + $dy; $dir1 = $dir;
  0         0  
  0         0  
2298             } else { # Turn Right
2299 0         0 ($x1, $y1, $dir1) = ($x, $y, ($dir + 2) % 8);
2300             }
2301 0         0 $nextEdge->[$dir][$y][$x] = [$x1, $y1, $dir1];
2302 0         0 ($x, $y, $dir) = ($x1, $y1, $dir1);
2303 0 0       0 return $c if $nextEdge->[$dir][$y][$x];
2304 0         0 $c++;
2305             }
2306             }
2307            
2308             sub _traverse_boundary($$$$$) { # The blob is on our right
2309 0     0   0 my ($x, $y, $dir, $blob, $nextEdge, $p, $dirOffset) = (shift, shift, shift, shift, shift, [], []);
2310             # warn "Enter _traverse_boundary()\n";
2311 0 0       0 return if $nextEdge->[$dir][$y][$x];
2312 0         0 while (1) { # Greedy algorithm: we always go left if we can, and only in even directions
2313             # warn "... x=$x, y=$y, d=$dir\n";
2314 0         0 my $dir1 = ($dir - 2) %8; # $dir - 2 points where there is NO blob
2315 0         0 my $dx = $dx[$dir];
2316 0         0 my $dy = $dy[$dir];
2317 0         0 my $dx1 = $dx[$dir1];
2318 0         0 my $dy1 = $dy[$dir1];
2319 0         0 my($x1, $y1) = ($x+$dx+$dx1, $y+$dy+$dy1); # diagonal directin
2320 0         0 push @$dirOffset, $dir;
2321 0 0       0 if ($blob->[$y1][$x1]) { # Turn Left (already precalculated)
    0          
2322 0         0 push @$p, [($dir-1)%8,$x,$y,$x1,$y1,$dirOffset];
2323 0         0 $dirOffset = [];
2324             } elsif ($blob->[$y + $dy][$x + $dx]) { # Continue
2325 0         0 $x1 = $x + $dx; $y1 = $y + $dy; $dir1 = $dir;
  0         0  
  0         0  
2326 0         0 push @$p, [$dir,$x,$y,$x1,$y1,$dirOffset];
2327 0         0 $dirOffset = [];
2328             } else { # Turn Right (In place!)
2329 0         0 ($x1, $y1, $dir1) = ($x, $y, ($dir + 2) % 8);
2330             }
2331 0         0 $nextEdge->[$dir][$y][$x] = [$x1, $y1, $dir1];
2332 0         0 ($x, $y, $dir) = ($x1, $y1, $dir1);
2333            
2334             # push(@$p, [$dir,$x,$y,$x1,$y1]),
2335             # warn( '[', join('], [', map "@$_", @$p), ']'),
2336 0 0       0 if ($nextEdge->[$dir][$y][$x]) {
2337 0 0       0 unshift @{ $p->[-1][5] }, @$p if @$p;
  0         0  
2338 0 0       0 push @$p, [undef, $x,$y] unless @$p; # Singleton
2339 0         0 return $p;
2340             }
2341             }
2342             }
2343            
2344             # start, end, and the encountered MForks are marked as already visited (into $traversedEdges)
2345             sub traverse_stroke ($$$$$$$;$) { # XXX Is there a duplication between seen/traversed???
2346 2     2 0 8 my($x,$y,$dir,$seenEndEdge,$nextEdge,$traversedEdges,$tips,$endstip) = (shift, shift, shift, shift, shift, shift, shift, shift);
2347 2         8 my($X,$Y) = ($x,$y);
2348 2         3 my @stroke;
2349 2         4 while (1) {
2350 4         10 my $x1 = $x + $dx[$dir];
2351 4         7 my $y1 = $y + $dy[$dir];
2352 4 50 0     24 last if $traversedEdges->{$x,$y,$dir}++ and not ($x1==$X and $y1==$Y); # applicable to loops: looped back (but not to tip)
      33        
2353 4         15 $traversedEdges->{$x1,$y1,($dir+4)%8}++;
2354 4         15 push @stroke, [$dir,$x,$y,$x1,$y1];
2355 4 100       17 $seenEndEdge->{$x1,$y1,($dir+4)%8}++, last unless defined(my $n = $nextEdge->[$y][$x][$dir]);
2356             #warn "found next edge: $x,$y $dir --> $x1,$y1 +$n\n";
2357 2 50       9 if (my $tip = $tips->{$x1,$y1}) {
2358 0         0 $tip = $tip->[2];
2359             # last if $tip == ($dir+4)%8; # When splitting a loop, happens on the 1st step (but we removed this tip!)
2360 0         0 my $x2 = $x1 + $dx[$tip];
2361 0         0 my $y2 = $y1 + $dy[$tip];
2362 0         0 push @stroke, [$tip,$x1,$y1,$x2,$y2,'tip'];
2363 0         0 push @stroke, [($tip+4)%8,$x2,$y2,$x1,$y1,'tip'];
2364 0         0 warn "doing tip from ($x1,$y1): dir=$tip, from_dir=$dir, next_dir=", ($dir + $n) % 8, "\n"
2365             if debug > 4;
2366 0         0 $traversedEdges->{$x1,$y1,$tip}++; # Protect from code which finds closed loops
2367 0         0 $traversedEdges->{$x2,$y2,($tip+4)%8}++; # Protect from code which finds closed loops
2368             }
2369 2         8 ($x,$y,$dir) = ($x1, $y1, ($dir + $n) % 8);
2370             # last if $seenEndEdge->{$x,$y,$dir}; # applicable to loops: looped back
2371             }
2372 2         4 warn "found edges in a stroke: ", scalar @stroke, ': (', join(') (', "@{$stroke[0]}[1,2]", map "@$_[3,4]", @stroke), ")\n"
2373             if debug > 4;
2374 2 50       8 $stroke[$_][5] = 'tip' for $endstip ? (0, -1) : ();
2375 2         22 \@stroke;
2376             }
2377            
2378             sub nnn6_do_Simple_and_edges ($$$$$$$$$$$$$) {
2379 2     2 0 67 my($width, $height, $edge, $cntedge,,$lastedge, $rays, $offs, $longedges, $blob, $pixels, $skipExtraBlob, $tailEdge, $coarse_blobs)
2380             = (shift, shift, shift, shift, shift, shift, shift, shift, shift, shift, shift, shift, shift);
2381 2         6 my(@nextEdge, @endEdge, %edges, %seenEndEdge, @strokes, %traversedEdges);
2382 2         8 for my $y (0..$#$edge) { # Effectively, “move” the position of the joint along the spur in MFork/Tail pairs from MFork to Tail
2383 11 100       29 next unless $edge->[$y]; # But only when there are exactly 3 edges (at Tail vertex, which is the branching point)
2384 6         11 for my $x ( 0..$#{ $edge->[$y] } ) {
  6         16  
2385 39 50 33     197 next unless $edge->[$y][$x] and my $t = $tailEdge->{$x,$y};
2386 0         0 my($dir, $rot) = @$t[2,3];
2387 0 0       0 next unless 3 == $cntedge->[$y][$x]; # was: ¤
2388 0         0 my @d = map +($dir+$_)%8, ($rot == 1) + 3, 5 - ($rot == -1);
2389 0         0 for my $branch (0, 1) {
2390 0         0 my $D = $d[$branch];
2391 0         0 my $x1 = $x + $dx[$D];
2392 0         0 my $y1 = $y + $dy[$D]; # special-case transversal of 2 edges leading into the branch point
2393 0         0 $nextEdge[$y1][$x1][($D+4)%8] = ($d[1-$branch] - $D - 4)%8; # would special-case transversal of the spur later
2394             }
2395             }
2396             }
2397 2         9 for my $y (0..$#$edge) { # For every directed-edge, find the next directed-edge. If none, mark the opposite as end-edge.
2398 11 100       27 next unless $edge->[$y]; # Except for spurs of the MFork (special-cased later).
2399 6         24 for my $x ( 0..$#{ $edge->[$y] } ) {
  6         15  
2400 39 50       84 next unless $edge->[$y][$x];
2401 39         71 for my $dir ( 0..$#{ $edge->[$y][$x] } ) {
  39         86  
2402 40 100       93 next unless $edge->[$y][$x][$dir];
2403 8         36 $edges{$x,$y,$dir} = [$x,$y,$dir];
2404 8         17 my $x1 = $x + $dx[$dir];
2405 8         14 my $y1 = $y + $dy[$dir];
2406 8 100       55 if ($cntedge->[$y1][$x1] == 2) {
    50          
    50          
2407 4         7 my @o;
2408 4         28 push @o, $_ for grep $edge->[$y1][$x1][$_], 0..7;
2409 4         12 my @oo = grep $_ != -4, map {($_- $dir + 4) % 8 - 4} @o; # find the other edge (is not it easier to find the sum???)
  8         23  
2410             #warn "found dirs [@o] at ($x,$y) --> $x1 $y1 $dir --> rot=$oo[0]\n";
2411 4         21 $nextEdge[$y][$x][$dir] = $oo[0];
2412             } elsif ($tailEdge->{$x,$y}) { # MFork, Tail; don't include in the end/nextEdge, special-case later
2413             # } elsif ($rays[$y][$x][$dir][0] =~ /^([MT])/) { # MFork, Tail; don't include in the end/nextEdge, special-case later
2414             } elsif ($tailEdge->{$x1,$y1}) { # Do not start at tail attachment; $nextEdge already set
2415             } else {
2416 4         32 push(@endEdge, [$x1,$y1,($dir+4)%8]); # Do not try to drive through junctions
2417             }
2418             }
2419             }
2420             }
2421             #warn "found endEdges: ", scalar @endEdge, "\n";
2422 2         5 my(@calc, %inCalcEdge);
2423 2         4 for my $edge (@endEdge) { # Find non-closed strokes (those having end-edge)
2424 4         12 my($x,$y,$dir) = @$edge;
2425 4 100       27 next if $seenEndEdge{$x,$y,$dir}++;
2426             # warn "endEdge: $x,$y, $dir, $cntedge->[$y][$x].\n";
2427 2         11 my $stroke = traverse_stroke($x,$y,$dir,\%seenEndEdge,\@nextEdge,\%traversedEdges, $tailEdge); # made of [$dir,$x,$y,$x1,$y1]
2428 2   33     10 my $closed = $stroke->[0][1] == $stroke->[-1][3] && $stroke->[0][2] == $stroke->[-1][4];
2429 2 50       6 if ($closed) {
2430 0         0 $closed = -2; # -2 means smooth, 2 means has a corner. Presume smooth (but with a junction)
2431 0         0 for my $i (0..$#$stroke) {
2432 0 0       0 $closed = 2, last unless abs(($stroke->[$i][0] - $stroke->[$i-1][0] + 4)%8 - 4) < 2; # At i=0, wraps back to the end
2433             }
2434             }
2435             # $closed &&= -2 if abs(($stroke->[0][1] - $stroke->[-1][1] + 4)%8 - 4) < 2;
2436 2         7 my($breaks, $runs) = [0];
2437 2 50       16 if ($closed < 0) { # loop known to be smooth; stroke_2_strokes() won't find anything except ends
2438 0         0 $runs = [[0],[$#$stroke+1]]; # fake corners at ends; [0] means: start at 0, no calculated lines until the next
2439             } else {
2440 2         14 ($runs, $breaks) = stroke_2_strokes($stroke, \%inCalcEdge, $closed); # Meaning: $runs->[$break] starts a new sub-stroke
2441             }
2442 2         9 push @strokes, [$closed, !'blob', $stroke, $runs, $breaks]; # (strokes with endpoints: “open”)
2443             }
2444             # warn "found open strokes: ", scalar @strokes, "\n";
2445 2         7 my(@closedStrokes, %edgesDone);
2446 2         0 my @E;
2447 2         9 for my $E (sort keys %$tailEdge) { # Best place to cut a closed stroke — if present.
2448 0         0 my $edge = $tailEdge->{$E}; # Need to normalize order, since bugs in fontforge are sensitive to the order
2449 0         0 my($x,$y,$dir,$rot) = @$edge;
2450 0         0 my $D = ($dir+4)%8;
2451 0         0 my $x1 = $x + $dx[$dir];
2452 0         0 my $y1 = $y + $dy[$dir]; # the encountered MForks are marked as already visited (by traverse_stroke())
2453 0         0 push @E, [$x1,$y1,$D,!!'tip',$x,$y,$E,$rot]; # start with MFork end of the tail
2454             } # (those already encoutnered are ignored by traverse_stroke() anyway)
2455 2         45 push @E, map [@$_,0], @edges{sort keys %edges};
2456 2         24 for my $e (@E) { # Handle closed strokes (without end-edge, need to loop through all edges)
2457 8         21 my($x,$y,$dir,$T,$x1,$y1,$E,$rot) = @$e; # (Need to normalize order, since bugs in fontforge are sensitive to the order)
2458 8 50       27 next if $traversedEdges{$x,$y,$dir};
2459 0 0       0 if ($T) { # starting at MFork; need to redo the structure of “next” edges; we
2460 0 0       0 $nextEdge[$y][$x][$dir] = ($rot == 1 ? 0 : 7); # go clockwise (same direction as blobs), assuming the tip is outside
2461 0         0 my $x2 = $x1 + $dx[($dir+($rot != -1))%8]; # (x,y,d) is tip→joint=(x1,y1); we continue same-dir, or 45° counter-clockw
2462 0         0 my $y2 = $y1 + $dy[($dir+($rot != -1))%8];
2463 0 0       0 $nextEdge[$y2][$x2][($dir+($rot != -1)+4)%8] = (($rot == -1 ? 0 : 7)); # at end of the loop, return to the tip (DUP???)
2464 0         0 delete $tailEdge->{$E};
2465             }
2466 0         0 push @closedStrokes, traverse_stroke($x,$y,$dir,\%seenEndEdge,\@nextEdge,\%traversedEdges, $tailEdge, $T); # of [$dir,$x,$y,$x1,$y1]
2467 0         0 push @{ $closedStrokes[-1] }, !'blob';
  0         0  
2468             }
2469 2         4 my(@nextEdgeBlob, @entryPointBlob); # With lastedge, includes ends of lines:
2470 2         11 find_blobs($blob, $width, $height, $pixels, $cntedge, $offs, $lastedge, $skipExtraBlob);
2471 2         5 for my $y (1..$height) {
2472 10         18 my $inner = 0;
2473 10         20 for my $x ( 1..$width ) {
2474 82 50       200 next unless !$blob->[$y][$x] == $inner;
2475 0         0 my $blobX = $x - $inner;
2476 0         0 $inner = 1 - $inner;
2477 0 0       0 my $dir = $inner ? 0 : 4; # $dir - 2 is a direction to exit the blob
2478 0 0       0 next if $nextEdgeBlob[$dir][$y][$blobX]; # already passed through
2479 0 0       0 if ($coarse_blobs) {
2480 0         0 push @entryPointBlob, [$blobX, $y, $dir];
2481 0         0 $entryPointBlob[-1][3] = traverse_boundary($blobX, $y, $dir, $blob, \@nextEdgeBlob);
2482             } else {
2483 0         0 push @closedStrokes, _traverse_boundary($blobX, $y, $dir, $blob, \@nextEdgeBlob);
2484 0         0 push @{ $closedStrokes[-1] }, !!'blob';
  0         0  
2485             }
2486             }
2487             }
2488 2         6 for my $stroke (@closedStrokes) {
2489 0         0 my $is_blob = pop @$stroke;
2490 0 0 0     0 push(@strokes, [undef, !!'blob', $stroke, undef, [0]]), next
2491             if @$stroke == 1 and not defined $stroke->[0][0];
2492             # Try to restart it on а corner (if present)
2493 0         0 my($i,$corner) = (-1, 2);
2494 0         0 while (++$i <= $#$stroke) {
2495 0         0 my($d,$prevd) = ($stroke->[$i][0], $stroke->[$i-1][0]); # At i=0, wraps back to the end
2496 0 0       0 last if abs((($d-$prevd) % 8) - 4) <= 2; # 135° angle is not a corner
2497             }
2498 0 0       0 $i = $corner = 0 if $i > $#$stroke;
2499 0 0       0 $stroke = [@$stroke[$i..$#$stroke, 0..($i-1)]] if $i;
2500 0         0 my($breaks, $runs) = [0];
2501 0 0       0 if ($corner == 0) { # loop known to be smooth; stroke_2_strokes() won't find anything except ends
2502 0         0 $runs = [[0],[$#$stroke+1]]; # fake corners at ends; [0] means: start at 0, no calculated lines until the next
2503             } else {
2504 0         0 ($runs, $breaks) = stroke_2_strokes($stroke, \%inCalcEdge, 'closed'); # Meaning: $runs->[$break] starts a new sub-stroke
2505             }
2506             # if $is_blob, we do not want to break loops; so the first element is reset to 0
2507 0         0 push @strokes, [$corner - 1, $is_blob, $stroke, $runs, $breaks]; # loop: 1 if have corners, -1 if smooth
2508             }
2509             # return if $opt{marked} and not ($marked and $marked2);
2510 2         7 for my $e (@$longedges) { # [$x, $y, $x+$dx+$dx1,$y+$dy+$dy1, $offset, $dir, $rot]
2511 0 0 0     0 next if not ref $e and $e eq 'erased';
2512 0         0 push @strokes, [0, !'blob', [[-20, @$e[0..3]]]]; # dir==-20
2513             }
2514             #warn "found strokes: ", scalar @strokes, "\n";
2515             # warn($edge->[8][7][5] ? "### <$edge->[8][7][5]>" : "###### not yet");
2516 2         36 [\@strokes, \@nextEdgeBlob, \@entryPointBlob, \%inCalcEdge];
2517             }
2518            
2519             1;
2520            
2521             __END__