postscript

更新时间:2022-12-29 02:11:43 阅读: 评论:0


2022年12月29日发(作者:abraham)发信人: SuperSB (孤鹰), 信区: Linux

标 题: [转载]unix环境高级编程-17

发信站: BBS 水木清华站 (Wed Mar 15 14:31:48 2000)









发信人: taosm (128+64-->cool), 信区: unix

标 题: unix环境高级编程--第17章 与PostScript打印机

发信站: 西十八BBS (Sat Mar 11 14:13:45 2000), 转信





第十七章 与PostScript打印机通信

17.1 引言

我们现在开发一个可以与Postscript打印机通信的程序。PostScript打印机目

前使用很广,它一般通过RS-232端口与主机相连。这样就使得我们有可能使用第十

一章中的终端I/O函数。同样,与PostScript打印机通信是全双工的,我们发送数

据给打印机时也要准备好从打印机读取状态消息。这样,我们又有可能使用12.5节

中的I/O多路转接函数:lect 和poll。我们所开发的这个程序是基于James Cla

rk 所写的lprps程序。这个程序和其他一些程序共同组成lprps软件包,可以在co

新闻组中找到(Volume 21,1991年7月)。

17.2 PostScript 通信机制

关于PostScript打印机所需要知道的第一件事就是我们并不是发送一个文件给

打印机去打印-我们只是发送一个PostScript程序给打印机让它去执行。在PostSc

ript打印机中通常有一个PostScript解释器来执行这个程序,生成输出的页面。如

果PostScript程序有错误,PostScript打印机(实际上是PostScript解释器)返回

一个错误消息,或许还会产生其他输出。

下面的PostScript程序在输出页面上生成一个熟悉的字符串hello, world。(

我们这里并不叙述PostScript编程,详细情况请参见Adobe Systems[1985和1986]

。我们着重在与PostScript打印机的通信上)。

%!

/Times-Roman findfont

15 scalefont % point size of 15

tfont % establish current font

300 350 moveto % x=300 y=350 (position on page)

(hello, world) show % output the string to current page

showpage % and output page to output device

如果我们在PostScript程序中改变tfont 为stfont,再把它发送到PostS

cript打印机,结果是什么也没有被打印。相反的,我们从打印机得到以下消息:



%% [ Error: undefined; OffendingCommand: stfont ]%%

%% [ Flushing: rest of job (to end-of-file) will be ignored ]%%

这些错误消息随时都可能产生,这也是处理PostScript打印机复杂的地方。我

们不能只是将整个PostScript程序发送给打印机后就不管了-我们必须处理这些潜

在的错误消息。(在这一章中,我们所说的"打印机",就是指PostScript解释器。



PostScript打印机通常通过RS-232串口与主机相连。这就如同终端的连接一样

,所以我们在第十一章中的终端I/O函数在这里也适用。(PostScript打印机也



以通过其它方式连接到主机上,例如网络接口逐渐流行。但目前占主导地位的还是

串口相连。)图17.2 显示了典型的工作过程。一个PostScript程序可以产生两种

形式的输出:通过showpage操作输出到打印机页面上,或者通过print操作输出到

它的标准输出(在这里是与主机的串口连接)。

PostScript解释器发送和接受的是七位ASCII字符。PostScript程序可包含所

有可打印的ASCII字符。一些不可以打印的字符有着特殊的含义(参见图17.1)。



图17.1 从主机发送到PostScript打印机的特殊字符

图17.2 用串口连接与Postscript打印机通信

PostScript的文件终止符(Control-D)用来同步打印机和主机。我们发送一

个PostScript程序到打印机,然后再发送一个EOF到打印机。当打印机执行完Post

Script程序后,它就发回一个EOF。

当PostScript解释器正在执行一个PostScript程序时,我们可以向它发送一个

中断(Control-C)。这通常使正在被打印机执行的程序终止。

状态查询消息(Control-T)会使得打印机返回一个一行的状态消息。所有的打印

机消息都是如下格式:

%% [ key : val ] %%

所有可能出现在状态消息中的key: val对,被分号分开。回忆前面例子的返回

消息:

%% [ Error: undefined; OffendingCommand: stfont ]%%

%% [ Flushing: rest of job (to end-of-file) will be ignored ]%%

这个状态消息具有这个格式:

%% [status : idle ]%%

除了idle(没有作业)外,其它状态指示还有busy(正在执行一个PostScrip

t程序)、 waiting(正在等待执行PostScript程序)、 printing(打印中)、i

nitializing(初始化)和printing test page(正打印测试页)。

现在来考虑PostScript解释器自己产生的状态消息。我们看到以下的消息

%% [ Error: error ; OffendingCommand : operator ]%%

总共大约会发生25种不同的错误。通常的错误有dictstackunderflow, invalidac

cess, typecheck, 和undefined。这里的operator是产生错误的PostScript操作。



一个打印机的错误用以下形式来指示。

%% [ PrinterError: reason ]%%

这儿的reason一般是Out Of Paper(缺纸)、Cover Open(盖打开)等错误。



当错误发生时,PostScript解释器经常会发出另外一个消息

%% [ Flushing : rest of job (to end-of-file) will be ignored ] %%

我们看一下在特殊字符序列%%[和]%%中的字符串,为了处理这个消息,我们必

须分析该字符串。一个PostScript程序也可以通过PostScript的print操作来产生

输出。这个输出应当传给发送程序给打印机的用户(虽然这并不是我们的打印程序

所期望解释的)。

图17.3 列出了PostScript解释器传送给主机的特殊字符。

图17.3 PostScript解释器传送

给主机的特殊字符

17.3 假脱机打印

我们在本章所开发的程序通过两种方式发送PostScript程序给PostScript打印

机,单独的方式或者通过BSD行式打印机假脱机系统。通常使用假脱机系统,但提

供一个独立的方式也是很有用的,如用于测试等。

Unix SVR4同样提供了一个假脱机打印系统,在AT&T手册[1991] 第一部分以l

p开头的手册页中可以找到假脱机系统的详细资料。Stevens[1990]的第13章详细说

明了BSD和pre-SVR4的假脱机系统。我们在这一章中并不着重在假脱机系统上,而

在于与PostScript打印机的通信。

在BSD的假脱机系统中,我们以如下形式打印一个文件

lpr -pps main.c

这个命令发送文件main.c到名为ps的打印机。如果我们没有指定-pps的选项,

那么或者输出到PRINTER环境变量对应的打印机上,或者输出到缺省的lp打印机上

。所用的打印机参数可以在/etc/printcap文件中查到。图17.4是对应一个PostSc

ript打印机的一项。

lp|ps:

:br#19200:lp=/dev/ttyb:

:sf:sh:rw:

:fc#0000374:fs#0000003:xc#0:xs#0040040:

:af=/var/adm/psacct:lf=/var/adm/pslog:sd=/var/spool/pslpd:

:if=/usr/local/lib/psif:

图17.4 一个PostScript打印机对应的printcap项

第一行给出了这个项的名称,ps或者lp。br的值指定了波特率是19200。lp指

定了该打印机的特殊设备文件路径名。sf是格式送纸,sh是指打印作业的开始加入

一个打印页头,rw是指定打印机以读写方式打开。如17.2节中所述,这一项是Pos

tScript打印机所必须的。

下面的四个域指定了在旧版本BSD风格的stty结构中需要打开和关闭的位。(

我们在这里对此进行叙述是因为大多数使用printcap文件的BSD系统都支持这种老

式的设置终端方式的方法。在这一章的源程序文件中,我们可以看到如何使用第十

一章中所述的POSIX.1函数来设置所有的终端参数。)首先,fc掩码清除sg_flags

元素中的下列值:EVENP和ODDP(关闭了奇偶校验)、RAW(关闭raw模式)、CRMO

D(关闭了在输入输出中的CR/LF映射)、ECHO(关闭回显)和LCASE(关闭输入输

出中的大小写映射)。然后,fs掩码打开了以下位: CBREAK(一次输入一个字符

)和TANDEM(主机产生Control-S,Control-Q流控制)。接着,xc掩码清除了本地

模式字中各位。最后,xs掩码设置了本地模式字中的下列两位:LDECCTQ(Contro

l-Q重新开始输出,Control-S则停止输出)和LLITOUT(压缩输出转换)。

af和lf字符串分别指定了记帐文件和日志文件。sd指定了假脱机的目录,而i

f指定了输入过滤器。

输入过滤器可被所有的打印文件所激活,格式如下:

filter -n loginname -h hostname acctfile

这里还有几个可选

的参数(这些参数有可能被PostScript打印机所忽略)。要

打印的文件在标准输入中,打印机(printcap文件中的lp项)设在标准输出。标准

输入也可以是一个管道。

使用一个PostScript打印机,输入过滤器首先查询输入文件的开始两个字符,

确定这个文件是一个ASCII文本文件还是一个PostScript程序。通常的惯例是前两

个字符为%!表示这是一个PostScript程序。如果这个文件是PostScript程序,lpr

ps程序(在下面会详细讨论)就把它发送到打印机。如果这个文件是文本文件,就

使用其他程序将它转换成PostScript程序。

在printcap文件中提到的psif过滤器是lprps软件包提供的。在这个包中的te

xtps可以将文本文件转换成PostScript程序。图17.5概略表示了这些程序。

图17.5 lprps系统示意图

图中有一个程序psrev没有表示出来,该程序将PostScript程序生成的页面反

转过来,当PostScript程序打印机在正面而不是在反面打印输出时,就可以使用此

程序。

概述以后,我们就来看一下lprps程序的设计和编码。

17.4 源程序

我们先看main()调用的函数,以及它们是如何与打印机交互作用的。图17.

6详细表明了这种相互作用。图中第二列标注为"int?",它指定该函数是否可以通

过接受SIGINT信号而中断。第三栏指定了各个函数的超时设置(以秒为单位)。注

意,当我们发送用户的PostScript程序到打印机时,没有超时设置。这是因为一个

PostScript程序可能用任意长的时间来执行。函数get_page行中的"我们的PostSc

ript程序"是指程序17.9,这个程序是用来记录当前页码的。

程序17.1列出了头文件lprps.h。该文件包含在所有的源文件中。该头文件包

含了各个源程序所需的系统头文件,定义了全局变量和全局函数的原型。

图17.6 被主程序调用的函数

_______________________________________________________________________

________

#include

#include

#include

#include

#include /* since we're a daemon */

#include "ourhdr.h"

#define EXIT_SUCCESS 0 /* defined by BSD spooling system */

#define EXIT_REPRINT 1

#define EXIT_THROW_AWAY 2

#define DEF_DEVICE "/dev/ttyb" /* defaults for debug mode */

#define DEF_BAUD B19200

/* modify following as a

propriate */

#define MAILCMD "mail -s "printer job" %s@%s < %s"

#define OBSIZE 1024 /* output buffer */

#define IBSIZE 1024 /* input buffer */

#define MBSIZE 1024 /* message buffer */

/* declare global variables */

extern char *loginname;

extern char *ho

stname;

extern char *acct_file;

extern char eofc; /* PS end-of-file (004) */

extern int debug; /* true if interactive (not a daemon) */

extern int in_job; /* true if nding ur's PS job to printer */

extern int psfd; /* file descriptor for PostScript printer */

extern int start_page;/* starting page# */

#include /* since we're a daemon */

#include "ourhdr.h"

#define EXIT_SUCCESS 0 /* defined by BSD spooling system */

#define EXIT_REPRINT 1

#define EXIT_THROW_AWAY 2

#define DEF_DEVICE "/dev/ttyb" /* defaults for debug mode */

#define DEF_BAUD B19200

/* modify following as a

propriate */

#define MAILCMD "mail -s "printer job" %s@%s < %s"

#define OBSIZE 1024 /* output buffer */

#define IBSIZE 1024 /* input buffer */

#define MBSIZE 1024 /* message buffer */

/* declare global variables */

extern char *loginname;

extern char *hostname;

extern char *acct_file;

extern char eofc; /* PS end-of-file (004) */

extern int debug; /* true if interactive (not a daemon) */

extern int in_job; /* true if nding ur's PS job to printer */

extern int psfd; /* file descriptor for PostScript printer */

extern int start_page;/* starting page# */

void msg_init(void); /* message.c */

void msg_char(int);

void proc_msg(void);

void out_char(int); /* output.c */

void get_page(int *); /* pagecount.c */

void nd_file(void); /* ndfile.c */

void block_write(const char *, int); /* tty.c */

void tty_flush(void);

void t_block(void);

void t_nonblock(void);

void tty_open(void);

_______________________________________________________________________

________

程序17.1 lprps.h头文件

文件vars.c(程序17.2)定义了全局变量。

程序17.3是main函数。因为此程序一般是作为精灵进程运行的,所以main函数调用

log_open函数(见附录B)。我们不能将错误消息写到标准错误上-为此我们使用了

13.4.2小节中描述的syslog。

_______________________________________________________________________

________

#include "lprps.h"

char *loginname;

char *hostname;

char *acct_file;

char eofc = '004'; /* Control-D = PostScript EOF */

int psfd = STDOUT_FILENO;

int start_page = -1;

int end_page = -1;

int debug;

int in_job;

volatile sig_atomic_t intr_flag;

volatile sig_atomic_t alrm_flag;

enum status status = INVALID;

_______________________________________________________

________________

________

程序17.2 声明全局变量

_______________________________________________________________________

________

#include "lprps.h"

static void usage(void);

int

main(int argc, char *argv[])

{

int c;

log_open("lprps", LOG_PID, LOG_LPR);

opterr = 0; /* don't want getopt() writing to stderr */

while ( (c = getopt(argc, argv, "cdh:i:l:n:x:y:w:")) != EOF) {

switch (c) {

ca 'c': /* control chars to be pasd */

ca 'x': /* horizontal page size */

ca 'y': /* vertical page size */

ca 'w': /* width */

ca 'l': /* length */

ca 'i': /* indent */

break; /* not interested in the */

ca 'd': /* debug (interactive) */

debug = 1;

break;

ca 'n': /* login name of ur */

loginname = optarg;

break;

ca 'h': /* host name of ur */

hostname = optarg;

break;

ca '?':

log_msg("unrecognized option: -%c", optopt);

usage();

}

}

if (hostname == NULL || loginname == NULL)

usage(); /* require both hostname and loginname */

if (optind < argc)

acct_file = argv[optind]; /* remaining arg = acct file */

if (debug)

tty_open();

if (atexit(clo_mailfp) < 0) /* register func for exit() */

log_sys("main: atexit error");

get_status();

get_page(&start_page);

nd_file(); /* copies stdin to printer */

get_page(&end_page);

do_acct();

exit(EXIT_SUCCESS);

}

static void

usage(void)

{

log_msg("lprps: invalid arguments");

exit(EXIT_THROW_AWAY);

}

_______________________________________________________________________

________

程序17.3 main函数

然后处理命令行参数,很多参数会被PostScript打印机所忽略。我们使用-d标

志指示这个程序是交互运行,而不是作为精灵进程。如果设置了这个标志,我们需

要初始化终端模式(tty_open)。然后我们将函数clo_mailfp指定为退出处理程

序。

我们就可以调用在图17.6中提到的函数:取得打印机状态保证它是就绪的(ge

t_status),得到打印机的起始页码(get_page),发送文件(PostScript程序)到

打印机(nd_file),得到打印机的结束页码(get_page),写记帐记

录(do_acct),

然后终止。

文件acct.c定义了函数do_acct(程序17。4)。它在main函数的结尾处被调用,

用来写下记帐记录。记帐文件的路径和名字从printcap文件中的相应记录项(图1

7.4)获得,并作为命令行的最后一个参数。

_______________________________________________________________________

________

#include "lprps.h"

/* Write the number of pages, hostname, and loginname to the

* accounting file. This function is called by main() at the end

* (if all was OK, by printer_flushing(), and by handle_intr() if

* an interrupt is received. */

void

do_acct(void)

{

FILE *fp;

if (end_page > start_page &&

acct_file != NULL &&

(fp = fopen(acct_file, "a")) != NULL) {

fprintf(fp, "%7.2f %s:%s
",

(double)(end_page - start_page),

hostname, loginname);

if (fclo(fp) == EOF)

log_sys("do_acct: fclo error");

}

}

_______________________________________________________________________

________

程序17.4 do_acct函数

从历史上看,所有的BSD打印过滤器都使用%7.2f的printf格式,把输出的页数

写到记帐文件中。这样就允许光栅设备不使用页数,而以英尺为单位报告输出长度



下面一个文件是tty.c(程序17.5),它包含了所有的终端I/O函数。它们调用

我们在第三章中提到的函数(fcntl, write和open)和第11章中的POSIX..1终端函

数(tcflush, tcgetattr, tctattr, cftispeed 和cftospeed)。如果我们

允许发生写阻塞,那么要调用block_write函数。如果我们不希望发生阻塞,则调

用t_nonblock函数,然后再调用read或者write函数。因为PostScript是一个全

双工的设备,打印机有可能发送数据回来(例如出错消息等),所以我们不希望阻

塞一个方向的写操作。如果打印机发送错误消息时,我们正因向其发送数据而处于

阻塞状态,则会出现死锁。

一般是核心为终端输入和输出进行缓冲,所以如果发生错误,我们可以调用t

ty_flush来刷清输入和输出队列。

如果以交互方式运行该程序,那么main函数将调用函数tty_open。我们需要把

终端设为非规范模式,设置波特率和其他一些终端标志。注意各种PostScript打印

机的这些设置并不都一样。检查你的打印机手册确定它的设置。(数据的位数可能

是7位或8位,起始位、停止位的数目以及奇偶校验等都可能因打印机而异。)

_______________________________________________________________________

_______

#include "lprps.h"

#include

#include

static int

block_flag = 1; /* default is blocking I/O */

void

t_block(void) /* turn off nonblocking flag */

{ /* called only by block_write() below */

int val;

if (block_flag == 0) {

if ( (val = fcntl(psfd, F_GETFL, 0)) < 0)

log_sys("t_block: fcntl F_GETFL error");

val &= ~O_NONBLOCK;

if (fcntl(psfd, F_SETFL, val) < 0)

log_sys("t_block: fcntl F_SETFL error");

block_flag = 1;

}

}

void

t_nonblock(void) /* t descriptor nonblocking */

{

int val;

if (block_flag) {

if ( (val = fcntl(psfd, F_GETFL, 0)) < 0)

log_sys("t_nonblock: fcntl F_GETFL error");

val |= O_NONBLOCK;

if (fcntl(psfd, F_SETFL, val) < 0)

log_sys("t_nonblock: fcntl F_SETFL error");

block_flag = 0;

}

}

void

block_write(const char *buf, int n)

{

t_block();

if (write(psfd, buf, n) != n)

log_sys("block_write: write error");

}

void

tty_flush(void) /* flush (empty) tty input and output queues */

{

if (tcflush(psfd, TCIOFLUSH) < 0)

log_sys("tty_flush: tcflush error");

}

void

tty_open(void)

{

struct termios term;

if ( (psfd = open(DEF_DEVICE, O_RDWR)) < 0)

log_sys("tty_open: open error");

if (tcgetattr(psfd, &term) < 0) /* fetch attributes */

log_sys("tty_open: tcgetattr error");

term.c_cflag = CS8 | /* 8-bit data */

CREAD | /* enable receiv

r */

CLOCAL; /* ignore modem

tatus lines */

/* no pa

ity, 1 stop bit */

term.c_oflag &= ~OPOST; /* turn off post processing */

term.c_iflag = IXON | IXOFF | /* Xon/Xoff flow control */

IGNBRK | /* ignore breaks

*/

ISTRIP | /* strip input t

7 bits */

IGNCR; /* ignore receiv

d CR */

term.c_lflag = 0; /* everything off in local flag:

disables canonical mo

e, disables

signal generation, di

ables echo */

term.c_cc[VMIN] = 1; /* 1 byte at a time, no timer */

term.c_cc[VTIME] = 0;

cftispeed(&term, DEF_BAUD);

cftospeed(&term, DEF_BAUD);

if (tctattr(psfd,

TCSANOW, &term) < 0) /* t attributes */

log_sys("tty_open: tctattr error");

}

_______________________________________________________________________

_______

程序17.5 终端函数

这个程序处理两个信号:SIGINT和SIGALRM。处理SIGINT是对BSD假脱机系统调

用的任何一种过滤器的要求。如果打印机作业被lprm(1)命令删除,那么这个信号

被发送给过滤器。我们使用SIGALRM来设置超时,对这两个信号用类似的方式处理

:我们提供了t_XXX函数来建立信号处理器,clear_XXX函数来取消这个信号处理

器。如果有信号传送给这个进程,信号处理器在设置一个全局的标记intr_flag和

alrm_flag后返回。程序的其它部分可在适当的时间来检测这些标记。有一个明显

的时间是在I/O函数返回错误EINTR时,该程序然后调用handle_intr或者handle_a

lrm来处理这种情况,调用signal_intr函数(程序10.13)来中断一个慢速的系统

调用。程序17.6是处理SIGINT信号的interrupt.c文件。

当一个中断发生时,我们必须发送PostScript的中断字符(Control-C)给打

印机,接着发送一个文件终止符(EOF)。这通常引起PostScript解释器终止它正在

解释的程序。然后我们等待从打印机返回的EOF(我们稍后将描述proc_upto_eof函

数)。我们读取最后的页码,写下记帐记录,然后就可以终止了。

_______________________________________________________________________

______

#include "lprps.h"

static void

sig_int(int signo) /* SIGINT handler */

{

intr_flag = 1;

return;

}

/* This function is called after SIGINT has been delivered,

* and the main loop has recognized it. (It not called as

* a signal handler, t_intr() above is the handler.) */

void

handle_intr(void)

{

char c;

intr_flag = 0;

clear_intr(); /* turn signal off */

t_alrm(30); /* 30 cond timeout to interrupt printer */

tty_flush(); /* discard any queued output */

c = '003';

block_write(&c, 1); /* Control-C interrupts the PS job */

block_write(&eofc, 1); /* followed by EOF */

proc_upto_eof(1); /* read & ignore up through EOF */

clear_alrm();

get_page(&end_page);

do_acct();

exit(EXIT_SUCCESS); /* success since ur lprm'ed the job */

}

void

t_intr(void) /* enable signal handler */

{

if (signal_intr(SIGINT, sig_int) == SIG_ERR)

log_sys("t_intr: signal_intr error");

}

void

clear_intr(void) /* ignore signal */

{

if (signal(SIGINT, SIG_IGN) == SIG_ERR)

log_sys("clear_intr: signal error");

}

______________________________________________

_________________________

______

程序17.6 处理中断信号的interrupt.c文件

图17.6写明了哪些函数设置超时时间。我们只是在以下情况下设置超时:查询

打印机状态(get_status)、读取打印机的页码(get_page)或者当我们正中断打印机

时(handle_intr)。如果发生了超时,我们只需要记录下错误,过一段时间后终

止。程序17.7是alarm.c文件。

_______________________________________________________________________

______

#include "lprps.h"

static void

sig_alrm(int signo) /* SIGALRM handler */

{

alrm_flag = 1;

return;

}

void

handle_alrm(void)

{

log_ret("printer not responding");

sleep(60); /* it will take at least this long to warm up */

exit(EXIT_REPRINT);

}

void /* Establish the signal handler and t the alarm. */

t_alrm(unsigned int nc)

{

alrm_flag = 0;

if (signal_intr(SIGALRM, sig_alrm) == SIG_ERR)

log_sys("t_alrm: signal_intr error");

alarm(nc);

}

void

clear_alrm(void)

{

alarm(0);

if (signal(SIGALRM, SIG_IGN) == SIG_ERR)

log_sys("clear_alrm: signal error");

alrm_flag = 0;

}

_______________________________________________________________________

______

程序17.7 处理超时的alarm.c文件

程序17.8是函数get_status,这个函数由main函数调用。它发送一个Control

-T到打印机以获取打印机的状态消息。打印机回送一行消息。如果我们接到的消息

是:

%%[ status : idle]%%

这意味着打印机准备好执行一个新的作业。这个消息被函数proc_some_input

读取和处理(下面我们会讨论这个函数)。

_______________________________________________________________________

______

#include "lprps.h"

/* Called by main() before printing job.

* We nd a Control-T to the printer to fetch its status.

* If we timeout before reading the printer's status, something

* is wrong. */

void

get_status(void)

{

char c;

t_alrm(5); /* 5 cond timeout to fetch status */

tty_flush();

c = '024';

block_write(&c, 1); /* nd Control-T to printer */

init_input(0);

while (status == INVALID)

proc_some_input(); /* wait for something back */

switch (status) {

ca IDLE: /* this is what we're looking for ... */

clear_alrm();

return;

ca WAITING: /* printer thinks it's in the middle of a job */

block_write(&eofc, 1); /* nd EOF to printer */

sleep(5);

exit(EXIT_REPRINT);

ca BUSY:

ca UNKNOWN:

sleep(15);

exit(E

XIT_REPRINT);

}

}

_______________________________________________________________________

______

程序17.8 get_status函数

如果我们收到下列消息:

%%[ status: waiting ]%%

这说明打印机正等待我们发送更多的数据以用于当前正打印的作业,这很可能

是前一打印作业出了些问题。为了清除这个状态,我们发送给打印机一个EOF终止

符。

PostScript打印机维护着一个页码计数器。这个计数器每打印一页就会增加,

它即使在关闭电源时也会保存着。为了读此计数器,我们需要发送给打印机一个P

ostScript程序。文件pagecount.c(程序17.9)包含了这个小PostScript程序(含

有大约10个PostScript操作符)和函数get_page。

_______________________________________________________________________

______

#include "lprps.h"

/* PostScript program to fetch the printer's pagecount.

* Notice that the string returned by the printer:

* %%[ pagecount: N ]%%

* will be pard by proc_msg(). */

static char pagecount_string[] =

"(%%[ pagecount: ) print " /* print writes to current output file *



"statusdict begin pagecount end " /* push pagecount onto stack */

"20 string " /* creates a string of length 20 */

"cvs " /* convert to string */

"print " /* write to current output file */

"( ]%%) print "

"flush
"; /* flush current output file */

/* Read the starting or ending pagecount from the printer.

* The argument is either &start_page or &end_page. */

void

get_page(int *ptrcount)

{

t_alrm(30); /* 30 cond timeout to read pagecount *



tty_flush();

block_write(pagecount_string, sizeof(pagecount_string) - 1);

/* nd query to printer

*/

init_input(0);

*ptrcount = -1;

while (*ptrcount < 0)

proc_some_input(); /* read results from printer */

block_write(&eofc, 1); /* nd EOF to printer */

proc_upto_eof(0); /* wait for EOF from printer */

clear_alrm();

}

_______________________________________________________________________

______

程序17.9 pagecount.c文件-得到打印机的计数器值

pagecount_string数组包含了这个小PostScript程序。虽然我们可以用如下方

法得到并打印页码:

statusdict begin pagecuont end = flush

但我们希望得到类似于打印机返回的状态消息的输出格式:

%% [ pagecount: N]%%

然后,proc_some_input函数处理这个消息,其方式与处理打印机的状态消息

相类似。

程序17.10中的函数nd_file由main函数调用,它将用户的PostScript程序发



到打印机上。

_______________________________________________________________________

______

#include "lprps.h"

void

nd_file(void) /* called by main() to copy stdin to printer */

{

int c;

init_input(1);

t_intr(); /* we catch SIGINT */

while ( (c = getchar()) != EOF) /* main loop of program */

out_char(c); /* output each character */

out_char(EOF); /* output final buffer */

block_write(&eofc, 1); /* nd EOF to printer */

proc_upto_eof(0); /* wait for printer to nd EOF back */

}

_______________________________________________________________________

______

程序17.10 nd_file函数

这个函数主要是一个while循环,其中先读取标准输入(getchar),然后调用

函数out_char把字符发送给打印机。当在标准输入上遇到EOF时,就发送一个EOF给

打印机(指示作业完成),然后我们就等待从打印机返回一个EOF(proc_upto_eof

)。

回忆图17.2中,连接在串口的PostScript解释器的输出可能是打印机状态消息

或者是PostScript的print操作符的输出。所以,我们所认为的"文件被打印"甚至

可能一页都不输出。这个PostScript程序文件执行后,把它的结果送回主机。Pos

tScript不是一种编程语言。但是,有时我们确实需要发送一个PostScript程序到

打印机并将结果返回主机,而不是打印在纸上。一个例子是取页码数的PostScrip

t程序,用其可以了解打印机的使用情况。

%!

statusdict begin pagecount end =

如果从PostScript解释器返回的不是状态消息,就以电子邮件形式发送给用户

。程序17.11的mail.c完成这一功能。

_______________________________________________________________________

______

#include "lprps.h"

static FILE *mailfp;

static char temp_file[L_tmpnam];

static void open_mailfp(void);

/* Called by proc_input_char() when it encounters characters

* that are not message characters. We have to nd the

* characters back to the ur. */

void

mail_char(int c)

{

static int done_intro = 0;

if (in_job && (done_intro || c != '
')) {

open_mailfp();

if (done_intro == 0) {

fputs("Your PostScript printer job "

"produced the following output:
", mailfp);

done_intro = 1;

}

putc(c, mailfp);

}

}

/* Called by proc_msg() when an "Error" or "OffendingCommand" key

* is returned by the PostScript interpreter. Send the key and

* val to the ur. */

void

mail_line(const char *msg, const char *val)

{

if (in_job) {

open_mailfp();



fprintf(mailfp, msg, val);

}

}

/* Create and open a temporary mail file, if not already open.

* Called by mail_char() and mail_line() above. */

static void

open_mailfp(void)

{

if (mailfp == NULL) {

if ( (mailfp = fopen(tmpnam(temp_file), "w")) == NULL)

log_sys("open_mailfp: fopen error");

}

}

/* Clo the temporary mail file and nd it to the ur.

* Registered to be called on exit() by atexit() in main(). */

void

clo_mailfp(void)

{

char command[1024];

if (mailfp != NULL) {

if (fclo(mailfp) == EOF)

log_sys("clo_mailfp: fclo error");

sprintf(command, MAILCMD, loginname, hostname, temp_file);

system(command);

unlink(temp_file);

}

}

_______________________________________________________________________

______

程序17.11 mail.c文件



每次在打印机返回一个字符,而且这个字符不是状态消息的一部分时,那么就

调用函数mail_char(下面我们会讨论函数proc_input_char,它调用mail_char)

。只有当函数nd_file正发送一个文件给打印机时,变量in_job才被设置。在其

它时候,例如我们正在读取打印机的状态消息或者打印机的页码计数器值时,它都

不会被设置。然后调用函数mail_line,它将一行写入邮件文件中。

当第一次调用函数open_mailfp时,它生成一个临时文件并把它打开。函数cl

o_mailfp由main函数设置为终止处理程序,当调用exit时就会调用该函数。如果

此时临时邮件文件已经产生,那么关闭这个文件,邮件传给用户。

如果我们发送一行的PostScript程序

%!

statusdict begin pagecount end =

来获得打印机的页码计数,那么返回给我们的邮件消息是

Your postscript printer job produced the following output:

11185

output.c(程序17.12)包含了函数out_char,nd_file调用此函数以便将字

符输出到打印机。

_______________________________________________________________________

______

#include "lprps.h"

static char outbuf[OBSIZE];

static int outcnt = OBSIZE; /* #bytes remaining */

static char *outptr = outbuf;

static void out_buf(void);

/* Output a single character.

* Called by main loop in nd_file(). */

void

out_char(int c)

{

if (c == EOF) {

out_buf(); /* flag that we're all done */

return;

}

if (outcnt <= 0)

out_buf(); /* buffer is full, write it first */

*outptr++ = c; /* just store in buffer */

outcnt--;

}

/* Output the buffer that out_char() has been storing into.

* We have our own output function, so that we never block on a write

* to the printer. Each time we output our buffer to the printer,

* we also e if the printer has something to nd us. If so,



* we call proc_input_char() to process each character. */

static void

out_buf(void)

{

char *wptr, *rptr, ibuf[IBSIZE];

int wcnt, nread, nwritten;

fd_t rfds, wfds;

FD_ZERO(&wfds);

FD_ZERO(&rfds);

t_nonblock(); /* don't want the write() to block */

wptr = outbuf; /* ptr to first char to output */

wcnt = outptr - wptr; /* #bytes to output */

while (wcnt > 0) {

FD_SET(psfd, &wfds);

FD_SET(psfd, &rfds);

if (intr_flag)

handle_intr();

while (lect(psfd + 1, &rfds, &wfds, NULL, NULL) < 0) {

if (errno == EINTR) {

if (intr_flag)

handle_intr(); /* no return */

} el

log_sys("out_buf: lect error");

}

if (FD_ISSET(psfd, &rfds)) { /* printer is readable */

if ( (nread = read(psfd, ibuf, IBSIZE)) < 0)

log_sys("out_buf: read error");

rptr = ibuf;

while (--nread >= 0)

proc_input_char(*rptr++);

}

if (FD_ISSET(psfd, &wfds)) { /* printer is writeable */

if ( (nwritten = write(psfd, wptr, wcnt)) < 0)

log_sys("out_buf: write error");

wcnt -= nwritten;

wptr += nwritten;

}

}

outptr = outbuf; /* ret buffer pointer and count */

outcnt = OBSIZE;

}

_______________________________________________________________________

______

程序17.12 output.c 文件

当传送给out_char的参数是EOF,那表明输入已经结束,最后的输出缓冲内容

应当发送到打印机。

函数out_char把每个字符放到输出缓冲中,当缓冲满了时调用out_buf函数。

我们必须小心编写out_buf函数:我们发送数据到打印机,打印机也可能同时送回

数据。为了避免写操作的阻塞,必须把描述符设置为非阻塞的。(可回忆程序12.

1的例子。)我们使用lect 函数来多路转接双向的I/O:输入和输出。我们在读

取和写入时都设置同一个描述符。还有一种可能是lect函数可能会被一个信号(

SIGINT)所中断,所以我们必须在任何错误返回时对此进行检查。

如果我们从打印机收到异步输入,我们调用proc_input_char来处理每一个字

符。这个输入可能是打印机状态消息或者发送给用户的邮件。

当我们写打印机时,我们必须处理write返回的计数比期望的数量少的情况。

同样,回忆程序12.1的例子,其中在每次写入时终端可以接收任意数量的数据。

文件input.c(见程序17.13),定义了处理所有从打印机输入的函数。这种输

入可以是打印机状态消息或者给用户的输出。

_______________________________________________________________________

______

#include "lprps.h"

static int eof_count;

static int ignore_input;

static enum par_state { /* state of parsing input from printer */

NORMAL,

HAD_ONE_PERCENT,

HAD_TWO_PERCENT,

IN_MESSAGE,

HAD_RIGHT_BRACKET,

HAD_RIGHT_BRACKET_AND_PERCENT

} par_state

;

/* Initialize our input machine. */

void

init_input(int job)

{

in_job = job; /* only true when nd_file() calls us */

par_state = NORMAL;

ignore_input = 0;

}

/* Read from the printer until we encounter an EOF.

* Whether or not the input is procesd depends on "ignore". */

void

proc_upto_eof(int ignore)

{

int ec;

ignore_input = ignore;

ec = eof_count; /* proc_input_char() increments eof_count */

while (ec == eof_count)

proc_some_input();

}

/* Wait for some data then read it.

* Call proc_input_char() for every character read. */

void

proc_some_input(void)

{

char ibuf[IBSIZE];

char *ptr;

int nread;

fd_t rfds;

FD_ZERO(&rfds);

FD_SET(psfd, &rfds);

t_nonblock();

if (intr_flag)

handle_intr();

if (alrm_flag)

handle_alrm();

while (lect(psfd + 1, &rfds, NULL, NULL, NULL) < 0) {

if (errno == EINTR) {

if (alrm_flag)

handle_alrm(); /* doesn't return */

el if (intr_flag)

handle_intr(); /* doesn't return */

} el

log_sys("proc_some_input: lect error");

}

if ( (nread = read(psfd, ibuf, IBSIZE)) < 0)

log_sys("proc_some_input: read error");

el if (nread == 0)

log_sys("proc_some_input: read returned 0");

ptr = ibuf;

while (--nread >= 0)

proc_input_char(*ptr++); /* process each character */

}

/* Called by proc_some_input() above after some input has been read.

* Also called by out_buf() whenever asynchronous input appears. */

void

proc_input_char(int c)

{

if (c == '004') {

eof_count++; /* just count the EOFs */

return;

} el if (ignore_input)

return; /* ignore everything except EOFs */

switch (par_state) { /* par the input */

ca NORMAL:

if (c == '%')

par_state = HAD_ONE_PERCENT;

el

mail_char(c);

break;

ca HAD_ONE_PERCENT:

if (c == '%')

par_state = HAD_TWO_PERCENT;

el {

mail_char('%'); mail_char(c);

par_state = NORMAL;

}

break;

ca HAD_TWO_PERCENT:

if (c == '[') {

msg_init(); /* message starting; init buffer */

par_state = IN_MESSAGE;

} el {

mail_char('%'); mail_char('%'); mail_char(c);

par_state = NORMAL;

}

break;

ca IN_MESSAGE:

if (c == ']')

par_state = HAD_RIGHT_BRACKET;

el

msg_char(c);

break;

ca HAD_RIGHT_BRACKET:

if (c == '%')

par_state = HAD_RIGHT_BRACKET_AND_PERCENT;

el {

msg_char(']'); msg_char(c);

par_state = IN_MESSAGE;

}

break;

ca HAD_RIGHT_BRACKET_AND_PERCENT:

if (c == '%') {

par_state = NORMAL;

proc_msg(); /* we have a message; process it */

} el {

msg_char(']'); msg_char('%'); msg_char(c);

par_state = IN_MESSAGE;

}

break;

default:

abort();

}

}

_______________________________________________________________________

______

程序17.13 input.c文件-读取和处理从打印机的

输入

每当我们等待从打印机返回EOF时就会调用函数proc_upto_eof。

函数proc_some_input从串口读取。注意我们调用lect 函数来确定什么时候

该描述符是可以读取的。这是因为lect 函数通常被一个捕捉到的信号所中断-它

并不自动地重启动。因为lect 函数能被SIGALRM或SIGINT所中断,我们并不希望

它重启动。回忆一下在12.5节中我们关于lect函数被正常中断的讨论。同样回忆

在10.5节中我们设置SA_RESTART来说明当一个特定信号出现时,应当自动重启动的

I/O函数。但是因为并不总是有一个附加的标志,使得我们可以说明I/O 函数不应

当重启动。如果不设置SA_RESTART,我们只能倚赖系统的缺省值,而这可能是自动

重新启动被中断的I/O函数。当从打印机返回输入时,我们以非阻塞模式读取,得

到打印机准备就绪的数据。然后调用函数proc_input_char来处理每一个字符。

处理打印机发送给我们的消息是由proc_input_char完成的。我们必须检查每

一个字符并记住我们的状态。变量par_state跟踪记录当前状态。调用msg_char

函数把序列%%[以后所有的字符串储存在消息缓冲中。当遇到结束序列]%%时,我们

调用proc_msg来处理消息。除了开始%%[ 和最后 ]%%序列以及二者之间的状态消息

其他字符串,都被认为是用户的输出,被邮递给用户(调用mail_char)。

我们现在查看那些处理由输入函数积累消息的函数。程序17.14是文件messag

e.c。

当检测到%%[ 后,调用函数msg_init。它只是初始化缓冲计数器。然后对于消

息中的每一个字符都调用msg_char函数。

函数proc_msg将消息分解为key:val对(关键字:值对),并检查key。调用A

NSI C的strtok函数将消息分解为记号,每个key: val对用分号分隔。

一个以下形式的消息

%%[ Flushing : rest of job (to end-of-fiel) will be ignored ]%%

引起函数printer_flushing 被调用。它清理终端的缓冲,发送一个EOF给打印

机,然后等待打印机返回一个EOF。

如果收到一个以下形式的消息

%%[ PrinterError: reason]%%

就调用log_msg函数来记录这个错误。带有Error Key的出错消息邮递传送回用

户。这些一般是PostScript程序的错误。

如果返回一个带有关键字status的状态消息,它很可能是由于函数get_statu

s发送给打印机一个状态请求(Control-T)而引起的。我们查看val,并按照它来

设置变量status。

_______________________________________________________________________

______

#include "lprps.h"

#include

static char msgbuf[MBSIZE];

static int msgcnt;

static void printer_flushing(void);

/* Called by proc_input_char() after it's en the "%%[" that

* starts a message. */

void

ms

g_init(void)

{

msgcnt = 0; /* count of chars in message buffer */

}

/* All characters received from the printer between the starting

* %%[ and the terminating ]%% are placed into the message buffer

* by proc_some_input(). This message will be examined by

* proc_msg() below. */

void

msg_char(int c)

{

if (c != '0' && msgcnt < MBSIZE - 1)

msgbuf[msgcnt++] = c;

}

/* This function is called by proc_input_char() only after the final

* percent in a "%%[ ]%%" has been en. It pars the

* , which consists of one or more "key: val" pairs.

* If there are multiple pairs, "val" can end in a micolon. */

void

proc_msg(void)

{

char *ptr, *key, *val;

int n;

msgbuf[msgcnt] = 0; /* null terminate message */

for (ptr = strtok(msgbuf, ";"); ptr != NULL;

ptr = strtok(NULL, ";")) {

while (isspace(*ptr))

ptr++; /* skip leading spaces in key */

key = ptr;

if ( (ptr = strchr(ptr, ':')) == NULL)

continue; /* missing colon, something wrong, ignore */

*ptr++ = '0'; /* null terminate key (overwrite colon) */

while (isspace(*ptr))

ptr++; /* skip leading spaces in val */

val = ptr;

/* remove trailing spaces in val */

ptr = strchr(val, '0');

while (ptr > val && isspace(ptr[-1]))

--ptr;

*ptr = '0';

if (strcmp(key, "Flushing") == 0) {

printer_flushing(); /* never returns */

} el if (strcmp(key, "PrinterError") == 0) {

log_msg("proc_msg: printer error: %s", val);

} el if (strcmp(key, "Error") == 0) {

mail_line("Your PostScript printer job "

"produced the error `%s'.
", val);

} el if (strcmp(key, "status") == 0) {

if (strcmp(val, "idle") == 0)

status = IDLE;

el if (strcmp(val, "busy") == 0)

status = BUSY;

el if (strcmp(val, "waiting") == 0)

status = WAITING;

el

status = UNKNOWN; /* "printing", "PrinterError",

"initializing", or "printing test page". */

} el if (strcmp(key, "OffendingCommand") == 0) {

mail_line("The offending command was `%s'.
", val);

} el if (strcmp(key, "pagecount") == 0) {

if (sscanf(val, "%d", &n) == 1 && n >= 0) {

if (start_page < 0)

start_page = n;

el

end_page = n;

}

}

}

}

/* Called only by proc_msg() when the "Flushing" message

* is received from the printer. We exit. */

static void

printer_flushing(void)

{

clear_intr(); /* don't catch SIGINT */

tty_flush(); /* empty tty input and output queues */

block_write(&eofc, 1); /* nd an EOF to the printer */

proc_upto_eof(1); /* this call won't be recursive,

since we specify to ignore input */

get_page(&end_page);

do_acct();

exit(EXIT_SUCCESS);

}

_______________________________________________________________________

______

程序17.14 message.c文件,处理从打印机返回消息

一个OffendingComman

d的关键字一般总是与其它key : val对一起出现,如

%% [ Error : stackunderflow ; OffendingCommand : pop ]%%

我们在送回给用户的邮件中就要添加一行。

最后,在函数get_page(程序17.9)中的PostScript程序产生一个页面计数值

。我们调用sscanf把val转换为二进制,设置起始或结束页面值变量。函数get_pa

ge中的while循环就在等待这个变量变成非负值。

17.5 摘要

在这一章中实现一个完整的程序-它发送一个PostScript程序给连接在RS-232

端口的PostScript打印机。这给我们一个实践机会,把前些章所介绍的很多函数用



到一个实用的程序中:例如I/O多路转接、非阻塞的I/O、终端I/O和信号等。

习题:

17.1 我们需要使用lprps来打印标准输入的文件,它也可能是一个管道。因为程序

psif一定要查看输入文件的前两个字节,那么应当如何开发psif程序(图17.5)来

处理这种情况呢?

17.2 实现psif过滤器,处理前一个练习中的实例。

17.3 参考12.5节中Adobe Systems[1998]关于在Postscript程序中字体请求的处理

,修改本章中的lprps程序以处理字体请求。





--





--

※ 来源:·BBS 水木清华站 ·[FROM: 202.38.248.38]



本文发布于:2022-12-29 02:11:43,感谢您对本站的认可!

本文链接:http://www.wtabcd.cn/fanwen/fan/90/50462.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

上一篇:jay怎么读
下一篇:indecision
标签:postscript
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图