% ReadOutline.jam -- Jam program to read an outline file and display it.

% Getting started with graphics JaM:
% BringOver /a/p [indigo]<cedar>top>TjamGraphics.df
% Run TjamGraphicsPackage  (.bcd)
% (graphics.jam) .run   --- to JaM
(loadg) .where {.pop}.cvx {(graphics.jam) .run}.cvx .ifelse

% --- for mesa IO interface and outline reader
(.oOpenIn) .where {.pop}.cvx {(outlineio.bcd) .loadbcd}.cvx .ifelse
(.iobytestream) .where {.pop}.cvx {(jamiostreamimpl.bcd) .loadbcd}.cvx .ifelse

% Since we're about to overflow a dictionary, put all this stuff in a new one.

(FontDict) 600 .dict .def
FontDict .begin

(debugOutline) .false .store
(accentedChar) 0 .store

(pr) {(          ).cvis .print}.cvx .def

% (fileName) ROStart -- inits file
(ROStart) { .oOpenIn .not { (Unable to open file.) .print }.cvx .if } .cvx .def

% RORead -- reads entries until eof and does appropriate moveto or drawto
%	-- returns -2 on stack if more to come
%	-- returns -3 on stack if EOF
(RORead) {
	{
	.oEntry .dup 0 .gt
	{.dup 1 .eq
		{.pop .rdrawto}.cvx
		{.dup 4 1 .roll .mul 3 1 .roll .mul .exch .rdrawto}.cvx .ifelse
	}.cvx
	{.dup -2 .eq {.exit} .cvx .if
	 .dup -3 .eq {.exit} .cvx .if
	 .dup -1 .eq {.pop .moveto} .cvx .if
	} .cvx .ifelse
	} .cvx .loop
	} .cvx .def

% ROReadBB -- reads and displays bounding box for a single outline
%	-- leaves in dictionary <xmin xmax ymin ymax startingfilepos>
%	-- returns on stack .true if character read, or .false if eof or error

(ROReadBB) {
	.oGetPos (startingfilepos) .exch .store
	.oEntry .dup -1 .eq
	{ .pop .oBB (ymax) .exch .store (ymin) .exch .store (xmax) .exch .store (xmin) .exch .store
	  xmin ymin .moveto xmin ymax .drawto xmax ymax .drawto xmax ymin .drawto xmin ymin .drawto
	  .true }.cvx
	{ .dup 0 .gt { .pop .pop (Not at start of character) .print }.cvx .if
	  .dup -2 .eq { (Not at start of character) .print }.cvx .if
	  .pop .false }.cvx .ifelse
	} .cvx .def


% (file) ROBuildBB -- builds a dictionary called "BBdict" in the main dictionary.
% This dictionary contains, under:
%	'file' the name of the file.
%	'bbcount' the number of bounding boxes in the file.
%	[1 .. bbcount] subdictionaries for each bounding box. A subdictionary contains:
%		'startingfilepos' position in input file where outline begins
%		'xmin' 'xmax' 'ymin' 'ymax' the bounding box

(ROBuildBB) {
	(BBdict) 500 .dict .store
	.dup ROStart
	BBdict (file) 3 2 .roll .put
	BBdict (bbcount) 0 .put
	{ ROReadBB .not {.exit}.cvx .if
		BBdict (bbcount) .get 1 .add .dup BBdict (bbcount) 3 2 .roll .put
		BBdict .exch 5 .dict .dup 4 1 .roll .put
		.dup (startingfilepos) startingfilepos .put
		.dup (xmin) xmin .put
		.dup (xmax) xmax .put
		.dup (ymin) ymin .put
		(ymax) ymax .put
	}.cvx .loop
	}.cvx .def

% Print out dictionary of bounding boxes created by ROBuildBB

(ROPrintBB) {
	1 1 BBdict (bbcount) .get
		{ (BB #) .print .dup (    ).cvis .print /lp
		BBdict .exch .get
			{ .exch (                   ).cvis .print (=).print (        ).cvis .print ( ).print }.cvx .dictforall
		/rp /cr }.cvx .for 
	}.cvx .def

% i ROGetBB -- defines in global dictionary all properties of BB # i

(ROGetBB) {
	BBdict .exch .get { .store }.cvx .dictforall 
	}.cvx .def

% x y ROFindBB -- puts on stack indices of all BB's surrounding (x,y)
%	starts looking with BB# roFindBBStart

(roFindBBStart) 1 .def

(ROFindBB) {
	(ytarget) .exch .store (xtarget) .exch .store
	roFindBBStart 1 BBdict (bbcount) .get
		{ .dup ROGetBB
		ytarget ymax .gt {.pop .exit}.cvx .if
		xtarget xmin .lt xtarget xmax .gt ytarget ymin .lt .or .or {.pop}.cvx .if
		}.cvx .for 
	}.cvx .def

% rid -- accepts mouse input and prints BB #'s that surround point

(rid) { /clr .touch ROFindBB /stk /clr}.cvx .def

% n RODisplayOutline => ..displays nth outline at full scale

(RODisplayOutline) {
	ROGetBB startingfilepos .oSetPos RORead
	}.cvx .def

% Routine to help build Page data structure for a font. This routine builds the
% data structure for a row of the strike page. It is called with an array of
% character codes on the stack -- this array contains the character code for each
% character in the row.

(BuildRowDS) {
	(buildRowArray) .exch .def
	0 1 buildRowArray .length 1 .sub
		{ .dup (Character ) .print 1 .add pr CollectBBs /cr
		(	[ ).print 3 2 .roll buildRowArray .exch .aget pr ( ).print PrintArray (]
) .print
		}.cvx .for
	}.cvx .def

% Waits for a "touch" and finds surrounding box(es), then another touch, etc.
% Finish by "touch"ing so as to find no bounding boxes.

(CollectBBs) {
	(collectCount) 0 .def (collectArray) 10 .array .def
	{
	.mark (+) .print .touch ROFindBB .cnttomrk .dup 0 .eq {.pop .pop .exit}.cvx .if
	1 1 3 2 .roll
		{.pop (collectBBcandidate) .exch .def
		collectArray collectCount collectBBcandidate .aput
		(collectCount) collectCount 1 .add .def
		0 1 collectCount 2 .sub
			{collectArray .exch .aget collectBBcandidate .eq
				{(collectCount) collectCount 1 .sub .def .exit}.cvx .if
			}.cvx .for
		}.cvx .for
	.pop %the mark 
	}.cvx .loop
	collectArray collectCount
	}.cvx .def

% array length PrintArray => .. prints [0..length) from array

(PrintArray) {
	.exch (printArray) .exch .def
	1 .sub 0 1 3 2 .roll
		{printArray .exch .aget (     ).cvis .print ( ).print }.cvx .for
	}.cvx .def

% Routines to compute deltaX and deltaY for referencing (0,0) point of character
% from a previous "period" character.

% BBofPeriod BBofA ComputeDY => dy

(ComputeDY) {
	ROGetBB ymin .exch ROGetBB ymin .sub
	}.cvx .def

% BBofPeriod BBofMDash WidthofMDash ComputeDX => dx

(ComputeDX) {
	.exch ROGetBB xmax xmin .sub .exch MergWidth .sub 2 .div xmin .add
	1 .add % standard width fudge
	.exch ROGetBB xmin .sub
	}.cvx .def

% Interface to fitter.
% bringover /p [indigo]<fit>top>fit.df
% * (fit.jam) .run
% * startup

% ROFit -- reads outine from file and passes points to fitter

(ROFit) {
	{
	.oEntry .dup 0 .gt
	{.dup 4 1 .roll .mul runy .add (runy) .exch .store
	       .mul runx .add (runx) .exch .store runx runy .addsa
	}.cvx
	{.dup -2 .eq {.exit} .cvx .if
	 .dup -3 .eq {.exit} .cvx .if
	 .dup -1 .eq {.pop 2 .copy (runy) .exch .store (runx) .exch .store .startsa
			(begx) runx .store (begy) runy .store} .cvx .if
	} .cvx .ifelse
	} .cvx .loop
	begx begy .addsa .pop
	} .cvx .def

% x y n ROFitOutline => ..fit nth outline at full scale. <x y> is location of char origin.
%		writes char to 'char.log' file, with origin at (100,100), full scale.

(ROFitOutline) {
	(char.log) 2 .iobytestream .iokillstream
% simulate some curve fitting if debugging
debugOutline
	{ .pop .pop .pop
	(char.log) 2 .iobytestream .dup
	(100 200 .moveto
300 400 500 600 700 800 .curveto
900 100 .lineto
)	.iowritebytes .iokillstream
	}.cvx
	{
	2 .setslen
	ROGetBB startingfilepos .oSetPos ROFit
	.neg 100 .add .exch .neg 100 .add .exch .transa
	0 .setslen .homesa .nextsa .thesa .addsa
	2 .interpolatesa e ms
	1 .dynnodes
	55 .squaretangents e ms mn
	5 1 2 2 .dynspline
	e dc mc
	(char.log) .openlogfile
	.true .setlog
	logcs
	.false .setlog
	.collectgarbage
	}.cvx .ifelse
	}.cvx .def

% Routines to process a whole "page" of a font. There are a number of data structures
% involved:

% 1. A width vector for the font, in Mergenthaler units. Index i corresponds to Mergenthaler
%		character code (MCC). Index 0 is unused. Stored as "fitWidths".
%
% 2. A vector describing the "page". Each element of the vector describes a "row" in the
%		page. Stored as "fitPage".
%				GetRowCount => n ..returns count of number of rows on this page.
%				i GetRow => ..stores ith row as "fitRow", i in [1..n].
%
% 3. Each row is described by a vector that contains (positional notation):
%		BB# for the period that begins the line
%		BB# for the period that ends the line (or 0 if none)
%		char1
%		char2
%		. . .
%		char n
%				GetPeriods => bb1 bb2 ..get period BB numbers.
%				GetCharCount => n
%				i GetChar => ..stores ith char as "fitChar", i in [1..n]
%
% 4. Each char is described simple as a vector containing:
%		MCC (Mergenthaler character code)
%		BB# for first outline of char (negative if outline direction should be reversed)
%				GetMCC => MCC
%				GetOutlineCount => n
%				i GetOutline => BB#, i in [i..n]
%
%	Widths:
%		mcc GetMergWidth => mergenthaler width in units of set
%		mcc GetPageWidth => mergenthaler width used to set this
%			character on the strike sheet. This is same as GetMergWidth
%			except for accent characters.

(GetRowCount) {fitPage .length}.cvx .def
(GetRow) {1 .sub fitPage .exch .aget (fitRow) .exch .store}.cvx .def
(GetPeriods) {fitRow 0 .aget fitRow 1 .aget}.cvx .def
(GetCharCount) {fitRow .length 2 .sub} .cvx .def
(GetChar) {1 .add fitRow .exch .aget (fitChar) .exch .store}.cvx .def
(GetMCC) {fitChar 0 .aget}.cvx .def
(GetOutlineCount) {fitChar .length 1 .sub}.cvx .def
(GetOutline) {fitChar .exch .aget}.cvx .def
(GetMergWidth) {fitWidths .exch .aget}.cvx .def

% If accentedChar#0, character codes [93..101] have page width of accentedChar
(GetPageWidth) {
	.dup IsAccentChar {.pop accentedChar}.cvx .if
	GetMergWidth
	}.cvx .def
	
% mcc IsAccentChar => boolean ..returns true if character code is an accent
(IsAccentChar) {.dup 92 .gt .exch 102 .lt accentedChar 0 .eq .not .and .and }.cvx .def

% Data structure is set with
%		<outFile pointFile fitWidths fitPage xOffset yOffset mergPeriodWidth accentedChar accentXOffset> SetPage
%		offsets are added to initial period's xmin,ymin to obtain origin of first char in row.

(SetPage)  {
	(accentXOffset) .exch .store
	(accentedChar) .exch .store
	(mergPeriodWidth) .exch .store
	(yOffset) .exch .store (xOffset) .exch .store
	(fitPage) .exch .store (fitWidths) .exch .store
	.pushdc 0.08 0.08 .scale
	ROBuildBB
	.popdc
	(outFile) .exch .store
	}.cvx .def

(StartOut) { (outStream) outFile 2 .iobytestream .store }.cvx .def
(StopOut) { outStream .iokillstream }.cvx .def

(DoPage) {
	StartOut
	1 1 GetRowCount
		{ GetRow 1 1 GetCharCount
			{ DoChar }.cvx .for
		}.cvx .for
	StopOut
	}.cvx .def
	
(DoPreview) {
	1 1 GetRowCount
		{ GetRow 1 1 GetCharCount
			{ DoCharPreview .touch .pop .pop }.cvx .for
		}.cvx .for
	}.cvx .def

% i DoChar -- do ith character on the current row.

(DoChar) {
	.erase
	DoCharPreamble
	{ OutCharBegin
	1 1 GetOutlineCount
		{ GetOutline .dup 0 .lt {.neg}.cvx .if
		xOrg yOrg 3 2 .roll ROFitOutline
		OutOutline }.cvx .for
	OutCharEnd }.cvx .if
	}.cvx .def
	
% i DoCharPreview -- show ith character on the current row.

(DoCharPreview) {
	.erase
	DoCharPreamble
	{1 1 GetOutlineCount
		{ .pushdc
		100 100 ShowCross
		GetMCC GetMergWidth MergWidth 100 .add 100 ShowCross
		xOrg .neg 100 .add yOrg .neg 100 .add .translate
		GetOutline RODisplayOutline .pop .popdc }.cvx .for
	}.cvx .if
	}.cvx .def

% x y ShowCross => puts out small cross

(ShowCross) {
	.pushdc .translate
	-20 0 .moveto 20 0 .drawto
	0 -20 .moveto 0 20 .drawto
	.popdc
	}.cvx .def

% i DoCharPreamble => boolean ..sets up for fitting a character. return true if should be fit

(DoCharPreamble) {
	(curCharIndex) .exch .store
	curCharIndex GetChar GetPeriods .pop ROGetBB
	xmin xOffset .add curCharIndex CumWidths .add
	curCharIndex GetChar GetMCC IsAccentChar {accentXOffset .add}.cvx .if
	(xOrg) .exch .store
	ymin yOffset .add (yOrg) .exch .store
	(Current MCC=) .print GetMCC =
	GetOutlineCount .0 .eq .not
	}.cvx .def

% OutCharBegin -- start writing out current character on outFile

(OutCharBegin) {
	GetMCC OutNum (.charCode
) OutString
	fitWidths GetMCC .aget OutNum 54 OutNum
	(.width
{) OutString
	}.cvx .def

% OutCharEnd -- finish writing out current character on outFile

(OutCharEnd) {
	(} .endChar
) OutString OutFlush
	}.cvx .def

% OutOutline -- copy outline from 'char.log' to output file.

(.savedmoveto) (.moveto) .load .store
(.savedcurveto) (.curveto) .load .store
(.savedlineto) (.lineto) .load .store

(OutOutline) {
	(.moveto) {.exch OutCoord OutCoord (.moveto
) OutString }.cvx .def
	(.curveto) {6 5 .roll OutCoord 5 4 .roll OutCoord
			4 3 .roll OutCoord 3 2 .roll OutCoord 2 1 .roll OutCoord OutCoord (.curveto
) OutString }.cvx .def
	(.lineto) {.exch OutCoord OutCoord (.lineto
) OutString }.cvx .def
	(char.log) .run
	(.moveto) (.savedmoveto) .load .store
	(.curveto) (.savedcurveto) .load .store
	(.lineto) (.savedlineto) .load .store
	}.cvx .def

(OutNum) {1000 .mul 4000.5 .add .cvi 4000 .sub 1000.0 .div (                 ).cvis
	outStream .exch .iowritebytes outStream ( ) .iowritebytes}.cvx .def
(OutCoord) {.cvr 100 .sub pointSize 10 .mul .div OutNum}.cvx .def
(OutString) {outStream .exch .iowritebytes}.cvx .def
(OutFlush) {outStream .ioflush}.cvx .def


% i CumWidths => n ..accumulate widths for characters [1..i) on this row

(CumWidths) {
	(cumWidth) 0 .store
	1 1 3 2 .roll 1 .sub
		{ GetChar GetMCC GetPageWidth MergWidth cumWidth .add 1 .add (cumWidth) .exch .store}.cvx .for
	cumWidth
	}.cvx .def

% Mergenthaler width calculation interface.

(pointSize) 64 .store

% n MergWidth => bits ..convert Merg width at pointsize points to bits (and round)

(MergWidth) { pointSize 10.0 .mul 54 .div .mul 0.5 .add .cvi }.cvx .def

% n1 . . . nn MergSum => bits  ..convert each entry on stack and sum

(MergSum) {
	(temp) 0 .store
	{.cntstk 0 .eq {.exit}.cvx .if
	  MergWidth temp .add (temp) .exch .store }.cvx .loop
	temp
	}.cvx .def

% Definitions to help build up row data structures for conventional font
(Row1) [ 27 28 29 30 31 32 33 34 35 36 37 38 39 40 ] .def
(Row2) [ 41 42 43 44 45 46 47 48 49 50 51 52 ] .def
(Row3) [ 1 2 3 4 5 6 7  8 9 10 11 12 13 14 15 16 17 18 19 ] .def
(Row4) [ 20 21 22 23 24 25 26 64 65 67 66 74 73 85 84 70 71 86 53 78 79 77 69 ] .def
(Row5) [ 63 54 55 56 57 58 59 60 61 62 64 72 76 64 64 75 89 88 87 80 68 ] .def
(Row6) [ 91 116 117 118 119 114 115 107 108 109 110 82 90 120 121 102 103 ] .def
(Row7) [ 104 105 113 111 106 112 83 81 93 94 95 96 97 98 101 99 100 ] .def

% Function to build "Row" array for a 10-char-per-line font
% n Row => array for row n (10n-9, 10n-8, ... 10n)

(Row) {
	10 .mul .dup 9 .sub 1 3 2 .roll { }.cvx .for
	10 .array .astore
	}.cvx .def