MODULE Printing;
(**
	project	= "BlackBox"
	organization	= "www.oberon.ch"
	contributors	= "Oberon microsystems"
	version	= "System/Rsrc/About"
	copyright	= "System/Rsrc/About"
	license	= "Docu/BB-License"
	changes	= "
	- 20150922, center #69, Printing.NewDefaultPar does not set footer font
	- 20151009, center #73, ExpandHook for custom tags added; using MapString for names of months
	"
	issues	= "
	- ...
	"

**)

	IMPORT Kernel, Fonts, Ports, Dates, Printers, Views, Dialog, Strings;
	
	CONST maxNrOfSegments = 16; bufLen = 256;

	TYPE
		PageInfo* = RECORD
			first*, from*, to*: INTEGER; (** current IN **)
				(** first, from, to: OUT, preset to (0, 0, 9999) **)
			alternate*: BOOLEAN;
			title*: Views.Title
		END;

		Banner* = RECORD
			font*: Fonts.Font;
			gap*: INTEGER;	(** OUT, prest to (0,0) **)
			left*, right*: ARRAY 128 OF CHAR	(** OUT, preset to "", "" **)
				(** anywhere in header or footer:
					&p - replaced by current page number as arabic numeral
					&r - replaced by current page number as roman numeral
					&R - replaced by current page number as capital roman numeral
					&a - replaced by current page number as alphanumeric character
					&A - replaced by current page number as capital alphanumeric character
					&d - replaced by printing date 
					&t - replaced by printing time
					&&- replaced by & character
					&; - specifies split point
					&f - filename without path/title
				**)
		END;

		Par* = POINTER TO LIMITED RECORD
			page*: PageInfo;
			header*, footer*: Banner;
			copies-: INTEGER
		END;

		Line = RECORD
			buf: ARRAY bufLen OF CHAR;
			beg: ARRAY maxNrOfSegments OF BYTE;
			len: INTEGER
		END;

		Hook* = POINTER TO ABSTRACT RECORD END;

		ExpandHook* = PROCEDURE (IN s: ARRAY OF CHAR; printView: Views.View; IN date: Dates.Date;
													IN time: Dates.Time; IN title: Views.Title; pno: INTEGER; printing: BOOLEAN;
													OUT sx: ARRAY OF CHAR);

	VAR
		par*: Par;

		month: ARRAY 12 * 3 + 1 OF CHAR;
		printingHook: Hook;

		printView: Views.View;  (* param for expandHook; set per printed document in PrintView *)
		expandHook-: ExpandHook; (* for expanding custom tags *)
		
	PROCEDURE (h: Hook) Print* (v: Views.View; par: Par), NEW, ABSTRACT;
	PROCEDURE (h: Hook) Current* (): INTEGER, NEW, ABSTRACT;

	PROCEDURE SetHook* (p: Hook);
	BEGIN
		printingHook := p
	END SetHook;

	PROCEDURE SetExpandHook* (p: ExpandHook);
	BEGIN
		expandHook := p
	END SetExpandHook;

	PROCEDURE NewPar* (IN page: PageInfo; IN header, footer: Banner; copies: INTEGER): Par;
		VAR par: Par;
	BEGIN
		NEW(par);
		par.page := page;
		par.header := header;
		par.footer := footer;
		par.copies := copies;
		IF par.header.font = NIL THEN par.header.font := Fonts.dir.Default() END;
		IF par.footer.font = NIL THEN par.footer.font := Fonts.dir.Default() END;
		RETURN par
	END NewPar;

	PROCEDURE NewDefaultPar* (title: Views.Title): Par;
		VAR par: Par;
	BEGIN
		NEW(par);
		par.page.first := 1;
		par.page.from := 0;
		par.page.to := 9999;
		par.page.alternate := FALSE;
		par.copies := 1;
		par.header.gap := 0; par.header.left := ""; par.header.right := ""; par.header.font := Fonts.dir.Default();
		par.footer.gap := 0; par.footer.left := ""; par.footer.right := ""; par.footer.font := Fonts.dir.Default();
		par.page.title := title;
		RETURN par
	END NewDefaultPar;

	PROCEDURE PrintView* (view: Views.View; p: Par);
	BEGIN
		ASSERT(view # NIL, 20); ASSERT(p # NIL, 21);
		ASSERT(par = NIL, 22); (* no recursive printing *)
		IF Printers.dir.Available() THEN
			ASSERT(p.page.first >= 0, 23);
			ASSERT(p.page.from >= 0, 24);
			ASSERT(p.page.to >= p.page.from, 25);
			ASSERT(printingHook # NIL, 100);
			printView := view;
			printingHook.Print(view, p);
			printView := NIL
		ELSE Dialog.ShowMsg("#System:NoPrinterFound")
		END
	END PrintView;

	PROCEDURE GetDateAndTime (IN date: Dates.Date; IN time: Dates.Time; 
										VAR d, t: ARRAY OF CHAR);
		VAR i, j, k: INTEGER; s: ARRAY 8 OF CHAR;
	BEGIN
		Strings.IntToStringForm (date.day, Strings.decimal, 0, "0", FALSE, d);
		
		j := date.month * 3; i := j - 3; k := 0;
		WHILE i < j DO s[k] := month[i]; INC(k); INC(i) END; s[k] := 0X;
		d := d + "-" + s;
		
		Strings.IntToStringForm (date.year, Strings.decimal, 0, "0", FALSE, s);
		d := d + "-" + s;
		
		Strings.IntToStringForm (time.hour, Strings.decimal, 0, "0", FALSE, t);
		Strings.IntToStringForm (time.minute, Strings.decimal, 2, "0", FALSE, s);
		t := t + ":" + s;
	END GetDateAndTime;

	PROCEDURE Expand (s: ARRAY OF CHAR; IN date: Dates.Date; IN time: Dates.Time;
										IN title: Views.Title; pno: INTEGER; printing: BOOLEAN; VAR line: Line);
		VAR i, l: INTEGER; ch: CHAR; j: BYTE;
			p, d, t, r, rl: ARRAY 32 OF CHAR;
			sx: ARRAY bufLen * 2 OF CHAR;
	BEGIN
		IF expandHook # NIL THEN
			expandHook(s, printView, date, time, title, pno, printing, sx)
		ELSE
			sx := s$
		END ;
		IF printing THEN 
			Strings.IntToStringForm (pno, Strings.decimal, 0, "0", FALSE, p);
			IF (0 < pno) & (pno < 4000) THEN 
				Strings.IntToStringForm(pno, Strings.roman, 0, " ", FALSE, r)
			ELSE
				r := p
			END;
		ELSE p := "#"; r := "#"
		END;
		
		GetDateAndTime(date, time, d, t);
		
		i := 0; ch := sx[i]; line.len := 0; j := 0;
		WHILE ch # 0X DO
			IF ch = "&" THEN
				INC(i); ch := sx[i];
				IF ch = "p" THEN
					l := 0; WHILE p[l] # 0X DO line.buf[j] := p[l]; INC(j); INC(l) END
				ELSIF ch = "r" THEN
					Strings.ToLower(r, rl);
					l := 0; WHILE rl[l] # 0X DO line.buf[j] := rl[l]; INC(j); INC(l) END
				ELSIF ch = "R" THEN
					l := 0; WHILE r[l] # 0X DO line.buf[j] := r[l]; INC(j); INC(l) END
				ELSIF (ch = "a") OR (ch = "A") THEN
					IF printing & (0 < pno) & (pno <= 26) THEN line.buf[j] := CHR(pno + ORD(ch) - 1); INC(j)
					ELSE l := 0; WHILE p[l] # 0X DO line.buf[j] := p[l]; INC(j); INC(l) END
					END
				ELSIF ch = "d" THEN
					l := 0; WHILE d[l] # 0X DO line.buf[j] := d[l]; INC(j); INC(l) END
				ELSIF ch = "t" THEN
					l := 0; WHILE t[l] # 0X DO line.buf[j] := t[l]; INC(j); INC(l) END
				ELSIF ch = "f" THEN
					l := 0; WHILE title[l] # 0X DO line.buf[j] := title[l]; INC(j); INC(l) END
				ELSIF ch = ";" THEN
					IF (line.len < maxNrOfSegments-1) THEN line.beg[line.len] := j; INC(line.len) 
					ELSE line.buf[j] := " "; INC(j)
					END
				ELSIF ch = "&" THEN
					line.buf[j] := "&"; INC(j)
				END;
				IF ch # 0X THEN INC(i); ch := sx[i] END
			ELSE line.buf[j] := ch; INC(j); INC(i); ch := sx[i]
			END
		END;
		line.buf[j] := 0X; line.beg[line.len] := j; INC(line.len)
	END Expand;

	PROCEDURE PrintLine (f: Views.Frame; font: Fonts.Font;
												x0, x1, y: INTEGER; VAR line: Line);
		VAR sp, dx, x: INTEGER; i, j, k: INTEGER; buf: ARRAY 128 OF CHAR;
	BEGIN
		sp := (x1 - x0 - font.StringWidth(line.buf));
		IF line.len = 1 THEN (* center *)
			f.DrawString(x0 + sp DIV 2, y, Ports.defaultColor, line.buf, font)
		ELSE
			IF sp > 0 THEN dx := sp DIV (line.len - 1) ELSE dx := 0 END;
			k := 0; j := 0; x := x0;
			WHILE k < line.len DO
				i := 0;
				WHILE j < line.beg[k] DO
					buf[i] := line.buf[j]; INC(i); INC(j)
				END;
				buf[i] := 0X;
				f.DrawString(x, y, Ports.defaultColor, buf, font);
				x := x + font.StringWidth(buf) + dx;
				INC(k)
			END
		END
	END PrintLine;

	PROCEDURE PrintBanner* (f: Views.Frame; IN p: PageInfo; IN b: Banner; 
			IN date: Dates.Date; IN time: Dates.Time; x0, x1, y: INTEGER);
		VAR line: Line; printing: BOOLEAN;
	BEGIN
		printing := par # NIL;
		IF printing THEN
			ASSERT(printingHook # NIL, 100);
			IF p.alternate & ~ODD(p.first + printingHook.Current()) THEN
				Expand(b.left, date, time, p.title, p.first + printingHook.Current(), printing, line)
			ELSE
				Expand(b.right, date, time, p.title, p.first + printingHook.Current(), printing, line)
			END
		ELSE
			Expand(b.right, date, time, p.title, 0, printing, line)
		END;
		PrintLine(f, b.font, x0, x1, y, line)
	END PrintBanner;

	PROCEDURE Current*(): INTEGER;
	BEGIN
		ASSERT(par # NIL, 21);
		ASSERT(printingHook # NIL, 100);
		RETURN printingHook.Current()
	END Current;

BEGIN
	Dialog.MapString("#System:JanFebMarAprMayJunJulAugSepOctNovDec", month)
END Printing.
