{$B-}

unit pathunit;

interface

const	pathlenunit = 50;
	maxpathlen = 10 * pathlenunit;
		(* 92 recommended for normal operation 
		   170 for longer paths with many characters *)

type t1cmd = 
	(callothersubr, callsubr, closepath, t1div, dotsection, endchar,
	 hlineto, hmoveto, hsbw, hstem, hstem3, hvcurveto, pop, return, 
	 rlineto, rmoveto, rrcurveto, sbw, seac, setcurrentpoint, 
	 vhcurveto, vlineto, vmoveto, vstem, vstem3);

const cmdcount = 25;
      cmdtab: array [1..cmdcount] of record
			cmd: t1cmd;
			str: string;
			case code1: byte of
			 12: (code2: byte)
	end = (
	(cmd: callothersubr; str: 'callothersubr'; code1: 12; code2: 16),
	(cmd: callsubr; str: 'callsubr'; code1: 10),
	(cmd: closepath; str: 'closepath'; code1: 9),
	(cmd: t1div; str: 'div'; code1: 12; code2: 12),
	(cmd: dotsection; str: 'dotsection'; code1: 12; code2: 0),
	(cmd: endchar; str: 'endchar'; code1: 14),
	(cmd: hlineto; str: 'hlineto'; code1: 6),
	(cmd: hmoveto; str: 'hmoveto'; code1: 22),
	(cmd: hsbw; str: 'hsbw'; code1: 13),
	(cmd: hstem; str: 'hstem'; code1: 1),
	(cmd: hstem3; str: 'hstem3'; code1: 12; code2: 2),
	(cmd: hvcurveto; str: 'hvcurveto'; code1: 31),
	(cmd: pop; str: 'pop'; code1: 12; code2: 17),
	(cmd: return; str: 'return'; code1: 11),
	(cmd: rlineto; str: 'rlineto'; code1: 5),
	(cmd: rmoveto; str: 'rmoveto'; code1: 21),
	(cmd: rrcurveto; str: 'rrcurveto'; code1: 8),
	(cmd: sbw; str: 'sbw'; code1: 12; code2: 7),
	(cmd: seac; str: 'seac'; code1: 12; code2: 6),
	(cmd: setcurrentpoint; str: 'setcurrentpoint'; code1: 12; code2: 33),
	(cmd: vhcurveto; str: 'vhcurveto'; code1: 30),
	(cmd: vlineto; str: 'vlineto'; code1: 7),
	(cmd: vmoveto; str: 'vmoveto'; code1: 4),
	(cmd: vstem; str: 'vstem'; code1: 3),
	(cmd: vstem3; str: 'vstem3'; code1: 12; code2: 1));

var cmdstring: array [t1cmd] of string;
var expand: Boolean;

type	commandpoi = ^ command;
	SubrTpointer = ^ SubrT;
	SubPathpointer = ^ SubPath;
	CharStrpointer = ^ CharStrT;
	command = record
			Subr: SubPathpointer;
			case Boolean of
			true (* if Subr = nil: *):
				(cmd: t1cmd;
				 paramnum: integer;
				 param: array [1..6] of integer;
				);
			false (* if Subr <> nil: *):
				(closed: Boolean;
				 offsetx, offsety: integer;
				)
		end;

	commandsarray0 = array [1..maxpathlen] of commandpoi;
	commandsarray1 = array [1..1 * pathlenunit] of commandpoi;
	commandsarray2 = array [1..2 * pathlenunit] of commandpoi;
	commandsarray3 = array [1..3 * pathlenunit] of commandpoi;
	commandsarray4 = array [1..4 * pathlenunit] of commandpoi;
	commandsarray5 = array [1..5 * pathlenunit] of commandpoi;
	commandsarray6 = array [1..6 * pathlenunit] of commandpoi;
	commandsarray7 = array [1..7 * pathlenunit] of commandpoi;
	commandsarray8 = array [1..8 * pathlenunit] of commandpoi;
	commandsarray9 = array [1..9 * pathlenunit] of commandpoi;

	varcommandspoi = record case byte of
			0: (cp0: ^commandsarray0);
			1: (cp1: ^commandsarray1);
			2: (cp2: ^commandsarray2);
			3: (cp3: ^commandsarray3);
			4: (cp4: ^commandsarray4);
			5: (cp5: ^commandsarray5);
			6: (cp6: ^commandsarray6);
			7: (cp7: ^commandsarray7);
			8: (cp8: ^commandsarray8);
			9: (cp9: ^commandsarray9);
			end;

	Path = object
		commands: varcommandspoi;
		pathlen: integer;
		mayuseothersubrs: Boolean;
		constructor ini;
		procedure appendtoken (var s: string);
		procedure clippath;
		procedure setdef (s: string); virtual;
		procedure splitpath;
		procedure cleanup;
		function usesothersubrs: Boolean;
	private
		procedure appendcommand (cmd: commandpoi);
		procedure delete (i: integer);
		function pathcmd (i: integer): t1cmd;
		function pathcomponent (i: integer): commandpoi;
		procedure getoffset (i: integer; var x, y: integer);
		procedure printpath (var out: text; checkzmove, printreturn: Boolean);
		function firstorlastcmd (i: integer; last: Boolean): t1cmd;
		procedure traceBBox (var x, y, llx, lly, urx, ury: integer; var errcmd: t1cmd);
	end;

	CharStrT = object (Path)
			name: string [30];
			function clearifempty: Boolean;
			procedure setname (n: string);
			procedure setdef (s: string); virtual;
			procedure print (var out: text);
			procedure copyhalf (var newc: CharStrT; newname: string);
			procedure downpath (var newc: CharStrT; newname: string);
			procedure copypath (var newc: CharStrT; newname: string);
			procedure compose (CS1, CS2: CharStrpointer; cc1, cc2, dx, dy: integer);
			procedure checkafm (checkBBox: Boolean);
		end;

	SubrT = object (Path)
			index: integer;
			procedure setindex (i: integer);
			procedure setdef (s: string); virtual;
			procedure print (var out: text);
		end;

	SubPath = object (SubrT)
			used: integer;
			closedunique, offsetunique, closed: Boolean;
			offsetx, offsety: integer;
			procedure checkunique (x, y: integer; cl: Boolean);
			procedure setdef (s: string); virtual;
			procedure printSubr (var out: text);
		end;

const	maxSubrs = 500;
var	Subrs: array [1..maxSubrs] of ^SubrT;
	Subrscount: integer;
	MoreSubrscount: integer;
	Subrstotal: integer;

procedure sortMoreSubrs;

procedure arrangeMoreSubrs (Subrscount: integer);

procedure printMoreSubrs (var out: text);

procedure printusedStat;

procedure swappaths (var c1, c2: CharStrT);

(************************************************************************)

implementation

uses afm, data;

function getSubr (i: integer): SubrTpointer;
var k: integer;
    result: SubrTpointer;
begin
	result := nil;
	if Subrs [i + 1]^.index = i then result := Subrs [i + 1]
	else for k := 1 to Subrscount do
		if Subrs [k]^.index = i then result := Subrs [k];
	getSubr := result
end;

var	NP, ND: string (* name of Subr / CharString definition command *);

type comparison = (less, equal, greater);

function pathcompare (var p1, p2: SubPath): comparison;
var i: integer;
    result: comparison;
	function cmdcompare (var c1, c2: command): comparison;
	var i: integer;
	    result: comparison;
	begin
		if c1.Subr <> nil then
			if c2.Subr = nil then cmdcompare := less
			else cmdcompare := pathcompare (c1.Subr^, c2.Subr^)
		else if c2.Subr <> nil then cmdcompare := greater
		else if c1.cmd < c2.cmd then cmdcompare := less
		else if c1.cmd > c2.cmd then cmdcompare := greater
		else if c1.paramnum < c2.paramnum then cmdcompare := less
		else if c1.paramnum > c2.paramnum then cmdcompare := greater
		else begin
		    i := 1;
		    result := equal;
		    while (result = equal) and (i <= c1.paramnum) do begin
			if c1.param [i] > c2.param [i] then result := greater
			else if c1.param [i] < c2.param [i] then result := less;
			i := i + 1;
		    end;
		    cmdcompare := result
		end
	end;
begin
	if p1.pathlen < p2.pathlen then pathcompare := less
	else if p1.pathlen > p2.pathlen then pathcompare := greater
	else begin
		i := 1;
		result := equal;
		while (result = equal) and (i <= p1.pathlen) do begin
		    result := cmdcompare (p1.commands.cp0^[i]^, p2.commands.cp0^[i]^);
		    i := i + 1;
		end;
		pathcompare := result
	end
end;

(*
procedure sortMoreSubrs;
	procedure quicksort (l, r: integer);
	var i, j: integer; x, w: SubPathpointer;
	begin	i := l; j := r;
		x := a [(l + r) div 2];
		repeat
			while pathcompare (a [i]^, x^) = less do i := i + 1;
			while pathcompare (x^, a [j]^) = less do j := j - 1;
			if i <= j then begin
				w := a [i]; a [i] := a [j]; a [j] := w;
				i := i + 1; j := j - 1
			end
		until i > j;
		if l < j then quicksort (l, j);
		if i < r then quicksort (i, r)
	end;
begin
	quicksort (1, MoreSubrscount)
end;
*)
procedure sortMoreSubrs;
begin
end;

type SubPathtree = ^SubPathrec;
     SubPathrec = record SB: SubPathpointer;
			 left, right: SubPathtree
		  end;

var	MoreSubrs: SubPathtree;


procedure disposeSubPath (SP: SubPathpointer);
var i: integer;
begin
	for i := 1 to SP^.pathlen do begin
		if SP^.commands.cp0^[i]^.Subr <> nil
		   then disposeSubPath (SP^.commands.cp0^[i]^.Subr);
		dispose (SP^.commands.cp0^[i])
	end;
	dispose (SP)
end;

function insertSubPath (var SBp: SubPathtree; SP: SubPathpointer): SubPathpointer;
begin
	if SBp = nil then begin
		new (SBp); SBp^.left := nil; SBp^.right := nil;
		SBp^.SB := SP; MoreSubrscount := MoreSubrscount + 1;
		insertSubPath := SP
	end else if pathcompare (SP^, SBp^.SB^) = equal then begin
		SBp^.SB^.used := SBp^.SB^.used + 1;
		disposeSubPath (SP);
		insertSubPath := SBp^.SB
	end else if pathcompare (SP^, SBp^.SB^) = less then
		insertSubPath := insertSubPath (SBp^.left, SP)
	else	insertSubPath := insertSubPath (SBp^.right, SP)
end;

type PathProc = procedure (sp: SubPathpointer; var out: text);
     (* The second parameter and its dummy setting at several places
	of one of the two application cases below is due to the
	silly restriction of Turbo-Pascal which doesn't allow nested
	procedures to be passed as parameters as standard Pascal does. *)
procedure applyMoreSubrs (PP: PathProc; var out: text);
    procedure apply (SBp: SubPathtree);
    begin
	if SBp <> nil then begin
		apply (SBp^.left);
		PP (SBp^.SB, out);
		apply (SBp^.right)
	end
    end;
begin
     apply (MoreSubrs)
end;

const maxused = 50;
var usedStat: array [0..maxused] of integer;
    zeromoves: integer;

procedure Pindex (sp: SubPathpointer; (*dummy*) var out: text); far;
begin	if (sp^.used > 1) and (sp^.index < 0) then begin
		sp^.setindex (Subrstotal);
		Subrstotal := Subrstotal + 1;
	end;
	if sp^.used > maxused then
		usedStat [0] := usedStat [0] + 1
	else	usedStat [sp^.used] := usedStat [sp^.used] + 1
end;
procedure arrangeMoreSubrs (Subrscount: integer);
begin	Subrstotal := Subrscount;
	applyMoreSubrs (Pindex, (*dummy*) output)
end;

procedure Pprint (sp: SubPathpointer; var out: text); far;
begin	if sp^.used > 1 then
		sp^.printSubr (out)
end;
procedure printMoreSubrs (var out: text);
begin	applyMoreSubrs (Pprint, out)
end;

procedure printusedStat;
var i: integer;
begin
	writeln ('extracted Subrs use statistics:');
	writeln ('# used  # Subrs');
	for i := 1 to maxused do
	    if usedStat [i] > 0 then
		writeln (i: 6, usedStat [i]: 6);
	if usedStat [0] > 0 then
		writeln (' more:', usedStat [0]: 6);
	if zeromoves > 0 then
		writeln (' inserted ', zeromoves, ' zero move commands for ATM''s sake');
end;

(************************************************************************)

function cmdstr (cmd: t1cmd): string;
begin	cmdstr := cmdstring [cmd] end;

function strcmd (var (* for some efficiency *) s: string): t1cmd;
var l, r, i: integer;
    cs: string;
begin	l := 1; r := cmdcount;
	repeat
		i := (l + r) div 2;
		cs := cmdtab [i].str;
		if s < cs then r := i - 1
		else if s > cs then l := i + 1
		else l := r + 1
	until l > r;
	if s = cs then strcmd := cmdtab [i].cmd
	else begin
		writeln ('% WARNING: unknown command "', s, '" detected');
		strcmd := endchar
	end
end;

procedure setNPND (var def: string; s: string);
begin	if def = '' then def := s
	else if s <> ''
		then if def <> s then writeln ('% WARNING: NP/ND command names differ')
end;


procedure printoffset (var out: text; x, y: integer);
begin
	if x <> 0 then
		if y <> 0 then writeln (out, '	', x, ' ', y, ' rmoveto')
		else writeln (out, '	', x, ' hmoveto')
	else if y <> 0 then writeln (out, '	', y, ' vmoveto')
	else begin (* atm (1.15) bug: also write zero move command *)
		zeromoves := zeromoves + 1;
		writeln (out, '	0 hmoveto')
	end
end;

(************************************************************************)

var commandactive: Boolean;

constructor Path.ini;
begin	pathlen := 0;
	commandactive := false;
	mayuseothersubrs := true;
	new (commands.cp0)
end;

function Path.usesothersubrs: Boolean;
var i: integer;
    othersfound: Boolean;
    icmd: t1cmd;
    sub: ^SubrT;
begin
    if not mayuseothersubrs then usesothersubrs := false
    else begin
	i := 1;
	othersfound := false;
	while (i <= pathlen) and not othersfound do begin
		icmd := pathcmd (i);
		if icmd = callothersubr then othersfound := true
		else if icmd = callsubr then with commands.cp0^[i]^ do begin
			if Subr = nil
			then if paramnum = 0
				then sub := nil (* something like pop callsubr *)
				else sub := getSubr (param [paramnum])
			else sub := Subr;
			if sub <> nil then othersfound := sub^.usesothersubrs;
		end;
		i := i + 1;
	end;
	if not othersfound then mayuseothersubrs := false;
	usesothersubrs := othersfound
    end
end;

procedure Path.appendtoken (var (* for some efficiency *) s: string);
var i, c: integer;
begin	if not commandactive then begin
	    if pathlen < maxpathlen then begin
		pathlen := pathlen + 1;
		new (commands.cp0^[pathlen]);
		commands.cp0^[pathlen]^.paramnum := 0;
		commands.cp0^[pathlen]^.Subr := nil;
	    end else begin
		writeln ('ERROR: max. path length exceeded');
		halt
	    end
	end;
	val (s, i, c);
	if c = 0 then with commands.cp0^[pathlen]^ do begin
		paramnum := paramnum + 1;
		param [paramnum] := i;
		commandactive := true
	end else begin
		commands.cp0^[pathlen]^.cmd := strcmd (s);
		commandactive := false
	end
end;

procedure Path.clippath;
(* shortens path storage according to current pathlen *)
var longpath: varcommandspoi;
    i: integer;
begin
	longpath := commands;
	case (pathlen - 1) div pathlenunit + 1 of
	  1:	new (commands.cp1);
	  2:	new (commands.cp2);
	  3:	new (commands.cp3);
	  4:	new (commands.cp4);
	  5:	new (commands.cp5);
	  6:	new (commands.cp6);
	  7:	new (commands.cp7);
	  8:	new (commands.cp8);
	  9:	new (commands.cp9);
	  else	new (commands.cp0);
	end;
	for i := 1 to pathlen do commands.cp0^[i] := longpath.cp0^[i];
	dispose (longpath.cp0)
end;

procedure Path.appendcommand (cmd: commandpoi);
begin	if pathlen < maxpathlen then begin
		pathlen := pathlen + 1;
		new (commands.cp0^[pathlen]);
		commands.cp0^[pathlen]^ := cmd ^;
	end else begin
		writeln ('ERROR: max. path length exceeded');
		halt
	end
end;

procedure Path.delete (i: integer);
var k: integer;
begin
     pathlen := pathlen - 1;
     dispose (commands.cp0^[i]);
     for k := i to pathlen do commands.cp0^[k] := commands.cp0^[k + 1]
end;

procedure Path.cleanup;
var i, k: integer;
    prevcmd: t1cmd;
    x, y: integer;
begin
	i := 1;
	while i <= pathlen do begin
	    if i = 1 then prevcmd := pop (* dummy, just not a moveto *)
	    else prevcmd := commands.cp0^[i - 1]^.cmd;
	    case commands.cp0^[i]^.cmd of
		closepath: if prevcmd in [closepath, endchar, hmoveto, hsbw, 
					  return, rmoveto, sbw, vmoveto]
				then delete (i)
				else i := i + 1;
		hmoveto, rmoveto, vmoveto:
			if prevcmd in [hmoveto, rmoveto, vmoveto] then begin
			   x := 0; y := 0;
			   for k := i - 1 to i do with commands.cp0^[k]^ do
				case cmd of
				hmoveto: x := x + param [1];
				vmoveto: y := y + param [1];
				rmoveto: begin	x := x + param [1];
						y := y + param [2]
					 end
				end;
			   delete (i);
			   i := i - 1;
			   with commands.cp0^[i]^ do begin
				cmd := rmoveto;
				param [1] := x;
				param [2] := y;
				paramnum := 2;
			   end
			end else with commands.cp0^[i]^ do begin
			(*	if (param [1] = 0) and
			(*		((cmd <> rmoveto) or (param [2] = 0))
			(*	then delete (i)
			(*   don't delete (i): atm (1.15) has a bug
			(*	else *)
				if (cmd = rmoveto) and (param [2] = 0)
				then begin
					cmd := hmoveto;
					paramnum := 1;
				end else
				if (cmd = rmoveto) and (param [1] = 0)
				then begin
					cmd := vmoveto;
					param [1] := param [2];
					paramnum := 1;
				end else 
				if (cmd = vmoveto) and (param [1] = 0)
				then begin
					cmd := hmoveto;
				end else 
					i := i + 1
			end;
		rlineto: with commands.cp0^[i]^ do begin
				if param [1] = 0 (* 0 y rlineto *)
				then begin
					cmd := vlineto;
					param [1] := param [2];
					paramnum := 1;
				end else if param [2] = 0 (* x 0 rlineto *)
				then begin
					cmd := hlineto;
					paramnum := 1;
				end;
				i := i + 1
			end;
		rrcurveto: with commands.cp0^[i]^ do begin
				if (param [1] = 0) and (param [6] = 0)
				then begin
					cmd := vhcurveto;
					for k := 1 to 4 do
						param [k] := param [k + 1];
					paramnum := 4;
				end
				else if (param [2] = 0) and (param [5] = 0)
				then begin
					cmd := hvcurveto;
					param [2] := param [3];
					param [3] := param [4];
					param [4] := param [6];
					paramnum := 4;
				end;
				i := i + 1
			end;
		else	i := i + 1
	    end
	end
end;

const hintcmds = [hstem, hstem3, vstem, vstem3, dotsection];
      movecmds = [hmoveto, rmoveto, vmoveto];
      drawcmds = [hlineto, hvcurveto, rlineto,
		  rrcurveto, vhcurveto, vlineto];
      curvecmds = [hvcurveto, rrcurveto, vhcurveto];

function Path.pathcmd (i: integer): t1cmd;
begin
	if (i > pathlen) or (i < 1) then pathcmd := pop
	else if commands.cp0^[i]^.Subr = nil then pathcmd := commands.cp0^[i]^.cmd
	else pathcmd := callsubr
end;

function Path.pathcomponent (i: integer): commandpoi;
begin
	if (i > pathlen) or (i < 1) then pathcomponent := nil
	else pathcomponent := commands.cp0^[i]
end;

procedure Path.getoffset (i: integer; var x, y: integer);
begin
	if pathcmd (i) in movecmds then with commands.cp0^[i]^ do begin
	   case cmd of
		hmoveto: begin x := param [1]; y := 0 end;
		vmoveto: begin y := param [1]; x := 0 end;
		rmoveto: begin x := param [1]; y := param [2] end;
	   end
	end else begin
		x := 0; y := 0
	end
end;

procedure Path.splitpath;
var i, pathstart, k: integer;
    newSubr: ^SubPath;
    x, y: integer;

begin
	i := 1;
	while i <= pathlen do begin
		while (i <= pathlen) and 
			not (pathcmd (i) in drawcmds + hintcmds) do
			i := i + 1;
		if i <= pathlen then begin
		    pathstart := i;
		(* heuristics which sorts of command groups to build: *)
		    if pathcmd (pathstart) in hintcmds then
			repeat i := i + 1 until not (pathcmd (i) in hintcmds)
		    else begin (* pathcmd (pathstart) in drawcmds *)
		      	repeat i := i + 1 until not (pathcmd (i) in drawcmds);
		     (* if pathcmd (i) = closepath then i := i + 1; *)
		    end;
		    if (i - pathstart > 1) or 
			(pathcmd (pathstart) in curvecmds) then begin
			new (newSubr, ini);
			newSubr^.used := 1;
			newSubr^.index := -1;
			newSubr^.pathlen := i - pathstart;
			newSubr^.clippath;
			for k := 1 to newSubr^.pathlen do
			    newSubr^.commands.cp0^[k] := commands.cp0^[pathstart+k-1];
			pathlen := pathlen - i + pathstart + 1;
			for k := pathstart + 1 to pathlen do begin
			    commands.cp0^[k] := commands.cp0^[k - pathstart - 1 + i];
			    (* don't dispose them - they are still needed in the Subr *)
			end;
			new (commands.cp0^[pathstart]);
			newSubr := insertSubPath (MoreSubrs, newSubr);
			(* to replace newSubr by unique one on second use *)
			commands.cp0^[pathstart]^.Subr := newSubr;
			getoffset (pathstart - 1, x, y);
			commands.cp0^[pathstart]^.offsetx := x;
			commands.cp0^[pathstart]^.offsety := y;
			if pathcmd (pathstart + 1) = closepath then begin
				newSubr^.checkunique (x, y, true);
				delete (pathstart + 1);
				commands.cp0^[pathstart]^.closed := true
			end else begin
				newSubr^.checkunique (x, y, false);
				commands.cp0^[pathstart]^.closed := false
			end;
			if pathcmd (pathstart - 1) in movecmds then begin
				pathstart := pathstart - 1;
				delete (pathstart)
			end;
			i := pathstart + 1;
		    end
		end
	end
end;

function Path.firstorlastcmd (i: integer; last: Boolean): t1cmd;
var cmd: t1cmd;
    sub: ^SubrT;
begin
	cmd := pathcmd (i);
	if cmd = callsubr then with commands.cp0^[i]^ do begin
		if Subr = nil
		then if paramnum <> 1
			then sub := nil (* something like pop callsubr 
					   or n m callsubr *)
			else sub := getSubr (param [paramnum])
		else sub := Subr;
		if sub = nil
		then firstorlastcmd := rmoveto (* pretend it's alright *)
		else if last
		     then if sub^.pathcmd (sub^.pathlen) = return
			then firstorlastcmd :=	(* last of original Subr *)
				sub^.firstorlastcmd (sub^.pathlen - 1, true)
			else firstorlastcmd :=	(* last of extracted Subr *)
				sub^.firstorlastcmd (sub^.pathlen, true)
		     else firstorlastcmd := sub^.firstorlastcmd (1, false)
	end else firstorlastcmd := cmd
end;

procedure Path.printpath (var out: text; checkzmove, printreturn: Boolean);
const nozeromovecmds = drawcmds + movecmds
			+ [setcurrentpoint, callothersubr, dotsection];
var i, k: integer;
begin	for i := 1 to pathlen - ord (not printreturn) do
	  with commands.cp0^[i]^ do begin
	    if Subr = nil then begin
		if expand and (cmd = callsubr)
		and (paramnum >= 1) and (param [paramnum] >= 4) then begin
		    if paramnum > 1 then begin
			write (out, '	');
			for k := 1 to paramnum - 1 do begin
				write (out, param [k], ' ');
			end;
		    end;
		    getSubr (param [paramnum])^.printpath (out, false, false)
		end else begin
		  if checkzmove then
		    if firstorlastcmd (i, false) in drawcmds
		       then if not (firstorlastcmd (i - 1, true) in nozeromovecmds)
			  then printoffset (out, 0, 0);
		  write (out, '	');
		  for k := 1 to paramnum do begin
			write (out, param [k], ' ');
		  end;
		  writeln (out, cmdstr (cmd));
		end
	    end else if (Subr^.used > 1) and not expand then begin
		if (not Subr^.offsetunique)
		   or ((Subr^.offsetx = 0) and (Subr^.offsety = 0)) then
		   if Subr^.pathcmd (1) in drawcmds then
			if (offsetx <> 0) or (offsety <> 0)
			   or not (firstorlastcmd (i - 1, true) in nozeromovecmds)
			then printoffset (out, offsetx, offsety);
		writeln (out, '	', Subr^.index, ' callsubr');
		if closed and not Subr^.closedunique then writeln (out, '	closepath');
	    end	else begin
		if Subr^.pathcmd (1) in drawcmds then
		   if (offsetx <> 0) or (offsety <> 0)
			or not (firstorlastcmd (i - 1, true) in nozeromovecmds)
		   then printoffset (out, offsetx, offsety);
		Subr^.printpath (out, false, true);
		if closed then writeln (out, '	closepath');
	    end
	end
end;

function CharStrT.clearifempty: Boolean;
(* clippath must not have been called! *)
var i: integer;
    isempty: Boolean;
begin	isempty := true;
	i := 1;
	while (i <= pathlen) and isempty do begin
		if pathcmd (i) in drawcmds + [callsubr, callothersubr, seac]
			then isempty := false;
		i := i + 1;
	end;
	if isempty then
	   if (name = '/.notdef') or (pos ('space', name) > 0) or (pos ('blank', name) > 0)
	   then isempty := false
	   else begin
		for i := 1 to pathlen do dispose (commands.cp0^[i]);
		dispose (commands.cp0);
	   end;
	clearifempty := isempty
end;

procedure CharStrT.setname (n: string);
begin	name := n end;

procedure CharStrT.print (var out: text);
begin
	write (out, name, ' {');
	printpath (out, true, true);
	writeln (out, '	} ', ND)
end;

procedure CharStrT.setdef (s: string);
begin	setNPND (ND, s); clippath end;

procedure SubrT.setindex (i: integer);
begin	index := i end;

procedure SubrT.print (var out: text);
begin
	write (out, 'dup ', index, ' {');
	printpath (out, false, true);
	writeln (out, '	} ', NP)
end;

procedure Path.setdef (s: string);
begin	clippath end;

procedure SubPath.checkunique (x, y: integer; cl: Boolean);
begin
	if used <= 1 then begin
		offsetx := x; offsety := y; offsetunique := true;
		closed := cl; closedunique := true
	end else begin
		if (x <> offsetx) or (y <> offsety) then offsetunique := false;
		if cl <> closed then closedunique := false
	end
end;

procedure SubPath.setdef (s: string);
begin	clippath end;

procedure SubPath.printSubr (var out: text);
begin
	write (out, 'dup ', index, ' {');
	if offsetunique then
	   if pathcmd (1) in drawcmds then
	      if (offsetx <> 0) or (offsety <> 0) then
		 printoffset (out, offsetx, offsety);
	printpath (out, false, true);
	if closedunique and closed then writeln (out, '	closepath');
	writeln (out, '	return');
	writeln (out, '	} ', NP)
end;

procedure SubrT.setdef (s: string);
begin	setNPND (NP, s); clippath end;

(************************************************************************)

function newafmcode (name: string): integer;
begin
	if stdenc then newafmcode := getSEcode (name)
	else newafmcode := -1
end;

procedure CharStrT.copyhalf (var newc: CharStrT; newname: string);
(* This is a heuristic attempt to generate single-part characters from 
   double-part ones, especially quote marks. It was designed for wfnboss 
   generated fonts which lack some single quote marks.
   The proceudre assumes that the path of the character to be split contains 
   exactly 2 move commands! *)
var i, m1, m2, x1, x2, y1, y2, widthsub: integer;
    C, WX, lx, ly, rx, ry: integer;
begin
	newc.setname (newname);
	i := 1;
	while (i <= pathlen) and not (pathcmd (i) in movecmds) do i := i + 1;
	m1 := i;
	repeat i := i + 1 until (i > pathlen) or (pathcmd (i) in movecmds);
	if i > pathlen then i := pathlen;
	m2 := i;
	getoffset (m1, x1, y1);
	getoffset (m2, x2, y2);
	for i := 1 to m2 - 1 do newc.appendcommand (commands.cp0^[i]);
	newc.appendcommand (commands.cp0^[pathlen]);
	newc.clippath;
	if x2 < 0 then begin
		(* first part of path was right part; shift it left *)
		with newc.commands.cp0^[m1]^ do begin
			cmd := rmoveto; (* might have been hmoveto or vmoveto *)
			paramnum := 2;
			param [1] := x1 + x2;
			param [2] := y1;
		end;
		widthsub := - x2;
	end else begin
		widthsub := x2;
	end;
	with newc.commands.cp0^[1]^ do (* reduce wx *)
		case cmd of
			hsbw: param [2] := param [2] - widthsub;
			sbw: param [3] := param [3] - widthsub;
		end;
	newc.cleanup;
	if afmopen then begin
		getafmchar (C, WX, name, lx, ly, rx, ry);
		putafmchar (newafmcode (newname), WX - widthsub, newc.name, lx, ly, rx - widthsub, ry);
	end
end;

procedure CharStrT.downpath (var newc: CharStrT; newname: string);
(* This is a heuristic attempt to generate base line quote marks from 
   upper quote marks *)
var i, m, x, y, heightsub: integer;
    C, WX, lx, ly, rx, ry: integer;
begin
	newc.setname (newname);
	i := 1;
	while (i <= pathlen) and not (pathcmd (i) in movecmds) do i := i + 1;
	m := i;
	getoffset (m, x, y);
	heightsub := y;	(* This might be more sophisticated *)
	for i := 1 to pathlen do newc.appendcommand (commands.cp0^[i]);
	newc.clippath;
	for i := 1 to m do
	    with newc.commands.cp0^[i]^ do
		case cmd of
			hmoveto: (* would only have to be handled
				    if we could derive non-zero down-shift 
				    values from this *);
			vmoveto: param [1] := param [1] - heightsub;
			rmoveto: param [2] := param [2] - heightsub;
			hstem: param [1] := param [1] - heightsub;
			hstem3: begin
				param [1] := param [1] - heightsub;
				param [3] := param [3] - heightsub;
				param [5] := param [5] - heightsub;
				end
			else ;
		end;
	newc.cleanup;
	if afmopen then begin
		getafmchar (C, WX, name, lx, ly, rx, ry);
		putafmchar (newafmcode (newname), WX, newc.name, lx, ly - heightsub, rx, ry - heightsub);
	end
end;

procedure CharStrT.copypath (var newc: CharStrT; newname: string);
var i, C, WX, lx, ly, rx, ry: integer;
begin
	newc.setname (newname);
	for i := 1 to pathlen do newc.appendcommand (commands.cp0^[i]);
	newc.clippath;
	newc.cleanup;
	if afmopen then begin
		getafmchar (C, WX, name, lx, ly, rx, ry);
		putafmchar (newafmcode (newname), WX, newc.name, lx, ly, rx, ry);
	end
end;

procedure CharStrT.compose (CS1, CS2: CharStrpointer; cc1, cc2, dx, dy: integer);
var seaccmd: commandpoi;
    WX, WX1, WX2, sbx1, sby1, sbx2, sby2: integer;
    C, llx1, lly1, urx1, ury1, llx2, lly2, urx2, ury2: integer;

  procedure getsb (CS: CharStrpointer; var sbx, sby: integer);
  begin
	sbx := CS^.pathcomponent (1)^.param [1];
	if CS^.pathcmd (1) = sbw
	then sby := CS^.pathcomponent (1)^.param [2]
	else sby := 0
  end;

  function getwidth (CS: CharStrpointer): integer;
  begin
	with CS^.pathcomponent (1)^ do case cmd of
		hsbw: getwidth := param [2];
		sbw: getwidth := param [3];
		else getwidth := 0
	end
  end;

begin
	appendcommand (CS1^.pathcomponent (1));
	WX1 := getwidth (CS1);
	WX := WX1;
	if length (CS2^.name) = 2 then begin
	   (* a letter appended to another *)
		WX2 := getwidth (CS2) + dx;
		if WX2 > WX1 then with pathcomponent (1)^ do begin
		   WX := WX2;
		   case cmd of
			hsbw: param [2] := WX;
			sbw: param [3] := WX;
		   end
		end
	end;
	new (seaccmd);
	getsb (CS1, sbx1, sby1);
	getsb (CS2, sbx2, sby2);
	with seaccmd^ do begin
		Subr := nil;
		cmd := seac;
		paramnum := 5;
		param [4] := cc1;
		param [5] := cc2;
		param [1] := sbx2;
		param [2] := dx + sbx2 - sbx1;
		param [3] := dy + sby2 - sby1;
	end;
	appendcommand (seaccmd);
	clippath;
	if afmopen then begin
		getafmchar (C, WX1, CS1^.name, llx1, lly1, urx1, ury1);
		getafmchar (C, WX2, CS2^.name, llx2, lly2, urx2, ury2);
		if llx2 + dx < llx1 then llx1 := llx2 + dx;
		if lly2 + dy < lly1 then lly1 := lly2 + dy;
		if urx2 + dx > urx1 then urx1 := urx2 + dx;
		if ury2 + dy > ury1 then ury1 := ury2 + dy;
		putafmchar (newafmcode (name), WX, name, llx1, lly1, urx1, ury1);
	end
end;

procedure swappaths (var c1, c2: CharStrT);
(* to fix wfnboss's exchange of bullet and periodcentered descriptions *)
var name: string; wx, wx1, wx2: integer;
begin
	name := c1.name;
	c1.name := c2.name;
	c2.name := name;
	if c1.commands.cp0^[1]^.cmd = hsbw then wx1 := 2 else wx1 := 3;
	if c2.commands.cp0^[1]^.cmd = hsbw then wx2 := 2 else wx2 := 3;
	wx := c1.commands.cp0^[1]^.param [wx1];
	c1.commands.cp0^[1]^.param [wx1] := c2.commands.cp0^[1]^.param [wx2];
	c2.commands.cp0^[1]^.param [wx2] := wx;
	(* the case that only one path has an sbw command
	   is not handled completely correct *)
	wx := c1.commands.cp0^[1]^.param [wx1 + 1];
	c1.commands.cp0^[1]^.param [wx1 + 1] := c2.commands.cp0^[1]^.param [wx2 + 1];
	c2.commands.cp0^[1]^.param [wx2 + 1] := wx;
end;

(************************************************************************)

procedure Path.traceBBox (var x, y, llx, lly, urx, ury: integer; var errcmd: t1cmd);
var i: integer;
    x1, y1: integer;
  procedure newpoint (newx, newy: integer);
  begin
	x := newx;
	y := newy;
	if x < llx then llx := x
	else if x > urx then urx := x;
	if y < lly then lly := y
	else if y > ury then ury := y;
  end;
begin
     i := 1;
     while (i <= pathlen) do with pathcomponent (i) ^ do begin
	if Subr <> nil then Subr^.traceBBox (x, y, llx, lly, urx, ury, errcmd)
	else
	 case cmd of
	  callothersubr: errcmd := callothersubr;
	  callsubr: getSubr (param [paramnum])^.traceBBox (x, y, llx, lly, urx, ury, errcmd);
	  t1div: errcmd := t1div;
	  closepath, return, endchar, pop: ;
	  dotsection, hstem, hstem3, vstem, vstem3: ;
	  hsbw: x := param [1];
	  sbw: begin x := param [1]; y := param [2] end;
	  seac: begin	x1 := x; y1 := y; (* save current sidebearing point *)
			getCharString (StandardEncoding [param [4]])^.
				traceBBox (x, y, llx, lly, urx, ury, errcmd);
			x := x1 + param [2] - param [1];
			y := y1 + param [3] (* - sby (accent), hopefully 0 *);
			getCharString (StandardEncoding [param [5]])^.
				traceBBox (x, y, llx, lly, urx, ury, errcmd)
		end;
	  hlineto: newpoint (x + param [1], y);
	  hmoveto: newpoint (x + param [1], y);
	  rlineto: newpoint (x + param [1], y + param [2]);
	  rmoveto: newpoint (x + param [1], y + param [2]);
	  vlineto: newpoint (x, y + param [2]);
	  vmoveto: newpoint (x, y + param [2]);
	    (* with the curves, hope for horizontal and vertical tangents: *)
	  hvcurveto: newpoint (x + param [1] + param [2],
				y + param [3] + param [4]);
	  rrcurveto: newpoint (x + param [1] + param [3] + param [5],
				y + param [2] + param [4] + param [6]);
	  vhcurveto: newpoint (x + param [2] + param [4],
				y + param [1] + param [3]);
	  setcurrentpoint: if paramnum = 2 then begin
				x := param [1]; y := param [2]
			   end
			   else errcmd := setcurrentpoint;
	 end;
	 i := i + 1
     end
end;

procedure CharStrT.checkafm (checkBBox: Boolean);
(* Check afm entries for correct width and (optionally) 
   bounding box information. The latter procedure seems to be 
   currently a great rubbish. *)
var C, WX, llx, lly, urx, ury,
    oldWX, oldllx, oldlly, oldurx, oldury: integer;
    x, y: integer;
    errcmd: t1cmd;
    putit: Boolean;
begin
   if name <> '/.notdef' then begin
      if pathcmd (1) = hsbw
      then WX := pathcomponent (1)^.param [2]
      else if pathcmd (1) = sbw
      then WX := pathcomponent (1)^.param [3]
      else begin
	writeln ('% path error: first command not hsbw or sbw in character ', name);
	WX := 0;
      end;
      getafmchar (C, oldWX, name, oldllx, oldlly, oldurx, oldury);
      putit := false;

      if C = -2 then begin
	writeln ('% afm error: missing entry for character ', name);
	putit := true;
	C := newafmcode (name);
      end
      else if oldWX <> WX then begin
	writeln ('% afm error: wrong width for character ', name);
	putit := true;
      end;

      if checkBBox then begin
	llx := maxint;
	urx := - maxint;
	lly := maxint;
	ury := - maxint;
	x := 0;
	y := 0;
	errcmd := return;
	traceBBox (x, y, llx, lly, urx, ury, errcmd);
	if errcmd <> return then begin
		writeln ('% tracing BBox of ', name, ': cannot handle ', cmdstr (errcmd));
		llx := oldllx; lly := oldlly; urx := oldurx; ury := oldury;
	end
	else if C = -2 then putit := true
	else if abs (llx - oldllx) + abs (lly - oldlly)
		+ abs (urx - oldurx) + abs (ury - oldury) < 30
	then begin	(* keep old values *)
		llx := oldllx; lly := oldlly; urx := oldurx; ury := oldury;
	end
	else begin
		putit := true;
		writeln ('% adjusting afm BBox information for ', name);
	end;
      end
      else begin
	llx := oldllx; lly := oldlly; urx := oldurx; ury := oldury;
      end;

      if putit then putafmchar (C, WX, name, llx, lly, urx, ury);
   end
end;

(************************************************************************)

var i: integer;
begin
	for i := 1 to cmdcount do with cmdtab [i] do
		cmdstring [cmd] := str;
	NP := ''; ND := '';
	MoreSubrscount := 0;
	MoreSubrs := nil;
	for i := 0 to maxused do usedStat [i] := 0;
	zeromoves := 0;
end.
