#!/usr/bin/perl -w
use strict;
use utf8;

my 	($rcs) = (' $Id: hex_parse.pl,v 1.159 2016/01/14 07:03:30 Vera Exp Vera $ ' =~ /(\d+(\.\d+)+)/);

#warn substr '·', 0, 1;
# TODO:		
#	Redo looking for stars after reducing the maybe-stars - not productive for ideographics
#	Generalize logic of "somebody else's rays" to ignore also parts of curves which pass next to a star (see circled 17).

# Ends: Ees	Joins: *	Corners: L^v><		Curves: cCfF|-\/u
# Rest: x

my %opt;
#warn "ARGV = <@ARGV>";
$opt{$1} = (defined($3) ? $3 : 1), shift while ($ARGV[0] || '') =~ /^--(\w+)(=(.*))?$/s;
#$opt{mono} = 0 if $opt{nolayouts};		# But the name will be as if --nolayouts was not given
my $nostroke_rex = $opt{nostroke_rex} && qr/^($opt{nostroke_rex})$/i;
print "$rcs\n" and exit if exists $opt{version};
die "No version of original font specified" if $opt{sfd} and not exists $opt{oversion};

my $height = 16;		# Should be a multiple of 4
my @dx = (0,1,1,1,0,-1,-1,-1);	# Start from "up", go clockwise
my @dy = (-1,-1,0,1,1,1,0,-1);	# +-direction is "down"
my $can_convert_to_join = qr/[cCsef]/;	# special-case cCs later...  s appears more or less as a bug
my @onebit;
$onebit[1<<$_] = $_ + 1 for 0..8;
$| = 1;
my $fuzzier_adjacent;		# Hard to debug interaction of nearby stars introduced in 1.43
my $dejavu_sizes = $opt{DejaVu};

BEGIN { my $debug = 0;
	$debug++ while @ARGV and $ARGV[0] eq '-d' and shift;
        eval ( $debug ? 'sub dwarn { warn @_, ("@_" =~ /\n$/ ? q() : "\n") }' : 'sub dwarn {1}') ;
        eval "sub debug () { $debug }";
}

sub c_bits8 ($) { my $n = shift; my $o = 0; $n & (1<<$_) and $o++ for 0..7; $o}
my @c_bits = map c_bits8 $_, 0..0xFF;

_test_encodes_line(), exit if $opt{test_calc_line};

my @rotate = ([0..0xFF],	# In 90deg increments, act on bitmaps of neighbors
	      [map 0xFF & (($_ << 2) | ($_ >> 6)), 0..0xFF]);
for my $i (2, 3) {
  my $prev = $i - 1;
  $rotate[$i] = [map $rotate[1][ $rotate[$prev][$_] ], 0..0xFF];
}

# Precalculate bitmap of neighbors for curves at angles ±1, ±2; assume we are at angle 0
my @neighb_0 = (	# Array of length 5 may be indexed by -2..2
  [],
  [1<<2, 1<<2, 1<<2, (1<<2) + (1<<1)],	# Array of length 4 may be indexed by -1..2
  [(1<<3) x 4],				# The index is the curvature of the curve (2 for undef)
  [(1<<5) x 4],
  [1<<6, (1<<6) + (1<<7), 1<<6, 1<<6]
);
my @neighb_1 = (	# Same assuming we are at angle 1; index is the relative angle
  [],
  [(1<<4) + (1<<3), 1<<4, 1<<4, (1<<4) + (1<<2)],	# Array of length 4 may be indexed by -1..2
  0,
  0,
  [(1<<6) + (1<<7), (1<<6) + (1<<0), 1<<6, 1<<6]
);

# Note that if a fake-curve is continued on the other side, we may prefer this to joining it to the dependent star

# 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)
#                       . .        . /   |.              d .                  .               \                          .... ..
# doubleray: *-- curve: *-.  Fork: *d.  ./-  fake-curve: *-.  d/-    rhombus: *d.  tail: --*-  *- ish;  serif --*  notch:.-* ..*
#           .|/          .\   |    . \  *.       \         \  *.       d * d  .|\        .|.  /   .|/           |        ./. ./|.
#    fork4: *d.  Near-corner: *.         m-joint: ||       elses-ray: *|  /      3fork3: *d.      *d.      Sharp: *---   /.  ..|
#           .|\              .d--                 *d                   d d               .|\   .  ..\     \..      \-       \
#  Note that dependent is not a neighbor for diagonal elses-ray (and is not unique)            -          .d-     ...\       \
#     fork4 and one flavor of fork3 are particular cases of fork!		Corner-curve: *.\ 3fork3: *|    bend-sharp: --*
#   Later may put: ignore, Ignore, Tail, 2fork3, Enforced, Arrow/(x-)arrow,     Probable-curve:    *|    Joint???: d*-.
#     1Spur; Rhombus-force, Zh/K-fake-curve is intended to be ½-of-segment                         .|-
#                                                                                                   |.


sub inspect_ray ($$$$$$$$$) { # returns: type, curvature or undef (0 for tail), is multiplicity checked, dependents, remove, unignore, actions.
 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
 for my $dir (@$dirs) {			# dependency: $x, $y, $dir,$dir1,...
  my $dx = $dx[$dir];
  my $dy = $dy[$dir];
  my($N, $Nmin) = ($near->[$y+$dy][$x+$dx], $nearmin->[$y+$dy][$x+$dx]);
  my($cNmin, $cN, $cN0min) = ($cntmin->[$y+$dy][$x+$dx], $cnt->[$y+$dy][$x+$dx], $cntmin->[$y][$x]);
  push @res, ['Dense'] and next if 6 < $cNmin;
  my($N0, $N0min) = ($near->[$y][$x], $nearmin->[$y][$x]);
  if ($cNmin <= 1) {					# below, if @perp, we automatically are diagonal
    push @res, ['tail', 0, 1, undef, undef, undef, ['t']] and next 
      if $N0->[($dir+4)%8] or $cN0min <= 2;
    unless (grep $N0min->[($dir+$_)%8], -1,1) {
      my @perp = grep $N0min->[($dir+$_)%8], -2,2;
      push @res, ['1Spur', 0, 1] and next if @perp <= 1; # with @perp==1, allow 2 at 135°: a continuation of perp, and of us
    }
  }
  if ($N->[$dir]) {
    my $cNN = $cntmin->[$y+2*$dy][$x+2*$dx] || 0;
    push @res, ['Dense'] and next if 6 < $cNN and $Nmin->[$dir];	# Nmin: be most forgiving
    push @res, ['doubleray', 0] and next
  }			# Now know no straight continuation; check 2 next neighbors
  my($seen_next2, %is_next, $d, $across2);
  $Nmin->[($dir+$_)%8] and $seen_next2++, $d = $_, $is_next{$_}++ for -1, 1;
  my($step, @NEAR) = 2 - ($dir & 0x1);	# detect notches in diag directions, and serifs in HV directions; also for forks
  # 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
  unless ($seen_next2) {		# No suitable curved continuation
    for my $D (-1, 1) {			# Protrusion?  ($dir is serif/notch/ maybe-m-joint???)
      my $DD = ($dir+$step*$D)%8;	# close H-or-V direction
      my $dx1 = $dx[$DD];
      my $dy1 = $dy[$DD];
      $across2++ if $nearmin->[$y+$dy1][$x+$dx1][$DD];	# can go 2 steps in the close H-or-V direction
      next if not $N0min->[$DD] or $N0min->[($DD+4)%8];	# Skip if extends on the other side
      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)
      my $curved = $nearmin->[$y+$dy1][$x+$dx1][($DD + $extra)%8];	# for m-joint: extra=1, and we have sloped perp continuation
      # ???? next if $other_dir and ($dir & 0x1 or );
      # x[2]: straight perp continuation for m-joints
      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
    }						# At most one element in @NEAR...
    my $n0;
    if (@NEAR) {
      $n0 = $NEAR[0][0];		# Avoid autovivication, do only if @NEAR...
      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
                  ['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
        if $NEAR[0][2] and $cNmin <= 3 and $cN >= 3 and $dir & 0x1
           and not $N0min->[($dir + 2*$n0)%8] and not grep $N0min->[($dir + $_*$n0+4)%8], 1, 2;
      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]]
        and next if $NEAR[0][2] and $cNmin <= 2 and not ($dir & 0x1);
# warn("m: $x $y $dir\n"),
      push @res, ['m-joint', $n0, 1, ['`', [$x + $dx, $y + $dy, ($dir+4)%8]],
                  undef, undef, ['L', $x, $y, ($dir+2*$n0)%8, -$n0, 1, 2]] and next	# check separately???
        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;
    }
    @NEAR = grep $_->[2], @NEAR;	# only curved!
    my($nnn, $DD, @NEAR1, $nnn1);	# What remains is near-corner, elses-ray, Corner-curve, bend-sharp, and diagonal notch
    for my $D (-1, 1) {			# Allow a neighbor come from a ray of the protrusion
      next unless $Nmin->[($dir+2*$D)%8];
      $nnn++, $DD = $D;
    }						# Here dy goes down!!! vvv
    if ($nnn) {
      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 
        if $nnn == 2;		# ??? Do we NEED to check the opposite dir:
      push @res, ['Near-corner', $DD, 0, ['.', [$x + $dx, $y + $dy, ($dir+2*$DD)%8]], undef, undef,
      		  ($dir & 0x1 ? () 		# TRY: remove the litation on not bending away
      		   : (['I', $x, $y, ($dir+$DD)%8], ['Ef', $x+$dx, $y+$dy, ($dir+4)%8]) )] and next
        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
           and not $N0min->[($dir+2*$DD)%8] 
           and ($px->[$y + $dy + 2*$DD*$dx][$x + $dx - 2*$DD*$dy]		# do not allow bending away from us
                or not($dir & 0x1) and $cntmin->[$y + $dy + $DD*$dx][$x + $dx - $DD*$dy] <= 3);
                		   # and $px->[$y + 2*$DD*$dx][$x - 2*$DD*$dy]);
      push @res, ['Corner-curve', $DD, 1] and next if $dir & 0x1 and not $N0min->[($dir+$DD)%8] and $cNmin <= 2;
      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
		  undef, undef, ['a', $DD]] and next 							# remove, unignore, @rest
        if $cNmin == 3 and $dir & 0x1 and $nnn == 1 and $N0min->[($dir+4)%8]
	   and $Nmin->[($dir + 4 - $DD)%8] and $Nmin->[($dir + 4 - 2*$DD)%8];
    } elsif (not @NEAR and $cNmin == 2 and $N0min->[($dir+4)%8]
    	     and $cN0min <= 3 + ($dir & 0x1) and $cnt->[$y][$x] >= 3 + ($dir & 0x1)) {	# bend-sharp?
      my $DDD;
      for my $D (-1,1) {
        $DDD=$D, last if $N0min->[($dir+$D*$step)%8];
      }
      die "bend-sharp: panic" unless $DDD;
      my $dx1 = $dx[($dir+$DDD*$step)%8];
      my $dy1 = $dy[($dir+$DDD*$step)%8];
      push @res, ['bend-sharp', $DDD, 1] and next if $cntmin->[$y+$dy1][$x+$dx1] <= 4 - ($dir & 0x1) 
        and $nearmin->[$y+$dy1][$x+$dx1][($dir+4-$DDD)%8];
    }
    push @res, ['?'] and next if $dir & 0x1;
    for my $D (-1, 1) {			# Allow a neighbor come from a ray of the protrusion
      next unless $Nmin->[($dir+$D)%8];
      $nnn1++, $DD = $D;
    }						# Here dy goes down!!! vvv
    push @res, ['notch', $n0, 1, undef, [$x+$dx, $y+$dy]] and next 
      if $cNmin <= 2 and not($dir & 0x1) and $nnn1 and not $N0min->[($dir + $DD + 4)%8];	# Miss double-notch=arrow
    push @res, ['Arrow'] and next 								# On shaft going to tip
      if $cNmin == 3 and not($dir & 0x1 or $nnn1 or $across2 or $N->[($dir+2)%8] or $N->[($dir-2)%8]);
    push @res, ['?'] and next;
  } elsif (2 == $seen_next2) {	# Only forks, Zh, K's here...	#   |.                    . /
    my($c, $DDD) = 0;						#  ./-	$c counts dots    *-
    $N0min->[($dir+$step*$_)%8] and $c++, $DDD=$_ for -1, 1;	#  *.                     . \
    if ($c == 1 and not($dir & 0x1)) {				# Work around ties between legs of K
      my $x1 = $x + $dx[($dir+$DDD*$step)%8];
      my $y1 = $y + $dy[($dir+$DDD*$step)%8];
      my($NNN, $dir1) = ($near->[$y1][$x1], ($dir-$DDD)%8);
      if ($NNN->[$dir] and $NNN->[$dir1]) {
        push @res, ['elses-ray', -$DDD, 0, ['"', [$x1, $y1, $dir, $dir1]]];
        next;
      }
    } elsif ($c == 1) {
      push @res, ['Probable-curve', $DDD] and next;
    } 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
# warn "K-joint: ($x,$y) $dir + $d\n";
      my @R;
      for my $d (-1, 1) {
        my $x1 = $x + $dx[($dir+$d)%8];
        my $y1 = $y + $dy[($dir+$d)%8];
        my($NNN, $dir1) = ($near->[$y1][$x1], ($dir+2*$d)%8);
        push @R, ['Zh-fake-curve', $d] if $NNN->[$dir] and not $NNN->[($dir1+1)%8] and not $NNN->[($dir1-1)%8];
      }
      push(@res, @R), next if @R == 1;
    }
    push @res, ['?'] and next if $c;
#    $N->[($dir+2*$_)%8] and $c++ for -1, 1;
#    push @res, ['fork4'] and next if $c and $c == 2;
    my $opp = ($dir + 4)%8;
    my @dep = grep { $_ ne $opp and $Nmin->[$_]} 0..7;
    push @res, ['Fork', undef, 1, ['°', [$x+$dx, $y+$dy, @dep]]] and next;	# join all forks with next2==2
  }			# Now have one secondary ray only, $d-curving:    curve fake-curve diamond sharp fork3
  my $baddir = ($dir - $step*$d)%8;
  my $bad = $N0min->[$baddir];		# on fake-curve only
  if ( $N0min->[($dir+$d)%8] ) {	# Parallelogram - essentially, two curves with the same end (diamond sharp fork3)
    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
      my $x1 = $x + $dx[($dir+$d)%8];
      my $y1 = $y + $dy[($dir+$d)%8];
      my($NNN, $dir1) = ($near->[$y1][$x1], ($dir+2*$d)%8);
      if ($NNN->[$dir] and not $NNN->[($dir1+1)%8] and not $NNN->[($dir1-1)%8]) {
        push @res, ['K-fake-curve', $d];
        next;
      }
    }
    push @res, ['?'] and next if $bad or not ($dir & 0x1) and $N0min->[($dir + 2*$d)%8];	# Check last . on diamond and fork3
    if ($dir & 0x1 and $Nmin->[($dir + 2*$d)%8] and not $Nmin->[($dir - 2*$d)%8]) {	# Avoid the situation in K
      my $dx1 = $dx[($dir + $d)%8];
      my $dy1 = $dy[($dir + $d)%8];
      push @res, ['Sharp', $d, 0, undef, undef, undef, ['L', $x+$dx, $y+$dy, ($dir+$d)%8, $d, 2, 3], 
      		  ['S', $x+$dx, $y+$dy, ($dir+4)%8], ['S', $x+$dx1, $y+$dy1, ($dir+4+$d)%8]] and next	# Enforce line at dist=2
        if $px->[$y+3*$dy1][$x+3*$dx1] and $px->[$y + 2*$dy + $dy1][$x + 2*$dx + $dx1] and not $pxmin->[$y+$dy + 2*$dy1][$x+$dx + 2*$dx1];
    }					# Now general catch-all:
    my $opp = ($dir + 4)%8;
    my @dep = grep { $_ ne $opp and $Nmin->[$_]} 0..7;
# warn "generic: $x,$y, $dir, $d; (@dep);; $seen_next2;;; ", map $N->[$_] || 0, 0..7;
    push @res, [($Nmin->[($dir - 2*$d)%8] ? '3fork3' : 'rhombus'),  $d, 0, ["'", [$x + $dx, $y + $dy, @dep]]] and next;
  }
#  warn "maybe curve: ($x,$y,$dir): ", $bad||0,"\n";
  push @res, ['curve', $d, undef, undef, undef, undef, ['C', $d]] and next unless $bad;
  if ($N0min->[($dir + 4)%8] and $cN0min <= 4 and $dir & 0x1) {	# check for double-arrow joint (21a0, 0239)
    my($mirY, $mirX) = ($y + 2*$dy[$baddir], $x + 2*$dx[$baddir]);
    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
		  undef, undef, ['a', -$d, 1]] and next 							# remove, unignore, @rest
      if $px->[$mirY][$mirX] and $cntmin->[$mirY][$mirX] <= 4 and $cnt->[$mirY][$mirX] >= 4 and $nearmin->[$mirY][$mirX][($dir-2*$d)%8];
  }
  push @res, ['fake-curve', $d, 0, ['curve', [$x + $dx[$baddir], $y + $dy[$baddir], ($dir+$d)%8]]] and next;
 }
 return \@res;
}			# dependent for a parallelogram (HV one) should be submitted only once...

sub post_inspect_ray ($$$$;$) { # Not finished (and it is not exactly clear for what it is best to check...)
 my($x, $y, $dir, $rays, $what) = (shift, shift, shift, shift, shift || '');
 $rays = $rays->[$y][$x];
 my $ray = $rays->[$dir] or die "Panic: x=$x, y=$y, dir=$dir - missing ray in post_inspect_ray($what)";
 for my $next (1,-1) {
#   warn "Checking x=$x, y=$y, dir=$dir in post_inspect_ray($what)\n" if $what =~ /^3/;
   return if ($rays->[($dir+$next)%8] || '') =~ /^[Dr3\WP]/;	# 'Probable-curve' put here as an experiment XXX
 }
# warn "Checking2: $ray->[0]\n" if $what =~ /^3/;
 # Putting 'elses-ray' into the allowed list is not a good idea 
 # (although it may help half-way with some, like ɫ, у, and helps with Ѭ); maybe allow the caller to permit it???
 return 1 if $ray->[0] =~ /^[dct1fKZE]/; # doubleray, curve, tail, 1Spur, [Zh/K-]fake-curve, Enforced with no ?/Dense/rhombus/3fork3 nearby
 $ray->[0] =~ /^m/ and $what =~ /^m/
}

sub remove_px ($$$$$$) {
  my($x, $y, $cnt, $px, $near, $off) = (shift, shift, shift, shift, shift, shift);
  for my $dir ( @{ $off->[$y][$x] } ) {
    my $dx = $dx[$dir];
    my $dy = $dy[$dir];
    $near->[$y+$dy][$x+$dx][($dir+4)%8] = 0;
    $cnt->[$y+$dy][$x+$dx]--;
  }
  undef $px->[$y][$x];
}

sub force_line ($$) {
  my($how, $rays) = (shift, shift);
  my($L, $x, $y, $dir, $rot, $rem1, $rem2) = @$how;	# $L ==eq== 'L' ignored
# warn "In force_line($L, $x, $y, $dir, $rot...)";
  my $dx = $dx[$dir];
  my $dy = $dy[$dir];
  $rays->[$y][$x][$dir] = ['Enforced', 0, 1];
  $rays->[$y+$dy][$x+$dx][($dir+4)%8] = ['Enforced', 0, 1];
  return unless $rot;
  my $dx1 = -$rot*$dy;
  my $dy1 =  $rot*$dx;
  if ($rem1) {		# Supported now: 0,1,2	(horizontal/vertical only)
    if ($rem1 > 1) {
      $rays->[$y][$x][($dir+2*$rot)%8][0] = 'ignore';			# Perpendicular direction on ≥2
      $rays->[$y+$dy1][$x+$dx1][($dir-2*$rot)%8][0] = 'ignore';
    }
    $rays->[$y][$x][($dir+$rot)%8][0] = 'ignore';
    $rays->[$y+$dy+$dy1][$x+$dx+$dx1][($dir-3*$rot)%8][0] = 'ignore';
  }
  if ($rem2) {		# Supported now: 0,1,2,3	(horizontal/vertical only)
    if ($rem2 > 1) {
      $rays->[$y+$dy][$x+$dx][($dir+3*$rot)%8][0] = 'ignore';			# 135° direction on ≥2
      $rays->[$y+$dy1][$x+$dx1][($dir-$rot)%8][0] = 'ignore';
      if ($rem2 > 2) {
        $rays->[$y+$dy][$x+$dx][($dir+$rot)%8][0] = 'ignore';			#  45° direction on ≥3
        $rays->[$y+2*$dy+$dy1][$x+2*$dx+$dx1][($dir-3*$rot)%8][0] = 'ignore';
      }
    }
    $rays->[$y+$dy][$x+$dx][($dir+2*$rot)%8][0] = 'ignore';		# Perpendicular direction on ≥1
    $rays->[$y+$dy+$dy1][$x+$dx+$dx1][($dir-2*$rot)%8][0] = 'ignore';
  }
}

my($E, $ER, $ord) = ([], ['']);
END {die '$E corrupted' if $E and @$E}
my $ALLGOOD = [(1) x 8];
my $ALLcnt = 60839;	# Std .hex of Unifont contains this many (+.undef) non-fake entries
print STDERR "# (progressBar valid for full UniFont)\n", '.' x int($ALLcnt/1000 + 1), "\r";

sub sfd_start ($$);
my($px_size, $px_descent, $eps_px_size) = (64, 2, 1);
print sfd_start(16*$px_size, $px_descent*$px_size) if $opt{sfd};
my $charcnt = 0;
END {
  print << "END" if $opt{sfd};

EndChars
EndSplineFont
END
}

my %override;
if ($opt{overridefile}) {
  open my $f, '<', $opt{overridefile} or die "file `$opt{overridefile}': $!";
  while (<$f>) {
    next if not /\S/ or /^#/;
    die "unknown format of override line: `$_'" unless /^(\S+):([\da-f]+)$/i or /^(\.null):()$/i;
    $override{lc $1} = $2;
  }
  close $f or die "close `$opt{overridefile}': $!";
}

my $filter_rex = (defined $opt{only} ? ($opt{only} eq '.0' ? qr/^[.0]/ :
							     ($opt{only}) =~ /^\w$/ ? qr/^(?:$opt{only})...$/i
										    : qr/^(?:$opt{only})/i) : qr/ /x);
#warn "filter_rex = $filter_rex";

my $width_to_8_ok = $opt{maxwd}||10;

sub need_char ($) {
  my $c = shift;
  return if $c eq '0000' and $opt{skip0000};	# Does not help with .notdef...
  return if $c =~ /^00[01].$/ and $opt{skipctrl};	# THESE conflict with console's .notdef
  return if $c =~ /^000.$/ and $opt{skipctrl0};	# THESE conflict with console's .notdef
  return if $c =~ /^000[09acd]$/i and $opt{skipctrlwhite};	# it is not THESE which conflict with console...
  return if $c =~ /^000[0-7]$/i and $opt{skipctrl08};	# THESE conflict with console's .notdef
  return if $c =~ /^000[0-3]$/i and $opt{skipctrl04};	# THESE conflict with console's .notdef
  return if $c =~ /^000[0-1]$/i and $opt{skipctrl02};	# THESE conflict with console's .notdef
  return if $opt{skiprex} and $c =~ /^($opt{skiprex})$/io;	# 000[01] were needed here to have .notdef active in Windows' console (now hidded by .add)
  return if $opt{nocjk} and $c =~ /^([5-9b-e]|4[ef])/i;
  return if $opt{'cjk-only'} and $c !~ /^([5-9]|4[ef])/i;
  return if $opt{'cjk-rest'} and $c !~ /^[b-e]/i;
#warn "Doing <$c> filter=$filter_rex";
  return unless $c =~ $filter_rex;
#warn "Doing <$c> !";
  return 1;
}

sub output_char_if_narrow ($$) {
  my ($c, $d) = (shift, shift);
  my $width=length($d)*4/$height;		# 4px per hex digit
#	next if $opt{mono} and $width != 8;
  my $s = unpack ("B*", pack ("H*", $d));

  my(@pixels) = ([]);
  my($Lb, $Rb, @rays, @cntmin, @pixelsmin, @nearmin) = ($width, 1, []);
  for my $i (0..($height - 1)) {
          my $l = substr($s, $i*$width, $width);
          # Extra at the end to make offset -1 "work" (=false)
          my $P = $pixels[$i+1] = ['', (map +($_ ? 'x' : ''), split //, $l), ''];
          $pixelsmin[$i+1] = [ @$P ];	# Deep copy
          $P->[$_] and $Lb = $_-1, last for 1..$Lb;
          $P->[$_] and $Rb = $_+1, last for reverse($Rb..$width);
  }		# Rb and Lb are one off from the rightmost and leftmost pixels
  (my $minwidth = $Rb - $Lb - 1) <= $width_to_8_ok
#	  or not $opt{mono} or $c =~ /^([0-2]|3[0-2]|4d[c-f]|a[0-b]|===f[9b-f]|f[b-f]|===f[a-f]|--f[9a-f])/i or next;
    or not $opt{mono} or (not $opt{wideskiprex} or $c !~ /^($opt{wideskiprex})$/io) or return;	# 3[3-9a-f]..|4[0-9a-cef]..|4d[0-9ab].|[5-9]...|a[c-f]..|[b-e]...|f[0-9a]..
  print "\t\t\t\t\t$c\n" unless $opt{sfd};
  
  push @pixels, [];	# make offset -1 "work" (=false)
#		print "$l\n";
  output_processed_bitmap($c, \@pixels, $width, $height, $Lb, $Rb);
}

my $nobaseline_blocks = <<EOB;		# Do not need alignment on baseline; align on bounding box
Controls				0000..001f
Controls				007f..009f
Devanagari — Myanmar			0900...109f
Arrows 					21d0-21ff
Pieces etc				231c-2321
Pieces, terminal			239b-23bd
extension				23d0-23d0
Control Pictures			2400..243F
Enclosed, Box, Block 			2460-259f
Dingbats				2700..27BF
Arrows+Braille 				27F0..297F
Symb+Arrows				2B00..2BFF
Hangul Compatibility Jamo — Vai 	3130..A63F
Syloti Nagri — Hangul Jamo Extended-B	A800..D7FF
CJK Compatibility Ideographs		F900..FAFF
CJK Compatibility Forms			FE30..FE4F
EOB
my @nobaseline_blocks = map [(/.*\b(\w+)(-|\.+)(\w+)\s*$/ or die 'Panic') and (hex $1, hex $3)], split /\n/, $nobaseline_blocks;
my $minus1_seen = 0;

my $splineP = 4/3*sqrt(2)/2/(1+sqrt(2)/2);	# works best for 90° angle; for smaller angles, is not best fit, but the mismatch is less important
my $splineQ = 1 - $splineP;

sub output_sfd_char ($$$$$$$$$$$$$) {
  my($c,$width,$height,$px_descent,$Lb,$minwidth,$strokes,$pixels,$cntedge,$offs,$edge, $nextEdge, $entryPoint) 
    = (shift,shift,shift,shift,shift,shift,shift,shift,shift,shift,shift,shift,shift);
  my $DEC  = ($c =~ /^(\.n[.\w]+)$/ ? -1 : hex $c);
  my $dec  = ($c =~ /^(\.n[.\w]+|$opt{hiderex})$/o ? -1 : hex $c);
  my $dec1 = ($c =~ /^(\.n[.\w]+|$opt{hiderex})$/o ? ($opt{manglenotdef} ? 4080+$minus1_seen++ : -1) : hex $c);	# Windows' console makes font-order chars 0,1 into .notdef
  $dec1 = 0x0fff - 3 + $dec1 if $dec1 <= 3;	# Ids 0..3 should be filled by special glyphs. U+0f** are Tibetan; end unused in 6.3
  my $ptwidth = (($opt{mono} and $c ne '.null') ? 8 : $width) * $px_size;
  my $shift_by_truetype = '';
  my $char = ($c =~ /^\./ ? '' : chr hex $c);
  # In non-mono situation, may use width=0 and negative offset; for monospace, must use Position2
  $shift_by_truetype = qq[Position2: "'mark' Zero-Width Marks lookup" dx=0 dy=0 dh=-$ptwidth dv=0\n]
    if $char =~ /\p{NonSpacingMark}/ and $opt{mono} and not $opt{nolayouts};
  # if $shift_by_truetype, do not use offsets
  # if mono, do not use offsets (mono and nolayout: will not show diacritics correct):
  my $no_offset = ($opt{mono} or $shift_by_truetype or not $char =~ /\p{NonSpacingMark}/);
  my $xoff = ($no_offset ? 0 : -($opt{mono} ? 8 : $width)) and $ptwidth = 0;
  my $scale = (($minwidth > 8 and $opt{mono}) ? 7/($minwidth-1) : 1);
  my $yscale = my $yscale_bottom = 1;
  my $yoff = 0;							# ($dejavu_sizes ? 0.9*$scale : $scale);
  if ($dejavu_sizes) {	# Top of DejaVu is at 12⅓, not 14;  bottom is at 3³⁄₁₆, not 2.  Total height is by 1/32 lower
    my $use_baseline = 1;
    for my $b (@nobaseline_blocks) {
# warn "$c: start block [@$b]" if $DEC == $b->[0];
# warn "$c: end   block [@$b]" if $DEC == $b->[1];
      $use_baseline = 0, last if $DEC >= $b->[0] and $DEC <= $b->[1];
    }
    if ($use_baseline) {
      $yscale = 0.9;
      $yoff = 14 * (1 - $yscale);	# Baseline is on $y = 14
      #$yscale_bottom = 1.18;		# So that -2 becomes -2.36, and is rendered the same at the natural size
      $yscale_bottom = $yscale;		# Otherwise need to have a separate offset!
    } else {
      $_ = 31/32 for $yscale, $yscale_bottom;
      $yoff = 14 * (1 - $yscale);	# Baseline is on $y = 14
      $yoff += 5/4;			# 3³⁄₁₆ - ³¹⁄₃₂*2; y grows ↓
    }
  }
  my $lEdge = (($width > 8 and $opt{mono}) ? $Lb+1 : 1);	# Want $ledge to shift to 0.5+$xoff
  $xoff = 0.5 + $xoff - $lEdge * $scale;	# $X*$new_scale + $new_xoff = (($X-$lEdge)*$scale+0.5+$xoff)*$px_size
  $_ *= $px_size for $scale, $xoff;
  my $pref = ($c =~ /^((0|1(?=0))?[\da-f])?[\da-f]{4}$/i ? ($1 ? 'u' : 'uni') : '');	# Needed to make cut/paste from Acrobat better
  my $out_comb = $char =~ /\p{NonSpacingMark}/ && "# is_comb $pref$c\n";
  $c =~ s/^\.nx\.//;				# was added to make sorting easier only
  my $post = ($c =~ $opt{hiderex}) ? '.hide' : '';	# disable resetting of position via the name
  print << "END";

StartChar: $pref$c$post
Encoding: $dec1 $dec $charcnt
Width: $ptwidth
Flags: HW
TeX: 0 0 0 0
Layers: 2
Fore
SplineSet
END
  $charcnt++;

  my $nostroke = $nostroke_rex && $c =~ $nostroke_rex;
  my $avoid_right_angle = $opt{avoid_right_angle} && $c =~ /^($opt{avoid_right_angle})$/oi;
  my $skip_straight = $opt{skip_straight} && $c =~ /^($opt{skip_straight})$/oi;
  my @DO;
  for my $Stroke (@$strokes) {
    my($loop, $stroke, $runs, $breaks) = @$Stroke;
#warn "processing stroke with ", scalar @$stroke, " edges, loop=$loop, breaks[@$breaks]=", scalar @$breaks, ", runs=", scalar @$runs, ".\n";
#warn "  runs=", join(', ', map "[@$_]", @$runs), "\n";
    # fontforge may crash on lines with many segments (expanding stroke)
#    next if @$stroke > 20;
    # Prearranged strokes have dir==-20; replace with 1 and it is done
    push(@DO, [-20, @{ $stroke->[0] }[1..4], -20]), next if @$stroke == 1 and $stroke->[0][0] == -20;	# Always a loner
    if ($skip_straight) {
      $runs = [[0],[$#$stroke+1]];		# fake corners at ends; [0] means: start at 0, no calculated lines until the next
      $breaks = [0];
    } elsif ($loop >= 0) {			# if it is a loop, it is not smooth, so stroke_2_strokes() was called
      $loop = 0 if @$breaks > 1;		# @$breaks starts with 0
      if ($loop and $runs->[-2][1]) {		# The last “real” run is a calculated one, so the logic below will not work
        push @$breaks, $#$runs - 1;
        $loop = 0;
      }
    }
    my($runn, $next_check, $in_run, $prev, $do_start, $run) = (0, 0, 0);
    for my $c (0..$#$stroke) {
      $in_run++;
      if ($c == $next_check) {
        $do_start = 1, shift @$breaks if @$breaks and $runn == $breaks->[0];
        $run = $runs->[$runn];
        $next_check = $runs->[++$runn][0];
        $in_run = 0;
      }
      next if $run->[1] and $in_run;		# skip the rest of the run replaced by a calculated line
      my $edge = $stroke->[$c];
      my($dir,$x,$y,$x1,$y1,undef,undef) = @$edge;
      my $DIR = 2*$dir;
      $prev = $dir unless defined $prev;
      if ($run->[1]) {				# replace the first one by the calculated stroke
        my @coord = ($x,$y,$x,$y);
        $coord[$_] += $run->[1][$_] for 0..3;
        ($x,$y,$x1,$y1) = @coord;
        undef $dir;
        $DIR = $run->[1][6];
      } elsif ($loop and $c == $#$stroke 	# break loop into two open parts
	      # Probable reason for fontforge’s crash: sharp turns (45° angle) compressed yet more by 16 --> 8 width transformation.
          or $nostroke
      	  or $opt{mono} and $in_run # and $minwidth > 11 and @$stroke > 20 
           and (2 > abs(($dir-$prev)%8-4)
      	     	  or 2==abs(($dir-$prev)%8-4) and ($avoid_right_angle or $minwidth > 11 and 0 == (($dir+$prev)%8)))) {	# \/ or /\

      	$do_start = 1;
      }
##warn "DIR undef in $do_start, $x, $y, $x1, $y1\n" if $#$stroke and not defined $DIR;
      push @DO, [$do_start, $x, $y, $x1, $y1, $DIR];
      $do_start = 0;
      $prev = $dir;
    }
  }
  my @prev;
  for my $i (0..$#DO) {
      my($do_start, $x, $y, $x1, $y1, $dir) = @{ $DO[$i] };	# We will add: 6:square-length, 7: merge_prev, 8: merge_next
      my($Dx,$Dy) = ($x1-$x, $y1 - $y);				#  9:square-len for prev, 10:same for next
      my $D2 = $Dx*$Dx + $Dy*$Dy;
      $DO[$i][6] = $D2;
      if ($i) {{
#        $DO[$i-1][10] = $D2;
        next if $do_start;
        my $dir1 = $DO[$i-1][5];
#warn "$i: dir=$dir dir1=$dir1; ($do_start, $x, $y, $x1, $y1, $dir)\n";
        $DO[$i][7] = $DO[$i-1][8] = 1 if $dir != $dir1 and 4 > abs(($dir - $dir1 + 8)%16 - 8);	# rotate less than 90°
      }}
#      $i < $#DO and $DO[$i+1][9] = $D2;
  }
  my ($prev_X, $prev_Y);
  for my $i (0..$#DO) {
      my($do_start, $x, $y, $x1, $y1, undef, $len2, $merge_prev, $merge_next) = @{ $DO[$i] };
      $_ ||= 0 for $merge_prev, $merge_next;
      my $no_mid;
      if ($merge_prev and $merge_next) {
        if ($len2 > 4) {
          $merge_next = $merge_prev = 1/sqrt($len2);
        } else {
          $no_mid = $merge_prev = $merge_next = 0.5;
        } 
      } elsif ($merge_prev) {
        if ($len2 > 1/0.49) {
          $merge_prev = 1/sqrt($len2);
        } else {
          $merge_prev = 0.7;
        } 
      } elsif ($merge_next) {
        if ($len2 >= 1/0.49) {
          $merge_next = 1/sqrt($len2);
        } else {
          $merge_next = 0.7;
        } 
      }
#  }
#  for my $i (0..$#DO) {
#      my($do_start, $x, $y, $x1, $y1) = @{ $DO[$i] };
##      my $do_end = $i != $#DO;
##      $do_end ||= $do_start1;
      if ($do_start) {
        my $X  =  $x * $scale + $xoff;
        my $Y = ($height - $y*($y<0 ? $yscale_bottom : $yscale) - $yoff - $px_descent + 0.5)*$px_size;
        print <<EOP;
$X $Y m 1
EOP
      }
      if ($merge_prev) {
        my $X  =  $x * $scale + $xoff;
        my $Y = ($height - $y*($y<0 ? $yscale_bottom : $yscale) - $yoff - $px_descent + 0.5)*$px_size;
        my $XX = ($x1 - $x) * $merge_prev + $x;
        my $YY = ($y1 - $y) * $merge_prev + $y;
        $XX  =  $XX * $scale + $xoff;
        $YY = ($height - $YY*($YY<0 ? $yscale_bottom : $yscale) - $yoff - $px_descent + 0.5)*$px_size;
        my($preX, $preY, $postX, $postY) 
          = ($splineP*$X+$splineQ*$prev_X, $splineP*$Y+$splineQ*$prev_Y, $splineP*$X+$splineQ*$XX, $splineP*$Y+$splineQ*$YY);
       	# For going with constant speed along a straigt line, use equally distanced control points for cubic spline;
       	# At t=.5, the point is the weighted average with weights 1,3,3,1; if we want it to be the middle of the arc connecting
       	# endpoints, T must divide the interval I=[point,controlpoint] in ratio 3:1; here T is the intersection of I
       	# with the tangent at midpoint of the arc.  If C is the intersection of tangents to the spline at endpoints, then
       	# controlpoint divides [point,C] in ratio 2:1 for parabolas (and small arcs), and in ratio 0.55:0.45 for arc of 90°
       	# (in general, its position on [point,C] is 4/3 · cos α/(1+cos α) ??? for an arc of 2α).
        print <<EOP;
  $preX $preY $postX $postY $XX $YY c 1
EOP
        $prev_X = $XX, $prev_Y = $YY if $no_mid;
      }
      $x1 = ($x1 - $x) * (1 - $merge_next) + $x;
      $y1 = ($y1 - $y) * (1 - $merge_next) + $y;
      unless ($no_mid) {
        my $X1  =  $x1 * $scale + $xoff;
        my $Y1 = ($height - $y1*($y1<0 ? $yscale_bottom : $yscale) - $yoff - $px_descent + 0.5)*$px_size;
        $prev_X = $X1;   $prev_Y = $Y1;
        my $is_corner = 1;	# $merge_next ? 0 : 1;
        print <<EOP unless $no_mid;
  $X1 $Y1 l $is_corner
EOP
      }
  }

  for my $e (@$entryPoint) {
    my($c, $x, $y, $dir, $C) = (0, @$e);
    my $X = $x * $scale + $xoff;
    my $Y = ($height - $y*($y<0 ? $yscale_bottom : $yscale) - $yoff - $px_descent + 0.5)*$px_size;
    my($dx,$dy) = ($dx[($dir-2)%8], $dy[($dir-2)%8]);
    $X += $dx * $eps_px_size; $Y -= $dy * $eps_px_size;
    print <<EOP;
$X $Y m 1
EOP
    while ($c++ < $C) {
      ($x,$y,$dir) = @{ $nextEdge->[$dir][$y][$x] };
      $X = $x * $scale + $xoff;
      $Y = ($height - $y*($y<0 ? $yscale_bottom : $yscale) - $yoff - $px_descent + 0.5)*$px_size;
      ($dx,$dy) = ($dx[($dir-2)%8], $dy[($dir-2)%8]);
      $X += $dx * $eps_px_size; $Y -= $dy * $eps_px_size;
    print <<EOP;
  $X $Y l 1
EOP
    }
  }
  print << "END";
EndSplineSet
$out_comb${shift_by_truetype}EndChar
END
}

sub find_blobs ($$$$$$;$$) {
  my($blob, $width, $height, $pixels, $cntedge, $offs, $lastedge, $skip, $c) = (shift, shift, shift, shift, shift, shift, shift, shift, 0);
  $blob->[0] = [];
  for my $y (1..$height) {
    $blob->[$y] = [];
    for my $x ( 1..$width ) {
      next unless $pixels->[$y][$x]; 
      $blob->[$y][$x] = 1, $c++ unless $cntedge->[$y][$x];
    }
  }
  push @$blob, [];
  my @doblob;
  if ($lastedge) {		# Add "better consider the same as blob" non-blob pixels
    for my $y (1..$height) {
      for my $x ( 1..$width ) {
        next if $blob->[$y][$x] or ($cntedge->[$y][$x] || 0) != 1; 
        my $D = ($lastedge->[$y][$x] + 4)%8;
        next unless $blob->[$y + $dy[$D]][$x + $dx[$D]];
        my $C = 0;
        for my $dir ( @{$offs->[$y][$x]} ) {
          $C++, last if $dir != $D and $blob->[$y + $dy[$dir]][$x + $dx[$dir]];
        }
        push(@doblob, [$y,$x]), $c++ if $C and not $skip->{$y,$x};
      }
    }
  }
  $blob->[$_->[0]][$_->[1]]++ for @doblob;
  for my $y (1..$height) {	# Replace 1 by 1 + count of neighbor blobs
    next unless $blob->[$y];
    for my $x ( 1..$width ) {
      next unless $blob->[$y][$x]; 
      for my $dir ( @{$offs->[$y][$x]} ) {
        $blob->[$y][$x]++ if $blob->[$y + $dy[$dir]][$x + $dx[$dir]];
      }
    }
  }
  $c;
}

# For correct operations of fontforge on the shape, we must move clockwise on the external boundary
sub traverse_boundary($$$$$) {	# The blob is on our right
  my ($x, $y, $dir, $blob, $nextEdge, $c) = (shift, shift, shift, shift, shift, 1);
#  warn "Enter traverse_boundary()\n";
  while (1) {			# Greedy algorithm: we always go left if we can  
# warn "... x=$x, y=$y, d=$dir\n";
    my $dir1 = ($dir - 2) %8;
    my $dx = $dx[$dir];
    my $dy = $dy[$dir];
    my $dx1 = $dx[$dir1];
    my $dy1 = $dy[$dir1];
    my($x1, $y1) = ($x+$dx+$dx1, $y+$dy+$dy1);
    if ($blob->[$y1][$x1]) {			# Turn Left (already precalculated)
    } elsif ($blob->[$y + $dy][$x + $dx]) {	# Continue
      $x1 = $x + $dx;  $y1 = $y + $dy;  $dir1 = $dir;
    } else {					# Turn Right
      ($x1, $y1, $dir1) = ($x, $y, ($dir + 2) % 8);
    }
    $nextEdge->[$dir][$y][$x] = [$x1, $y1, $dir1];
    ($x, $y, $dir) = ($x1, $y1, $dir1);
    return $c if $nextEdge->[$dir][$y][$x];
    $c++;
  }
}

sub traverse_stroke ($$$$$$) {		# XXX Is there a duplication between seen/traversed???
  my($x,$y,$dir,$seenEndEdge,$nextEdge,$traversedEdges) = (shift, shift, shift, shift, shift, shift);
  my @stroke;
  while (1) {
    last if $traversedEdges->{$x,$y,$dir}++;		# applicable to loops: looped back
    my $x1 = $x + $dx[$dir];
    my $y1 = $y + $dy[$dir];
    $traversedEdges->{$x1,$y1,($dir+4)%8}++;
    push @stroke, [$dir,$x,$y,$x1,$y1];
    $seenEndEdge->{$x1,$y1,($dir+4)%8}++, last unless defined(my $n = $nextEdge->[$y][$x][$dir]);
#warn "found next edge: $x,$y $dir  --> $x1,$y1 +$n\n";
    ($x,$y,$dir) = ($x1, $y1, ($dir + $n) % 8);
#    last if $seenEndEdge->{$x,$y,$dir};		# applicable to loops: looped back
  }
#warn "found edges in a stroke: ", scalar @stroke, ': (', join(') (', "@{$stroke[0]}[1,2]", map "@$_[3,4]", @stroke), ")\n";
  \@stroke;
}

my @LINES = (qw( | / - \ ) x 2);
my @LINESL = (qw( ◫ ⧄ ⊟ ⧅ ) x 2);
my $withblob = {qw( ! ‼ * ⊛)};
my $noblob = {qw( ! ! * *)};

sub output_human_readable ($$$$$$$$$$) {
  my($width, $height, $pixels, $rays, $edge, $cntedge, $cnt, $Simple, $InLong, $blob) 
    = (shift, shift, shift, shift, shift, shift, shift, shift, shift, shift);
  for my $y (1..$height) {	# Print out identifications of rays
    my(@row, @row1);
    for my $x ( 1..$width ) {
      my $rays = $rays->[$y][$x] || $E;
      my $smpl = $edge->[$y][$x] || $E;
      my $inLong = $InLong->{$x,$y};
      push @row,  [ map { defined() ? substr $_->[0], 0, 1 : ' ' } @$rays[0..7] ];
      my @r = map { $smpl->[$_] ? ($inLong->{$_} ? $LINESL[$_] : $LINES[$_]) : $row[-1][$_] } 0..7;
      push @row1, \@r;
    }
    for my $subrow ([7,0,1],[6,8,2],[5,4,3]) {	# 8 will be converted to * below
      my ($o, $o1) = ('', '');
      for my $x ( 0..$width-1 ) {
        $o  .= join '', map  $row[$x][$_] || ($pixels->[$y][$x+1] ? '*' : ' '), @$subrow;
        $o1 .= join '', map $row1[$x][$_] || ($pixels->[$y][$x+1] 
        				      ? (($cntedge->[$y][$x+1] || !$cnt->[$y][$x+1]) 
        					 ? (($blob->[$y][$x+1] and $cnt->[$y][$x+1]) ? $withblob : $noblob)
        					           ->{$Simple->[$y][$x+1] 
        					    ? '!' : '*'} : 'X')
        					 : ' '), @$subrow;
      }
      $o .= ( ' ' x (56 - length $o) . $o1 );
      $o =~ s/\s+$//;
      s/\s\s+$/\t/ for my @o = ($o =~ /(.{1,8})/g);
      ($o = join('', @o));	# =~ tr/…P""/????/;
      print "$o\n";
    }
  }
}

sub output_processed_bitmap ($$$$$$) {
  my ($c, $px, $width, $height, $Lb, $Rb) = (shift, shift, shift, shift, shift, shift);
  my @pixels = @$px;
  my @pixelsmin = map [@$_], @$px;		# deep copy
  my(@near, @cnt, @ang,@offs, @angle, @star, @single, @doublerays, @singleray, @fakeray, @mod4neighb, @elses, @x_stripbits);
  my(@rays, @cntmin, @nearmin, @longedges, %seenlong, %inLong, %midLong) = ([]);
  my $minwidth = $Rb - $Lb - 1;
	for my $y (1..$height) {	# Enumerate neighbors of the pixel, doublearys, directions having neighbors on both sides
	  for my $x ( 1..$width ) {
	    next unless $pixels[$y][$x];
	    my($prev, @OFF) = 0;
	    for my $n (0..7) {
	      my $dx = $dx[$n];
	      my $dy = $dy[$n];
#	      warn 'dx' unless defined $dx;
#	      warn 'dy' unless defined $dy;
	      next unless $pixels[$y + $dy][$x+$dx];
	      $near[$y][$x][$n] = 1; 
	      $ang[$y][$x] = 4 - abs($n - $prev - 4), $prev = $n;
	      push @OFF, $n;
              next unless $pixels[$y + 2*$dy][$x + 2*$dx];
              $doublerays[$y][$x]++, ($mod4neighb[$y+$dy][$x+$dx] ||= 0) |= 1<<($n % 4);
	    }
	    $cntmin[$y][$x] = $cnt[$y][$x] = @OFF if @OFF;
	    $nearmin[$y][$x] = [ @{ $near[$y][$x] } ] if $near[$y][$x];	# deep copy
	    $offs[$y][$x]  = \@OFF;
	  }
	}
       DO_RAYS:
        for my $ray_round (0,1) {	# On the second round, some pixels may be decided to be insignificant, and removed
           for my $y (1..$height) {	# Inspect angular neighborhods 
             my(@r, @row) = [];
             for my $x ( 1..$width ) {
               push @r, [] and next unless my $o = $offs[$y][$x];
               my $r = $rays[$y][$x];
               my @o = grep { !$r->[$_] or $r->[$_][0] =~ /^[D\WP]/ } @$o;	# 'Probable-curve' put here as an experiment XXX
               my @rr;
               @rr = @$r if $r;
               @rr[ @o ] = @{ inspect_ray $x, $y, \@cnt, \@cntmin, \@pixels, \@pixelsmin, \@near, \@nearmin, \@o };
               push @r, \@rr;
             }
             $rays[$y] = \@r;
           }
           my(@b_postpone, @g_postpone, @rem_postpone, @un_postpone, @protect, @rhombi, %rhombi, @extra_postpone);
           for my $y (1..$height) {	# 2nd order inspection: check identified dependencies (all dependencies must match;
             for my $x ( 1..$width ) {	#			every dependency must be good in at least one direction)
               for my $dir ( @{$offs[$y][$x]} ) {
                 my $ray = $rays[$y][$x][$dir] || next;
                 my($keep, $TO, $what, $rot, $checked, $DEP, $remove, $unignore, @rest) = (1, undef, @$ray);
                 next unless $DEP or @rest or $remove;
                 my @DEP = (($DEP and not ref $DEP->[0]) ? $DEP : ($DEP ? @$DEP : ()));
                 for my $ddep (@DEP) {	# Switch to the first "alternative variant" with non-satisfied dependencies
                   $keep = 0;
                   for my $dep (@$ddep[1..$#$ddep]) {	# we are OR-ing over the dependencies: we keep if any one matches
                     my($KEEP,$X,$Y,@DIR) = (1, @$dep);
                     for my $DIR (@DIR) {	# we are AND-ing over the directions of a dependence: KEEP if all match
                       $KEEP = 0, last unless post_inspect_ray($X, $Y, $DIR, \@rays, "$what/[$x,$y,$dir]/" . ($rot || '-') . ":$c");	# Can be optimized by merging neighbors???
                     }
                     $keep = 1 if $KEEP;
                   }
                   $TO = $ddep, last unless $keep;
                 }
                 push @rem_postpone, $remove if $remove and $keep;
                 push @un_postpone, $unignore if $unignore and $keep;
                 push @g_postpone, [$y, $x, $dir, @rest] if @rest and $keep;
                 push @b_postpone, [$y, $x, $dir, $TO->[0] || '.'] unless $keep;
                 if ($keep and $what =~ /^[3r]/) {{		# 3fork3, rhombus
                   my($dx,$dy) = ($dx[$dir],$dy[$dir]);
                   next unless $rays[$y+$dy][$x+$dx][($dir+4)%8][0] =~ /^([dcfs])/;	# part of a curve (maybe falsely fake)
                   $rhombi{$x,$y,$dir}++ unless $1 eq 's';	# serif
                   push @rhombi, [$x, $y, $dir, $rot, $dx, $dy, "$1"];
                 }}
               }
             }
           }
           while (my $p = shift @rhombi) {
             my($x, $y, $dir, $rot, $dx, $dy, $t)  = @$p;
             my $dir1 = ($dir+$rot)%8;
             my($dx1,$dy1, @opp) = ($dx[$dir1],$dy[$dir1]);
             unless (@opp = grep $rhombi{$x+$dx+$dx1,$y+$dy+$dy1,($_+4)%8}, $dir, $dir1) {
               $rays[$y][$x][$dir][0] = 'Rhombus-force' if $t eq 's' and $cnt[$y+$dy1][$x+$dx1] <= 4;	# As in д, and skip dense
               next;
             }
             next if $t eq 's';			# Done for in-serif
             # Should watch so that we do not break 0663,0d96,1ba7
             if (!($dir%2) and $cnt[$y][$x] == 3 and ($rays[$y][$x][($dir+4)%8] || $ER)->[0] eq 'tail'
                 and $cnt[$y+$dy1][$x+$dx1] == 4 and ($rays[$y+$dy1][$x+$dx1][$dir] || $ER)->[0] eq 'doubleray'
                 and ($rays[$y+$dy+$dy1][$x+$dx+$dx1][$dir] || $ER)->[0] =~ /^[dc]/) {	# Ддщц   doubleray
               $rays[$y][$x][$dir][0] = 'Rhombus-force';
               next;
             }
             if ($cnt[$y+$dy+$dy1][$x+$dx+$dx1] == 3
             	 and grep +(!($_%2) and ($rays[$y+$dy+$dy1][$x+$dx+$dx1][$_] || $ER)->[0] eq 'tail'), @opp) {
               # Ддщц - on the other side.  Detect which one is better.  Should watch so that we do not break 0663,0d96,1ba7
               my($good, $other) = ([$dir, $dx, $dy], [$dir1, $dx1, $dy1]);			# default
               for my $g ($other) {			# no need to check for $good!
                 ($good, $other) = ($g, $good) if $rays[$y+$g->[2]][$x+$g->[1]][($g->[0]+4)%8][0] eq 'doubleray';
               }
               if ($cnt[$y+$good->[2]][$x+$good->[1]] == 4 
               	   and $rays[$y+$good->[2]][$x+$good->[1]][($good->[0]+4)%8][0] eq 'doubleray'
               	   and $rays[$y][$x][($good->[0]+4)%8][0] =~ /^[dc]/) {		# doubleray
                 $rays[$y][$x][$other->[0]][0] = $rays[$y+$other->[2]][$x+$other->[1]][($other->[0]+4)%8][0] = 'ignore';
                 push @extra_postpone, ['R', $y, $x, $good->[0]];	# may be changed to ' very soon; postpone until this
                 next;
               }
             }
             $rays[$y][$x][$dir][0] = '2fork3';
             unless ($seenlong{$x, $y, $x+$dx+$dx1,$y+$dy+$dy1}) {
               push @longedges, [$x, $y, $x+$dx+$dx1,$y+$dy+$dy1, scalar @longedges, $dir, $rot];
               $seenlong{$x, $y, $x+$dx+$dx1,$y+$dy+$dy1} = $seenlong{$x+$dx+$dx1,$y+$dy+$dy1,$x, $y} = $longedges[-1];
               $midLong{2*$x+$dx+$dx1,2*$y+$dy+$dy1}++;
               $inLong{$x+$dx+$dx1,$y+$dy+$dy1}++;
               $inLong{$x,$y}++;
             }
           }
           while (my $p = shift @b_postpone) {
             my($y, $x, $dir, $what)  = @$p;
             $rays[$y][$x][$dir][0] = $what;
           }
           while (my $p = shift @un_postpone) {
             my($x, $y, $dir)  = @$p;
             $protect[$y][$x][$dir]++
           }
           while (my $p = shift @g_postpone) {
             my($Y, $X, $DIR, @p)  = @$p;
             for my $pp (@p) {
   #  warn "In g_postpone: (@$p)";
               if ($pp->[0] =~ /^I(I)?$/) {				# (only fix '?' if II, and only in one direction
                 (undef, my ($x, $y, @pp)) = @$pp;
                 for my $dir (@pp) {
                   next if $protect[$y][$x][$dir] or $1 and $rays[$y][$x][$dir][0] ne '?';
                   $rays[$y][$x][$dir][0] = 'ignore';
                   next if $1;
                   my $dx = $dx[$dir];
                   my $dy = $dy[$dir];
                   $rays[$y+$dy][$x+$dx][($dir+4)%8][0] = 'ignore';
                 }
                 next
               }
               force_line($pp, \@rays), next if $pp->[0] eq 'L';
               if ($pp->[0] =~ /^E([Ef])?$/) {				# Enforce a sane type (only on '?' if EE, on f if Ef)
                 (undef, my ($x, $y, $dir)) = @$pp;
                 next unless $rays[$y][$x][$dir][0] =~ ($1 ? ($1 eq 'f' ? qr/^f/ : qr/^[?P]/) : qr/^[\WP]/);	# '?'
                 $rays[$y][$x][$dir][0] = 'Enforce';
                 next;
               }
               if ($pp->[0] eq 'n') {				# Enforce notch
                 (undef, my ($x, $y, $dir)) = @$pp;
                 next unless $cnt[$y][$x] == 3 and $rays[$y][$x][$dir][0] eq '?';
                 $rays[$y][$x][$dir][0] = 'Enforce';
                 next;
               }
               if ($pp->[0] eq 'S') {				# Enforce Sharp
                 (undef, my ($x, $y, $dir)) = @$pp;
                 next unless $cnt[$y][$x] == 4 and $rays[$y][$x][$dir][0] =~ /^[?fP]/;	# '?'
                 $rays[$y][$x][$dir][0] = 'Enforce';
                 next;
               }
               if ($pp->[0] =~ 'a') {				# Check arrow backwards
                 my $t = $rays[$Y][$X][($DIR+4)%8][0];
		 push @extra_postpone, ['a', $Y, $X, $DIR, @$pp[1,2], $t];
                 next if $t =~ /^[dctNC]/;		# doubleray, curve, tail, Near-corner or Corner-curve
                 $rays[$Y][$X][$DIR][0] = '…';
                 next;
               }
               if ($pp->[0] =~ 't') {				# tail; check cedilla
                 my $dx = $dx[$DIR];
                 my $dy = $dy[$DIR];
                 my $T = $rays[$Y+$dy][$X+$dx][($DIR+4)%8];
                 next unless $T->[0] =~ /^c/;
                 my $rot = $T->[1];
                 next unless $rays[$Y][$X][($DIR+$rot+4)%8][0] =~ /^d/;	# doubleray
                 my $dx1 = $dx[($DIR+$rot+4)%8];
                 my $dy1 = $dy[($DIR+$rot+4)%8];
                 next unless $cnt[$Y+$dy1][$X+$dx1] == 4;
                 next unless $rays[$Y+$dy1][$X+$dx1][($DIR+$rot+4)%8][0] =~ /^d/;	# doubleray
                 next unless $cnt[$Y+2*$dy1][$X+2*$dx1] == 3;				# See 6a81
                 next unless ($rays[$Y+$dy1][$X+$dx1][($DIR+2*$rot+4)%8] || $ER)->[0] =~ /^e/;	# elses-ray (opp fake-curve)
                 next unless ($rays[$Y+$dy1][$X+$dx1][($DIR+3*$rot+4)%8] || $ER)->[0] =~ /^f/;	# fake-curve
                 my $dx2 = $dx[($DIR+2*$rot+4)%8];
                 my $dy2 = $dy[($DIR+2*$rot+4)%8];
                 next unless $cnt[$Y+$dy1+$dy2][$X+$dx1+$dy2] == 3;
                 next unless ($rays[$Y+$dy1+$dy2][$X+$dx1+$dx2][($DIR+2*$rot)%8] || $ER)->[0] =~ /^f/;	# fake-curve
                 $rays[$Y+$dy1][$X+$dx1][($DIR+$rot+4)%8][0] = $rays[$Y+2*$dy1][$X+2*$dx1][($DIR+$rot)%8][0] = 'ignore';
                 $rays[$Y+$dy1][$X+$dx1][($DIR+2*$rot+4)%8][0] = 'Enforced';
                 $rays[$Y+$dy1+$dy2][$X+$dx1+$dx2][($DIR+2*$rot)%8][0] = 'curve';
                 next;
               }
               if ($pp->[0] =~ 'C') {				# Check spurious connectors
                 my $dx = $dx[$DIR];
                 my $dy = $dy[$DIR];
                 my $T = $rays[$Y+$dy][$X+$dx][($DIR+4)%8];
                 next if $T->[0] !~ /^(c)/i or $midLong{2*$X+$dx,2*$Y+$dy};
                 my($opp, $good) = ($1);
                 # For curves (in both directions), check that going their intendend continuation (which is long) in opposite direction
                 # has another choice (is a Fork) that this (spurious!) line.
                 if ($opp eq 'c') {
                   my($seen, $arrows, $deg_corner) = ('', 0);
                   for my $C ([$X,$Y,($DIR + 4 + $T->[1])%8, $T->[1]], [$X+$dx,$Y+$dy,($DIR+$pp->[1])%8,$pp->[1]]) {
                     my($XX,$YY,$DD,$R) = @$C;
                     my $dx1 = $dx[$DD];
                     my $dy1 = $dy[$DD];
#                     warn "$X, $Y, $DIR; $XX, $YY, $DD, $R;  $rays[$YY+$dy][$XX+$dx][($DD+4)%8] $rays[$YY][$XX][$DD]"
#                       unless defined $rays[$YY+$dy][$XX+$dx][($DD+4)%8] and defined $rays[$YY][$XX][$DD];
                     # Combination of 1 (Tail) and F is good (2af7, 0593)
                     $good = 1 unless $rays[$YY+$dy1][$XX+$dx1][($DD+4)%8][0] =~ /^([F°])/
                                  and ($1 eq 'F' or $rays[$YY][$XX][($DD-3*$R)%8][0] =~ /^C/ and $deg_corner=1)	# see 2aa1, 2af7
                                  and $rays[$YY][$XX][$DD][0] =~ /^([cF1d])/	# Inserting d here requires test for 22, and hurts ῳ
                                  and not ((my $m1 = $1) eq 'd' and $rays[$YY+$dy1][$XX+$dx1][$DD][0] =~ /^d/);
                     $seen .= $m1 unless $good;		# Matchess succeeded!
                     $arrows++ if $rays[$YY][$XX][$DD][0] =~ /^d/			# doubleray
                                  and $rays[$YY+$dy1][$XX+$dx1][$DD][0] =~ /^a/
                                  and $rays[$YY+$dy1][$XX+$dx1][$DD][1] == -$R;	# arrow 21f6 but not 222e
                   }
                   $good = 1 if !$good and $seen =~ /F1|1F|(11)/ and ($1 or not $deg_corner);
                   $good = 0 if $arrows == 2;
                 } else {	# E.g., 2a85
                   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]]) {
                     my($XX,$YY,$DD,$rev,$R) = @$C;
                     my $dx1 = $dx[$DD];
                     my $dy1 = $dy[$DD];
                     if ($rev) {	# Went in the direction of 'Corner-curve'
                       $good = 1 unless $rays[$YY+$dy1][$XX+$dx1][($DD+4)%8][0] =~ /^C/
                                    and $rays[$YY][$XX][$DD][0] =~ /^c/ and $rays[$YY][$XX][$DD][1] == $R;
                     } else {
                       $good = 1 unless $rays[$YY+$dy1][$XX+$dx1][($DD+4)%8][0] =~ /^°/
                                    and ($rays[$YY][$XX][($DD-3*$R)%8] || $ER)->[0] =~ /^([1])/	# cC break a lot of stuff
                                    	 and ($1 ne 'C' 
                                    	      or $rays[$YY][$XX][($DD-3*$R)%8][1] == $R)
                                    and $rays[$YY][$XX][$DD][0] =~ /^([cd])/;
                     }
                   }
                 }
                 unless ($good) {
                   $rays[$Y][$X][$DIR][0] = '¢';
                   $opp =~ tr/cC/¢₡/;
                   $rays[$Y+$dy][$X+$dx][($DIR+4)%8][0] = $opp;
                 }
                 next;
               }
               die "Unknown postpone action: <$pp->[0]>"
             }
             # $rays[$y][$x][$dir][0] = $what;
           }
           while (my $p = shift @extra_postpone) {
             my($type, $Y, $X, $DIR, @p)  = @$p;
             if ($type eq 'a') {			# Check for 'a' on the other side of the arrow
               my $x = $X + 2*$dx[($DIR+$p[0])%8];
               my $y = $Y + 2*$dy[($DIR+$p[0])%8];
               my $dir = ($DIR-2*$p[0])%8;
               $rays[$Y][$X][$DIR][0] = ($p[1] ? 'x-arrow' : '…')
                 unless $rays[$y][$x][$dir][0] =~ /^a/ and (not $p[1] or $rays[$y][$x][($dir+4)%8][0] eq $p[2]);
             }
             if ($type eq 'R') {			# Check for 'a' on the other side of the arrow
               $rays[$Y][$X][$DIR][0] = 'Rhombus-force';
             }
           }
           last DO_RAYS unless @rem_postpone;
           my @SEEN;
           while (my $r = shift @rem_postpone) {
             my($x, $y)  = @$r;
             remove_px($x, $y, \@cntmin, \@pixelsmin, \@nearmin, \@offs) unless $SEEN[$y][$x]++;
           }
        }				# end DO_RAYS
	for my $y (1..$height) {	# In a pair '?'/'f', change '?' to 'Ignore'
	  for my $x ( 1..$width ) {
            next unless my $rays = $rays[$y][$x];
            next unless @$rays;
            for my $dir (@{$offs[$y][$x]}) {
              next unless $rays->[$dir][0] =~ /^(f)/i;			# fake-curve/Fork
              next if (my $code = $1) eq 'F' and $cnt[$y][$x] != 1;	# Sharp corners (as in V) may result in fork with |stem|=1
              my $x1 = $x + $dx[$dir];
              my $y1 = $y + $dy[$dir];
              my $dir1 = ($dir+4)%8;
              $rays[$y1][$x1][$dir1][0] =~ s/^[?1P].*/Tail/, next if $code eq 'F';	# '?', 'Probable-curve', '1Spur'
	      $rays[$y1][$x1][$dir1][0] =~ s/^[?P""].*/Ignore/;				# '?', 'Probable-curve', '"'
            }
          }
        }
	my(@Simple, @simpleray, @edge, @cntedge, @lastedge, @update);	# Simple points/rays; decided edges
	for my $y (1..$height) {	# Identify simple points/rays
	  for my $x ( 1..$width ) {
            next unless my $rays = $rays[$y][$x];
            next unless @$rays;		# Contamination???
            $Simple[$y][$x] = 1, next unless grep { $rays->[$_][0] =~ /^[D\WP]/ } @{$offs[$y][$x]};	# Dense; junk
          }
        }
	for my $y (1..$height) {	# In a pair '?'/'e' with simple neighbor, change '?' to 'Ignore'
	  for my $x ( 1..$width ) {
            next unless my $rays = $rays[$y][$x];
            next unless @$rays;
            next unless $Simple[$y][$x];
            for my $dir (@{$offs[$y][$x]}) {
              next unless $rays->[$dir][0] =~ /^e/;
              my $x1 = $x + $dx[$dir];
              my $y1 = $y + $dy[$dir];
              my $dir1 = ($dir+4)%8;
	      $rays[$y1][$x1][$dir1][0] =~ s/^[?P""].*/Ignore/;		# 'Probable-curve', '?' '"'
            }
          }
        }
	for my $y (1..$height) {	# Identify simple points/rays
	  for my $x ( 1..$width ) {
            next unless my $rays = $rays[$y][$x];
            next unless @$rays;		# Contamination???
#            next if $Simple[$y][$x];
		# Recalc simple:
            $Simple[$y][$x] = 1, next unless grep { $rays->[$_][0] =~ /^[D\WP]/ } @{$offs[$y][$x]};	# Dense; junk; Probable-curve
           FIND_GOOD:
            for my $dir ( @{$offs[$y][$x]} ) {	# For non-simple vertices, find simple directions (it+neighbors non-dense/junk)
              next if $rays->[$dir][0] =~ /^[D\WP]/;
              for my $rot (1, -1) {				# Skip if closest angular neighbor is bad (Dense/Probable)
                next FIND_GOOD if ($rays->[($dir+$rot)%8] || $ER)->[0] =~ /^[D\WP]/;
              }
	      $simpleray[$y][$x][$dir]++;
#	      warn "Simple RAY at x=$x y=$y dir=$dir\n";
            }
	  }
	}
#	      warn "Simple RAY ====\n";
	for my $y (1..$height) {	# Identify simple edges (should be simple in both directions, and of non-fake types)
	  for my $x ( 1..$width ) {
            next unless my $rays = $rays[$y][$x];
            my $ok = $Simple[$y][$x] 
              or my $smpl = $simpleray[$y][$x];			# Not cleared if $ok; we do not care
            for my $dir ( @{$offs[$y][$x]} ) {
              last if $dir > 3;					# Inspect one end only
              my $semi_bad = 0;
              next unless $ok or $smpl->[$dir] or 2 >= $cnt[$y][$x] and $rays->[$dir][0] eq '°' and ++$semi_bad; # ° = not confirmed Fork
              my $x1 = $x + $dx[$dir];
              my $y1 = $y + $dy[$dir];
              my $dir1 = ($dir+4)%8;
	      next unless $Simple[$y1][$x1] or $simpleray[$y1][$x1][$dir1]
	           or 2 >= $cnt[$y1][$x1] and $rays[$y1][$x1][$dir1][0] eq '°' and ++$semi_bad < 2;
# warn "Candidate for °-ray at x=$x y=$y dir=$dir\n" if $semi_bad;
      # doubleray, curve, notch, serif, tail, 1Spur, Fork, Enforced, Sharp, m-joint, Near-corner, Corner-curve, bend-sharp, Tail, A/arrow, Rhombus-force
	      #   Omit: [Zh/K-]fake-curve, Ignore, rhombus, i, fork4, elses-ray, 3fork3, 2fork3, Dense, x-arrow, \W-junk
	      next unless 2 - $semi_bad == grep /^[dcnst1FESmNCbTAaR]/, $rays->[$dir][0], $rays[$y1][$x1][$dir1][0];
	      $edge[$y][$x][$dir]++;	$edge[$y1][$x1][$dir1]++;
	      $cntedge[$y][$x]++;	$cntedge[$y1][$x1]++;
	      $lastedge[$y][$x] = $dir;	$lastedge[$y1][$x1] = $dir1;	# Good only for 1-edge pixels
            }
	  }
	}
	my %candidates_way_out;
	for my $y (1..$height) {	# Identify singletons with a valid way out (one d in a group of d,e,f,K)
	  for my $x ( 1..$width ) {
            next unless $Simple[$y][$x] and ($cntedge[$y][$x] || 0) <= 1;	# If already have two edges, do not try to find complicated...
#            die "x=$x, y=$y, Simple=$Simple[$y][$x], rays=<@{$rays[$y][$x]}>" unless defined $cnt[$y][$x];
            next if $cnt[$y][$x] > 6;					# Give up if too many neighbors
            my @Neighbors = @{$offs[$y][$x]};
            push @Neighbors, shift @Neighbors while $Neighbors[-1] == ($Neighbors[0] + 7)%8; # Rotate to start of a run
            my(@res, $bad, @good, @maybe, %forced, $L, @Zh);
            my $rays = $rays[$y][$x];
            # We assumed that only one edge is found before
            $L = $lastedge[$y][$x] if $cntedge[$y][$x];	# Connect only as a curve continuation, and only if it continues back
            for my $d ( 0..$#Neighbors ) {
              my $dir = $Neighbors[$d];
              (!$bad and (   @Zh == 1 and @good == 1 and push @res, [$Zh[0]] 
              		  or @good == 1  and push @res, [$good[0]]
              		  or @maybe == 1 and push @res, [$maybe[0], 1])), # Finish previous group
                $bad=0, @good = @maybe = @Zh = ()			# Start processing a new group
                  if $dir != ($Neighbors[$d-1] + 1)%8;			#   if $dir is after a gap
              $bad++, next unless $rays->[$dir][0] =~ /^[dKZfIeiFxR]/;	# doubleray,[Zh/K-]fake-curve,Ignore,elses-ray,ignore,Fork,x-arrow,Rhombus-force
              my $sharp_angle = (defined $L and abs(4 - abs($L-$dir)) >= 2);
              push(@good, $dir), next
                if $rays->[$dir][0] =~ /^[dR]/ and not $sharp_angle;		# Pick up doubleray, Rhombus-force
              $forced{$dir}++, push(@maybe, $dir), next
                if $rays->[$dir][0] =~ /^F/ and defined $L and not $sharp_angle;	# Fork; pairs of Fc should end here...
              $forced{$dir}++, push(@Zh, $dir), next if $rays->[$dir][0] =~ /^Z/;	# special-case Zh-joint
              my $x1 = $x + $dx[$dir];
              my $y1 = $y + $dy[$dir];
              my $rays1 = $rays[$y1][$x1] or next;
              my $R = $rays1->[($dir+4)%8] or next;
              $forced{$dir}++, push(@good, $dir),  next if $rays->[$dir][0] =~ /^e/    and $R->[0] =~ /^d/; # reversed `d'oublerays
              $forced{$dir}++, push(@maybe, $dir), next if $rays->[$dir][0] =~ /^[fK]/ and $R->[0] =~ /^d/; # reversed `d'oublerays
            }
            !$bad and (   @Zh == 1 and @good == 1 and push @res, [$Zh[0]]
            	       or @good == 1  and push @res, [$good[0]]
            	       or @maybe == 1 and push @res, [$maybe[0], 1]);
#   warn "c=$c, x=$x, y=$y ==> ways out @res (out of @Neighbors): ", join '|', map $rays->[$_][0], @Neighbors;
            next if @res > 2;		# Do not get too carried away...
            if ($cntedge[$y][$x]) {		# Connect only as a curve continuation, and only if it continues back
              @res = grep 1 >= abs(4 - abs($L-$_->[0])), @res;	# i.e., almost opposite
              # Maybe if two are left, chose by good vs maybe?
              if (0 and @res > 1) {{	# does not actually change anything...
                my @r = grep !$_->[1], @res or last;
                @r < @res or last;
                @res = @r;
              }}
# warn "filtered: (@res)\n";
	    }
	    $candidates_way_out{$y,$x} = {map {+( $_->[0] => [$forced{$_->[0]}, @$_] )} @res} if @res;
	  }
	}
	for my $y (1..$height) {	# Identify singletons with a valid way out (one d in a group of d,e,f,K)
	  for my $x ( 1..$width ) {
	    next unless my $cand = $candidates_way_out{$y,$x};
	    my @ways = values %$cand;
	    my @res;
            # We assumed that only one edge is found before
#            my $L = $cntedge[$y][$x] and $lastedge[$y][$x]; # Connect only as a curve continuation, and only if it continues back
            if ($cntedge[$y][$x]) {		# Connect only as a curve continuation, and only if it continues back
              # Do not connect to something which is not a simple edge — or at least doubleray or curve!
              for my $d (@ways) {
                my $dir = $d->[1];
                push(@res, $dir), next if $d->[0];	# forced
                my $x1 = $x + $dx[$dir];
                my $y1 = $y + $dy[$dir];
                my $good = ($edge[$y1][$x1][$dir] or $rays[$y1][$x1][$dir][0] =~ /^[dcR]/	# doubleray curve Rhombus-forced
                	    or $candidates_way_out{$y1,$x1}{$dir});
                unless ($good) {
                  my $x2 = $x + 2*$dx[$dir];
                  my $y2 = $y + 2*$dy[$dir];
                  my $dir2 = ($dir+4)%8;
                  $good = $candidates_way_out{$y2,$x2}{$dir2};
                }
                push @res, $dir if $good;
              }
              last if @res > 1;
            } else {
              @res = map $_->[1], @ways;
            }
# warn "filtered:: (@res)\n";
            push @update, [$x,$y,@res];
	  }
	}
	my %updated;
	while (my $u = shift @update) {
	  my($x, $y, @res) = @$u;
          for my $dir (@res) {
            my $x1 = $x + $dx[$dir];
            my $y1 = $y + $dy[$dir];
            my $dir1 = ($dir+4)%8;
            next if $updated{$x,$y,$dir}++ or $updated{$x1,$y1,$dir1}++;
            $edge[$y][$x][$dir]++;	$edge[$y1][$x1][$dir1]++;
            $cntedge[$y][$x]++;		$cntedge[$y1][$x1]++;
	    $lastedge[$y][$x] = $dir;	$lastedge[$y1][$x1] = $dir1;	# Good only for 1-edge pixels
#warn "Update: $x,$y --> $x1,$y1\n";
          }
	}
        for my $e (@longedges) {	# If a prefered way is found elsewhere, replace longedge by the prefered way
          next if not ref $e and $e eq 'erased';
          my($x, $y, $x1,$y1, $offset, $dir, $rot) = @$e;
          my @atBEG = grep $edge[$y][$x][$_],         $dir, ($dir+$rot)%8;
          my @atEND = grep $edge[$y1][$x1][($_+4)%8], $dir, ($dir+$rot)%8;
          next unless @atBEG or @atEND;
          my @add;	# Had a longedge since couldn’t choose 1 of 2 ways around a rhombus; looks like something made a preference…
          unless (@atBEG and @atEND) {		# If have a joiner on both sides, may just drop the longedge altogether
            my @have = (@atBEG, @atEND);	# actually, one of them
            next if @have == 2;			# XXX It is not clear what to add, so do not drop!  ???
            my $DIR = ($dir + (($have[0] == $dir) && $rot))%8;	# Add $dir+$rot on the OTHER side.
            my($dx,$dy) = ($dx[$DIR],$dy[$DIR]);
            if (@atEND) {
              @add = [$x, $y, $x + $dx, $y + $dy, $DIR];
            } else {
              @add = [$x1, $y1, $x1 - $dx, $y1 - $dy, ($DIR+4)%8];
            }
          }
          for my $a (@add) {
            my($x, $y, $x1, $y1, $dir) = @$a;
            my $dir1 = ($dir+4)%8;
            $edge[$y][$x][$dir]++;	$edge[$y1][$x1][$dir1]++;
            $cntedge[$y][$x]++;		$cntedge[$y1][$x1]++;
            $lastedge[$y][$x] = $dir;	$lastedge[$y1][$x1] = $dir1;	# Good only for 1-edge pixels
          }
          $longedges[$offset] = 'erased';
          delete $seenlong{$x,$y,$x1,$y1};
          delete $seenlong{$x1,$y1,$x,$y};
          $midLong{$x+$x1,$y+$y1}--;
          $inLong{$x1,$y1}--;
          $inLong{$x,$y}--;
        }
	my($do_more, @blob, @clearEdge, %suspectShaft, %skipExtraBlob) = 1;
	my $blobs = find_blobs(\@blob, $width, $height, \@pixels, \@cntedge, \@offs);
	while ($blobs and $do_more) {	# clear edges with two “noisy” surroundings
          for my $y (0..$#edge) {
            next unless $edge[$y];
            for my $x ( 0..$#{ $edge[$y] } ) {
              next unless $edge[$y][$x]; 
              for my $dir ( 0..3 ) {				# Do only once per edge
                next unless $edge[$y][$x][$dir ];
                my $x1 = $x + (my $dx = $dx[$dir]);
                my $y1 = $y + (my $dy = $dy[$dir]);
                if ($dir % 2) {
                  my(@CC, $CC, $clear);
                  for my $rot ( -1, 1 ) {		# Three big, bad blobs on the same side of an edge
                    my($dx1, $dy1) = ($rot*$dy, -$rot*$dx);
                    my($dx2, $dy2, $c, $C) = (($dy==$dy1 ? (0, $dy1) : ($dx1, 0)), 0, 0);	# dot product with $dxy
                    # Go in the natural order of 3 neighbors (projection on $dxy):
                    my @DD = (($dy==$dy1 ? ([$y, $x+$dx1], [$y+$dy1,$x]) : ([$y+$dy1,$x], [$y, $x+$dx1])), [$y1+$dy2,$x1+$dx2]);
                    for my $DD (0..2) {
                      my $D = $DD[$DD];
                      $CC[$DD]++, $C++ if ($blob[$D->[0]][$D->[1]] || 0) >= 3 - ($DD==1);	# More forgiving for middle; 0909
                      $c++ if $pixels[$D->[0]][$D->[1]];
                    }
                    $clear++ and last if $c == 3 and $C >= 2;
                    $CC += $C
                  }					# This gives reasonable (?) results
                  # warn "$c: diag blob? x,y=$x,$y,$dir $clear CC=$CC <@CC>" if $CC >= 2;
                  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]);
#                      if ($blob[$y][$x+$dx1] || 0) >= 3 and ($blob[$y+$dy1][$x] || 0) >= 3 and ($blob[$y1+$dy2][$x1+$dx2] || 0) >= 3;
                } else {
                  my($tot, $done, %neigh, $lastN, $lastR, $lastDx, $lastDy) = (0);
                  for my $rot ( -1, 1 ) {		# Two big, bad blobs on the same side of an edge
                    my($dx1, $dy1) = (-$rot*$dy, $rot*$dx);
                    $neigh{$rot} = [($blob[$y+$dy1][$x+$dx1] || 0) >= 3, ($blob[$y1+$dy1][$x1+$dx1] || 0) >= 3];
                    $neigh{$rot}[$_] and ++$tot and ($lastN, $lastR, $lastDx, $lastDy) = ($_, $rot, $dx1, $dy1) for 0, 1;
                    ++$done and push @clearEdge, [$x, $y, $dir, $x+$dx, $y+$dy] and last if $neigh{$rot}[0] and $neigh{$rot}[1];
                  }
                  if (!$done and $cntedge[$y][$x] == 1 and $cntedge[$y1][$x1] == 1) {	# Detect bold arrow tips/barbs
                    ++$done and push @clearEdge, [$x, $y, $dir, $x+$dx, $y+$dy] if grep 2 == $neigh{1}[$_] + $neigh{-1}[$_], 0, 1;
                    if (!$done and $tot == 1) {		# fake serifs near blobs
                      my($X, $Y, $D) = ($x, $y);
                      if ($lastN) {
                        ($x, $y, $D) = ($x1, $y1, $dir);
                      } else {
                        ($dx, $dy, $D, $lastR) = (-$dx, -$dy, ($dir+4)%8, -$lastR);
                      }
                      if (($blob[$y+$dy][$x+$dx] || 0) >= 3 and ($blob[$y+2*$lastDy][$x+2*$lastDx] || 0) >= 3 
                      	  and ($blob[$y+$dy+$lastDy][$x+$dx+$lastDx] || 0) >= 3 and not $blob[$y+$dy-$lastDy][$x+$dx-$lastDx]) {
                      	push @clearEdge, [$X, $Y, $dir, $x1, $y1];
                      	# warn sprintf "barb: $X, $Y, $dir, $x1, $y1 (%d %d %d) $lastN $lastR", $y+2*$lastDy,$x+2*$lastDx,($D+3*$lastR)%8;
                      	$suspectShaft{$y+2*$lastDy,$x+2*$lastDx,($D+3*$lastR)%8}++;
                      }
                    }
                  }
                }
              }
            }
          }
          # warn "remove: @$_[0..2]" for @clearEdge;
          # @clearEdge = ();
          $do_more = @clearEdge;
          while (my $e = pop @clearEdge) {
            my($x, $y, $dir, $x1, $y1) = @$e;
            my $dir1 = ($dir+4)%8;
            $edge[$y][$x][$dir] = 0;	$edge[$y1][$x1][$dir1] = 0;
            $cntedge[$y][$x]--;		$cntedge[$y1][$x1]--;
            for my $l ([$x, $y], [$x1, $y1]) {
              next if $cntedge[$l->[1]][$l->[0]] != 1;
              my $D = -1;
              $edge[$l->[1]][$l->[0]][$_] and $D = $_, last for 0..7;
	      $lastedge[$l->[1]][$l->[0]] = $D;		# Good only for 1-edge pixels
	    }
          }
          # my $rep;
          for my $K (keys %suspectShaft) {
            last if $suspectShaft{$K} != 2;
            my($y, $x, $dir) = split /$;/o, $K;
            # warn "rep shafts" unless $rep++;
            # warn "shaft ($c) $x $y, $dir <$K> $suspectShaft{$K}";
            my($x1, $y1) = ($x+$dx[$dir], $y+$dy[$dir]);
            last if ($cntedge[$y1][$x1] || 0) != 1 or $lastedge[$y1][$x1] != $dir;
            my $dir1 = ($dir+4)%8;
            $edge[$y][$x][$dir]++;	$edge[$y1][$x1][$dir1]++;
            $cntedge[$y][$x]++;		$cntedge[$y1][$x1]++;
            $lastedge[$y][$x] = $dir;	$lastedge[$y1][$x1] = $dir1;
            $do_more++;
            $skipExtraBlob{$y,$x}++;
          }
          %suspectShaft = ();
	  $blobs = find_blobs(\@blob, $width, $height, \@pixels, \@cntedge, \@offs);
	  # warn "blobs: $blobs ($do_more edges removed)";
        }
        my @edgeAdd;
        for my $y (1..$height) {
          for my $x ( 1..$width ) {
            next unless 1 == ($cntedge[$y][$x] || 0) and not $inLong{$x,$y};
            my $dir = $lastedge[$y][$x];
            my $x1 = $x - (my $dx = $dx[$dir]);
            my $y1 = $y - (my $dy = $dy[$dir]);
            next if $inLong{$x1,$y1};
            if ($dir < 4) {		# symmetric operations
              push @edgeAdd, [$x, $y, $dir, $x1, $y1]
                if 1 == ($cntedge[$y1][$x1] || 0) and $lastedge[$y1][$x1] == ($dir+4)%8;	# end-to-end edges
            }
            my $r;
            push @edgeAdd, [$x, $y, $dir, $x1, $y1]			# Not good for 210a 2274 fffd
              if 1 == ($blob[$y1][$x1] || 0)				# blob singleton
                 or !($dir & 0x1) and 1 == ($cntedge[$y1][$x1] || 0) 	# 04fa
                    and 2 == abs(($lastedge[$y1][$x1] - $dir)%8 - 4)	# perpendicular
                    and $rays[$y][$x][$dir][0] =~ /^([tdcN])/		# tail doubleray curve Near-corner
                    and ($1 ne 'c' or ($r = $rays[$y][$x][$dir][1]	# curve's curving (04fe)
                    		       and not $rays[$y][$x][($dir+4+$r)%8]));
          }
        }
        while (my $K = shift @edgeAdd) {
          my($x, $y, $dir1, $x1, $y1) = @$K;
          my $dir = ($dir1+4)%8;
          $edge[$y][$x][$dir]++;	$edge[$y1][$x1][$dir1]++;
          $cntedge[$y][$x]++;		$cntedge[$y1][$x1]++;
          $lastedge[$y][$x] = $dir;	$lastedge[$y1][$x1] = $dir1;
        }
	my(@nextEdge, @endEdge, %edges, %seenEndEdge, @strokes, %traversedEdges);
        for my $y (0..$#edge) {
          next unless $edge[$y];
          for my $x ( 0..$#{ $edge[$y] } ) {
            next unless $edge[$y][$x]; 
            for my $dir ( 0..$#{ $edge[$y][$x] } ) {
              next unless $edge[$y][$x][$dir];
              $edges{$x,$y,$dir} = [$x,$y,$dir];
              my $x1 = $x + $dx[$dir];
              my $y1 = $y + $dy[$dir];
              push(@endEdge, [$x1,$y1,($dir+4)%8]), next unless $cntedge[$y1][$x1] == 2; # Do not try to drive through intersections
              my @o;
              push @o, $_ for grep $edge[$y1][$x1][$_], 0..7;
              my @oo = grep $_ != -4, map {($_- $dir + 4) % 8 - 4} @o;
#warn "found dirs [@o] at ($x,$y) --> $x1 $y1 $dir --> rot=$oo[0]\n";
              $nextEdge[$y][$x][$dir] = $oo[0];
            }
          }
        }
#warn "found endEdges: ", scalar @endEdge, "\n";
	my(@calc, %inCalcEdge);
        for my $edge (@endEdge) {		# Handle non-closed strokes (those having end-edge)
          my($x,$y,$dir) = @$edge;
          next if $seenEndEdge{$x,$y,$dir}++;
#warn "endEdge: $x,$y, $dir, $cntedge[$y][$x].\n";
	  my $stroke = traverse_stroke($x,$y,$dir,\%seenEndEdge,\@nextEdge,\%traversedEdges);
	  my $closed = $stroke->[0][1] == $stroke->[-1][3] && $stroke->[0][2] == $stroke->[-1][4];
	  if ($closed) {
	    $closed = -2;			# Presume smooth (but with a junction)
	    for my $i (0..$#$stroke) {
	      $closed = 1, last unless abs(($stroke->[$i][0] - $stroke->[$i-1][0] + 4)%8 - 4) < 2; # At i=0, wraps back to the end
	    }
	  }
#	  $closed &&= -2 if abs(($stroke->[0][1] - $stroke->[-1][1] + 4)%8 - 4) < 2;
          my($breaks, $runs) = [0];
          if ($closed < 0) {		# loop known to be smooth; stroke_2_strokes() won't find anything except ends
            $runs = [[0],[$#$stroke+1]];		# fake corners at ends; [0] means: start at 0, no calculated lines until the next
          } else {
            ($runs, $breaks) = stroke_2_strokes($stroke, \%inCalcEdge);	# Meaning: $runs->[$break] starts a new sub-stroke
          }
          push @strokes, [$closed, $stroke, $runs, $breaks];	# open
        }
#warn "found open strokes: ", scalar @strokes, "\n";
        for my $e (sort keys %edges) {		# Handle closed strokes (without end-edge, need to loop through all edges)
          my $edge = $edges{$e};		# Need to normalize order, since bugs in fontforge are sensitive to the order
          my($x,$y,$dir) = @$edge;
          next if $traversedEdges{$x,$y,$dir};
          my $stroke = traverse_stroke($x,$y,$dir,\%seenEndEdge,\@nextEdge,\%traversedEdges);
          # Try to restart it on corners
          my($i,$corner) = (-1, 2);
          while (++$i <= $#$stroke) {
            my($d,$prevd) = ($stroke->[$i][0], $stroke->[$i-1][0]);	# At i=0, wraps back to the end
            last if abs((($d-$prevd) % 8) - 4) <= 2;			# 135° angle is not a corner
          }
          $i = $corner = 0 if $i > $#$stroke;
          $stroke = [@$stroke[$i..$#$stroke, 0..($i-1)]] if $i;
          my($breaks, $runs) = [0];
          if ($corner == 0) {		# loop known to be smooth; stroke_2_strokes() won't find anything except ends
            $runs = [[0],[$#$stroke+1]];		# fake corners at ends; [0] means: start at 0, no calculated lines until the next
          } else {
            ($runs, $breaks) = stroke_2_strokes($stroke, \%inCalcEdge);	# Meaning: $runs->[$break] starts a new sub-stroke
          }
          push @strokes, [$corner - 1, $stroke, $runs, $breaks];	# 1 if have corners, -1 if smooth
        }
        for my $e (@longedges) {	# [$x, $y, $x+$dx+$dx1,$y+$dy+$dy1, $offset, $dir, $rot]
          next if not ref $e and $e eq 'erased';
          push @strokes, [0, [[-20, @$e[0..3]]]];		# dir==-20
        }
#warn "found strokes: ", scalar @strokes, "\n";
	my(@nextEdgeBlob, @entryPointBlob);	# With lastedge, includes ends of lines:
	find_blobs(\@blob, $width, $height, \@pixels, \@cntedge, \@offs, \@lastedge, \%skipExtraBlob);
        for my $y (1..$height) {
          my $seen = 0;
          for my $x ( 1..$width ) {
            next unless !$blob[$y][$x] == $seen;
            my $blobX = $x - $seen;
            $seen = 1 - $seen;
            my $dir = $seen ? 0 : 4;
            next if $nextEdgeBlob[$dir][$y][$blobX];
            push @entryPointBlob, [$blobX, $y, $dir];
            $entryPointBlob[-1][3] = traverse_boundary($blobX, $y, $dir, \@blob, \@nextEdgeBlob);
          }
        }
	if ($opt{sfd}) {
	  output_sfd_char($c,$width,$height,$px_descent,$Lb,$minwidth, \@strokes,
	  		  \@pixels, \@cntedge, \@offs, \@edge, \@nextEdgeBlob, \@entryPointBlob);
	  return;
	}
	output_human_readable($width, $height, \@pixels, \@rays, \@edge, \@cntedge, \@cnt, \@Simple, \%inCalcEdge, \@blob);
}

my($notdef, $private) = ('00542A542A542A542A542A542A542A00', 'FFB9C5EDD5D5D5D5D5D5D5D5EDB991FF');
my($mynotdef, $notdef_done, @doing_rest, $doing_rest) = 'FF808E9181A2A485A1254581897101FF';
$opt{hiderex} ||= '(?!)';
$opt{hiderex} = qr/^($opt{hiderex})$/i;

my $seen_d1 = '';
while(1) {
	my($c,$d);
	if ($notdef_done++ and not $doing_rest) {		# Step 2
	  $doing_rest++, (@doing_rest = sort keys %override), next unless defined($_ = <>);
	  chomp;
	  ($c,$d)=split(/:/);
	  $d = delete($override{lc $c}) || $d;
	  # unrecognized; distinct from "SHADE" characters; should be done via a glyph for .undef
	  next if $d eq $notdef or $d eq $private;
	  (need_char $c and $override{$c} = $d), next if $opt{sfd} and $c =~ /^00[01].$/;		# Postpone to make SPACE the 4th char in TTF
	} elsif ($doing_rest) {					# Step 3
	  defined ($c = shift @doing_rest) or last;
	  $d = $override{$c};
	} else {						# Step 1
	  next unless $opt{generatenotdef};
	  ($c,$d) = ('.notdef', $mynotdef);
	}
	next unless need_char $c;
	my $d1 = ($ord ? sprintf '%x', int($ord/0x1000) : '');
	print STDERR ($d1 eq $seen_d1 ? '#' : ($seen_d1 = $d1)) if $ord++ and 0 == $ord % 1000;
	output_char_if_narrow($c, $d);
}
## my @O = sort keys %override;
## warn "override=<@O>\n";


# How to recognize rasterization of 1px-wide line?
# Going along a line, there are only two types of delta (neighbors, one 
# diagonal, one coordinate). 
# For slopes <= 1/2 diagonal delta cannot come in pairs; for slopes above 1/2 
# horizontal cannot come in pairs.  Hence one gets stretches of one type
# of delta, separated by single deltas of the other type.

# Be more specific: which stretches may appear?  Use continuous fractions!

# May assume slope M >= 1, take intersection of the line with the vertical grid 
# line.  Make a path between two copies of the line shifted +- 1/2 
# horizontally; color squares with centers inside the path.  
# Hence the diagonal-UR delta appears after a square with center (A + .5,B + .5) 
# if y-coordinate of the intersection with x = A+1 is between B+0.5 and B+1.5.

# Hence stretches are determined by closest integers to Mn + b, n in Z.  Hence 
# they are related if M' = +-M + M0 with integer M0.  Hence may reduce to
# 0 < M <= 0.5.  Hence stretches are of (max) 2 lengths (differing by 1);
# one of the lengths appears single, the other comes in groups (2-stretches).

# Which 2-stretches may appear?  Boundaries are determined by when the line
# intersects y = n + 0.5 with n in Z.  Now exchanging x and y and doing M=1/M
# reduces to the previous step.

# How deep one may go on 24x24 grid?  The shortest non-constant stretch is 1,2;
# such 3-stretch gives the shortest 2-stretch 2,1,1; this gives the shortest
# stretch 1,1,2,1,2,1,2 which is -/-/--/-/--/-/-- which is 19-long.  Hence
# 3-stretches may appear...  On the other hand, it may be interpreted as a part
# of 2,1 repeated indefinitely (prepend -); is avoided by prepending /...

#                xxx
#              xx
#           xxx
#         xx
#      xxx
#    xx
#  xx
# x

# This also can take into account that the line may be cut into interval
# somewhere inside a stretch...

# These transformation may also define "the best" b in y=Mx+b.  When we
# reduce to 0 < M <= 0.5 with constant stretches (pattern ----/ repeated),
# the best line passes through middles of /-deltas.

# On the next layer: if 2-stretches are constant (so stretches are n,m with
# m single, and n coming in groups of N), the line passes through the middle
# of m-stretches.


# http://www.sourcecodebrowser.com/autotrace/0.31.1/pxl-outline_8h.html
# http://tug.org/texinfohtml/fontu.html#Limn
# http://stuff.mit.edu/afs/athena/astaff/project/tex/fontutil/fontutils-0.6/limn/fit.c

# ???  After we found "long strokes", remove them, but keep pixels which
# on both vertical (or horizontal; or diag?) sides have "remaining pixels".
# Try to find strokes in remaining+kept pixels...  -- works for "#"

# ??? Try to find long vert/hor strokes by brute force.  Exclude those who
# have too many pixels on neighboring lines.  -- works for "$".
# Considering striked-snakes (such as $): k neighbors for 2k-1 is not "too many"

# Currency ¤ is tricky...  -- too many "extendable" lines.

# Ec   E	3/4	Note that ^ in 4 is genuine one, but v is fake...
#   >  c
#  <  / 
#   xx  
# Ex x  
#   x  *
#   x CC
#  / x f
# c  Lxx
# E    v

# Input encodes a sequence of rectangles made of grid squares; rectangles share UR/LL corners:
#                □	is encoded as 2,4,4,3,1 (all positive)
#             □□□
#         □□□□
#     □□□□
#   □□
# We want to find a line which rasterizes to these squares, i.e., intersect the (“red”) vertical disector of every square.
#
# It is the same as intersecting a (“green”) horizontal line of length=1 centered at the shared corners, 
# plus intersecting the red lines of the leftmost and the rightmost square.  Suppose that non-on-edge rectanles
# 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
# collection of red lines of a new configuration of rectanges.  This gives a step of recursion.  Above configuration is moved to
#     □□
#    □
#   □
# On this picture, the “old” red lines (“pink”, two at edges) become sloped lines with slope -s, with horizontal projection 1
# (ending on horizontal grid lines, below the center of first square, and below the center of the last square.  

# 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
# 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 
# (or above) the top of the left square, everything is OK.  

# 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
# from the left end of the pink like.  Obviously, the fitting line intersects the pink line iff it intersects the new green line.

# There are several cases when we may exclude the new green line being below the bottom of the left square:
#    • if all rectangles are actually squares, one could replace s by s+1 above, and have one rectangle instead; exclude this;
#    • if there is one rectangle longer than 2 (or two of length 2) the slope of the fitting line is < 1, so intersection with 
#      such green line is impossible;
#    • 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
#      is unique; this may be repeated on both ends).

# Only two cases remain: the rectangles consist of 1 square (total), and that with a pink line which may be either forgotten,
# or replaced by an “additional” green line.  (The additional green lines have no associated red lines, so on the NEXT step of induction
# they would give no pink lines.)  

# In the first (“trivial”) case, the preceding step is of two rectangles with no added green lines.

# So induction step:  We start with n rectangles with k≤2 added green lines; coordinate change gives rectangles of total length 
# 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
# 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 
# squares and ≤2-k added green lines.  The only cases when the number of squares did not decrease is:
#    All rectangles at start are squares except one of length 2; we had 2 added green lines.
# But then on the next step we have no added green lines, so the next step is the trivial one.

# (To avoid the trivial step [which is tricky] we ensure that we call recursively, there are at least two squares.
#  This means at least two green intervals on the previous stage.)
# Provided that this case is handled in the caller, the additional green lines appear when the length of the start/end rectangle
# is s+1; if it is above s+1, this is an impossible situation.

# In particular, every case is reduced to a “unique solution” one, or the “trivial” one.  The last one is equivalent to
# having 3 equidistant paralle lines with an an interval [AB] on the middle one (the preimage [AB] of the last red line), and
# opposite to each other rays XA' and YB' on the other two lines.  The fitting line must intersect all 3 of them. 
# Oone of ray may be the whole line).  It is easy to see that this is equivalent to the line intersecting intervals [XA], [AB] 
# 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
# on a side.  If it is convex, then intersecting [AB] is a corollary of other two.  ???

# Possibly unknown squares: the 2nd and 3rd row “share” a x-coordinate; assume that intersecting a red line in any of them is OK.
#                □	This is equivalent to having the red line of double length, which is equivalent to
#             □□□		the green line of double length at this position (assuming it is not at edge).
#         □□□□		Hence this allows the induction step as well.
#     □□□□□		(Encode ignoring the extra square at second line, with second line marked as: $extended->{1}=1.)
#   □□

# 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);
# The other two arguments as as above.  LL corner of the leftmost of bottom squares is at 0,0.
sub encodes_line ($;$$$);		# Refused degenerated cases when there is a unique solution — with choices in rasterization
sub encodes_line ($;$$$) { 		# %$extended should not have negative keys
  my ($CC, $green_at_left, $green_at_right, $extended) = (shift, shift, shift, shift||{}); # Every elt encodes delta between cells
  # A horrible mess of special-cases before we can recognize "runs", and flip axes...
#  warn "Got: [@$CC], $green_at_left, $green_at_right";
  # Banal case: one rectangle
  return 0, 1/2 if 1 >= @$CC and not ($green_at_left or $green_at_right);		# No greens at all; one rectangle
  my(@jumps, $left_red, $right_red, %seen) = @$CC;	# jumps between greens
  if ($green_at_left) { $left_red = $CC->[0] - 1 }
  else { shift @jumps }
  if ($green_at_right) { $right_red = $CC->[-1] - 1 }
  else { pop @jumps }
  # Need to exclude a trivial case
  unless (@jumps) {					# Only one green
    if ($green_at_left or $green_at_right) {		# Maximize some metric (∑distances to mid-red points)
      	my $sl = 3/(4*$CC->[0] - 1);			#    ==> intersects right at height ≈¾.
      	return ($green_at_left ? ($sl, $sl/4) : ($sl, 1/4 - $sl/2)) # Cut the green interval in the same proportion
    } else {						# Two rectangles; likewise: if ratio of lengths is t≤1, use ¾(1+t²)/(1+t³)
      my $addHalf = 0;
      if ($extended->{0} and $CC->[0] < $CC->[1] - 1) {
        $CC = [$CC->[0] + 1, $CC->[1] - 1];
      } elsif ($extended->{0} and $CC->[0] == $CC->[1] - 1) {
        $addHalf = 1;
      }
      if ($CC->[0] + $addHalf == $CC->[1]) {	# Go through the center of symmetry, with intersection of edge red lines as above:
        my $sl = 3/(4*$CC->[0] + 2*$addHalf - 2);
        return($sl, 1/4 - $sl/2);
      } elsif ($CC->[0] < $CC->[1]) {		# One strategy is to continue periodically, then make a best fit; this joints
        # midpoints of rectangles.  If differ by one, this breaks the green line 1:3 (with 1 on the side of longer rectangle).
        #    On the other hand, to avoid close-to pathological rasterizations, we should divide the green line in the middle!
        # The best derasterization of 1+2 cuts the red lines at heights ¾,¼,¾ (the last ¾ makes it also good-in-L²-norm).
        #    (It is also better since there are two ways to treat it: one can consider the main direction to be horizontal,
        #     or to be diagonal.  This “best” approximation is the same in these two approaches.)
        my $t = $CC->[0] / ($CC->[1] - 1);		# Break green as 1:3, but use the slope as above with t-correction
        my $sl = 3*(1+$t*$t)/(1+$t*$t*$t)/(4*$CC->[1] - 2);
        return($sl, 1 - $sl*$CC->[0]);
      } else {				# Likewise
        my $t = $CC->[1] / ($CC->[0] - 1); # If differ by 1, the distances to red lines are ¼, ¾, ⁵⁄₄,... exactly as ½,3/3 for equal lengths
        my $sl = 3*(1+$t*$t)/(1+$t*$t*$t)/(4*$CC->[0] - 2);
        return($sl, 1 - $sl*$CC->[0]);
      }
    }
  }
  # Up to this moment, always successfully return; below, unsuccessful returns indented (the only successful is the last one):
  my %jump_pre_ext = map { ($_ + !!$green_at_left, $extended->{$_}) } keys %$extended;	# shift keys of extended to be pre-jump
  my $tot_jumps = 0;
  $tot_jumps += $_ for @jumps;
  my($slope_min, $slope_max) = (($tot_jumps - !!$jump_pre_ext{0})/@jumps, ($tot_jumps + !!$jump_pre_ext{@jumps})/@jumps);
  if (int($slope_min) != int $slope_max) {	# differ by ≤1 unless @jumps = 1
    # There is a chance that after shear transformation with slope = int $slope_max, we have both increasing and decreasing paths.
    # But then there is also a horizontal path (yes!); choose it.
    my($H, @ok) = (0, (1) x (1+!!$jump_pre_ext{0}));	# Cur min-height; OK-Height of horizontal line (now = before the jump № 0)
    for my $j (0..$#jumps) {
      $H += ( $jumps[$j] - int $slope_max );
      my $add = $jump_pre_ext{$j + 1};
      @ok = grep { $H <= $_ and $H + !!$add >= $_ } @ok or
        return;
    }
    die "Bug: need to fix the constant term for the shear transform";	# XXX ???  And axes flip! Check start/end segments too!
    my $tot = (grep $_, @ok) || 0;	#  □□□□□□□□□□□□□		Example.  (after shear transform)
    return int $slope_max, $tot/@ok;	#  □□□□  □   □□□		@ok = 1, $tot = 1
  } elsif (%$extended) {    #  In general: may always look for non-decreasing path (after flip+shear).
    # Find rightmost consecutive run on each row (of those joining to the preceeding row)
    my($H, @cur) = (0, (1) x (1+!!$jump_pre_ext{0}));	# Cur min-height; OK-Height of horizontal line (now = before the jump № 0)
    my(@starts) = ((0) x (1+!!$jump_pre_ext{0}));
    for my $j (0..$#jumps) {
      $H += ( $jumps[$j] - int $slope_max );
      my $add = !!$jump_pre_ext{$j + 1};
      # if one of cur is above $H + $add, remove the run at this height
      return if $cur[0] > $H + $add;
      pop @starts, pop @cur if $#cur and $cur[1] > $H + $add;
      @cur = grep { $H <= $_ and $H + $add >= $_ } @cur or
        return;
    }
  }
  my($U,$D,@jU,@jD,%seenU,%seenD) = (0,0);	# jumps, seen: modifiable up/down
  $#jU = $#jD = @jumps + 5;			# Make small negative indices access unreachable elts
  $extended->[$_] and $jD[$_ + !!$green_at_left]++ and $jU[$_ + !!$green_at_left - 1]++ for keys %$extended;	# may access -1
  for my $n (0..$#jumps) {
    $seen {$jumps[$n]}++;
    $seenU{$jumps[$n]}++ if $jU[$n];
    $seenD{$jumps[$n]}++ if $jD[$n];
  }
  $seen{$_}++ for @jumps;
#  warn("many keys\n"), 
    return if 2 + !!$U + !!$D < keys %seen;	# There should be at most 2 different jumps (after correction UP/DOWN)
  my @JUMPS = sort {$a <=> $b} keys %seen;
#  print("jumps=@JUMPS\n"), 
  my($min, $max) = @JUMPS[0,-1];
#  warn("jumps=@JUMPS\n"), 
    return if @JUMPS > 1 and $max - $min > 1 + !!$U + !!$D;	# If two different jumps, must differ by 1
  my($min_can_U, $max_can_D) = ($seen{$min} == ($seenU{$min} || 0), $seen{$max} == ($seenD{$max} || 0));
  if ($min_can_U) {	# Cannot correct if two mins are adjacent
    $min_can_U = 0 if grep { $jumps[$_] == $min and $jumps[$_+1] == $min } 0..($#jumps-1);
  }	# min and max cannot conflict!
  if ($max_can_D) {	# Cannot correct if two mins are adjacent
    $max_can_D = 0 if grep { $jumps[$_] == $max and $jumps[$_+1] == $max } 0..($#jumps-1);
  }
#  warn("edge too long, jumps=@JUMPS\n"), 
    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)
      or      not $green_at_right and (my $r_O = $CC->[-1] - $min - 1) > 0; # right end too long
  # Now may do the induction step (the trivial case @JUMPS == 1 and $min = 1 is already excluded)
  my @rect = 1;
  for my $j (@jumps) {
    $rect[-1]++, next if $j == $min;
    push @rect, 1;
  }			# Found new rectangles
    return unless my($sl, $sh) = encodes_line \@rect, (!$green_at_left and !$l_O), (!$green_at_right and !$r_O);
#  print "sub-slant=$sl <-- (@rect), ", (!$green_at_left and !$l_O), ", ", (!$green_at_right and !$r_O), "\n";
  $sh += $sl*0.5;	# Recalc so that the origin is at bottom of the leftmost new-red (=old-green) line
  $sl += $min;		# Undo the shear transformation; now we are in x-y-exchanged coordinate system; origin = bot. of 1st green
  # If we had green_at_left, the first of old green intervals is (in x-y-exchanged coordinate system) vertical, centered at (0,0)
  # Otherwise it was centered at (1,$CC->[0])
  my($X,$Y) = ($green_at_left ? (0,0) : (1, $CC->[0]));
  $Y -= 0.5;
  $sh += $Y - $X*$sl;	# Now $sh is w.r.t. the unshifed x-y-exchanged coordinate system
  return (1/$sl, -$sh/$sl);	# Finally, exchange the axes back
}

sub test_encodes_line($$$;$$) {
  my ($out, $out_shift) = (shift, shift);
  my $ret = (my ($res, $shift) = &encodes_line(@_));
  $res = 1/$res if $ret;			# old system was in exchanged coordinate system
  if (defined $out xor $ret) {
   print "! ", ($ret ? " expect: NONE;\t$res <- " : "expect: $out;\tNONE <- ");
  } elsif ($ret and abs($res-$out) < 1e-9) {
   print "OK expect: $out; $res <- ";
  } else {
   print +($ret ? "! expect: $out;\t$res <- " : 'OK NONE <- ');
  }
#  print "\n";
#  return;
  my $in = shift;
  my @r = map {defined() ? $_ : '<undef>'} @_;
  print "[@$in] @r\n";
  if (defined $out_shift) {
    if (abs($shift - $out_shift) <= 1e-9 * (abs($shift) + abs($out_shift))) {
      print "shift OK: $shift\n";
    } else {
      print "! shift OK: $shift; expect = $out_shift\n";
    }
  }
}

sub _test_encodes_line {	# Need to check incredible amount of branches...
  # Actually, the slopes below are for the old algorithm; the new one gives slightly different fits.
  test_encodes_line($_->[0], $_->[1], $_->[2], $_->[3], $_->[4])
    for [7, undef, [7], 1, 1], [7, undef, [7], 0, 1], 
        [7, undef, [7], 1], [undef, undef, [7, 9], 1, 1], [undef, undef, [7,9], 1], [undef, undef, [5,7], 1], [undef, undef, [7, 5], 0, 1], 
        [9, undef, [7, 9], 0, 1], [9, undef, [7,9]], [5.5, undef, [5,6], 1], [5.5, undef, [6, 5], 0, 1], 
    	[9, undef, [7, 9], 0, 1], [9, undef, [9, 7], 1], [7.5, undef, [7,8], 1], [4.5, undef, [5,4], 0, 1], [15, undef, [7, 15], 0, 1], [15, undef, [15, 12], 1], 
    	[9, undef, [7, 9]], [9, undef, [9, 7]],
        [undef, undef, [2,2,3,3], 1], [undef, undef, [2,2,3,4,3]], [undef, undef, [2,2,4,3]], [undef, undef, [2,3,3,4], 1], [undef, undef, [4,3,3,2], 0, 1], 
        [2+2/3, undef, [2,2,3,3]], [2.75, undef, [2,2,3,3,3]], [3.5, undef, [2,3,4,3]], [2.75, undef, [2,3,3,3], 1], [3+1/3, undef, [4,3,3,2]], 
        [3, undef, [2,3,3,3], 0, 1], [3, undef, [1,3,3,3], 0, 1], [3, undef, [2,3,3,3]], [3, undef, [1,3,3,3]], [3, undef, [1,3,3,3,1]], 
        [3, undef, [3,3,3,2], 1], [3, undef, [3,3,3,1], 1], [3, undef, [3,3,3,2]], [3, undef, [3,3,3,1]], 
        [2+1/3, undef, [3,2,2,2], 1], [2+1/3, undef, [3,2,2,2]], [2+1/3, undef, [2,2,2,3], 0, 1], [2+1/3, undef, [2,2,2,3]], 
        [3-1/3, undef, [2,3,3,2], 1], [3-1/3, undef, [2,3,3,2], 0, 1], [3-1/3, undef, [2,3,3,2], 1, 1], 
        [undef, undef, [3,3,2,2,3]],    [undef, undef, [3,2,2,3,3]],       [undef, undef, [3,3,2,2,2]],    [undef, undef, [2,2,2,3,3]], 
        [undef, undef, [3,3,4,4,3], 1], [undef, undef, [3,4,4,3,3], 0, 1], [undef, undef, [3,3,4,4,4], 1], [undef, undef, [4,4,4,3,3], 0, 1], 

        [2+1/3, undef, [2,3,2,2,3]],    [2+2/3, undef, [2,3,3,2,2]],       [2.75, undef, [3,3,3,2,2]],    [2.75, undef, [2,2,3,3,3]], 
        [3+2/3, undef, [3,3,4,4,3]],    [3+2/3, undef, [3,4,4,3,3]],       [3.75, undef, [3,3,4,4,4]],    [3.75, undef, [4,4,4,3,3]], 

        [5.5, undef, [5,6,5,6,5,6]],       [5.5, undef, [5,6,5,6,5,5]],    [5.5, undef, [5,5,6,5,6,5,6]], 
        [5.5, undef, [5,6,5,6,5,6], 1, 1], [5.5, undef, [5,6,5,6,5,5], 1], [5.5, undef, [5,5,6,5,6,5,6], 0, 1], 
        [undef, undef, [4,2,3,3]], [undef, undef, [2,2,3,4]], [undef, undef, [2,3,4,3], 1], [undef, undef, [4,3,4,2], 0, 1], 
        [undef, undef, [2,2,2,3,3]], [undef, undef, [2,2,3,3,3], 1], 

        [2+2/3, undef, [3,2,3,3]], [2+2/3, undef, [2,2,3,3]], [3.5, undef, [2,3,4,3]],     [3.5, undef, [4,3,4,2]], 
        [2+1/3, undef, [2,2,2,3,2]], [2.75, undef, [2,2,3,3,3]], 

        [2.4, undef, [3,2,2,3,2,3]], [undef, undef, [3,2,2,2,2,3,2,2,3]], [2 + 2/9, undef, [3,2,2,2,2,3,2,2,2,3]],
        [2 + 2/9, undef, [2,2,3,2,2,2,2,3,2,2,2,3,2]], [2 + 2/9, undef, [1,2,3,2,2,2,2,3,2,2,2,3,1]],
        [2.2, undef, [3,2,2,2,2,3,2,2,2]], 
        [5/3,  0.25, [1,2]], 
        [5/3, -0.05, [2,1]], 
        [2, 0.5, [1,2,2]], 
        [2, 0.5, [1,2,2,2,2,1]], 
        ;
}

sub stroke_2_line ($) {
  my($s, %seen, %dup) = shift;		# $s->[$i][0] is dir∈(0..7);
  my @d = map $_->[0], @$s;
  $seen{$_}++ for @d;
  2 >= (my @D = keys %seen) or return;
  if (@D == 1) {
    my($dx,$dy) = ($dx[$d[0]], $dy[$d[0]]);
    return [0,0, @d * $dx, @d * $dy, 0,0, 2*$D[0]];	# move-beg, vector, move-end, 2*dir
  }					# now @D == 2;
  $d[$_-1] == $d[$_] and $dup{$d[$_]}++ for 1..$#d;
# warn "@d --> dup ", join(' ', %dup), "\n";
  1 >= (my @DD = keys %dup) or return;
  my $dup = @DD ? $DD[0] : $d[$d[0] % 2];	# if @DD is empty, two dirs alternate; assume that the odd one is a separator; ==>
  my $dir = $D[0] + $D[1];
  my $sep = $dir - $dup;		# directional-independence — there is no guarantie that y=x-y preserves the best fit
  $dir += 8 if $dir == 7 and !($D[0] * $D[1]);
  # Do not “optimize” horizontal/vertical lines of len≥2 with one diag stroke at the end:
  return if $sep % 2 and $seen{$sep} == 1 and @d > 2 and grep $_ == $sep, @d[0,-1];
  my($i,$col,@col) = (0,1);
  while ($i < @d) {
    if ($d[$i] == $sep) {
      push @col, $col;
      $col = 1;
    } else {
      $col++;
    }
    $i++;
  }
  push @col, $col;
# warn "Scan of col: @col\n";
  my($slope, $offset) = encodes_line \@col or return;
  $offset -= 0.5 - $slope*0.5;			# Recalc offset to be w.r.t. the center of the first square
#### warn "slope=$slope; offset=$offset of @col; dup=$dup, sep=$sep [in = @d]\n";
  # “Reflection” below moves squares on diagonal to a horizontal sequence of squares; it preserves the square centered at (½,½)
  # Offsets w.r.t. this center are inverted
  ($dup, $sep, $slope, $offset) = ($sep, $dup, 1-$slope, -$offset) if $dup % 2; # goes more diagonally than horizontally/vertically
  # Now $dup is horizontal/vertical, and $sep is diagonal
  my($dx,$dy)   = ($dx[$dup],     $dy[$dup]);
  my($dx1,$dy1) = ($dx[$sep]-$dx, $dy[$sep]-$dy);	# orthogonal direction
  my $C = grep $_ == $sep, @d;				# Naive move in orthogonal direction
  my $lineC = @d * $slope + $offset;
  my $end_off = $lineC - $C;
  return [$dx1 * $offset, $dy1 * $offset, @d * $dx + $lineC * $dx1, @d * $dy + $lineC * $dy1, $dx1 * $end_off, $dy1 * $end_off, $dir];
}

sub stroke_subdivide ($) {	# We suppose it is known that this is not suitable for calculated lines, but rotates at most by ±1
  my ($edges, $last_snake, $last_r, @runs, @turns, @t_pos, @parts) = (shift, -1, 0, 1);	# @runs starts with the first REAL elt, 1
  $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
  for my $i (1..$#$edges) {
    next unless my $r = ($edges->[$i][0] - $edges->[$i-1][0])%8;
    $last_r ||= $r;
    push @turns, $r;		# 135° corners:	direction
    push @t_pos, $i;		# same:		ordinal of edge which is after the turn
    next if $r == $last_r;
    push @runs, $#turns;	# Which corner starts a new convex sequence of corners (including sequences of length=1 in snakes!)
    $last_r = $r;
  }	# a run is in turns, from $runs[$j] inclusive to $runs[$j+1] exclusive; has the same direction of turns
  push(@turns, 0); push(@t_pos, scalar @$edges);	# These are not REAL, and not included in @runs due to end-exclusion
  push @runs, $#turns;	# runs are turns between consecutive elts of @runs, begin-inclusive, end-exclusive (REAL elts only!)
  for my $j (1..$#runs) {
    if ($runs[$j] - $runs[$j-1] > 1) {	# a convex run: ≥2 corners, so cannot be a part of a snake
      $parts[-1][1][1] = $runs[$j-2] if $parts[-1] and $parts[-1][1];	# previous part is a snake (unterminated yet); terminate
      push @parts, [[$runs[$j-1], $runs[$j]-1]];	# parts are both-ends-inclusive
    } elsif (++$last_snake != $j) {		# start of a new snake (ends are dealed with on hext non-snake)
      push @parts, [undef, [$runs[$j-1]]];	# Which turn starts a new snake; the termination slot is bogus so far
      $last_snake = $j;
    }
  }
#  $snakes[-1][1] = @turns if $finish_snake;
  $parts[-1][1][1] = $#turns - 1 if $parts[-1][1];	# parts are both-ends-inclusive; include the last REAL element
#my @T = map { $t_pos[$_] . ($turns[$_] > 6 ? '-' : ($turns[$_] ? '+' : '')) } 0..$#turns;
#warn "turns=(@T), runs=(@runs), snake-parts ", (map 0+!!$_->[1], @parts), 
#       ", lines (turn#) ", (map {"$_->[1][0]..$_->[1][1], "} grep $_->[1], @parts),
#       ", lines (edge) ",  (map {($t_pos[$_->[1][0]]-1)."..$t_pos[$_->[1][1]], "} grep $_->[1], @parts), "\n";
  my @parts_edges;
#  @parts_edges = map [$t_pos[$_->[-1][0]] - 1, $t_pos[$_->[-1][0]]], @parts;	# start: edge before corner
  for my $part (@parts) {
    if ($part->[0]) {
      my $mid = $part->[0][1] != $#t_pos;		# Avoid accessing out-of-bound value (will be overwritten later anyway)
      push @parts_edges, [ $t_pos[$part->[0][0] - 1], $t_pos[$part->[0][1] + $mid] - $mid ];	# both sides inclusive
    } else {
      push @parts_edges, [ $t_pos[$part->[1][0]] - 1, $t_pos[$part->[1][1]] ];	# overlaps by 1 the neighbors
    }
  }
#  for my $i (0..$#parts) {
#    my $part = $parts[$i];
#    if ($part->[0]) {
#      my $b = $t_pos[$part->[0][0] - !!$i];
#      my $mid = $i != $#parts;
#      my $e = $t_pos[$part->[0][1] + $mid] - $mid;
#      push @parts_edges, [ $b, $e ];	# both sides inclusive
#    } else {
#      push @parts_edges, [ $t_pos[$part->[1][0]] - 1, $t_pos[$part->[1][1]] ];	# overlaps by 1 the neighbors
#    }
#  }
#warn "predivide 0..$#$edges: ", join(' ', map $_->[0], @$edges), " => ", (map "$_->[0]...$_->[1]" . (!!$_->[2] && ':L') . " ", @parts_edges), "\n";
  # The logic above breaks for first/last segments:
  $parts_edges[0][0]  = 0	 ;#if $parts[0][1];  # incorporate the full preceding segment into the snake,
  $parts_edges[-1][1] = $#$edges ;#if $parts[-1][1]; # and not just one edge of it
#warn "predivide 0..$#$edges: ", join(' ', map $_->[0], @$edges), " => ", (map "$parts_edges[$_]->[0]...$parts_edges[$_]->[1]" . (!!$parts[$_]->[1] && ':S') . " ", 0..$#parts_edges), "\n";
#warn "parts_edges=$#parts_edges, edges=$#$edges; @ -1:  b: $parts_edges[-1][0];   e: $parts_edges[-1][1]\n";
#warn "parts_edges=$#parts_edges, edges=$#$edges; @  2:  b: $parts_edges[2][0];   e: $parts_edges[2][1]\n" if $#parts_edges==2;
  my($J, $donext, @out) = (0, 1);	# up to $J-1 are written to @out
  #
  # We split the sequence of directions into snakes and convex parts.  (May be overlapping where they join.)
  # 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);
  # if cannot, we join the "unrecognized" parts together.
  #
  for my $j (0..$#parts) {			# Linearize (sub)snakes; remove overlap between snakes and ???
    next unless $donext++;			# May skip the convex part after an unrecognized snake
    my $part = $parts[$j];
#warn "    fixing... j=$j"; # part=<@$part>";
#warn("  $J <-- $j"), 
    $out[$J++] = $parts_edges[$j], next unless my $snake = $part->[1];	# convex: at start, or after convex/recognized-line
    my($b,$e) = @{ $parts_edges[$j] };
    my @S = @$edges[$b..$e];			# Try first extension by 1 on both sides
    my($line) = stroke_2_line \@S;
#warn "to line: #$j (end: parts=$#parts, parts_edges=$#parts_edges, edges=$#$edges;  b: $b;   e: $e;   OK: ", 0+!!$line,"\n";
##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;
    if ($line) {
#warn "  longline: j=$j J=$J\n";
       $parts_edges[$j][2] = $line;
       $J and $parts_edges[$J-1][1]--;	# A convex run contains at least 3 edges, so we will not annihilate it completely
       $j == $#parts or $parts_edges[$j+1][0]++;
       $out[$J++] = $parts_edges[$j]; 
       next
    }		# Now last resort: try to shorten to min possible; since stroke_2_line() failed, we know that @S is long enough…
    my($b1, $e1) = ($b + 1, $e - 1);
    $b1 = $b unless $b;
    $e1 = $e if $e == $#$edges;		# Does not make sense to shorten the snake at start/end of the stroke
    @out = @parts_edges, next if $b == $b1 and $e == $e1;		# stroke is a wholesale snake
    @S = @$edges[$b1..$e1];			# Now try shortened by 1 on both sides
    unless (($line) = stroke_2_line \@S) { 
#warn "  no-short-line: j=$j J=$J  end->", ($j || 1 ? '' : '(omitted)' ), $parts_edges[$j + ($j!=$#parts)][1], "\n";
      $out[$J++] = $parts_edges[$j] unless $J;	# create a previous part, if none
      $donext = 0; 
      $out[$J-1][1] = $parts_edges[$j + ($j!=$#parts)][1]; # Extend the preceding part
      next;
    }
#warn "shortened: #$j;  b: $b --> $b1;   e: $e -> $e1\n";
#warn "  short-line: j=$j J=$J\n";
    if ($b == $b1) {
      $J and $parts_edges[$J-1][1]--;
    } else {
      $parts_edges[$j][0]++
    }
    if ($e == $e1) {
      $j == $#parts or $parts_edges[$j+1][0]++;
    } else {
      $parts_edges[$j][1]--
    }
    $parts_edges[$j][2] = $line;
    $out[$J++] = $parts_edges[$j]; 
  }
  @out;
}

sub stroke_2_strokes ($$) {
  # The 1st version should not be applied to smooth closed loops: we assume that 0 is a corner
  my($s, $calc_hash) = (shift, shift);			# $s->[$i][0] is dir (0..7);
  my @d = map $_->[0], @$s;
#warn "stroke: @d\n";						# start is before the segment with dir = $dir[start]
  my($prev_corner, @corners, @calc) = (0, [0]);	# corners, at index: 0=start; optional: 1=calc_line, 2=start_moved, 3=end_moved
  for my $i (0..$#d) {	# Between $corners[-1] (inclusive) and $prev_corner (exclusive) there is a region without calculated segments
    if ($i == $#d or abs(($d[$i+1] - $d[$i])%8 - 4) <= 2) {		# found a corner at $i+1  (max+1 is AT_END)
      my(@SS, @parts) = @$s[$prev_corner..$i];
      if (@SS <= 1) {
        @parts = [$prev_corner, $i];
      } elsif (my($Line) = stroke_2_line \@SS) {
        @parts = [$prev_corner, $i, $Line];
      } else { 
        @parts = stroke_subdivide(\@SS);
#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";
	$_->[0] += $prev_corner, $_->[1] += $prev_corner for @parts;
#        @parts = [$prev_corner, $i];
##        $prev_corner = $i+1; 
##        next;	# Not found
      }
      for my $part (@parts) {
#warn "  prev=$prev_corner line=<@{$part->[2] || []}> part[1]=$part->[1] last=$corners[-1][0]";
        $prev_corner = $part->[1] + 1, next unless my $line = $part->[2];
        # Now we found a calculated segment (at least 2 edges)
        push @corners, [$prev_corner] if $prev_corner != $corners[-1][0];		# create a new unrecognized chunk
        @{$corners[-1]}[1,2,3] = ($line, !($line->[0] == 0 and $line->[1] == 0), !($line->[4] == 0 and $line->[5] == 0));
        push @corners, [$prev_corner = $part->[1]+1];	# start new segment
        next if $part->[0] == $part->[1];
        for my $S (@$s[$part->[0]..$part->[1]]) {
          $calc_hash->{$S->[1],$S->[2]}{$S->[0]}++;	# x,y,d
          my $d1 = ($S->[0] + 4)%8;
          $calc_hash->{$S->[3],$S->[4]}{$d1}++;		# x1,y1,d1
        }
      }
    }
  }
  push @corners, [$#d+1] unless $corners[-1][0] == $#d+1;		# end last segment
  # Fixing involves inserting new segments (making new corners); to avoid changing indices, do it back to front:
  for my $i (reverse(0..$#corners-1)) {		# Try to fix misplaced joints (currently, only on unrecognized/calculated joins
#warn "doing segment=$i; @{$corners[$i]}\n";
    unless ($corners[$i][1]) {		# no calc_line => from $i to $i+1 consists of 1-edge segments
      my $move_start = ($i and $corners[$i-1][3]) && [@{$corners[$i-1][1]}[4,5]];
      my $move_end   =         $corners[$i+1][2]  && [@{$corners[$i+1][1]}[0,1]];
#warn "Fixing segment=$i: start=$move_start end=$move_end\n";
      next unless $move_start or $move_end;
#warn "Fixing segment=$i: start=$move_start end=$move_end\n";
      my $len = $corners[$i+1][0] - $corners[$i][0];
      my @do = (($move_start ? 0 : ()), ($move_end ? $len-1 : ()));
      $#do = 0 if @do == 2 and not $do[1];	# len = 1
      my $kill = (@do == $len);
      for my $seg (reverse @do) {		# reverse: as above
        my $start = ($move_start and not $seg) ? $move_start : [0,0];
        my $end = ($move_end and $seg == $len-1) ? $move_end : [0,0];        
        my $dir = $d[$corners[$i][0] + $seg];				# direction before correction (1 after 2*$dir means approx)
    warn "i=$i, seg=$seg d=<@d> #d=$#d corners[$i]=<@{$corners[$i]}> #corners=$#corners\n\tcorners=<",
		join('> <', map "@$_", @corners), '>' unless defined $dir;
        my $line = [$start->[0], $start->[1], $end->[0] + $dx[$dir], $end->[1] + $dy[$dir], $end->[0], $end->[1], 2*$dir, 1];
        if ($seg) {
          $corners[$i+1][2] = 0;		# No longer have a mismatch
          splice @corners, $i+1, 0, [$corners[$i+1][0] - 1, $line]; 		# with no mismatches
        } else {
          my $pos = $corners[$i][0];
          $corners[$i][0]++, splice @corners, $i, 0, [] unless $kill;
          @{$corners[$i]} = ($pos, $line);
          $corners[$i-1][3] = 0 if $i;			# No longer have a mismatch
          $corners[$i+1][2] = 0 if $corners[$i+1];	# No longer have a mismatch
        }
      }
    }
  }
  my @breaks = 0;			# Meaning: $corner[$break] starts a new sub-stroke
  for my $i (0..$#corners-2) {		# check for mismatch at end
    push @breaks, $i+1 if $corners[$i][3] or $corners[$i+1][2];
  }
  \@corners, \@breaks, \@calc;
}

# The following logic looks very productive into decomposition into
# strokes (not implemented yet; does not include recognition of "extra"
# vert/hor/diag strokes, and forced recognition of long "straight strokes"):

# A "star" is a pixel where several strokes ("curves", "rays") join.
# There is one ray per a neighbor of a star.  The logic to recognize a pixel
# as a star: we need to find out where each ray continues on the "second step".

# A) Every ray (essentially: every neighbor pixel) is classified into one
#    of a dozen (or so) types (depending on the angular neighborhood).

# B) If a pixel has a ray of unknown type, the pixel is not a star.
# B-dep) Each ray may have one (or more) "dependent" pixels, which must be
#        (at least "partial") stars for the ray to be recognized.  So if all
#        of dependent pixels are rejected on the step "B", one rejects the ray.
#        Hence the pixel the ray originates from is also not a star.
# B0) What remains are ("isotropic") star-candidates.
# B-recursive: should we do B-dep recursively???

# C) More detailed examination of rays: one wants to find one to which
#    pixel at distance 2 the ray goes (or whether it is a length=1 spur).
#    So we need to find which neighbors of the first pixel of the ray
#    "come from OTHER rays of star-candidates nearby".  (Also: ignore the
#    originating pixel of the ray.  If at most 1 neighbor remains, it is called
#    "the continuation", and the ray is "a confirmed ray-candidate".
 
#      Aside: more on dependencies: it is a combination (by ORing) of several
#                                   neighbors, each of them having several
#				    required directions of "good" rays.

# C-dep) Now re-examine dependencies: check that at least one of dependent
#        pixels has all its required rays "confirmed" on step "C".
# C-recursive) One DOES NOT run dependencies recursively!  If dependent rays
#        "look nice", but one of the "secondary dependent" rays does not,
#	 we do not consider it as a reason to "ignore a ray".

#    Looks like: by the same logic, we should not reject pixels early, on (B).
#    So better: the final answer is "a star, but only in the following sectors"

# Results: Pixels may be marked as
# (0) stars; (1) "the first pixel on a ray"; (2) "the second pixel of a ray";
# (3) (for sharp angles): same with 3rd; (4) "spurs of exactly one star".
# How these may be combined?

# 0x1200 - starts bold; ethiopic.  Do we want to recognize it???

# Also: recognize solid angles: all rays in a certain range are neighbors.
# Join all such neighboring regions together into one blob of paint???
# What to do with pixels on boundary next to incoming angle ("?"s below)???

####
##?#
#?
##

# Returned ray is:
# (*) a tail/notch/serif etc, and multiplicity is checked
# (*) or curve of known curvature, and need to check multiplicity
#		(take into account only the continuation star???)
# (*) or a dependency - then we check _its_ rays instead of ours.
# TRUE???  For sharp, also need to check multiplicity.
#	Looks like for (multiple) dependency, do not need to check multiplicity...

# If a doubleray fails multiplicity check, treat it as dependency on one of two
# pixels.

# Result would be: a pixel is a combination of following possibilities (every
# one wins if comes earlier:

# (*) a star;  (check mult≤2, angle ≥135°; if so, change to a curve).
# (*) on a curve next to a star; (store dir1,dir2 to two neighbors).
# (*) on a curve 2 steps from a star (on a sharp angle); do likewise.

#    In the last two cases, accumulate this info for all neighbor stars;
#	inspect for contradictions (then what???)

# (*) extra step (possible???): if all our neighbors are known to be curves
#     entering us (or missing us???), propagate us to a star (and possibly
#     propagate the star to a curve, as above).

# ==== One should shortcircuit multiplicity check - if succeeds early, mark as OK.

# Do we want to join nearby stars (dependencies - of each other, or only one of another)???

# Put a sharp-joint or round-joint on ^-nodes depending on whether two arcs
# are both straight.

sub sfd_start ($$) {
  my ($height, $descent) = (shift, shift);
  my $ascent = $height - $descent;
  (my $Mono = my $mono = !!$opt{mono} && ' Mono') =~ s/^\s+//;	# Even with --nolayouts, make correct name
  (my $comments = <<EOC) =~ s/\s*\n\s*/ /g;
Created from the version $opt{oversion} of the GNU Unifont
with Ilya Zakharevich's Perl and FontForge scripts
from http://ilyaz.org/software/fonts/ .
See http://www.lgm.cl/trabajos/unifont/index.en.html for
information on the original, Luis Gonzalez Miranda' scripts.
See http://czyborra.com/unifont
and http://unifoundry.com/unifont.html
for information on GNU Unifont.  Copywrite of GNU Unifont is applicable.
EOC
    $comments =~ s/\s+$//g;
    my $underline = int(-100/64*$px_size);
    my $underl_wd = int(  40/64*$px_size);
    # Non-0 gap is visible on Window's console (characters which should merge vertically do not)
    my $gap	  = 0;			# int(  72/64*$px_size);
    # Autogenerated: bits 17-19 set:		600f01ff.ffff0000 (cp932 JIS, cp936 Simpl.Chinese, cp949 Korean Wansung)
    my $os2CP = ($opt{noCJKcp} ? "OS2CodePages: 600101ff.ffff0000\n" : '');	# XXXX With --noCJKcp, SHOWS (!) CJK icon in the font viewer???
    ### With UnicodeBMP, needed 65536 chars
    return << "END" if $opt{nolayouts};	# The old version; does not allow "Lookup"s. (Apparently, spaces end FontName
SplineFontDB: 1.0
FontName: UnifontSmooth${Mono}-Medium
FullName: Unifont Smooth$mono
FamilyName: Unifont Smooth$mono
Weight: Medium
UComments: "$comments"
Version: $opt{oversion}-$rcs
ItalicAngle: 0
UnderlinePosition: $underline
UnderlineWidth: $underl_wd
Ascent: $ascent
Descent: $descent
FSType: 0
PfmFamily: 33
TTFWeight: 500
TTFWidth: 5
LineGap: $gap
VLineGap: 0
Panose: 2 0 6 9 0 0 0 0 0 0
OS2WinAscent: 0
OS2WinAOffset: 1
OS2WinDescent: 0
OS2WinDOffset: 1
HheadAscent: 0
HheadAOffset: 1
HheadDescent: 0
HheadDOffset: 1
${os2CP}ScriptLang: 1
 1 latn 1 dflt 
Encoding: UnicodeFull
UnicodeInterp: none
DisplaySize: -24
AntiAlias: 1
FitToEm: 1
WinInfo: 0 50 22
TeXData: 1 0 0 346030 173015 115343 0 1048576 115343 783286 444596 497025 792723 393216 433062 380633 303038 157286 324010 404750 52429 2506097 1059062 262144
BeginChars: 1114112 0
END
    my $lookup = qq<Lookup: 257 0 0 "'mark' Zero-Width Marks in Basic"  {"'mark' Zero-Width Marks lookup"  } ['mark' ('DFLT' <> 'cyrl' <'SRB ' 'dflt' > 'grek' <'dflt' > 'latn' <'ISM ' 'KSM ' 'LSM ' 'MOL ' 'NSM ' 'ROM ' 'SKS ' 'SSM ' 'dflt' > ) ]\n>;
    $lookup = '' unless $opt{mono} and not $opt{nolayouts};
    <<EOD;
SplineFontDB: 3.0
FontName: unifont$Mono
FullName: GNU Unifont$mono
FamilyName: unifont$mono
Weight: Medium
UComments: "$comments"
Version: 1.0-$rcs
ItalicAngle: 0
UnderlinePosition: $underline
UnderlineWidth: $underl_wd
Ascent: $ascent
Descent: $descent
LayerCount: 2
Layer: 0 1 "Back"  1
Layer: 1 1 "Fore"  0
FSType: 0
OS2Version: 0
OS2_WeightWidthSlopeOnly: 0
OS2_UseTypoMetrics: 0
PfmFamily: 33
TTFWeight: 500
TTFWidth: 5
LineGap: $gap
VLineGap: 0
Panose: 2 0 6 9 0 0 0 0 0 0
OS2TypoAscent: 0
OS2TypoAOffset: 1
OS2TypoDescent: 0
OS2TypoDOffset: 1
OS2TypoLinegap: 0
OS2WinAscent: 0
OS2WinAOffset: 1
OS2WinDescent: 0
OS2WinDOffset: 1
HheadAscent: 0
HheadAOffset: 1
HheadDescent: 0
HheadDOffset: 1
OS2Vendor: 'PfEd'
Encoding: UnicodeBmp
UnicodeInterp: none
NameList: Adobe Glyph List
DisplaySize: -24
AntiAlias: 1
FitToEm: 1
$lookup# 65536, but a few will be added at end (0000, 0001, .notdef etc)	- with 65520 crashes FF
BeginChars: 65536 0
EOD
}

