\font\logo=logo10 \def\MF{{\logo META}\-{\logo FONT}} \def\<#1>{$\langle\hbox{#1}\rangle$} \font\sf=cmss10 \def\bull{$\bullet$} @* Introduction. The code in this file provides a set of library routines for interfacing with the GraphOn-140 terminal. The code will be compiled into a sharable library and then loaded by an application to handle certain generic tasks for graphic output. In addition to providing GraphOn-140 support, this code can also serve as a model for other Tektronix-emulators supporting an erase mode. Our commission is to provide the following routines: \yskip\hang\leavevmode |libinitsc| handles whatever initializations are necessary before we can use the display. It takes two arguments, both |var|, which will tell us what the co\"ordinate space of our display is. \yskip\hang\leavevmode |libstarts| starts up graphics mode for real. \yskip\hang\leavevmode |libblrect| erases a rectangle from the screen. \yskip\hang\leavevmode |libdrwrow| draws a row of \MF\ output on the screen. \yskip\hang\leavevmode |libupdtsc| makes sure that the screen is fully updated. \yskip\hang\leavevmode |libclossc| Takes care of whatever termination is necessary for a clean shutdown. \yskip\noindent All routines should return the display to a state suitable for writing text upon completion. The code in this file was written by Don Hosek. The following definition should be changed whenever this code is modified. @d banner == 'GraphOn-140 Support. Version 1.0a' @ The following macro is used to increment a variable. @d incr(#) == #:=#+1 @ To compile this code, we need to declare it as a module, which is syntactically similar to a program but identifies a block of code compiled independently. @f module == program @= [psect($code)] @> @p module go140; type @@; var @@; @* Output. We need to define appropriate handling for various output tasks. In particular, we need to be certain that we can write to the terminal. @d VAX_static==@=static@> @= @!disp_out: @{[VAX_static]@} text ; {file variable for writing to the display} @ Here we provide the code for opening the output display. The code is lifted from VMS \MF, but simplified since we don't need the functionality of |wake_up_terminal|. @d VAX_open_file == @= open @> @d VAX_sys_output == @= 'SYS$OUTPUT' @> @d VAX_carriage_control == @= carriage_control @> @d VAX_none == @= none @> @= VAX_open_file(disp_out, VAX_sys_output, VAX_carriage_control:=VAX_none); linelimit(disp_out,maxint); rewrite(disp_out); @ Symbolic names for ASCII control codes 0--31~and~255 are defined as |chr| sequences. The symbolic names have no bearing on these codes' usage here; they are just the names given in the back of my Pascal book. @d nul==chr(@"00) {NULl} @d soh==chr(@"01) {Start Of Heading} @d stx==chr(@"02) {Start of TeXt} @d etx==chr(@"03) {End of TeXt} @d eot==chr(@"04) {End Of Transmission} @d enq==chr(@"05) {ENQuiry} @d ack==chr(@"06) {ACKnowledge} @d bel==chr(@"07) {BELl} @d bs ==chr(@"08) {BackSpace} @d ht ==chr(@"09) {Horizontal Tab} @d lf ==chr(@"0A) {Line Feed} @d vt ==chr(@"0B) {Vertical Tabulation} @d ff ==chr(@"0C) {Form Feed} @d cr ==chr(@"0D) {Carriage Return} @d so ==chr(@"0E) {Shift Out} @d si ==chr(@"0F) {Shift In} @d dle==chr(@"10) {Data Link Escape} @d dc1==chr(@"11) {Device Control 1} @d dc2==chr(@"12) {Device Control 2} @d dc3==chr(@"13) {Device Control 3} @d dc4==chr(@"14) {Device Control 4} @d nak==chr(@"15) {Negative AcKnowledge} @d syn==chr(@"16) {SYNchronous idle} @d etb==chr(@"17) {End of Transmission Block} @d can==chr(@"18) {CANcel} @d em ==chr(@"19) {End of Medium} @d sub==chr(@"1A) {SUBstitute} @d esc==chr(@"1B) {ESCape} @d fs ==chr(@"1C) {File Separator} @d gs ==chr(@"1D) {Group Separator} @d rs ==chr(@"1E) {Record Separator} @d us ==chr(@"1F) {Unit Separator} @d del==chr(@"FF) {DELete} @ We define macros to handle writing to the terminal. @d wterm(#) == write_ln(disp_out,#) @d wterm_ln(#) == write_ln(disp_out,#,cr,lf) @* GraphOn-140 features. The GraphOn-140 provides some special sequences of its own: these handle such things as the drawing color, switching in and out of Tektronix mode, and the like. We will define some named modules to handle these assorted tasks. The GraphOn-140's physical resolution is $512\*392$ with a logical resolution of $1024\*784$. Since \MF\ will view the terminal as having its origin at the upper left corner with $y$-values increasing going down the screen and the Tektronix controls assume that the origin is in the {\it lower\/} left corner with $y$-values increasing going up the screen, we will define some macros that will automatically adjust for this as well as mapping \MF's resolution onto the virtual resolution provided by the Tektronix interface. @d max_scr_x = 511 @d max_scr_y = 391 @d fix_x(#) == (#*2) @d fix_y(#) == ((max_scr_y-#)*2) @ The first task we will handle is transmitting the sequence to the terminal to switch to Tektronix mode. This is accomplished by transmitting \\.1 to the terminal: @= wterm(esc,'1'); @ When we have finished any graphics, we will want to return to text mode. There are two ways of handling this: we can switch directly back to VT100 mode or, what we will find more useful, we can switch to the GraphOn's ``view'' mode. This will leave the display in Tektronix mode (the GraphOn has two display planes, one for text and the other for graphics) and only switch the display to VT100 mode if the user types a key or the program transmits some text. Since it is likely that we will have several graphics commands being executed consecutively, and a switch to graphics mode in this state will cause no visible effect, this avoids annoying ``flicker'' as \MF\ draws on the display. The mode switch is effected by transmitting \. @= wterm(can); @ The GraphOn has a ``block mode'' in which a large rectangular area can be drawn (or erased) quickly. The application sends the Tektronix code to draw a diagonal of the rectangle and the terminal takes care of actually drawing it. Entering block mode is accomplished by transmitting \\ to the terminal. Block mode is turned off by transmitting \\. @= wterm(esc,stx); @ @= wterm(esc,etx); @ The GraphOn is capable of drawing in three colors: black, white and inverse. The default is white. In order to erase blocks, we will want to be able to switch between black and white which is accomplished by setting the ``color'' to ``data on'' (for white) or ``data off'' (for black). Data on is selected with \\; data off with \\. @= wterm(esc,soh); @ @= wterm(esc,dle); @* Tektronix graphics. There are two basic Tektronix commands for plotting: moveto and drawto. The moveto command consists of \ followed by a Tektronix co\"ordinate; drawto is specified simply by transmitting a Tektronix co\"ordinate to the terminal. The basic format of a Tektronix co\"ordinate is a series of four bytes defined as |chr(y div @'40+@'40)|, |chr(y mod @'40+@'140)|, |chr(x div @'40+@'40)|, |chr(x mod @'40+@'100)|. However, if the last Tektronix co\"ordinate did not differ significantly from the current Tektronix co\"ordinate, certain bytes may be omitted. If we refer to the above series of bytes as High~$Y$, Low~$Y$, High~$X$ and Low~$X$ respectively, the following table shows the differences: \yskip\null\yskip \centerline{% \vbox{\sf\tabskip=0pt \offinterlineskip \def\tablerule{\omit&&\multispan5\hrulefill\cr} \def\TABLERULE{\noalign{\hrule height.4pt depth.4pt}} \halign{\strut#&\vrule width.8pt\ \hfil#\hfil\ &\vrule width.8pt\ #\hfil\ &\vrule width.8pt\ \hfil#\hfil\ &\vrule\ \hfil#\hfil\ &\vrule\ \hfil#\hfil\ &\vrule\ \hfil#\hfil\ \vrule width.8pt\cr \TABLERULE &\multispan2\vrule width.8pt\hfil &\multispan4\vrule width.8pt\ \hfil Bytes which must be transmitted\hfil\ \vrule width.8pt\cr \omit&\multispan2\vrule width.8pt\hfil &\multispan4\leaders\hrule height.4pt depth.4pt\hfill\cr &\multispan2\vrule width.8pt\hfil &High $Y$&Low $Y$&High $X$&Low $X$\cr \TABLERULE & Bytes & High $Y$ & \bull & & & \bull \cr \tablerule & which & Low $Y$ & & \bull & & \bull \cr \tablerule & change& High $X$ & & \bull & \bull & \bull \cr \tablerule & & Low $X$ & & & & \bull \cr \TABLERULE }}} \yskip With this in mind, we can define two macros; the first, |send_XY| given the variables |x| and |y| will transmit to the terminal the appropriate 4-byte sequence to move or draw to that co\"ordinate without doing any compression. This is useful for the initial moveto command which may or may not be the first Tektronix command of the session. The second macro, |send_XY_c| given the variables |x|, |y|, |xh_l|, |yh_l| and |yl_l| (these last three are set by both |send_XY| and |send_XY_c|), will send the appropriate sequence of bytes, omitting as much as possible. @d send_XY == begin yh_l:=y div @'40+@'40; yl_l:=y mod @'40+@'140; xh_l:=x div @'40+@'40; wterm(chr(yh_l),chr(yl_l),chr(xh_l), chr(x mod @'40+@'100)); end @d send_XY_c == begin if yh_l<>y div @'40+@'40 then begin yh_l:=y div @'40+@'40; wterm(chr(yh_l)); end; if yl_l<>y mod @'40+@'140 then begin yl_l:=y mod @'40+@'140; wterm(chr(yl_l)); if xh_l<>x div @'40+@'40 then begin xh_l:=x div @'40+@'40; wterm(chr(xh_l)); end; end else if xh_l<>x div @'40+@'40 then begin xh_l:=x div @'40+@'40; wterm(chr(yl_l),chr(xh_l)); end; wterm(chr(x mod @'40+@'100)); end @ As a special case, we also supply macros |send_XY_hc| and |send_XY_vc| which work just like |send_XY| but is optimized for drawing a line known to be horizontal or vertical, respectively. @d send_XY_hc == begin if xh_l<>x div @'40+@'40 then begin xh_l:=x div @'40+@'40; wterm(chr(yl_l),chr(xh_l)); end; wterm(chr(x mod @'40+@'100)); end @d send_XY_vc == begin if yh_l<>y div @'40+@'40 then begin yh_l:=y div @'40+@'40; wterm(chr(yh_l)); end; if yl_l<>y mod @'40+@'140 then begin yl_l:=y mod @'40+@'140; wterm(chr(yl_l)); end; wterm(chr(x mod @'40+@'100)); end @ Any procedure which calls these macros directly or indirectly will need the following variable declarations: @= @!x,@!y,@!yh_l,@!yl_l,@!xh_l: integer; @ Now we can wterm a macro which will draw a vector in Tektronix mode. This is done by transmitting \ followed by the full co\"ordinate specification for the first endpoint and then the compressed co\"ordinate specification for the second endpoint. |full_vec| acts as if it were a four argument macro with the syntax |full_vec(x1)(y1)(x2)(y2)| where |(x1,y1)| is the starting point for the line and |(x2,y2)| is the ending point. This macro also takes care of mapping \MF\ co\"ordinates into Tektronix co\"ordinates. @d fv3(#) == y:=fix_y(#); send_XY_c; end @d fv2(#) == x:=fix_x(#); fv3 @d fv1(#) == y:=fix_y(#); wterm(gs); send_XY; fv2 @d full_vec(#) == begin x:=fix_x(#); fv1 @ Another useful ability is that of drawing a horizontal line. We will define two macros for this: one for drawing any horizontal line and the second for drawing a horizontal line at the same $y$-co\"ordinate as the last horizontal line. |hline| will act as if it were a three argument macro with the syntax |hline(y)(x1)(x2)| which will draw a line from |(x1,y)| to |(x2,y)|. |c_hline| will act as if it were a two argument macro with the syntax |cline(x1)(x2)| which will draw a horizontal line between |x1| and |x2| at the current $y$-co\"ordinate. These macros also take care of mapping \MF\ co\"ordinates into Tektronix co\"ordinates. @d hl2(#) == x:=fix_x(#); send_XY_hc; end @d hl1(#) == x:=fix_x(#); wterm(gs); send_XY; hl2 @d hline(#) == begin y:=fix_y(#); hl1 @d c_hline(#) == begin x:=fix_x(#); wterm(gs); send_XY_hc; hl2 @ The Tektronix 4010 command set provides one more function, clear screen. This is accomplished by transmitting the sequence \\ to the display. @d clear_screen == wterm(esc,ff) @* Initializing the display. We'll begin by defining the |libinitsc| routine. This prints a banner message and sets the size of the addressable graphics screen. Whenever possible, we will run at the screen's physical resolution, in this case $512\times391$ (10 bonus points to the first one to explain the numbers below). Note that when communicating with the terminal, we will actually be using doubled co\"ordinates. @d VAX_global == @= global @> @p [VAX_global] procedure libinitsc(var @!xsize, @!ysize : integer); begin @@; wterm_ln(banner); xsize:=max_scr_x; ysize:=max_scr_y; end; @* Starting the display. The only initialization necessary for the GraphOn-140 is to clear the graphics plane. This is accomplished by switching to Tektronix mode, sending the Tektronix clear screen sequence, and then changing to View mode. @p [VAX_global] procedure libstarts; begin @@; clear_screen; @@; end; @* Drawing and erasing rectangles. Here we will erase a rectangle on the display. This is done by the following steps: (1)~enter Tektronix mode, (2)~switch to block mode, (3)~select data off as the drawing color, (3)~draw a vector from the upper left corner to the lower right corner (which since we are in block mode with data off as the drawing color, erases the rectangle with those diagonally opposite corners), (4)~turn off block mode and (5)~enter VT100 mode through the ``view'' state. Note that since we always select the drawing color on entry to this procedure and also in |libdrwrow|, there is no need to reset the drawing color when done. @p [VAX_global] procedure libblrect (left_col,right_col: screen_col; top_row, bot_row: screen_row); var @@; begin @@; @@; @@; full_vec(left_col)(top_row)(right_col)(bot_row); @@; @@; end; @ Drawing a rectangle is similar: it works as above but we change the color to ``data ON.'' @p [VAX_global] procedure libdrrect (left_col,right_col: screen_col; top_row, bot_row: screen_row); var @@; begin @@; @@; @@; full_vec(left_col)(top_row)(right_col)(bot_row); @@; @@; end; @* Drawing a row. Here is the code for drawing a row. \MF\ will use this code both for filling and for erasing regions within its ``windows''. We are passed four arguments: |r| is the row that we are drawing, |b| will be the color we are beginning with (if it is 0, we use data off, if it is 1 we use data on), |a| is an array of |integer| whose elements satisfy $$ 0\le a[0]@; @!k:integer; label finito; begin @@; k:=1; if b=0 then begin @@; hline(r)(a[0])(a[1]); if n=1 then goto finito; @@; c_hline(a[1])(a[2]); k:=2; end else begin @@; hline(r)(a[0])(a[1]); if n=1 then goto finito; end; while k@; c_hline(a[k])(a[k+1]); incr(k); if k=n then goto finito; @@; c_hline(a[k])(a[k+1]); incr(k); end; finito: @@; end; @ @= @!screen_row=integer; @!screen_col=integer; @!color=0..1; @!trans_spec=array [0..65536] of integer; @* Updating the screen. Since we do all screen writing immediately, this is not necessary. We provide a null routine for the benefit of the calling program, however. @p [VAX_global] procedure libupdtsc; begin {Do nothing} end; @* Closing the screen. No special shut down is necessary for the GraphOn-140 so we provide another null routine here. @p [VAX_global] procedure libclossc; begin {Do nothing} end; @* Setting the library level. Since different graphics libraries might have differing levels of support, we need to have an interface to let the calling program know what routines are available. At present, only the level~0 support is defined (e.g., those routines necessary for \MF\ support). @p [VAX_global] function liblevel: integer; begin liblevel:=0; end; @* Index. Before we print the index, let's toss in one more item for VAX Pascal. @p end.