From 6c29488ae32ccdf1d594d24a286bdb6f1c53ad32 Mon Sep 17 00:00:00 2001 From: short <> Date: Sun, 14 Apr 2002 21:32:16 +0000 Subject: [PATCH 1/1] ftp://ftp.metalab.unc.edu/pub/Linux/apps/serialcomm/fax/efax-0.9.tar.gz md5sum: 23bd3767f87c455c58ccae7f88bce725 size: 96736 --- COPYING | 340 +++++++++ Makefile | 46 ++ README | 84 +++ efax.1 | 1289 +++++++++++++++++++++++++++++++++ efax.c | 2354 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ efaxio.c | 428 +++++++++++ efaxio.h | 54 ++ efaxlib.c | 2306 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ efaxlib.h | 218 ++++++ efaxmsg.c | 180 +++++ efaxmsg.h | 31 + efaxos.c | 510 +++++++++++++ efaxos.h | 86 +++ efix.1 | 264 +++++++ efix.c | 447 ++++++++++++ fax | 1040 +++++++++++++++++++++++++++ fax.1 | 236 +++++++ 17 files changed, 9913 insertions(+) create mode 100644 COPYING create mode 100644 Makefile create mode 100644 README create mode 100644 efax.1 create mode 100644 efax.c create mode 100644 efaxio.c create mode 100644 efaxio.h create mode 100644 efaxlib.c create mode 100644 efaxlib.h create mode 100644 efaxmsg.c create mode 100644 efaxmsg.h create mode 100644 efaxos.c create mode 100644 efaxos.h create mode 100644 efix.1 create mode 100644 efix.c create mode 100755 fax create mode 100644 fax.1 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..60549be --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ffefde8 --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +# Makefile for efax + +# Change the following to the name of your ANSI C compiler +# (normally gcc). + +CC=gcc + +# Compile/load options. Add -DNO_STRERROR to CFLAGS if _strerror +# is undefined + +CFLAGS= +LDFLAGS= + +# Change the following to the destination directories for +# binaries and man pages. Probably /usr/bin and /usr/man on +# Linux, /usr/local/{bin,man} on other systems. + +BINDIR=/usr/bin +MANDIR=/usr/man + +.c.o: + $(CC) $(CFLAGS) -c $< + +all: efax efix + +efax: efax.o efaxlib.o efaxio.o efaxos.o efaxmsg.o + $(CC) -o efax $(LDFLAGS) efax.o efaxlib.o efaxio.o efaxos.o efaxmsg.o + strip efax + +efix: efix.o efaxlib.o efaxmsg.o + $(CC) -o efix $(LDFLAGS) efix.o efaxlib.o efaxmsg.o + strip efix + +install: + cp fax efax efix $(BINDIR) + cp fax.1 efax.1 efix.1 $(MANDIR)/man1 + +clean: + rm -f efax efix efax.o efix.o efaxlib.o efaxio.o efaxos.o efaxmsg.o + +efax.o: efax.c efaxmsg.h efaxlib.h efaxio.h efaxos.h +efaxio.o: efaxio.c efaxmsg.h efaxio.h efaxos.h +efaxos.o: efaxos.c efaxmsg.h efaxlib.h efaxos.h +efix.o: efix.c efaxmsg.h efaxlib.h +efaxlib.o: efaxlib.c efaxmsg.h efaxlib.h +efaxmsg.o: efaxmsg.c efaxmsg.h diff --git a/README b/README new file mode 100644 index 0000000..07c107d --- /dev/null +++ b/README @@ -0,0 +1,84 @@ + + Introduction + +This is the README file for version 0.9 of efax, a small ANSI +C/POSIX program that sends and receives faxes using any fax modem +(Class 1, 2 or 2.0). + +efax is smaller and easier to install than HylaFAX or +mgetty+sendfax. As one user put it ``EFAX is a nice simple +program for single user systems.'' + +The ``fax'' command, a shell script, lets you send, receive, view +and print faxes. In larger systems, faxes can be sent by +printing to a ``fax'' printer and received faxes can be e-mailed +as MIME attachments to an administrator for distribution. efax +can also pass incoming data calls to a getty program. + +The efax package includes ``efix,'' a program to convert between +various image file formats. To fax Postscript files you will +need Ghostscript. To view faxes you can use any program that +displays PGM files (e.g. xloadimage or xv). efix can convert +received files to Postscript or HP Laserjet formats for printing. + +This version of the program was written & tested under Linux 2.0. +Previous versions have been compiled and used under most versions +of Unix and should work with minor changes on any Unix with an +ANSI C compiler and libraries that include select(2) and +termios(4). + +efax is distributed as a gzip'ed tar file, efax-0.9.tar.gz. It +may be obtained by anonymous FTP from metalab.unc.edu in +/pub/Linux/apps/serialcomm/fax. + + Changes from version 0.8a to version 0.9 + +- fixed bad (0x0 pixel) file output on new glibc systems + +- fixed bad file output on 64-bit systems + +- automatic selection of baud rate and class + +- hardware flow control made optional + +- modernized directory and file names + +- many bugs removed, others added + + Manifest + +The efax distribution should contain the following files: + +README - this file +COPYING - the GNU General Public License +Makefile - makefile to make all/install/clean +efax.c - program to send and receive faxes +efix.c - program to convert between file formats +efaxmsg.{h,c} - functions to print errors, warnings, etc +efaxlib.{h,c} - functions common to efax and efix +efaxio.{h,c} - low-level modem i/o functions +efaxos.{h,c} - OS-dependent functions +fax.1 - man page for fax(1) +efax.1 - man page for efax(1) +efix.1 - man page for efix(1) +fax - a (Bourne) shell script to create, send, + receive, view and print faxes. + + Installation + +Edit the makefile and change the compile command and destination +directories if required. + +Type "make" to compile the efax and efix binaries. + +Edit the configuration information at the beginning of the +``fax'' script according to the comments. + +Type "make install" to install the fax script, the binaries and +the man pages. + +Read the fax(1) man page first for information on using efax. + +The efax(1) man page has details on resolving problems, setting +up a network fax server and using efax to handle both fax and +data calls. diff --git a/efax.1 b/efax.1 new file mode 100644 index 0000000..8b61c48 --- /dev/null +++ b/efax.1 @@ -0,0 +1,1289 @@ +.TH EFAX 1 "February 1999" "" "" +.UC 1 +.SH NAME +efax \- send/receive faxes with Class 1, 2 or 2.0 fax modem + +.ce 1 +(Please read the \fBfax\fP man page first.) +.SH SYNOPSIS + +.B efax +[ +\fIoptions\fP +] +[ +\fB-t\fP \fInum\fP [ \fIfile\fP... ] +] + +.SH OPTIONS +Where \fIoptions\fP are: + +.TP 9 +.B -a \fIcmd\fP +use the command \fBATcmd\fP when answering the phone. The +default is "A". + +.TP 9 +.B -c \fIcaps\fP +set the local modem capabilities. See the section on +capabilities below for the format and meaning of \fIcaps\fP. For +Class 1 the default is 1,n,0,2,0,0,0,0 where n is the highest +speed supported by the modem. For Class 2 the default is +determined by the modem. + +.TP 9 +.B -d \fIdev\fP +use the fax modem connected to device \fIdev\fP. The default is +\fB/dev/modem\fP. + +.TP 9 +.B -f \fIfnt\fP +use font file \fIfnt\fP for generating the header. The default +is a built-in 8x16 font. See the efix(1) -f option for the font +file format. + +.TP 9 +.B -g \fIcmd\fP +if a \fBCONNECT\fP (or \fBDATA\fP) response indicates a data +call, the shell \fB/bin/sh\fP is exec(2)'ed with \fIcmd\fP as its +command. \fIcmd\fP is a printf(3) format that may contain up to +6 %d escapes which are replaced by the baud rate following the +most recent \fBCONNECT\fP message. \fIcmd\fP typically exec's +getty(8). + +.TP 9 +.B -h \fIhdr\fP +put string `hdr' at the top of each page. The first %d in `hdr' +is replaced by the page number and the second, if any, is +replaced by the number of pages being sent. + +.TP 9 +.B -i \fIstr\fP +.TP 9 +.B -j \fIstr\fP +.TP 9 +.B -k \fIstr\fP +send the command \fBAT\fP\fIstr\fP to the modem to initialize it. +-i commands are sent before the modem is put into fax mode, -j +commands after the modem is in fax mode, and -k commands just +before efax exits. The only default is a hang-up (ATH) command +that is sent before exiting only if no other -k options are +given. Multiple options may be used. + +.TP 9 +.B -l \fIid\fP +set the local identification string to \fIid\fP. \fIid\fP should +be the local telephone number in international format (for +example "+1 800 555 1212"). This is passed to the remote fax +machine. Some fax machines may not accept characters other than +numbers, space, and '+'. + +.TP 9 +.B -o \fIopt\fP +use option \fIopt\fP to accommodate a non-standard fax modem +protocol. See the MODEM REQUIREMENTS section below for more +details. The \fIopt\fPions are: + +.TP 9 +.B + 0 +Force use of Class 2.0 fax modem commands. The modem must +support Class 2.0. + +.TP 9 +.B + 2 +Force use of Class 2 fax modem commands. The modem must support +Class 2. + +.TP 9 +.B + 1 +Force use of Class 1 fax modem commands. The modem must support +Class 1. By default efax queries the modem and uses the first of +the three above classes which is supported by the modem. + +.TP 9 +.B + a +use software adaptive answer method. If the first attempt to +answer the call does not result in a data connection within 8 +seconds the phone is hung up temporarily and answered again in +fax mode (see "Accepting both fax and data calls" below). + +.TP 9 +.B + e +ignore errors in modem initialization commands. + +.TP 9 +.B + f +use "virtual flow control". efax tries to estimate the number of +bytes in the modem's transmit buffer and pauses as necessary to +avoid filling it. The modem's buffer is assumed to hold at least +96 bytes. This feature does not work properly with Class 2 +modems that add redundant padding to scan lines. Use this option +only if you have problems configuring flow control. + +.TP 9 +.B + h +use hardware (RTS/CTS) in addition to software (XON/XOFF) flow +control. Many modems will stop responding if this option is +used. See the section `Resolving Problems' before using this +option. + +.TP 9 +.B + l +halve the time between testing lock files when waiting for other +programs to complete. By default this is 8 seconds. For example +-olll sets the interval to 1 second. + +.TP 9 +.B + n +ignore requests for pages to be retransmitted. Use this option if +you don't care about the quality of the received fax or if the +receiving machine is too fussy. Otherwise each page may be +retransmitted up to 3 times. + +.TP 9 +.B + r +do not reverse bit order during data reception for Class 2 +modems. Only Multitech modems require this option. Not normally +required since efax detects these modems. + +.TP 9 +.B + x +send XON (DC1) instead of DC2 to start data reception. Applies +to a very few Class 2 modems only. + +.TP 9 +.B + z +delay an additional 100 milliseconds before each modem +initialization or reset command. The initial delay is 100 +ms. For example, -ozzz produces a 400 ms delay. Use with modems +that get confused when commands arrive too quickly. + + +.TP 9 +.B -q \fIn\fP +ask for retransmission of pages received with more than \fIn\fP +errors. Default is 10. + +.TP 9 +.B -r \fIpat\fP +each received fax page is stored in a separate file. The file +name is created using \fIpat\fP as a strftime(3) format string. +A page number of the form .001, .002, ... is appended to the +file name. If \fIpat\fP is blank ("") or no -r option is given a +default string of "%m%d%H%M%S" is used. + +.\" If a file already exists, efax terminates with an error. + +.TP 9 +.B -s +remove lock file(s) after initializing the modem. This allows +outgoing calls to proceed when efax is waiting for an incoming +call. If efax detects modem activity it will attempt to re-lock +the device. If the modem has been locked by the other program +efax will exit and return 1 (``busy''). Normally a new efax +process is then started by init(8). The new efax process will +then check periodically until the lock file disappears and +then re-initialize the modem. + +.TP 9 +.B -t \fInum [file\fP...] +dial telephone number \fInum\fP and send the fax image files +\fIfile\fP.... If used, this must be the last argument on the +command line. The telephone number \fInum\fP is a string that +may contain any dial modifiers that the modem supports such as a +T prefix for tone dialing or commas for delays. If no file names +are given the remote fax machine will be polled. If no -t +argument is given efax will answer the phone and attempt to +receive a fax. + +.TP 9 +.B -v \fIstrng\fP +select types of messages to be printed. Each \fIlower-case\fP +letter in \fIstrng\fP enables one type of message: + +.RS 12 +.B +e - +errors +.br +.B +w - +warnings +.br +.B +i - +session progress information +.br +.B +n - +capability negotiation information +.br +.B +c - +modem (AT) commands and responses +.br +.B +h - +HDLC frame data (Class 1 only) +.br +.B +m - +modem output +.br +.B +a - +program arguments +.br +.B +r - +reception error details +.br +.B +t - +transmission details +.br +.B +f - +image file details +.br +.B +x - +lock file processing + +.RE +.RS 9 +Up to two -v options may be used. The first is for messages +printed to the standard error and the second is for messages to +the standard output. The default is "ewin" to the standard error +only. +.RE + +.TP 9 +.B -w +wait for an OK or CONNECT prompt instead of issuing an answer +(\fBATA\fP) command to receive a fax. Use this option when the +modem is set to auto-answer (using S0=\fIn\fP) or if another +program has already answered the call. + +.TP 9 +.B -x \fIlkf\fP +use a UUCP-style lock file \fIlkf\fP to lock the modem device +before opening it. If the device is locked, efax checks every 15 +seconds until it is free. Up to 16 -x options may be used if +there are several names for the same device. A `#' prefix on the +file name creates an binary rather than text (HDB-style) lock +file. This is the reverse of what was used by previous efax +versions. + +.SH FAX FILE FORMATS + +efax can read the same types of files as \fBefix(1)\fP including +text, T.4 (Group 3), PBM, single- and multi-page TIFF (G3 and +uncompressed). efax automatically determines the type of file +from its contents. TIFF files are recommended as they contain +information about the image size and resolution. + +Each page to be sent should be converted to a separate TIFF +format file with Group 3 (G3) compression. Received files are +also stored in this format. The EXAMPLES section below shows how +efix and other programs can be used to create, view and print +these files. + +.SH OPERATING SYSTEM REQUIREMENTS + +The operating system must provide short response times to avoid +protocol timeouts. For Class 2 and 2.0 modems the delay should +not exceed 1 or 2 seconds. + +When using Class 1 modems the program must respond to certain +events within 55 milliseconds. Longer delays may cause the fax +protocol to fail in certain places (between DCS and TCF or +between RTC and MPS). Class 1 modems should therefore not be +used on systems that cannot guarantee that the program will +respond to incoming data in less than 55 milliseconds. In +particular, some intelligent serial cards and terminal servers +may introduce enough delay to cause problems with Class 1 +operation. + +The operating system must also provide sufficient low-level +buffering to allow uninterrupted transfer of data between the +modem and a disk file at the selected baud rate, typically 9600 +bps. Since the fax protocol does not provide end-to-end flow +control the effectiveness of flow control while receiving is +limited by the size of the modem's buffer. This can be less than +100 bytes. Efax does not use flow control during reception. + +.SH MODEM REQUIREMENTS + +The "Group" is the protocol used to send faxes between fax +machines. Efax supports the Group 3 protocol used over the +public telephone network. + +The "Class" is the protocol used by computers to control fax +modems. Efax supports Class 1, 2 and 2.0 fax modems. + +Most fax modems use XON/XOFF flow control when in fax mode. This +type of flow control adds very little overhead for fax use. Many +modems have unreliable hardware (RTS/CTS) flow control in fax +mode. By default efax enables only XON/XOFF flow control and the +-oh option must be used to add hardware flow control. + +While some modems have serial buffers of about 1k bytes, many +inexpensive modems have buffers of about one hundred bytes and +are thus more likely to suffer overruns when sending faxes. + +A few older modems may need a delay between commands of more than +the default value used by efax (100 milliseconds). If the delay +is too short, commands may not echo properly, may time out, or +may give inconsistent responses. Use one or more \fB-oz\fP +options to increase the delay between modem initialization +commands and use the E0 modem initialization command to disable +echoing of modem commands. + +By default efax sends DC2 to start the data flow from the modem +when receiving faxes from Class 2 modems. A few older modems +require XON instead. Use of DC2 would cause the modem to give an +error message and/or the program to time out. The \fB-ox\fP +option should be used in this case. + +A few older Class 2 modems (e.g. some Intel models) don't send +DC2 or XON to start the data flow to the modem when sending +faxes. After waiting 2 seconds efax will print a warning and +start sending anyways. + +A very few Class 2 modems do not reverse the bit order (MSB to +LSB) by default on receive. This might cause errors when trying +to display or print the received files. The \fB-or\fP option can +be used in this case. + +Some inexpensive "9600 bps" fax modems only \fItransmit\fP at +9600 bps and reception is limited to 4800 bps. + +The following Class 1 modems have been reported to work with efax: +AT&T DataPort, +.\" Andrea Gozzi , v0.6, SCO 3.2.0, (Class 1) +Cardinal Digital Fax Modem (14400), +.\" awk0%navajo@gte.com, v0.6, linux 1.0, downloading fax144c.car +Digicom Scout+, +.\" umlin000@CC.UManitoba.CA, v 0.6, Linux 1.1.12 +Motorola Lifestyle 28.8, +.\" mortbay@ozemail.com.au +Motorola Power 28.8, +.\" danz@wv.mentorg.com, Linux 1.2.10 +QuickComm Spirit II, +.\" umlin000@CC.UManitoba.CA, v 0.6, Linux 1.1.12 +.\" gsmith@softsmiths.oz.au, v 0.7a, add "*F1" for Xon/Xoff +Smartlink 9614AV-Modem, +.\" gt@sky.gun.de, v0.6, Linux 1.1.15 +Supra Faxmodem 144LC, +.\" john@johncon.johncon.com, v0.6, Consensys (ie., Unixware) 4.2 +USR Courier V.32bis Terbo, +.\" meyer@geomatic.no, v0.6, SunOS 4.1.3 +USR Sportster (V.32 and V.34), +.\" satyr!kayvan@apple.com (Kayvan Sylvan), v0.6, Linux (?) +Zoom AFC 2.400, +.\" hausutzu@pse.panic.bln.sub.org (Utz-Uwe Haus), v0.6, Linux +Zoom VFX14.4V. +.\" edc@ee.ubc.ca (me!), v0.6, Linux + +The following Class 2 modems have been reported to work with efax: +14k4 Amigo Communion fax/modem, +.\" bekker@tn.utwente.nl, efax0.5 +Adtech Micro Systems 14.4 Fax/modem, +.\" gmaughan@grape.fcit.monash.edu.au, Linux 1.2.10, efax 07a +askey modem type 1414VQE, +.\" thowi@chiba.escape.de, efax06?, Linux? +AT&T DataPort, +.\" ingber@alumni.caltech.edu (Class 2) +ATT/Paradyne, +.\" john@johncon.johncon.com +AT&T Paradyne PCMCIA, +.\" jh@datanet.tele.fi (Juha Heinanen) +Boca modem, +.\" ? +BOCA M1440E, +.\" v0.6a, SunOS 4.1.1, Linux 1.0.9 +.\" hsw1@papa.attmail.com +Crosslink 9614FH faxmodem, +.\" ? +FuryCard DNE 5005, +.\" a PCMCIA Class 3 faxmodem +.\" ron@draconia.hacktic.nl +GVC 14.4k internal, +.\" jchen@ee.mcgill.ca, 0.6a w/ stty fax patch, Linux kernel 1.1.59 +Intel 14.4 fax modem, +.\" (matloff@cs.ucdavis.edu) +Megahertz 14.4, +,\" norman@bellcore.com +Microcom DeskPorte FAST ES 28.8, +.\" Skip Montanaro (skip@automatrix.com), 0.6a, Linux +Motorola UDS FasTalk II, +.\" Raj Mathur (root@darbari.ncst.ernet.in), 0.6a, Linux 1.1.48 +MultiTech 1432MU, +.\"reb@pdsf.ssc.gov +Practical Peripherals PM14400FXMT, +.\" (DEC Alpha AXP 3000/500 running OSF/1 V1.3) +Supra V32bis, +.\" john@johncon.johncon.com, v0.5b, SysV R4.2 +.\" tbucks!timothy@csn.org +.\" (ROCKWELL) +.\" Alec.Muffett@UK.Sun.COM (Alec Muffett), Linux 1.1.51, +.\" Supra FAXModem v.32bis +Telebit Worldblazer, +.\" blurfl!jhood@Dartmouth.EDU +.\" Telebit Worldblazer with ROM version LA7.02. (requires -or) +.\" (my configuration required hardware flow control) +.\" Dario_Ballabio@milano.europe.dg.com, v 0.6, Version LA7.05C. +TKR DM-24VF+, +.\" rainer.dorsch@student.uni-ulm.de +Twincom 144/DFi, +.\" (ROCKWELL, V.32AC, V1.270 TR14-Jxxx-001) +ViVa 14.4/Fax modem, +.\" Robert.Sprockeels@csc.be, v0.6a, Linux +Vobis Fax-Modem (BZT-approved), +.\" klein@pc-klein.zxa.basf-ag.de (Peter Klein), Linux, kernel 0.99.14 +.\" beck@irs.inf.tu-dresden.de (Andre Beck), v 0.6, Ultrix 4.3, gcc V2.5.8: +.\" gcc -ansi -D_XOPEN_SOURCE -O2 efax.c -o efax -lcP +Zoom VFX14.4V, +.\" edc@ee.ubc.ca (me!), v0.6, Linux +ZyXEL U-1496E[+], +.\" plph@umcc.umich.edu, v0.3 & faxmodem ROM version 5.05M) +.\" requires -or +.\" Marc@Synergytics.Com, v0.5a & ZyXEL 1496E Plus, ROM Version 6.11a) +.\" -or -i '+FCLASS=2;+FCR=1' -c '+FDCC=1,5,2,2,0,0,0,0' +ZyXEL Elite 2864I. +.\" schlatt@dial.eunet.ch, v0.7a, using -Xn (n<4) + +.SH MODEM INITIALIZATION OPTIONS + +The required modem initialization commands are generated by efax. +Additional commands may be supplied as command-line arguments. +The modem must be set up to issue verbose(text) result codes. +The following command does this and is sent by efax before trying +to initialize the modem. + +.TP 9 +.BR Q0V1 +respond to commands with verbose result codes + +.PP +The following commands may be useful for special purposes: + +.TP 9 +.BR X3 +don't wait for dial tone before dialing. This may be used to +send a fax when the call has already been dialed manually. In +this case use an empty string ("") as the first argument to the +\fB-t\fP command. Use \fBX4\fP (usual default) to enable all +result codes. + +.TP 9 +.BR M2 +leave the monitor speaker turned on for the duration of the call +(use \fBM0\fP to leave it off). + +.TP 9 +.BR L0 +turn monitor speaker volume to minimum (use \fBL3\fP for maximum). + +.TP 9 +.BR E0 +disable echoing of modem commands. See the Resolving Problems +section below. + +.TP 9 +.BR &D2 +returns the modem to command mode when DTR is dropped. The +program drops DTR at the start and end of the call if it can't +get a response to a modem command. You can use \fB&D3\fP to +reset the modem when DTR is dropped. + +.TP 9 +.BR S7=120 +wait up to two minutes (120 seconds) for carrier. This may be +useful if the answering fax machine takes a long time to start +the handshaking operation (e.g. a combined fax/answering machine +with a long announcement). + +.SH CAPABILITIES + +The capabilities of the local hardware and software can be set +using a string of 8 digits separated by commas: + +.BR \fIvr\fP,\fIbr\fP,\fIwd\fP,\fIln\fP,\fIdf\fP,\fIec\fP,\fIbf\fP,\fIst\fP + +where: + +.TP 9 +.I vr \fP (vertical resolution) = +0 for 98 lines per inch +.br +1 for 196 lpi + +.TP 9 +.I br \fP (bit rate) = +0 for 2400 bps +.br +1 for 4800 +.br +2 for 7200 +.br +3 for 9600 +.br +4 for 12000 (V.17) +.br +5 for 14400 (V.17) + +.TP 9 +.I wd \fP (width) = +0 for 8.5" (21.5 cm) page width +.br +1 for 10" (25.5 cm) +.br +2 for 12" (30.3 cm) + +.TP 9 +.I ln \fP (length) = +0 for 11" (A4: 29.7 cm) page length +.br +1 for 14" (B4: 36.4 cm) +.br +2 for unlimited page length + +.TP 9 +.I df \fP (data format) = +0 for 1-D coding +.br +1 for 2-D coding (not supported) + +.TP 9 +.I ec \fP (error correction) = +0 for no error correction +.\" .br +.\" 1 for EC mode with 64 byte frames (not supported) +.\" .br +.\" 2 for EC mode with 256 byte frames (not supported) + +.TP 9 +.I bf \fP (binary file) = +0 for no binary file transfer + +.TP 9 +.I st \fP (minimum scan time) = +0 for zero delay per line +.br +1 for 5 ms per line +.br +3 for 10 ms per line +.br +5 for 20 ms per line +.br +7 for 40 ms per line + +.PP + +When \fIreceiving\fP a fax the \fIvr\fP, \fIwd\fP, and \fIln\fP +fields of the capability string should be set to the maximum +values that your display software supports. The default is 196 +lpi, standard (8.5"/21.5cm) width and unlimited length. + +When \fIsending\fP a fax efax will determine \fIvr\fP and +\fIln\fP from the image file and set \fIwd\fP to the default. + +If the receiving fax machine does not support high resolution +(\fIvr\fP=1) mode, efax will reduce the resolution by combining +pairs of scan lines. If the receiving fax machine does not +support the image's width then efax will truncate or pad as +required. Most fax machines can receive \fIln\fP up to 2. Few +machines support values of \fIwd\fP other than 0. + + +.SH HEADERS + +efax adds blank scan lines at the top of each image when it is +sent. This allows room for the page header but increases the +length of the image (by default about 0.1" or 2.5mm of blank +space is added). + +The header placed in this area typically includes the date and +time, identifies the, and shows the page number and total pages. +Headers cannot be disabled but the header string can be set to a +blank line. + +The default font for generating the headers is the built-in 8x16 +pixel font scaled to 12x24 pixels (about 9 point size). + +Note that both efax and efix have -f options to specify the font. +efIx uses the font to generate text when doing text-to-fax +conversions (during "fax make") while efAx uses the font to +generate the header (during "fax send"). + +.SH SESSION LOG + +A session log is written to the standard error stream. This log +gives status and error messages from the program as selected by +the \fB-v\fP option. A time stamp showing the full time or just +minutes and seconds is printed before each message. Times +printed along with modem responses also show milliseconds. + +.SH RETURN VALUES + +The program returns an error code as follows: + +.TP 9 +0 +The fax was successfully sent or received. + +.TP 9 +1 +The dialed number was busy or the modem device was in use. Try +again later. + +.TP 9 +2 +Something failed (e.g. file not found or disk full). Don't retry. +Check the session log for more details. + +.TP 9 +3 +Modem protocol error. The program did not receive the expected +response from the modem. The modem may not have been properly +initialized, the correct \fB-o\fP options were not used, or a bug +report may be in order. Check the session log for more details. + +.TP 9 +4 +The modem is not responding. Operator attention is required. +Check that the modem is turned on and connected to the correct +port. + +.TP 9 +5 +The program was terminated by a signal. + +.SH EXAMPLES + +.B Creating fax (G3) files + +The efix program can be used to convert text files to TIFF-G3 +format. For example, the following command will convert the text +file \fBletter\fP to the files \fBletter.001\fP, +\fBletter.002\fP, etc,: + +.IP +.nf +.ft CW +efix -nletter.%03d letter +.ft P +.fi +.LP + +Ghostscript's \fBtiffg3\fP driver can generate fax files in +TIFF-G3 format from postscript files. For example, the command: + +.IP +.nf +\f(CW gs -q -sDEVICE=tiffg3 -dNOPAUSE \\ + -sOutputFile=letter.%03d letter.ps &1 >> fax.log +.ft P +.fi +.LP + +.B Sharing the modem with outgoing calls + +The modem device can be shared by programs that use the UUCP +device locking protocol. This includes pppd, chat, minicom, +kermit, uucico, efax, cu, and many others others. However, +locking will only work if all programs use the same lock file. + +efax will lock the modem device before opening it if one or more +UUCP lock file names are given with \fB-x\fP options. Most +programs place their lock files in the \fR/usr/spool/uucp\fP or +\fR/var/lock\fP directories and use the name \fRLCK..\fP\fIdev\fP +where \fIdev\fP is the name of the device file in the /dev +directory that is to be locked. + +If the \fB-s\fP (share) option is used, the lock file is removed +while waiting for incoming calls so other programs can use the +same device. + +If efax detects another program using the modem while it is +waiting to receive a fax, efax exits with a termination code of +1. A subsequent efax process using this device will wait until +the other program is finished before re-initializing the modem +and starting to wait for incoming calls again. + +Programs that try to lock the modem device by using device +locking facilities other than UUCP lock files not be able to use +this arbitration mechanism because the device will still be open +to the efax process. In this case you will need to kill the efax +process (e.g. "fax stop") before starting the other program. + +When efax is waiting for a fax it leaves the modem ready to +receive in fax mode but removes the lock file. When a slip or +PPP program takes over the modem port by setting up its own lock +file efax cannot send any more commands to the modem -- not even +to reset it. Therefore the other program has to set the modem +back to data mode when it starts up. To do this add a modem +reset command (send ATZ expect OK) to the beginning of your slip +or PPP chat script. + +.B Accepting both fax and data calls + +Many modems have an adaptive data/fax answer mode that can be +enabled using the \fB-j+FAE=1\fP (for Class 1) or \fB-jFAA=1\fP +(for Class 2[.0]) initialization string. The type of call (data +or fax) can then be deduced from the modem's responses. + +Some modems have limited adaptive answer features (e.g. only +working properly at certain baud rates or only in Class 2) or +none at all. In this case use the initialization string +\fB-i+FCLASS=0\fP to answer in data mode first and the \fB-oa\fP +option to then hang up and try again in fax mode if the first +answer attempt was not successful. This method only works if +your telephone system waits a few seconds after you hang up +before disconnecting incoming calls. + +If the \fB-g\fP option is used then the option's argument will be +run as a shell command when an incoming data call is detected. +Typically this command will exec \fBgetty\fP(8). This program +should expect to find the modem already off-hook and a lock file +present so it should not try to hang up the line or create a lock +file. Note that the modem should be set up to report the DCE-DTE +(modem-computer, e.g. CONNECT 38400) speed, not the DCE-DCE +(modem-modem, e.g. CONNECT 14400) speed. For many modems the +initialization option -iW0 will set this. + +The following command will make efax answer incoming calls on +\fB/dev/cua1\fP on the second ring. This device will be locked +using two different lock files but these lock files will be +removed while waiting for incoming calls (\fB-s\fP). If a data +call is detected, the \fBgetty\fP program will be run to +initialize the terminal driver and start a \fBlogin\fP(1) +process. Received fax files will be stored using names like +\fBDec02-12.32.33.001\fP, in the \fB/usr/spool/fax/incoming\fP +directory and the log file will be appended to +\fB/usr/spool/fax/faxlog.cua1\fP. + +.IP +.nf +.ft CW +efax -d /dev/cua1 -j '+FAA=1' \\ + -x /usr/spool/uucp/LCK..cua1 \\ + -x /usr/spool/uucp/LCK..ttyS1 \\ + -g "exec /sbin/getty -h /dev/cua1 %d" \\ + -iS0=2 -w -s \\ + -r "/usr/spool/fax/incoming/%b%d-%H.%I.%S" \\ + >> /usr/spool/fax/faxlog.cua1 2>&1 +.ft P +.fi +.LP + +Note that adaptive answer of either type will not work for all +callers. For some data calls the duration of the initial +data-mode answer may be too short for data handshaking to +complete. In other cases this duration may be so long that +incoming fax calls will time out before efax switches to fax +mode. In addition, some calling fax modems mistake data-mode +answering tones for fax signaling tones and initiate fax +negotiation too soon. If you use software adaptive answer you +can reduce the value of the initial data-mode answer (set by +TO_DATAF in efax.c) to get more reliable fax handshaking or +increase it for more reliable data handshaking. However, if you +need to provide reliable fax and data service to all callers you +should use separate phone numbers for the two types of calls. + +When a call is answered the modem goes on-line with the +computer-to-modem baud rate fixed at the speed used for the most +recent AT command. When efax is waiting for a fax or data call +it sets the interface speed to 19200 bps since this is the speed +required for fax operation. This prevents full use of 28.8kbps +modem capabilities. + + +.SH USING INIT TO RUN EFAX + +efax can answer all incoming calls if you place an entry for efax +in \fB/etc/inittab\fP (for SysV-like systems) or +\fB/etc/ttytab\fP (for BSD-like systems). The \fBinit\fP(8) +process will run a new copy of efax when the system boots up and +whenever the previous efax process terminates. The inittab or +ttytab entry should invoke efax by running the \fBfax\fP script +with an \fBanswer\fP argument. + +For example, placing the following line in \fB/etc/inittab\fP +(and running "kill -1 1") will make init run the \fBfax\fP script +with the argument \fBanswer\fP every time previous process +terminates and \fBinit\fP is in runlevel 4 or 5. + +.IP +.nf +.ft CW +s1:45:respawn:/bin/sh /usr/bin/fax answer +.ft P +.fi +.LP + +For BSD-like systems (e.g. SunOS), a line such as the following +in \fB/etc/ttytab\fP will have the same effect: + +.IP +.nf +.ft CW +ttya "/usr/local/bin/fax answer" unknown on +.ft P +.fi +.LP + +You should protect the fax script and configuration files against +tampering since init will execute them as a privileged (root) +process. If you will be allowing data calls via getty and login +you should ensure that your system is reasonably secure +(e.g. that all user id's have secure passwords). + +If efax exec()'s getty properly but you get a garbled login +prompt then there is probably a baud rate mismatch between the +modem and the computer. First, check the efax log file to make +sure the modem's CONNECT response reported the serial port speed +(e.g. 19200), \fBnot\fP the modem-modem speed (e.g. 14400). +Next, check the getty options and/or configuration files +(e.g. /etc/gettydefs) for that particular baud rate. Then run +getty manually with the same arguments and verify the port +settings using ``stty $LOCKF + $BIN $* + rm $LOCKF +fi +.ft P +.fi +.LP + + +.SH DELIVERING RECEIVED FAXES BY E-MAIL + +The "fax answer" script described above can be configured to +e-mail the fax files received by the previous fax answer process +to a "fax manager" who can then forward the fax to the correct +recipient. The received fax files are send as MIME attachments, +one file per page, using the ``base64'' text encoding and the +``image/tiff'' file format. + +To view the fax images directly from your e-mail reader you will +have to configure it with an application that can display files +of type image/tiff. Typically this is specified in a ``mailcap'' +file. For example, placing the following line in /etc/mailcap +will cause the fax file attachments to be displayed using the +``fax view'' command. + +.ft CW +image/tiff; fax view %s +.ft P + +.SH SENDING FAXES USING THE PRINT SPOOLER + +You can configure a "fax" printer into the lpr print spooler that +will fax a document out using efax instead of printing it. This +allows a network server running efax to send faxes on behalf of +other machines, including non-Unix clients. In the following +steps use the directories specified in the fax script if they are +different than /usr/bin and /var/spool/fax (FAXDIR). To set up a +fax printer do the following as root: + +(1) Create a link to the fax script called ``faxlpr'' so the fax +script can determine when it is being invoked from the print +spooler: + +.ft CW +ln /usr/bin/fax /usr/bin/faxlpr +.ft P + + +(2) Edit /etc/printcap and add an entry such as: + +.IP +.nf +.ft CW +fax:lp=/dev/null:sd=/var/spool/fax:if=/usr/bin/faxlpr: +.ft P +.fi +.LP + +to define a printer called "fax". Print files will be spooled to +the /var/spool/fax (sd=) directory and then piped to the +/usr/bin/faxlpr filter (if=). + +(3) Create and/or set the permissions to allow anyone to read and +write in the fax spool directory. For example: + +.IP +.nf +.ft CW +mkdir /var/spool/fax +chmod 777 /var/spool/fax +.ft P +.fi +.LP + +(4) Create a printer daemon lock file that is readable by anyone: + +.IP +.nf +.ft CW +touch /var/spool/fax/lock +chmod 644 /var/spool/fax/lock +.ft P +.fi +.LP + +You should now be able to send a fax using the lpr interface by +using a command such as: + +.IP +.nf +.ft CW +lpr -P fax -J "555 1212" file.ps +.ft P +.fi +.LP + +where the -J option is used to specify the phone number or alias +to be dialed. + +Note that if more than one file is given on the command line they +will be concatenated before being passed to "fax send". TIFF-G3, +Postscript or PBM files must therefore be sent one file at a time +although TIFF and Postscript files may contain multiple pages. +Only multiple \fItext\fP files can be sent in one command. Page +breaks in text files can be marked with form-feed characters. +Files will be converted and sent at the default (high) +resolution. + +You can use lpq(1) to check the fax queue, lprm(1) to remove fax +jobs and lpc(8) to control the spooler. In each case use the +-Pfax option to specify the fax ``printer.'' A log file will be +mailed to the user when the fax is sent. + +You should also be able to send a fax from any networked computer +that has lpr-compatible remote printing software and that allows +you to set the job name (-J option) to an arbitrary string. Such +software is available for most computers. + +See the lpd(8) and printcap(5) man pages for information on the +print spooler and for restricting access by host name +(/etc/host.lpd) or by user group (the `rg' printcap entry). + +.SH RESOLVING PROBLEMS + +Double check the configuration setup in the first part of the fax +script, particularly the modem device name and the lock file +names. + +If efax hangs when trying to open the modem device (typically +/dev/ttyX), the device is either already in use by another +process (e.g. pppd) or it requires the carrier detect line to be +true before it can be opened. Many systems define an alternate +device name for the same physical device (typically cuaX) that +can be opened even if carrier is not present or other programs +are already using it. + +If responses to modem initialization commands are being lost or +generated at random, another processes (e.g. getty or an efax +auto-answer process) may be trying to use the modem at the same +time. Try running efax while this other program is running. If +efax does not report "/dev/ttyX locked or busy. waiting." then +the lock files names are not specified correctly. + +Attempt to send a fax. Check that the modem starts making the +calling signal (CNG, a 0.5 second beep every 3 seconds) as soon +as it's finished dialing. This shows the modem is in fax mode. +You may need to set the SPKR variable to -iM2L3 to monitor the +phone line to do this. + +Listen for the answering fax machine and check that it sends the +answer signal (CED, a 3 second beep) followed by "warbling" +sounds (DIS frames) every 3 seconds. If you hear a continuous +sound (tones or noise) instead, then you've connected to a data +modem instead. + +Your modem should send back its own warble (DCS frame) in +response to DIS immediately followed by 1.5 seconds of noise (a +channel check). If everything is OK, the receiving end will send +another warble (CFR frame) and your modem will start to send +data. If you have an external modem, check its LEDs. If flow +control is working properly the modem's send data (SD) LED will +turn off periodically while the fax data is sent. + +Check the message showing the line count and the average bit rate +when the page transmission is done. Low line counts (under 1000 +for a letter size image) or the warning "fax output buffer +overflow" while sending indicate that the image data format is +incorrect. Check the file being sent using the "fax view" +command. + +If you get the error message ``flow control did not work'' then +flow control was not active. This usually results in a garbled +transmission and the receiving machine may reject the page, abort +the call, print a distorted or blank image and/or hang up. + +The warning "characters received while sending" or an +character appearing after the transmission means that the +operating system ignored the modem's XOFF flow control character. +Ensure that you are not running other programs such as getty or +pppd at the same time as efax since they will turn off xon/xoff +flow control. + +If you cannot get flow control to work properly then enable +``virtual flow control'' with the \fB-of\fP option or hardware +flow control with the \fB-oh\fP option. + +Check that the remote machine confirms reception with a +FPTS:1 +response (Class 2) or an MCF frame (Class 1). + +For Class 2 modems, the error message "abnormal call termination +(code \fInn\fP)" indicates that the modem detected an error and +hung up. + +Many companies advertise services that will fax back information +on their products. These can be useful for testing fax +reception. + +The message "run length buffer overflow" when receiving indicates +an error with the image data format. You may need to use the +\fB-or\fP option with certain Class 2 modems. + +If efax displays the message "can't happen (
)" please +send a bug report to the author. + +Finally, don't play "option bingo," if you can't resolve the +problem send a verbose log of the failed session (the output from +\fBfax -v ...\fP) to the address below. + +.SH WEB PAGE + +A Web Page with pointers to the latest version, known bugs and +patches is available at: +.IP +.ft CW +http://casas.ee.ubc.ca/efax/ +.ft P +.LP + +.SH RELATED SOFTWARE + +For Linux Systems + +Independent packages provide more user-friendly interfaces to +efax (xfax, tefax) and provide an e-mail-to-fax (Qfax) gateway +using efax. All are available by anonymous FTP from +metalab.unc.edu in /pub/Linux/apps/serialcomm/fax/. + +For Amiga Systems + +A port of an early version of efax for the Amiga is available as +a component of a shareware voice mail package, AVM, distributed +by Al Villarica (rvillari@cat.syr.edu). + +Other Ports + +efax is relatively easy to port. All system-dependent code is in +\fBefaxos.c\fP. An early version of efax was ported to VMS. +Version 0.8a was ported to Win32 by Luigi Capriotti. Contact the +author if you would like to integrate the Win32 code into the +current version. + +.SH AUTHOR + +Efax was written by Ed Casas. Please send comments or bug +reports to edc@cce.com. + +.SH BUG REPORTS + +Bug reports should include the operating system, the type of the +modem and a copy of a verbose session log that demonstrates the +problem. It's usually impossible to help without a verbose log. +Please do \fBnot\fP send fax image files. + +.SH COPYRIGHT + +efax is copyright 1993 -- 1999 Ed Casas. It may be used, copied +and modified under the terms of the GNU Public License. + +.SH DISCLAIMER + +Although \fBefax\fP has been tested it may have errors that will +prevent it from working correctly on your system. Some of these +errors may cause serious problems including loss of data and +interruptions to telephone service. + +.SH REFERENCES + +CCITT Recommendation T.30, "Procedures for Document Facsimile +Transmission in the General Switched Telephone Network". 1988 + +CCITT Recommendation T.4, "Standardization of Group 3 Facsimile +Apparatus for Document Transmission". 1988. + +For documentation on Class 1 and Class 2 fax commands as +implemented by Connexant (formerly Rockwell) modems see +http://www.conexant.com/techinfo. + +For the TIFF specification see +http://partners.adobe.com/supportservice/devrelations/PDFS/TN/TIFF6.pdf +or RFC 2301 (ftp://ftp.isi.edu/in-notes/rfc2301.txt). + +For information on Ghostscript see +http://www.cs.wisc.edu/~ghost/. + +The pbm utilities can be obtained by ftp from wuarchive.wustl.edu +in /graphics/graphics/packages/NetPBM/netpbm-1mar1994.tar.gz. + +PCX and many other file formats are described in: Gunter Born, +The File Formats Handbook, International Thomson Computer Press, +1995. + +The "Fax Modem Source Book" by Andrew Margolis, published by John +Wiley & Sons in 1994 (ISBN 0471950726), is a book on writing fax +applications which includes source code. + +Dennis Bodson et. al., "FAX: Digital Facsimile Technology and +Applications", Second Edition. Artech House, Boston. 1992. + +.SH SEE ALSO + +.BR fax(1), +.BR efix(1), +.BR gs(1), +.BR init(8), +.BR inittab(5), +.BR ttytab(5), +.BR printcap(5), +.BR lpd(8), +.BR printf(3), +.BR strftime(3). + +.SH BUGS + +Can't read TIFF files with more than 1 strip + +Class 1 operation may fail if the program can't respond to +certain data received from the modem within 55 milliseconds. + +May fail if multitasking delays cause the received data to +overflow the computer's serial device buffer or if an under-run +of transmit data exceeds 5 seconds. + +Polling does not work. + +Does not support 2-D coding, ECM, or BFT. diff --git a/efax.c b/efax.c new file mode 100644 index 0000000..42880d2 --- /dev/null +++ b/efax.c @@ -0,0 +1,2354 @@ +#define Copyright "Copyright 1999 Ed Casas" + +#define Version "efax v 0.9" + +/* + Copyright (C) 1999 Ed Casas + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Please contact the author if you wish to use efax or efix in + ways not covered by the GNU GPL. + + You may contact the author by e-mail at: edc@cce.com, by mail + at: 2629 West 3rd Ave, Vancouver, BC, Canada, V6K 1M4, or by + fax at: +1 604 734 5291. + +*/ + +const char *Usage = + "Usage:\n" + " %s [ option ]... [ -t num [ file... ] ]\n" + "Options:\n" + " -a str use command ATstr to answer\n" + " -c cap set modem and receive capabilites to cap\n" + " -d dev use modem on device dev\n" + " -e cmd exec \"/bin/sh -c cmd\" for voice calls\n" + " -f fnt use (PBM) font file fnt for headers\n" + " -g cmd exec \"/bin/sh -c cmd\" for data calls\n" + " -h hdr use page header hdr (use %%d's for current page/total pages)\n" + " -i str send modem command ATstr at start\n" + " -j str send modem command ATstr after set fax mode\n" + " -k str send modem command ATstr when done\n" + " -l id set local identification to id\n" + " -o opt use protocol option opt:\n" + " 0 use class 2.0 instead of class 2 modem commands\n" + " 1 use class 1 modem commands\n" + " 2 use class 2 modem commands\n" + " a if first [data mode] answer attempt fails retry as fax\n" + " e ignore errors in modem initialization commands\n" + " f use virtual flow control\n" + " h use hardware flow control\n" + " l halve lock file polling interval\n" + " n ignore page retransmission requests\n" + " r do not reverse received bit order for Class 2 modems\n" + " x use XON instead of DC2 to trigger reception\n" + " z add 100 ms to pause before each modem comand (cumulative)\n" + " -q ne ask for retransmission if more than ne errors per page\n" + " -r pat save received pages into files pat.001, pat.002, ... \n" + " -s share (unlock) modem device while waiting for call\n" + " -v lvl print messages of type in string lvl (ewinchamr)\n" + " -w don't answer phone, wait for OK or CONNECT instead\n" + " -x fil use uucp-style lock file fil\n" + "Commands:\n" + " -t dial num and send fax image files file... \n" + ; + +#include /* ANSI C */ +#include +#include +#include +#include + +#include "efaxio.h" /* EFAX */ +#include "efaxlib.h" +#include "efaxmsg.h" +#include "efaxos.h" + +/* constants... */ + + /* delays and timeouts (t/o), in deciseconds */ +#define T1 350 /* T.30 T1 - waiting for DIS/DCS before Phase B */ +#define T2 60 /* T.30 T2 - waiting for frame in Phase B */ +#define T3S 30 /* T.30 response timeout (not T3) */ +#define T4 30 /* T.30 T4 - between [re]transmissions of DIS */ + +#define TMOD 55 /* T.30 pause between v.21&v.29, 75-20 ms */ + +#define TO_A 1200 /* dial/answer (Phase A) - modem may t/o first */ +#define TO_ABRT 20 /* max delay after sending abort sequence */ +#define TO_CHAR 102 /* per data character (2x max FILL length) */ +#define TO_DATAF 80 /* software adaptive answer data connect t/o */ +#define TO_DRAIN_H 136 /* minimum HDLC buffer drain time (4k/300cps) */ +#define TO_DRAIN_D 300 /* minimum data buffer drain time */ +#define TO_FT 31 /* max delay after +F[TR][MH] command */ +#define TO_RTCMD 20 /* return to command mode after DLE-ETX (rx) */ + +#define TO_C2B 450 /* Class 2 DIS to CONNECT:(DCS+TCF+CFR)xretries */ +#define TO_C2X 20 /* Class 2 wait for XON: 2/5 of 5s timeout */ +#define TO_C2PP 200 /* Class 2 wait for ppr: (ppm+ppr)x3retries + 2 */ +#define TO_C2R 600 /* Class 2 receive: (TCF+FTT)x11 retrains + 5 */ +#define TO_C2EOR 120 /* Class 2 end of data rx (4 retrans x 3 s) */ + +#define ANSCMD "A" /* default modem command to answer calls */ +#define DCSLEN 3 /* length of FIF for DCS commands sent */ +#define DEFDISLEN 3 /* length of DIS initially transmitted */ +#define DEFCAP 1,3,0,2,0,0,0,0 /* default local capabilities */ +#define DEFID " " /* default local ID */ +#define DEFPAT "%m%d%H%M%S" /* default received file name pattern */ +#define HDRSHFT 54 /* shift header right 6.7mm into image area */ +#define HDRSPCE 20 /* number of scan lines inserted before image */ +#define HDRSTRT 4 /* scan line where header is placed on image */ +#define HDRCHRH 24 /* header character height (pels, at 196lpi) */ +#define HDRCHRW 12 /* header character width (pels) */ +#define IDLEN 20 /* length of T.30 ID strings, must be 20 */ +#define MAXDIS 8 /* maximum DIS frames sent without response (T1) */ +#define MAXERRPRT 32 /* maximum number of reception errors to report */ +#define MAXFIFLEN 125 /* max FIF len = MAXFRLEN - (adx+ctl+FCF) - FCS */ +#define MAXFRLEN 130 /* max frame length = 3.45s x 300 bps / 8 */ +#define MAXGETTY 512 /* maximum length of exec'ed (-g, -e) commands */ +#define MAXICMD 100 /* maximum # of modem setup/reset commands */ +#define MAXLKFILE 16 /* maximum number of lock files */ +#define MAXNULLS 2 /* maximum consecutive received nulls saved */ +#define MAXPGERR 10 /* maximum received errors allowed per page */ +#define MAXTRAIN 2 /* maximum training retries at lowest speed */ +#define MAXRETRY 3 /* maximum retries of unacknowledged commands */ +#define NCAP 8 /* number of fields in a capability string */ +#define NTXRETRY 3 /* maximum re-sends per page */ + +typedef int cap [ NCAP ] ; /* remote/local capabilities */ + + /* capability fields... */ +enum captype { VR, BR, WD, LN, DF, EC, BF, ST } ; +int capmax [ NCAP ] = { 1, 7, 2, 2, 3, 2, 1, 7 } ; + /* & maximum values */ + + /* vertical resolution, dpi */ +int vresolution [ 2 ] = { 98, 196 } ; + + /* characters per second for br */ +int cps [ 8 ] = { 300, 600, 900, 1200, 1500, 1800, 900, 1200 } ; + + /* next br = fallback [ br ] */ + /* 0, 1, 2, 3, 4, 5, 6, 7 */ +int fallback [ 8 ] = {-1, 0, 1, 2, 7, 4, 3, 6 } ; + + /* negotiation speed index */ + /* 0, 1, 2, 3, 4, 5, 6, 7 */ +int brindex [ 8 ] = { 0, 1, 2, 3, 4, 7, 5, 6 } ; + + /* minimum scan time in ms */ +int mst [ 8 ] = { 0 , 5, 10, 10, 20, 20, 40, 40 } ; + + /* page width in pixels */ +int pagewidth [ 5 ] = { 1728, 2048, 2432, 1216, 864 } ; + +/* Table to convert between T.30 DIS/DCS/DTC FIF and Class 2-like + capability codes. Uses br=6, 7 for V.17 at 7200, 9600. */ + +typedef struct t30tabstruct +{ + char *name ; + uchar byte, shift, mask ; + uchar safeval ; + uchar captodis[8], distocap[16], captodcs[8], dcstocap[16] ; +} t30tabst ; + +#define X 0xff /* invalid values */ + +t30tabst t30tab [ NCAP ] = { + { "vr", 1, 1, 0x01, 0, { 0, 1 } , { 0, 1 } , { 0, 1 } , { 0, 1 } }, + { "br", 1, 2, 0x0f, 0, + { 0, 4, 12, 12, 13, 13 } , + { 0, X, X, X, 1, X, X, X, 3, X, X, X, 3, 5, 3, X } , + { 0, 4, 12, 8, 5, 1 } , + { 0, 5, 5, X, 1, 4, 4, X, 3, 7, X, X, 2, 6, X, X } } , + { "wd", 2, 6, 0x03, 0, { 0, 2, 1 } , { 0, 2, 1, 2 } , + { 0, 2, 1 } , { 0, 2, 1, 2 } }, + { "ln", 2, 4, 0x03, 0, { 0, 2, 1 } , { 0, 2, 1, X } , + { 0, 2, 1 } , { 0, 2, 1, X } }, + { "df", 1, 0, 0x01, 0, { 0, 1 } , { 0, 1 } , { 0, 1 } , { 0, 1 } }, + { "ec", 3, 4, 0x03, 0, { 0, 2, 2 } , { 0, X, 2, X } , + { 0, 3, 2 } , { 0, 0, 2, 1 } }, + { "bf", 5, 5, 0x01, 0, { 0, 1 } , { 0, 1 } , { 0, 1 } , { 0, 1 } }, + { "st", 2, 1, 0x07, 7, + { 7, 4, 3, 2, 6, 0, 5, 1 } , { 5, 7, 3, 2, 1, 6, 4, 0 } , + { 7, 4, X, 2, X, 0, X, 1 } , { 5, 7, 3, 1, X, X, X, 0 } } +} ; + + /* values of capability fields */ +char *capvaluestr [ NCAP ] [8] = { + { " 98lpi", "196lpi" } , + { " 2400bps", " 4800bps", " 7200bps", " 9600bps", " 12kbps", "14.4kbps", + "7200V.17", "9600V.17" } , + { "8.5\"/215mm", " 10\"/255mm", " 12\"/303mm", + " 6\"/151mm", "4.2\"/107mm" } , + { "11\"/A4", "14\"/B4", " any " } , + { "1D" , "2D" }, { " - ", "ECM-256", "ECM-64 " }, { " - ", "BFT" }, + { "0ms", "5ms", "10/5ms", "10ms", "20/10ms", "20ms", "40/20ms", "40ms" } +} ; + +/* T.30 control frames */ + +enum frametype { + DIS=0x01, CSI, NSF=0x04, + CFR=0x21, FTT, + MCF=0x31, RTN, RTP, PIN, PIP, + DCS=0x41, TSI, NSS=0x44, + CRP=0x58, DCN=0x5f, + EOM=0x71, MPS, EOP=0x74, PRI_EOM=0x79, PRI_MPS, PRI_EOP=0x7c, + DTC=0x81, CIG, NSC=0x84 + } ; + +enum commanddtype { RCV=0, SND=1, DTA=0, TRN=1 } ; + +/* Class 1 commands to [receive=0/transmit=1] [data=0/training=1] for + [baud rate=BR]. */ + +char *c1cmd [ 2 ] [ 2 ] [ 8 ] = { +{ { "+FRM=24", "+FRM=48", "+FRM=72", "+FRM=96", "+FRM=122", "+FRM=146" , + "+FRM=74", "+FRM=98" } , + { "+FRM=24", "+FRM=48", "+FRM=72", "+FRM=96", "+FRM=121", "+FRM=145" , + "+FRM=73", "+FRM=97" } } , +{ { "+FTM=24", "+FTM=48", "+FTM=72", "+FTM=96", "+FTM=122", "+FTM=146" , + "+FTM=74", "+FTM=98", } , + { "+FTM=24", "+FTM=48", "+FTM=72", "+FTM=96", "+FTM=121", "+FTM=145" , + "+FTM=73", "+FTM=97" } } +} ; + +struct c2msgstruct +{ + int min, max ; + char *msg ; +} c2msg [] = { + { 0, 9, "Call Placement and Termination:" }, + { 0, 0, " Normal and proper end of connection" }, + { 1, 1, " Ring Detect without successful handshake" }, + { 2, 2, " Call aborted, from +FK[S] or CAN" }, + { 3, 3, " No Loop Current" }, + { 4, 4, " Ringback Detected, no answer" }, + { 5, 5, " Ringback Detected, answer without CED" }, + + { 10, 19, "Transmit Phase A & Miscellaneous Errors:" }, + { 10, 10, " Unspecified Phase A error" }, + { 11, 11, " No Answer (T.30 T1 timeout)" }, + { 20, 39, "Transmit Phase B Hangup Codes:" }, + { 20, 20, " Unspecified Transmit Phase B error" }, + { 21, 21, " Remote cannot receive or send" }, + { 22, 22, " COMREC error in transmit Phase B" }, + { 23, 23, " COMREC invalid command received" }, + { 24, 24, " RSPREC error" }, + { 25, 25, " DCS sent three times without response" }, + { 26, 26, " DIS/DTC received 3 times; DCS not recognized" }, + { 27, 27, " Failure to train at 2400 bps or +FMINSP value" }, + { 28, 28, " RSPREC invalid response received" }, + { 40, 49, "Transmit Phase C Hangup Codes:" }, + { 40, 40, " Unspecified Transmit Phase C error" }, + { 41, 41, " Unspecified image format error" }, + { 42, 42, " Image conversion error" }, + { 43, 43, " DTE to DCE data underflow" }, + { 44, 44, " Unrecognized transparent data command" }, + { 45, 45, " Image error, line length wrong" }, + { 46, 46, " Image error, page length wrong" }, + { 47, 47, " Image error, wrong compression code" }, + { 50, 69, "Transmit Phase D Hangup Codes:" }, + { 50, 50, " Unspecified Transmit Phase D error" }, + { 51, 51, " RSPREC error" }, + { 52, 52, " No response to MPS repeated 3 times" }, + { 53, 53, " Invalid response to MPS" }, + { 54, 54, " No response to EOP repeated 3 times" }, + { 55, 55, " Invalid response to EOP" }, + { 56, 56, " No response to EOM repeated 3 times" }, + { 57, 57, " Invalid response to EOM" }, + { 58, 58, " Unable to continue after PIN or PIP" }, + + { 70, 89, "Receive Phase B Hangup Codes:" }, + { 70, 70, " Unspecified Receive Phase B error" }, + { 71, 71, " RSPREC error" }, + { 72, 72, " COMREC error" }, + { 73, 73, " T.30 T2 timeout, expected page not received" }, + { 74, 74, " T.30 T1 timeout, after EOM received" }, + { 90, 99, "Receive Phase C Hangup Codes:" }, + { 90, 90, " Unspecified Receive Phase C error" }, + { 91, 91, " Missing EOL after 5 seconds" }, + { 92, 92, " Unused code" }, + { 93, 93, " DCE to DTE buffer overflow" }, + { 94, 94, " Bad CRC or frame (ECM or BFT modes)" }, + { 100, 119, "Receive Phase D Hangup Codes:" }, + { 100, 100, " Unspecified Receive Phase D errors" }, + { 101, 101, " RSPREC invalid response received" }, + { 102, 102, " COMREC invalid response received" }, + { 103, 103, " Unable to continue after PIN or PIP" }, + { 120, 255, "Reserved Codes" }, + { -1, -1, "" } +} ; + +/* meaning of efax return codes */ + +char *errormsg [] = { + "success", + "number busy or modem in use", + "unrecoverable error", + "invalid modem response", + "no response from modem", + "terminated by signal", + "internal error" } ; + +/* Functions... */ + +/* Return name of frame of type 'fr'. */ + +char *frname ( int fr ) +{ + static struct framenamestruct { int code ; char *name ; } + framenames [] = { + + {NSC,"NSC - poller features"}, /* these 3 frames must be first */ + {CIG,"CIG - poller ID"}, + {DTC,"DTC - poller capabilities"}, + {NSF,"NSF - answering features"}, + {CSI,"CSI - answering ID"}, + {DIS,"DIS - answering capabilities"}, + {NSS,"NSS - caller features"}, + {TSI,"TSI - caller ID"}, + {DCS,"DCS - session format"}, + + {CFR,"CFR - channel OK"}, + {FTT,"FTT - channel not OK"}, + + {MPS,"MPS - not done"}, + {EOM,"EOM - not done, new format"}, + {EOP,"EOP - done"}, + + {PRI_MPS,"PRI-MPS - not done, call operator"}, + {PRI_EOM,"PRI-EOM - not done, new format, call operator"}, + {PRI_EOP,"PRI-EOP - done, call operator"}, + + {MCF,"MCF - page OK"}, + {RTP,"RTP - page OK, check channel"}, + {PIP,"PIP - page OK, call operator"}, + {RTN,"RTN - page not OK, check channel"}, + {PIN,"PIN - page not OK, call operator"}, + + {CRP,"CRP - repeat command"}, + {DCN,"DCN - disconnect"}, + {0,0} }, + *p ; + + for ( p=framenames ; p->code ; p++ ) + if ( fr == p->code || ( fr & 0x7f ) == p->code) break ; + return p->code ? p->name : "UNKNOWN" ; +} + +/* Range-check capability. */ + +int checkcap ( cap c ) +{ + int err=0, i ; + + for ( i=0 ; i capmax[i] || c[i] < 0 ) { + err = msg ( "E3%s = %d out of range, set to 0", t30tab[i].name, c[i] ) ; + c[i]=0 ; + } + return err ; +} + + +/* Print cap[ability] c using text values and prefix s. */ + +void printcap ( char *s , cap c ) +{ + int i ; + msg ( "N-+ %s" , s ) ; + checkcap ( c ) ; + for ( i=0 ; i DCSLEN ? DCSLEN : len ; + + fif[0] = 0 ; + fif[1] = ( isdis && t4tx ? 0x80 : 0 ) | 0x40 ; + for ( i=2 ; icaptodis : p->captodcs ) [ c [ i ] ] ) == X ) + msg ( "E3mkdis: can't happen (invalid %s)", p->name ), k=0 ; + if ( p->byte < len ) fif [ p->byte ] |= k << p->shift ; + } +} + + +/* Return length of DIS/DTC FIF (counts extension bits). */ + +int dislen ( uchar *fif ) +{ + int n ; + for ( n=3 ; fif [ n-1 ] & 0x01 && n < MAXFIFLEN ; n++ ) ; + return n ; +} + + +/* Convert received DIS/DCS/DTC FIF to cap. Returns 0 or 3 if bad DIS/DCS + field. */ + +int mkcap ( uchar *fif, cap c, int dis ) +{ + int err=0, i, j, k, len ; + t30tabst *p ; + + len = dislen ( fif ) ; + + for ( i=0 ; ibyte >= len ) { + c [ i ] = 0 ; + } else { + j = ( fif [ p->byte ] >> p->shift ) & p->mask ; + k = ( dis ? p->distocap : p->dcstocap ) [ j ] ; + if ( k == X ) { + c [ i ] = p->safeval ; + err = msg("E3mkcap: bad %s field (%d) set to %d", + p->name, j, c [ i ] ) ; + } else { + c [ i ] = k ; + } + } + } + return err ; +} + + +/* Compute compatible local/remote capabilities. Used by the + sending station only and only for Class 1. Returns 0 if OK or + 3 if no compatible settings possible. */ + +int mincap ( cap local, cap remote, cap session ) +{ + int err=0, i ; + int msttab[2][8] = { { 0,1,3,3,5,5,7,7 } , { 0,1,1,3,3,5,5,7 } } ; + + printcap ( "local ", local ) ; + printcap ( "remote ", remote ) ; + + for ( i=0 ; i session[LN] || + local[DF] != session[DF] ) + err = msg ("W3incompatible local and remote capabilities" ) ; + + return err ; +} + + +/* Skip to start of first/next page (or to start of previous page + if dp is 0). If ppm in not null, it is then set to EOP if + there are no pages following this one, MPS if the next page + has the same format as `local' (assumed to be the format of + the previous page), EOM if the page has a different format. + If local is non-NULL its format fields are set according to + the format of the new page. Currently only considers the + file's y-resolution. + + This function is called before send_data() and obtains the ppm + for that page. It can be called again with dp=0 if a PIN or + RTN is received to restart the page. Returns 0 or 2 on + errors. */ + +int rdpage ( IFILE *f, int dp, int *ppm, cap local, int *changed ) +{ + int err=0, m=EOP, yres, fVR, nVR ; + + if ( nextipage ( f, dp ) ) + err = msg ( "E2 can't happen (rdpage: can't go to %s page)", + dp ? "next" : "same" ) ; + + if ( ! err ) { + + yres = f->page->yres ; + fVR = ( yres > (196+98)/2 ) ? 1 : 0 ; + + if ( local && yres ) { + if ( local [ VR ] != fVR ) { + local [ VR ] = fVR ; + if ( changed ) *changed = 1 ; + } else { + if ( changed ) *changed = 0 ; + } + } + + if ( lastpage ( f ) ) { + m = EOP ; + } else { + PAGE *p = f->page + 1 ; + nVR = ( p->yres > (196+98)/2 ) ? 1 : 0 ; + m = ( nVR != fVR ) ? EOM : MPS ; + } + + } + + if ( ppm ) { + *ppm = err ? EOP : m ; + } + + return err ; +} + + +/* Terminate previous page if page number is non-zero and start + next output page if page number is non-negative. If page is -1 + removes the most recently opened file. Returns 0 if OK, 2 on + errors. */ + +int wrpage ( OFILE *f, int page ) +{ + int err=0 ; + + err = nextopage ( f, page ) ; + + if ( ! err && page == -1 ) { + if ( remove ( f->cfname ) ) { + err = msg ( "ES2can't delete file %s:", f->cfname ) ; + } else { + msg ( "Fremoved %s", f->cfname ) ; + } + } + + return err ; +} + + +/* Send data for one page. Figures out required padding and 196->98 lpi + decimation based on local and session capabilitites, substitutes page + numbers in header string and enables serial port flow control. Inserts + the page header before the input file data. Converts each scan line to + T.4 codes and adds padding (FILL) and EOL codes before writing out. + Sends RTC when done. Sends DLE-ETX and returns serial port to command + mode when done. Returns 0 if OK, non-0 on errors. */ + +int send_data ( TFILE *mf, IFILE *f, int page, int pages, + cap local, cap session, char *header, faxfont *font ) +{ + int done=0, err=0, noise=0, nr=0, lastnr=0, line, pixels ; + int i, decimate, pwidth, minlen, dcecps, inheader, skip=0 ; + uchar buf [ MAXCODES + 2*EOLBITS/8 + 1 ], *p ; + short runs [ MAXRUNS ], lastruns [ MAXRUNS ] ; + char headerbuf [ MAXLINELEN ] ; + ENCODER e ; + + newENCODER ( &e ) ; + + dcecps = cps[session[BR]] ; + minlen = ( (long)dcecps * mst[session[ST]] - 1500 + 500 ) / 1000 ; + pwidth = pagewidth [ session [ WD ] ] ; + decimate = local[VR] > session[VR] ; + + msg ( "T padding to %d bytes/scan line.%s", minlen+1, + decimate ? " reducing 196->98 lpi." : "" ) ; + + if ( vfc ) + msg ( "T limiting output to %d bps for %d byte modem buffer", + dcecps*8, MAXDCEBUF + MINWRITE ) ; + + if ( ckfmt ( header, 6 ) ) + msg ( "W too many %%d escapes in header format string \"%s\"", header ) ; + else + sprintf ( headerbuf, header, page, pages, page, pages, page, pages ) ; + msg ("I header:[%s]", headerbuf ) ; + + done = err = ttymode ( mf, SEND ) ; + + mf->start = time(0) ; + mf->mstart = proc_ms() ; + mf->bytes = mf->pad = mf->lines = 0 ; + + /* start T.4 data with some FILL and an EOL */ + + for ( i=0 ; i<32 ; i++ ) + p = putcode ( &e, 0, 8, buf ) ; + p = putcode ( &e, EOLCODE, EOLBITS, p ) ; + + if ( ! f || ! f->f ) + err = msg ( "E2can't happen(send_data)" ) ; + + mf->lines=0 ; + for ( line=0 ; ! done && ! err ; line++ ) { + + if ( line < HDRSPCE ) { /* insert blank lines at the top */ + runs[0] = pwidth ; + pixels = pwidth ; + nr = 1 ; + } else { + if ( ( nr = readline ( f, runs, &pixels ) ) < 0 ) { + done = 1 ; + continue ; + } + } + /* generate and OR in header pixels */ + if ( line >= HDRSTRT && line < HDRSTRT + HDRCHRH ) { + int hnr ; + short hruns [ MAXRUNS ] ; + hnr = texttorun ( (uchar*) headerbuf, font, line-HDRSTRT, + HDRCHRW, HDRCHRH, HDRSHFT, + hruns, 0 ) ; + nr = runor ( runs, nr, hruns, hnr, 0, &pixels ) ; + } + + inheader = line < HDRSTRT + HDRCHRH ; + + if ( decimate || ( inheader && local[VR] == 0 ) ) { + if ( ++skip & 1 ) { /* save the first of every 2 lines */ + memcpy ( lastruns, runs, nr * sizeof(short) ) ; + lastnr = nr ; + continue ; /* get next line */ + } else { /* OR previous line into current line */ + nr = runor ( runs, nr, lastruns, lastnr, 0, &pixels ) ; + } + } + + if ( nr > 0 ) { + if ( pixels ) { + /* make line the right width */ + if ( pixels != pwidth ) nr = xpad ( runs, nr, pwidth - pixels ) ; + /* convert to MH coding */ + p = runtocode ( &e, runs, nr, p ) ; + /* zero pad to minimum scan time */ + while ( p - buf < minlen ) { + p = putcode ( &e, 0, 8, p ) ; + mf->pad ++ ; + } + /* add EOL */ + p = putcode ( &e, EOLCODE, EOLBITS, p ) ; + sendbuf ( mf, buf, p - buf, dcecps ) ; + mf->bytes += p - buf ; + mf->lines++ ; + } else { + /* probably read an EOL as part of RTC */ + } + if ( tdata ( mf, 0 ) ) noise = 1 ; + p = buf ; + } + } + + for ( i=0 ; i < RTCEOL ; i++ ) + p = putcode ( &e, EOLCODE, EOLBITS, p ) ; + p = putcode ( &e, 0, 0, p ) ; + sendbuf ( mf, buf, p - buf, dcecps ) ; + mf->bytes += p - buf ; + + if ( noise ) msg ("W- characters received while sending" ) ; + + return err ; +} + + +int end_data ( TFILE *mf, cap session, int ppm, int *good ) +{ + int err=0, c ; + uchar *p ; + long dt, draintime ; + + if ( ! ppm ) p = DLE_ETX ; + else if ( ppm == MPS ) p = "\020," ; + else if ( ppm == EOM ) p = "\020;" ; + else if ( ppm == EOP ) p = "\020." ; + else { + p = "" ; + err = msg ( "E2 can't happen (end_data)" ) ; + } + + tput ( mf, p, 2 ) ; + + dt = time(0) - mf->start ; + /* time to drain buffers + 100% + 4s */ + draintime = ( 2 * ( mf->bytes / cps[ session[BR] ] + 1 - dt ) + 4 ) * 10 ; + draintime = draintime < TO_DRAIN_D ? TO_DRAIN_D : draintime ; + + c = ckcmd ( mf, 0, 0, (int) draintime, OK ) ; + + if ( good ) *good = ( c == OK ) ? 1 : 0 ; + + dt = time(0) - mf->start ; + + msg ( "Isent %d+%d lines, %d+%d bytes, %d s %d bps" , + HDRSPCE, mf->lines-HDRSPCE, + mf->bytes-mf->pad, mf->pad, (int) dt, (mf->bytes*8)/dt ) ; + + if ( mf->bytes / (dt+1) > cps[session[BR]] ) + msg ( "E flow control did not work" ) ; + + if ( ! err ) err = ttymode ( mf, COMMAND ) ; + + return err ; +} + + +/* Read one scan line from fax device. If pointer pels is not + null it is used to save pixel count. Returns number of runs + stored, EOF on RTC, or -2 on EOF, DLE-ETX or other error. */ + +int readfaxruns ( TFILE *f, DECODER *d, short *runs, int *pels ) +{ + int err=0, c=EOF, x, n ; + dtab *tab, *t ; + short shift ; + short *p, *maxp, *q, len=0 ; + uchar rd_state ; + + maxp = ( p = runs ) + MAXRUNS ; + + x = d->x ; shift = d->shift ; tab = d->tab ; /* restore decoder state */ + rd_state = f->rd_state ; + + do { + do { + while ( shift < 0 ) { + c = tgetd ( f, TO_CHAR ) ; + + rd_state = ( rd_state & rd_allowed[c] ) ? + ( ( rd_state & rd_nexts[c] ) ? rd_state <<= 1 : rd_state ) : + RD_BEGIN ; + + if ( rd_state == RD_END ) + msg ( "W+ modem response in data" ) ; + + if ( c < 0 ) { + x = ( x << 15 ) | 1 ; shift += 15 ; /* EOL pad at EOF */ + } else { + x = ( x << 8 ) | c ; shift += 8 ; + } + } + t = tab + ( ( x >> shift ) & 0x1ff ) ; + tab = t->next ; + shift -= t->bits ; + } while ( ! t->code ) ; + if ( p < maxp ) *p++ = t->code ; + } while ( t->code != -1 ) ; + + d->x = x ; d->shift = shift ; d->tab = tab ; /* save state */ + f->rd_state = rd_state ; + + if ( p >= maxp ) msg ( "Wrun length buffer overflow" ) ; + + /* combine make-up and terminating codes and remove +1 offset + in run lengths */ + + n = p - runs - 1 ; + for ( p = q = runs ; n-- > 0 ; ) + if ( *p > 64 && n-- > 0 ) { + len += *q++ = p[0] + p[1] - 2 ; + p+=2 ; + } else { + len += *q++ = *p++ - 1 ; + } + n = q - runs ; + + /* check for RTC and errors */ + + if ( len ) + d->eolcnt = 0 ; + else + if ( ++(d->eolcnt) >= RTCEOL ) err = EOF ; + + if ( c < 0 ) err = - 2 ; + + if ( pels ) *pels = len ; + + return err ? err : n ; +} + + +/* Receive data. Reads scan lines from modem and writes to output + file. Checks for errors by comparing received line width and + session line width. Check that the output file is still OK + and if not, send one CANcel character and wait for protocol to + complete. Returns 0 if OK or 2 if there was a file write error. */ + +int receive_data ( TFILE *mf, OFILE *f, cap session, int *nerr ) +{ + int err=0, line, lines, nr, len ; + int pwidth = pagewidth [ session [ WD ] ] ; + short runs [ MAXRUNS ] ; + DECODER d ; + + if ( ! f || ! f->f ) { + msg ( "E2 can't happen (writeline)" ) ; + } + + newDECODER ( &d ) ; + *nerr = 0 ; + + lines=0 ; + for ( line=0 ; ( nr = readfaxruns ( mf, &d, runs, &len ) ) >= 0 ; line++ ) { + if ( nr > 0 && len > 0 && line ) { /* skip first line+EOL and RTC */ + if ( len != pwidth ) { + (*nerr)++ ; + if ( *nerr <= MAXERRPRT ) msg ("R-+ (%d:%d)", line, len ) ; + nr = xpad ( runs, nr, pwidth - len ) ; + } + writeline ( f, runs, nr, 1 ) ; + lines++ ; + } + if ( ferror ( f->f ) ) { + err = msg ("ES2file write:") ; + tput ( mf, CAN_STR, 1 ) ; + msg ("Wdata reception CANcelled") ; + } + } + + if ( *nerr ) { + if ( *nerr > MAXERRPRT ) msg ("R-+ ....." ) ; + msg ("R- : reception errors" ) ; + msg ("W- %d reception errors", *nerr ) ; + } + + if ( nr == EOF ) + while ( tgetd ( mf, TO_CHAR ) >= 0 ) ; /* got RTC, wait for DLE-ETX */ + + msg ( "I- received %d lines, %d errors", lines, *nerr ) ; + + return err ; +} + + +/* Send training check sequence of n zeroes. Returns 0 or 2 on error. */ + +int puttrain ( TFILE *f, char *s, int n ) +{ + int i, m, err=0 ; + uchar buf [ MINWRITE ] = { 0 } ; + + ckcmd ( f, &err, s, TO_FT, CONNECT ) ; + + if ( ! err ) { + ttymode ( f, SEND ) ; + + for ( i=0 ; i < n ; i += m ) { + m = n-i < MINWRITE ? n-i : MINWRITE ; + sendbuf ( f, buf, m, 0 ) ; + } + + buf[0] = 1 ; /* make sure last byte is non-zero */ + buf[1] = DLE ; + buf[2] = ETX ; + sendbuf ( f, buf, 3, 0 ) ; + + ttymode ( f, COMMAND ) ; + + ckcmd ( f, &err, 0, TO_DRAIN_D, OK ) ; + msg ( "I- sent TCF - channel check of %d bytes", n ) ; + } + + return err ; +} + + +/* Checks for an error-free run of at least n bytes in the + received training check sequence. Sets good if it's not null, + the run was long enough and there were no errors. Returns 0 or + 3 on other errors. */ + +int gettrain ( TFILE *f, char *s, int n, int *good ) +{ + int err=0, c, i=0, maxrunl=0, runl=0 ; + + ckcmd ( f, &err, s, T2, CONNECT ) ; + + if ( ! err ) { + + for ( i=0 ; ( c = tgetd ( f, T3S ) ) >= 0 ; i++ ) + if ( c ) { + if ( runl > maxrunl ) maxrunl = runl ; + runl = 0 ; + } else { + runl ++ ; + } + + if ( c == EOF ) + err = msg ( "E3timed out during training check data" ) ; + else + ckcmd ( f, &err, 0, TO_RTCMD, NO ) ; + + } + + if ( runl > maxrunl ) maxrunl = runl ; + + if ( good ) *good = !err && maxrunl > n ; + + if ( !err ) { + msg ( "I- received TCF - channel check (%sOK: run of %d in %d)", + maxrunl > n ? "" : "not ", maxrunl, i ) ; + } + + return err ; +} + + +/* Log a sent/received HDLC frame. Display of these messages is delayed to + avoid possible timing problems. */ + +void logfr ( char *s , char *nm , uchar *p , int n ) +{ + int i=0 ; + msg ( n > 10 ? "H- %s %d bytes:" : "H-+ %s %d bytes:" , s, n ) ; + for ( i=0 ; i= 1 ; i-- ) + buf[i]=buf[i-1] ; + buf[i] = 0xff ; + msg ("W HDLC frame missing initial 0xff" ) ; + n++ ; + } + + if ( buf[1] == 0x03 || buf[1] == 0x13 ) { + for ( i=0 ; i < n ; i++ ) + buf[i]=normalbits[buf[i]] ; + msg ("W bit-reversed HDLC frame, reversing bit order" ) ; + f->ibitorder = f->ibitorder == normalbits ? reversebits : normalbits ; + } + + return n ; +} + + +/* Read HDLC frame data. Returns 0 if OK, 1 on frame error, 3 on + timeout, invalid response or too-long frame. */ + +int receive_frame_data ( TFILE *f, uchar *buf, int n, int *len ) +{ + int err=0, c, i ; + + for ( i=0 ; ( c = tgetd ( f, T3S ) ) >= 0 ; i++ ) + if ( i < n ) buf[ i ] = c ; + + if ( c == EOF ) { + + err = msg ( "E3timed out reading frame data" ) ; + + } else { + + switch ( cmd ( f, 0, TO_RTCMD ) ) { + case OK: + case CONNECT: + break ; + case ERROR: + case NO: + err = msg ( "W1frame error" ) ; + break ; + case EOF: + err = msg ( "E3no response after frame data" ) ; + break ; + default: + err = msg ( "E3wrong response after frame data" ) ; + break ; + } + + } + + if ( i >= n ) + err = msg ( "E3frame too long (%d, > %d max bytes)", i, n ) ; + + if ( len ) *len = i ; + + return err ; +} + + +/* Get a Class 1 command or response frame. An attempt to match + and combine T.30 "Response Received?" and "Command Received?" + protocol flowcharts. + + When receiving commands returns after first correct + non-optional frame or after the time given by getcmd has + elapsed. This is instead of looping through main flowchart. + + When receiving responses returns on the first detected + non-optional frame, after timeout T4, or on errors. + + Returns immediately if gets a +FCERROR response so can retry + as data carrier. Returns DCN as a valid frame instead of + hanging up. + + Returns the command/response received, or EOF on timeout or + error. + +*/ + +int getfr ( TFILE *mf, uchar *buf, int getcmd ) +{ + int err=0, frame=0, frlen, c, t ; + char remoteid [ IDLEN + 1 ] ; + time_t start ; + uchar *fif=buf+3 ; + + start = 10*time(0) ; + + t = getcmd ? ( getcmd > 1 ? getcmd : T2 ) : T4 ; + + Enter: + + err = 0 ; + + if ( nframes++ ) { + c = cmd ( mf, "+FRH=3", t ) ; + } else { + c = CONNECT ; /* implied by ATA or ATD */ + } + + switch ( c ) { + case EOF: /* time out */ + tput ( mf, CAN_STR, 1 ) ; + ckcmd ( mf, 0, 0, TO_ABRT, OK ) ; + err = 1 ; + break ; + case NO: /* S7 time out */ + err = 1 ; + break ; + case MODULATION: /* data carrier (or DHS) */ + return -msg ( "W-2 wrong carrier" ) ; + break ; + case CONNECT: /* frame */ + break ; + default: /* shouldn't happen */ + err = msg ( "E3wrong response to receive-frame command" ) ; + break ; + } + + if ( ! err ) + err = receive_frame_data ( mf, buf, MAXFRLEN, &frlen ) ; + + if ( ! err && frlen < 3 ) + err = msg ( "E3received short frame (%d bytes)", frlen ) ; + + if ( ! err ) { + + frlen = fixframe ( buf, frlen, mf ) ; + logfr ( "received", frname ( buf [ 2 ] ), buf, frlen ) ; + frame = buf [ 2 ] & 0x7f ; + + switch ( frame ) { + case CRP: + err = 1 ; + case NSF: + case NSC: + case NSS: + goto Enter ; + case CIG: + case CSI: + case TSI: + revcpy ( fif , (uchar*) remoteid ) ; + msg ( "I- remote ID -> %*.*s", IDLEN, IDLEN, remoteid ) ; + goto Enter ; + } + + } + + if ( err && getcmd && ( t -= 10*time(0) - start ) > 0 ) + goto Enter ; + + return err ? EOF : frame ; +} + + +/* Class 1 send/receive. + + The logic in this function is a mess because it's meant to + mirror the flowchart in ITU-T recommendation T.30 which is the + protocol specification. + + */ + +int c1sndrcv ( + TFILE *mf, cap local, char *localid, + OFILE *outf, IFILE *inf, + int pages, char *header, faxfont *font, + int maxpgerr, int noretry, int calling ) +{ + int err=0, rxpage=0, page=1, t, disbit, good, frame, last, nerr ; + int rxdislen, ppm, try=0, pagetry=0, retry=0, remtx=0, remrx=0 ; + int writepending=0, dp=0 ; + cap remote = { DEFCAP }, session = { DEFCAP } ; + char *fname=0 ; + uchar buf [ MAXFRLEN ], *fif=buf+3 ; + + if ( ! calling ) goto RX ; + + /* Class 1 Transmitter: */ + + T: /* Transmitter Phase B - wait for DIS or DTC */ + + pagetry = 0 ; + + frame = getfr ( mf, buf, T1 ) ; + + if ( frame <= 0 ) { + err = msg ( "E3no answer from remote fax" ) ; + goto B ; + } + + if ( frame != DIS && frame != DTC ) { + msg ( "W2 can't open page" ) ; + goto C ; + } + + disbit = ( frame == DIS ) ? 0x80 : 0x00 ; + try = 0 ; + + A: /* decide to send or receive after DIS/DTC */ + + if ( frame == DIS || frame == DTC ) { + rxdislen = dislen ( fif ) ; + mkcap ( fif, remote, 1 ) ; + remtx = fif[1] & 0x80 ; + remrx = fif[1] & 0x40 ; + } + + msg ( "N remote has %sdocument(s) to send, and can %sreceive", + remtx ? "" : "no ", remrx ? "" : "not " ) ; + + if ( pages > 0 ) { + if ( ! remrx ) msg ( "W remote cannot receive, trying anyways" ) ; + goto D ; + } else { + if ( ! remtx ) msg ( "W remote has nothing to send, trying anyways" ) ; + goto R ; + } + + D: /* send DCS */ + + if ( rdpage ( inf, dp, &ppm, local, 0 ) ) { + err = msg ( "E2can't open page" ) ; + goto B ; + } + + D_2: + + mincap ( local, remote, session ) ; + + revcpy ( (uchar*) localid, fif ) ; + if ( ! err ) + err = putframe ( TSI | MORE_FR | disbit, buf, IDLEN, mf, -1 ) ; + + mkdis ( session, fif, DCSLEN, 0, pages ) ; + if ( ! err ) + err = putframe ( DCS | SUB_FR | disbit, buf, DCSLEN, mf, -1 ) ; + + if ( cmd ( mf, "+FTS=8", T3S ) != OK ) + msleep ( TMOD ) ; /* if +FTS not supported */ + + if ( ! err ) + err = puttrain ( mf, c1cmd[SND][TRN][session[BR]], + 1.5*cps [ session[BR] ] ) ; + try++ ; + + if ( ! err ) { + cmd ( mf, "+FRS=1", T3S ) ; /* wait for TCF carrier to drop */ + frame = getfr ( mf, buf, 0 ) ; + } + + if ( err || frame < 0 ) { + if ( try >= 3 ) { + goto C_timeout ; + } else { + goto D_2 ; + } + } + + switch ( frame ) { + + case DIS: + case DTC: + if ( try >= 3 ) goto C_timeout ; + else goto A ; + + case FTT: + msg ( "I channel not usable at %d bps", 8*cps[session[BR]] ) ; + remote[BR] = fallback[session[BR]] ; + if ( remote[BR] >= 0 ) goto D_2 ; + else { err = msg ( "E2 channel not usable at lowest speed" ) ; goto C ; } + + case CFR: + goto I_2 ; + + default: + err = msg ( "E3 invalid response to DCS (0x%02x)", frame ) ; + goto C ; + } + + I: /* send a page */ + + if ( rdpage ( inf, dp, &ppm, local, 0 ) ) { + err = msg ( "E2can't open page" ) ; + goto B ; + } + + I_2: + + ckcmd ( mf, &err, c1cmd [SND][DTA][session[BR]], TO_FT, CONNECT ) ; + if ( !err ) + err = send_data ( mf, inf, page, pages, local, session, header, font ) ; + + pagetry++ ; + + if ( !err ) + err = end_data ( mf, session, 0, 0 ) ; + + if ( cmd ( mf, "+FTS=8", T3S ) != OK ) + msleep ( TMOD ) ; /* if +FTS not supported */ + + /* fix ppm if on last page of stdin */ + if ( lastpage ( inf ) ) ppm = EOP ; + + try = 0 ; + sendppm: + if ( !err ) err = putframe ( ppm | disbit, buf, 0, mf, -1 ) ; + try++ ; + + frame = getfr ( mf, buf, 0 ) ; + if ( frame < 0 ) { + if ( try >= 3 ) { + goto C_timeout ; + } else { + goto sendppm ; + } + } + + fname = inf->page->fname ; + + switch ( noretry ? MCF : frame ) { /* common retry logic */ + case MCF: + case RTP: + case PIP: + fname = inf->page->fname ; + if ( fname ) msg ( "Isent -> %s", fname ) ; + pagetry=0 ; + page++ ; + dp = 1 ; + break ; + case PIN: + case RTN: + dp = 0 ; + retry = pagetry < NTXRETRY ; + break ; + default: + err = msg ( "E3invalid post-page response (0x%02x)", frame ) ; + goto C ; + } + + switch ( ppm ) { + + case MPS: + switch ( frame ) { + case PIN: goto E ; + case PIP: goto E ; + case MCF: goto I ; + case RTP: goto D ; + case RTN: goto D ; + } + + case EOP: + switch ( frame ) { + case PIN: goto E ; + case PIP: goto E ; + case MCF: + case RTP: + nextipage ( inf, 1 ) ; /* skip ahead to mark all files done */ + if ( remtx ) goto R ; /* poll after sending */ + else goto C ; + case RTN: + if ( retry ) goto D ; + else goto C ; + } + + case EOM: + switch ( frame ) { + case PIN: goto E ; + case PIP: goto E ; + case MCF: + case RTP: + case RTN: + cmd ( mf, "+FRS=20", T3S ) ; /* wait for ppr carrier to drop */ + if ( retry ) goto T ; + else goto T ; + } + + } + + E: /* ignore PIN and PIP */ + msg ( "W interrupt request ignored" ) ; + try=0 ; + goto A ; + + /* Class 1 Receiver */ + + RX: + + R: /* Receiver Phase B */ + + if ( ! err ) err = wrpage ( outf, rxpage ) ; + + disbit=0x00 ; + + for ( t=0 ; !err && t 0 ) { + disbit = ( frame == DIS ) ? 0x80 : 0x00 ; + goto F_2 ; + } + } + if ( err ) goto C ; + else goto C_timeout ; + + + F: /* get a command */ + + last = frame ; + frame = getfr ( mf, buf, 1 ) ; + + if ( writepending ) { /* do postponed file close/open */ + writepending=0 ; + err = wrpage ( outf, rxpage ) ; + if ( err ) goto C ; + } + + if ( frame < 0 ) { + if ( frame == -2 ) goto getdata ; /* data carrier detected */ + if ( last == EOM ) goto R ; + else { err = msg ("E3 timed out waiting for command" ) ; goto B ; } + } + + F_2: + + switch ( frame ) { + + case DTC: + goto D ; + + case DIS: + try=0 ; + goto A ; + + case DCS: + mkcap ( fif, session, 0 ) ; + printcap ( "session", session ) ; + + cmd ( mf, "+FTS=1", T3S ) ; /* make sure DCS is over */ + + gettrain ( mf, c1cmd [RCV][TRN][session[BR]], cps[session[BR]], &good ) ; + + if ( putframe ( ( good ? CFR : FTT ) | disbit, buf, 0, mf, -1 ) || + ! good ) goto F ; + + getdata: + + outf->w=pagewidth[session[WD]]; + outf->h=0; + outf->xres=204.0; + outf->yres=vresolution[session[VR]]; + + if ( cmd ( mf, c1cmd [RCV][DTA][session[BR]], TO_FT ) != CONNECT ) + goto F ; /* +FCERROR -> DCS resent */ + + if ( receive_data ( mf, outf, session, &nerr ) == 0 ) { + good = nerr < maxpgerr ; + msg ( "I-received -> %s", outf->cfname ) ; + writepending=1 ; /* ppm follows immediately, don't write yet */ + rxpage++ ; + } else { + good = 0 ; + } + ckcmd ( mf, 0, 0, TO_RTCMD, NO ) ; + goto F ; + + /* III: */ + + case PRI_EOM: + case PRI_MPS: + case PRI_EOP: + frame &=0xf7 ; /* ignore PRocedure Interrupt bit */ + case MPS: + case EOP: + case EOM: + putframe ( ( good ? MCF : RTN ) | disbit, buf, 0, mf, -1 ) ; + if ( good && frame == MPS ) goto getdata ; + else goto F ; + + case DCN: + goto B ; + + default: + err = msg ( "E3 unrecognized command" ) ; + goto B ; + + } + + C_timeout: + err = msg ( "E3 no command/response from remote" ) ; + + C: + putframe ( DCN, buf, 0, mf, -1 ) ; + + B: + ckcmd ( mf, 0, "H", TO_RESET, OK ) ; /* hang up */ + + if ( rxpage > 0 ) + wrpage ( outf, -1 ) ; /* remove last file */ + + return err ; +} + + +/* Check for hangup message. Assumes hsc is initialized to a + negative value. Returns 0 if no hangup message, 1 if there + was one. If perr is not null, sets it to 2 if the hsc was + non-zero (error). */ + +int gethsc ( int *hsc, int *perr ) +{ + int err=0, i ; + if ( sresponse ( "+FHNG:", hsc ) || sresponse ( "+FHS:", hsc ) ) { + if ( hsc && *hsc > 0 ) { + err = msg ( "E2abnormal termination (code %d)", *hsc ) ; + for ( i=0 ; c2msg[i].min >= 0 ; i++ ) { + if ( *hsc >= c2msg[i].min && *hsc <= c2msg[i].max ) { + msg ( "E %s", c2msg[i].msg ) ; + } + } + if ( perr && ! *perr ) { + *perr = 2 ; + } + } else { + err = 1 ; + } + } + return err ; +} + + +/* Print remote ID and store DCS values in session as per + responses since last command. */ + +void getc2dcs ( cap session ) +{ + char *p ; + if ( ( p = sresponse ( "+FTI:", 0 ) ) != 0 || + ( p = sresponse ( "+FTSI:", 0 ) ) != 0 ) { + msg ( "I- remote ID -> %s", p ) ; + } + if ( ( p = sresponse ( "+FCS:", 0 ) ) != 0 || + ( p = sresponse ( "+FDCS:", 0 ) ) != 0 ) { + str2cap ( p, session ) ; + printcap ( "session", session ) ; + } +} + +/* Wait for a starting character XON or DC2. Display & ignore + any other characters received. */ + +void getstartc ( TFILE *mf ) +{ + int c, noise ; + + for ( noise=0 ; ( c = tgetc ( mf, TO_C2X ) ) != XON && c != DC2 ; noise++ ) { + if ( c == EOF ) { + msg ( "Wno XON/DC2 received after CONNECT") ; + break ; + } else { + msg ( "W-+%s", cname ( c ) ) ; + noise++ ; + } + } + + if ( noise ) + msg ( "W : %d characters received while waiting to send", noise ) ; +} + + +/* Class 2 send and receive. + + If calling, polls if no files to send, otherwise sends. If + not calling sends documents if files to send, else receives. + + When sending, issues +FDIS to change session parameters if + file format changes, then sends +FDT followed by data and a + post-page message determined by format of next page, if any. + Retransmits each page up to NTXRETRY times. + + When receiving extracts file format from responses to +FDR or + ATA and saves them in the file. Receives data to a file and + sets page transfer status if too many errors. + + Returns 0 if OK or 2 on errors. */ + + +int c2sndrcv ( + TFILE *mf, cap local, char *localid, + OFILE *outf, IFILE *inf, + int pages, char *header, faxfont *font, + int maxpgerr, int noretry, int calling ) +{ + int err=0, done=0, page, pagetry, nerr, c, dp=0 ; + int ppm=0, good, hsc, changed ; + int remtx=0 ; + char *fname=0 ; + cap session = { 0,0,0,0, 0,0,0,0 } ; + char buf [ CMDBUFSIZE ] ; + + hsc=-1 ; /* will be set >= 0 on hangup */ + + if ( sresponse ( "+FPO", 0 ) ) { + remtx = 1 ; + msg ( "N remote has document(s) to send." ) ; + } + + if ( calling ) { + if ( pages ) goto send ; + else goto poll ; + } else { + if ( pages ) goto pollserver ; + else goto receive ; + } + + /* Class 2 Send */ + + pollserver: + + /* with +FLP[L]=1 the modem should accept +FDT. */ + + send: + + page=1 ; + + pagetry=0 ; + while ( ! err && ! done ) { + + err = rdpage ( inf, dp, &ppm, local, &changed ) ; + + if ( ! err && changed ) { + sprintf ( buf, c20 ? "+FIS=%d,%d,%d,%d" : "+FDIS=%d,%d,%d,%d", + local[0], local[1], local[2], local[3] ) ; + ckcmd ( mf, 0, buf, TO_FT, OK ) ; + if ( gethsc ( &hsc, &err ) ) { + continue ; + } + } + + ckcmd ( mf, &err, "+FDT", -TO_C2B, CONNECT ) ; + if ( err || gethsc ( &hsc, &err ) ) { + done=1 ; + continue ; + } + + getc2dcs ( session ) ; + + if ( ! c20 ) getstartc ( mf ) ; + + send_data ( mf, inf, page, pages, local, session, header, font ) ; + pagetry++ ; + + if ( c20 ) { + end_data ( mf, session, ppm, &good ) ; + } else { + end_data ( mf, session, 0, 0 ) ; + + gethsc ( &hsc, &err ) ; + + if ( ! err && hsc < 0 ) { + ckcmd ( mf, &err, ppm == EOP ? "+FET=2" : + ppm == EOM ? "+FET=1" : "+FET=0" , TO_C2PP, OK ) ; + } + + gethsc ( &hsc, &err ) ; + + if ( ! err && hsc < 0 ) { + if ( sresponse ( "+FPTS:", &good ) ) { + good &= 1 ; /* odd values mean received OK */ + } else { /* no +FPTS and +FHNG probably NG */ + good = gethsc ( 0, 0 ) ? 0 : + msg ( "W1no +FPTS response, assumed received" ) ; + } + } + + } + + if ( noretry ) good = 1; + + if ( good ) { + fname = inf->page->fname ; + if ( fname ) msg ( "Isent -> %s", fname ) ; + pagetry=0 ; + page++ ; + dp = 1 ; + if ( ppm == EOP ) { + nextipage ( inf, 1 ) ; /* skip ahead to mark all files done */ + done = 1 ; + } + } else { + dp = 0 ; + if ( pagetry >= NTXRETRY ) + err = msg ( "E2too many page send retries" ) ; + } + + if ( gethsc ( &hsc, &err ) ) done=1 ; + + if ( good && lastpage ( inf ) ) { + done = 1 ; + } + } + + goto done ; + + /* Class 2 Receive */ + + poll: + + /* with +FSP[L]=1 and +FPO[LL]: the modem should now accept +FDR. */ + + receive: + + getc2dcs ( session ) ; /* get ATA responses */ + + done=0 ; + for ( page=0 ; ! err && ! done ; page++ ) { + + if ( ! ( err = wrpage ( outf, page ) ) ) { + c = cmd ( mf, "+FDR", -TO_C2R ) ; + + switch ( c ) { + + case CONNECT: + getc2dcs ( session ) ; + + outf->w=pagewidth[session[WD]]; + outf->h=0; + outf->xres=204.0; + outf->yres=vresolution[session[VR]]; + + tput ( mf, &startchar, 1 ) ; + + if ( receive_data ( mf, outf, session, &nerr ) == 0 ) { + good = nerr < maxpgerr ; + msg ( "I-received -> %s", outf->cfname ) ; + } else { + good = 0 ; + } + + ckcmd ( mf, &err, 0, TO_C2EOR, OK ) ; + + if ( err || gethsc ( &hsc, &err ) ) { + wrpage ( outf, +1 ) ; + wrpage ( outf, -1 ) ; + done=1 ; + continue ; + } + + if ( ! good ) { + msg ( "Wreception errors" ) ; + ckcmd ( mf, 0, c20 ? "+FPS=2" : "+FPTS=2", T3S, OK ) ; + if ( gethsc ( &hsc, &err ) ) continue ; + } + break ; + + case OK: + wrpage ( outf, -1 ) ; /* no more pages */ + done=1 ; + if ( gethsc ( &hsc, &err ) ) continue ; + break ; + + default: + wrpage ( outf, -1 ) ; /* oops */ + err = msg ( "E3receive (+FDR) command failed") ; + break ; + } + } + } + + + done: + if ( hsc < 0 ) ckcmd ( mf, 0, c20 ? "+FKS" : "+FK", TO_RESET, OK ) ; + + return err ; +} + + +/* Dial the phone number given by string s. If nowait is true + adds a ';' to the dial string to avoid waiting for a + CONNECTion (might allow ersatz polling). Also resets the + global "nframes" if appropriate so getfr() and putframe() know + not to issue +FRH/+FTH. Returns 0 if dialed OK, 1 if busy, 2 + on errors. */ + +int dial ( TFILE *f, char *s, int nowait ) +{ + int err=0, hsc=-1 ; + char c, dsbuf [ 128 ], *p ; + + sprintf ( dsbuf, nowait ? "D%.126s;" : "D%.127s" , s ) ; + msg ( "Idialing %s", dsbuf+1 ) ; + + c = cmd ( f, dsbuf, TO_A ) ; + + if ( ( p = sresponse ( "+FCSI:", 0 ) ) != 0 || + ( p = sresponse ( "+FCI:", 0 ) ) != 0 ) { + msg ( "I- remote ID -> %s", p ) ; + } + + if ( nowait && c == OK ) { + msg ( "Icalled" ) ; + nframes = 1 ; + } else if ( c1 && c == CONNECT ) { + msg ( "Iconnected" ) ; + nframes = 0 ; + } else if ( !c1 && c == OK ) { + msg ( "Iconnected" ) ; + } else if ( c == BUSY ) { + err = msg ( "W1number is busy" ) ; + } else { + err = msg ( "E2dial command failed" ) ; + } + + gethsc ( &hsc, err ? 0 : &err ) ; + + return err ; +} + + +/* Figure out which mode the modem answered in (fax, data, voice + or none) based on modem class and responses to the previous + command. Sets crate (connect rate) for DATAMODE and hsc + (hangup status code) if detects a class 2 hangup message. */ + +enum connectmode { NONE, DATAMODE, FAXMODE, VOICEMODE } ; + +enum connectmode ansmode ( int *crate, int *hsc ) +{ + enum connectmode mode = NONE ; + int x=0 ; + + if ( c1 && sresponse ( "CONNECT", &x ) ) { + mode = x ? DATAMODE : FAXMODE ; + } + + if ( !c1 && sresponse ( "OK", 0 ) ) { + mode = FAXMODE ; + } + + if ( !c1 && ( sresponse ( "CONNECT", &x ) || sresponse ( "+FDM", 0 ) ) ) { + mode = DATAMODE ; + } + + if ( sresponse ( "DATA", 0 ) || sresponse ( "CONNECT DATA", 0 ) ) { + mode = DATAMODE ; + sresponse ( "CONNECT", &x ) ; + } + + if ( sresponse ( "FAX", 0 ) || sresponse ( "+FCO", 0 ) ) { + mode = FAXMODE ; + } + + if ( sresponse ( "VCON", 0 ) ) { + mode = VOICEMODE ; + } + + if ( gethsc ( hsc, 0 ) ) { + mode = NONE ; + } + + if ( DATAMODE && x ) *crate = x ; + + return mode ; +} + + +/* Answer the phone. Remove our lock if sharing device with + outgoing calls. If waiting for call, wait for modem activity, + else answer phone. Figure out what mode we answered in and + handle call appropriately. Re-lock if necessary. Exec *getty + or *vcmd for data or voice calls. */ + +int answer ( TFILE *f, char **lkfile, + int wait, int share, int softaa, + char *getty, char *vcmd, char *acmd ) +{ + int err=0, c ; + int crate=19200, hsc=-1, i ; + enum connectmode mode=NONE ; + + if ( ! err && share ) { + err = ttymode ( f, COMMAND ) ; + if ( ! err ) + err = unlockall ( lkfile ) ; + } + + if ( ! err && wait ) { + msg ( "Iwaiting for activity") ; + tdata ( f, -1 ) ; + msg ( "Iactivity detected") ; + } + + if ( ! err && share ) { + msleep ( 500 ) ; /* let other programs lock port */ + err = lockall ( lkfile, 1 ) ; + if ( err ) + err = msg ( "W1can't answer: can't lock device" ) ; + else + err = ttymode ( f, COMMAND ) ; /* in case it was changed silently */ + } + + for ( i=0 ; ! err && mode == NONE && ( i==0 || ( i==1 && softaa ) ) ; i++ ) { + + c = cmd ( f, wait ? 0 : acmd, ( i==0 && softaa ) ? TO_DATAF : TO_A ) ; + + if ( c == DATA ) cmd ( f, c1 ? "O" : 0, TO_A ) ; /* +FAE=1 weirdness */ + + mode = ansmode ( &crate, &hsc ) ; + + switch ( mode ) { + case DATAMODE : + msg ( "Idata call answered") ; + if ( getty && *getty ) { + char buf [ MAXGETTY ] ; + if ( ckfmt ( getty, 6 ) ) { + err = msg ( "E3 too many %%d escapes in command (%s)", getty ) ; + } else { + sprintf ( buf, getty, crate, crate, crate, crate, crate, crate ) ; + msg ( "Iexec'ing /bin/sh -c \"%s\"" , buf ) ; + execl ( "/bin/sh" , "sh" , "-c" , buf , (void*) 0 ) ; + err = msg ( "ES2exec failed:" ) ; + } + } else { + err = msg ( "E2no getty command defined for data call" ) ; + } + break ; + case FAXMODE : + nframes = 0 ; + msg ( "Ifax call answered") ; + break ; + case VOICEMODE : + msg ( "Ivoice call answered") ; + if ( vcmd && *vcmd ) { + char buf [ MAXGETTY ] ; + if ( ckfmt ( vcmd, 6 ) ) { + } else { + sprintf ( buf, vcmd, f->fd, f->fd, f->fd, f->fd, f->fd, f->fd ) ; + msg ( "Iexec'ing /bin/sh -c \"%s\"" , buf ) ; + execl ( "/bin/sh" , "sh" , "-c" , buf , (void*) 0 ) ; + err = msg ( "ES2exec failed:" ) ; + } + } else { + err = msg ( "E2no voice command defined for voice call" ) ; + } + break ; + case NONE: + if ( i==0 && softaa && hsc < 0 && getty && *getty ) { + int j ; /* switch to fax for 2nd try */ + for ( j=0 ; j<3 ; j++ ) + if ( cmd ( f, c1 ? "+FCLASS=1" : + ( c20 ? "+FCLASS=2.0" : "+FCLASS=2" ), -TO_RESET ) + == OK ) break ; + wait = 0 ; + acmd = ANSCMD ; + } else { + err = msg ( "E3unable to answer call") ; + } + break ; + default: + err = msg ( "E3can't happen(answer)" ) ; + break ; + } + + } + + return err ; +} + + +/* Initialize modem. Determine class to use and issue + class-specific fax initialization commands. If poll is true, + issues commands to enable polling also. Returns 0 or 3 if a + mandatory setup command fails. */ + +int modem_init ( TFILE *mf, cap c, char *id, + int calling, int poll, int capsset, int *preverse ) +{ + int err=0, t=-TO_RESET ; + char buf [ CMDBUFSIZE ], model [ CMDBUFSIZE ] = "" ; + char **p, *q, *modelq [2][4] = { { "+FMFR?", "+FMDL?", 0 }, + { "+FMI?", "+FMM?", "+FMR?", 0 } } ; + + + /* diasable command echo and get firmware revision */ + + cmd ( mf, "E0", t ) ; + + if ( cmd ( mf, "I3", t ) == OK ) { + getresp ( "", model, CMDBUFSIZE-1 ) ; + strcat ( model, " " ) ; + } + + /* if not already chosen, pick safest class; set it */ + + if ( ! err && ! c1 && !c2 && ! c20 ) { + if ( cmd ( mf, "+FCLASS=?", t ) != OK ) { + err = msg ("E3 modem does not support fax" ) ; + } else { + if ( strinresp ( "2.0" ) ) c20 = 1 ; + else if ( strinresp ( "2" ) ) ; + else if ( strinresp ( "1" ) ) c1 = 1 ; + else err = msg ("E3 can't determine fax modem class support" ) ; + if ( strstr ( model, "Sportster" ) ) { /* USR Sporsters are buggy */ + c1 = 1 ; + c2 = c20 = 0 ; + } + } + } + + ckcmd ( mf, &err, c1 ? "+FCLASS=1" : + ( c20 ? "+FCLASS=2.0" : "+FCLASS=2" ), t, OK ) ; + + /* get make & model if using Class 2 or 2.0 */ + + if ( ! c1 ) { + for ( p = modelq [ c20 ] ; ! err && *p ; p++ ) { + if ( cmd ( mf, *p, t ) == OK ) { + getresp ( "", model, CMDBUFSIZE-1 ) ; + strcat ( model, " " ) ; + } + } + + if ( ! c1 && strstr ( model, "Multi-Tech" ) ) { + *preverse = 0 ; + msg ("I Multi-Tech bit order set" ) ; + } + } + + if ( ! err ) + msg ( "I using %sin class %s", model, c1 ? "1" : c20 ? "2.0" : "2" ) ; + + /* get maximum modem speed if not already set (Class 1 only) */ + + if ( ! err && c1 && ! capsset ) { + int i ; + char *c1spstr [6] = { "24", "48", "72", "96", "121", "145" } ; + ckcmd ( mf, &err, calling ? "+FTM=?" : "+FRM=?", t, OK ) ; + for ( i=0 ; i < 6 && strinresp ( c1spstr[i] ) ; i++ ) ; + c[1]=i?i-1:0; + } + + /* issue essential commands and set/get modem capabilities (Class 2 only) */ + + if ( ! err && ! c1 ) { + + if ( c20 ) { + ckcmd ( mf, 0, "+FIP", t, OK ) ; + ckcmd ( mf, 0, "+FNR=1,1,1,1", t, OK ) ; + } + + ckcmd ( mf, &err, "+FCR=1", t, OK ) ; + + if ( ! capsset ) { + if ( cmd ( mf, c20 ? "+FIS?" : "+FDIS?", -t ) == OK && + ( q = strinresp ( "," ) ) ) { + str2cap ( q-1, c ) ; + } else { + msg ( "W can't get modem capabilities, set to default" ) ; + capsset = 1 ; + } + } + + if ( capsset ) { + sprintf ( buf, c20 ? "+FCC=%d,%d,%d,%d,%d,%d,%d,%d" : + "+FDCC=%d,%d,%d,%d,%d,%d,%d,%d", + c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7] ) ; + ckcmd ( mf, 0, buf, -t, OK ) ; + } + + sprintf ( buf, c20 ? "+FLI=\"%.*s\"" : "+FLID=\"%.*s\"" , + CMDBUFSIZE-9, id ) ; + ckcmd ( mf, 0, buf, -t, OK ) ; + + if ( ! err && poll ) { + + ckcmd ( mf, 0, c20 ? "+FSP=1" : "+FSPL=1", -t, OK ) ; + + sprintf ( buf, c20 ? "+FPI=\"%.*s\"" : "+FCIG=\"%.*s\"" , + CMDBUFSIZE-9, id ) ; + ckcmd ( mf, 0, buf, -t, OK ) ; + + } + } + + return err ; +} + + +/* the following are global so can terminate properly on signal */ + +char *icmd[3][ MAXICMD+1 ] = {{0},{0},{0}} ; /* initialization commands */ +TFILE faxdev = { -1, 0,0, {0}, 0, 0 } ; /* modem */ +char *lkfile [ MAXLKFILE+1 ] = {0} ; /* lock file names */ +IFILE ifile = { 0 } ; /* files being sent */ +int locked = 0 ; /* modem locked */ + + +/* print names of files not sent and reset modem before + exiting. */ + +int cleanup ( int err ) +{ + /* log names of files not sent */ + logifnames ( &ifile, "I failed -> %s" ) ; + + if ( ! locked && faxdev.fd >= 0 ) + end_session ( &faxdev, icmd[2], lkfile, err != 4 ) ; + + msg ( "Idone, returning %d (%s)", err, + errormsg [ err >= 0 && err <= 5 ? err : 6 ] ) ; + + return err ; +} + + +/* signal handler */ + +void onsig ( int sig ) +{ + msg ( "E terminating on signal %d", sig ) ; + exit ( cleanup ( 5 ) ) ; +} + + +/* Fax send/receive program for Class 1, 2 and 2.0 fax + modems. Returns 0 on success, 1 if number busy or device + locked, 2 for errors, 3 for protocol errors, 4 if no modem + response, 5 on signal. */ + +int main( int argc, char **argv) +{ + int err=0, doneargs=0, c=0, i ; + int testing=0, calling=0 ; + + int nicmd[3]={0,0,0}, nlkfile=0, nverb=0 ; + + char *faxfile = FAXFILE ; + + int softaa=0, share=0, wait=0, reverse=1, ignerr=0, noretry=0, hwfc=0 ; + int capsset=0 ; + char *getty = "", *vcmd = "", *acmd=ANSCMD ; + + cap local = { DEFCAP } ; + char localid [ IDLEN + 1 ] = DEFID ; + + int maxpgerr = MAXPGERR ; + + time_t now ; + char *header = 0, headerbuf [ MAXLINELEN ] ; + char *fontname = 0 ; + faxfont font ; + + OFILE ofile ; + int pages = 0 ; + char *phnum="", *ansfname = DEFPAT ; + char fnamepat [ EFAX_PATH_MAX ] ; + + /* print initial message to both stderr & stdout */ + argv0 = argv[0] ; + verb[1] = "ewia" ; + msg ( "I " Version " " Copyright ) ; + argv0 = efaxbasename ( argv0 ) ; + msg ( "A compiled "__DATE__ " " __TIME__ ) ; + verb[1] = "" ; + + while ( ! err && ! doneargs && + ( c = nextopt ( argc,argv, + "a:c:d:e:f:g:h:i:j:k:l:o:p:q:r:st:v:wx:T" ) ) != -1 ) { + + switch (c) { + case 'a': + acmd = nxtoptarg ; + break ; + case 'c': + err = str2cap ( nxtoptarg, local ) ; + capsset = 1 ; + break ; + case 'l': + if ( strlen ( nxtoptarg ) > IDLEN ) + msg("Wlocal ID (%s) truncated to %d characters", nxtoptarg, IDLEN ) ; + if ( strspn ( nxtoptarg, " +0123456789" ) != strlen ( nxtoptarg ) ) + msg("Wlocal ID (%s) has non-standard characters", nxtoptarg ) ; + sprintf ( localid, "%*.*s", IDLEN, IDLEN, nxtoptarg ) ; + break ; + case 'i': + if ( nicmd[0] < MAXICMD ) icmd[0][ nicmd[0]++ ] = nxtoptarg ; + else err = msg ( "E2too many '-i' options"); + break ; + case 'j': + if ( nicmd[1] < MAXICMD ) icmd[1][ nicmd[1]++ ] = nxtoptarg ; + else err = msg ( "E2too many '-j' options"); + break ; + case 'k': + if ( nicmd[2] < MAXICMD ) icmd[2][ nicmd[2]++ ] = nxtoptarg ; + else err = msg ( "E2too many '-k' options"); + break ; + case 'h': + header = nxtoptarg ; + break ; + case 'f': + fontname = nxtoptarg ; + break ; + case 'd': + faxfile = nxtoptarg ; + break ; + case 'e': + vcmd = nxtoptarg ; + break ; + case 'g': + getty = nxtoptarg ; + break ; + case 'o': /* most protocol options are globals */ + for ( ; *nxtoptarg ; nxtoptarg++ ) + switch ( *nxtoptarg ) { + case '0' : c20 = 1 ; break ; + case '1' : c1 = 1 ; break ; + case '2' : c2 = 1 ; break ; + case 'a' : softaa = 1 ; break ; + case 'e' : ignerr = 1 ; break ; + case 'f' : vfc = 1 ; break ; + case 'h' : hwfc = 1 ; break ; + case 'l' : lockpolldelay /= 2 ; break ; + case 'n' : noretry = 1 ; break ; + case 'r' : reverse = 0 ; break ; + case 'x' : startchar = XON ; break ; + case 'z' : cmdpause += T_CMD ; break ; + default : msg ( "Wunrecognized protocol option (%c)", *nxtoptarg ) ; + } + break ; + case 'q': + if ( sscanf ( nxtoptarg , "%d", &maxpgerr ) != 1 || maxpgerr < 0 ) + err=msg ("E2bad quality (-q) argument (%s)", nxtoptarg ) ; + break; + case 'r': + ansfname = nxtoptarg ; + break; + case 's': + share = 1 ; + break; + case 't': + calling=1; + /* fall through */ + case 'p': + if ( argv [ argc ] ) err = msg ("E2can't happen(unterminated argv)") ; + if ( ! err ) err = newIFILE ( &ifile, argv + nxtoptind ) ; + pages = argc - nxtoptind - ( c == 'p' ? 1 : 0 ) ; + pages = pages < 0 ? 0 : pages ; + phnum = nxtoptarg ; + doneargs=1 ; + break; + case 'v': + verb[nverb] = nxtoptarg ; + nverb=1; + break ; + case 'w': + wait = 1 ; + break ; + case 'x': + if ( nlkfile < MAXLKFILE ) lkfile[ nlkfile++ ] = nxtoptarg ; + else err = msg ( "E2too many lock files" ) ; + break ; + case 'T': /* test: begin+end session */ + testing=1; + doneargs=1 ; + break ; + default : fprintf ( stderr, Usage, argv0 ) ; err = 2 ; break ; + } + } + + for ( i=0 ; i /* ANSI C */ +#include +#include +#include + +#include "efaxio.h" /* EFAX */ +#include "efaxmsg.h" +#include "efaxos.h" + +#define MAXRESPB 1024 /* maximum bytes of modem responses saved */ + +char *prompts[] = { /* modem responses that are prompts */ + "OOK", "-CONNECT FAX", "CCONNECT", "NNO CARRIER", "EERROR", + "NNO DIALTONE", "BBUSY", "NNO ANSWER", "M+FCERROR", "VVCON", + "DDATA", 0 } ; + +int lockpolldelay = 8000 ; /* milliseconds between checks of lock files */ + + /* signals to be caught so can hang up phone */ +int catch [] = { CATCHSIGS, 0 } ; + + +/* Modem features */ + +int c1=0, c20=0 ; /* use class 1/class 2.0 */ +int c2=0 ; /* force class 2 */ +int cmdpause = T_CMD ; /* delay before each init command */ +int vfc = 0 ; /* virtual flow control */ +uchar startchar = DC2 ; /* character to start reception */ + + /* response detector lookup tables */ +uchar rd_nexts [ 256 ] = { 0 }, rd_allowed [ 256 ] = { 0 } ; + +/* Initialize two lookup tables used by a state machine to detect + modem responses embedded in data. The first shows which + characters are allowed in each state. The second shows which + characters in each state increment the state. Each table is + indexed by character. The detector sequences through 6 states + corresponding to sequences of the form: AX... + where A is an upper-case letter and X is an u.c. letter or + space. The state values are 01, 02, 04, 08, 10 and 20 (hex) + and are used to mask in a bit from the tables. When the state + reaches 0x20 a modem response has been detected. With random + data there is a small O(10^-10) chance of spurious + detection. */ + +void rd_init ( void ) +{ + int c ; + + rd_nexts[CR] = rd_allowed[CR] = 0x01 | 0x08 ; + rd_nexts[LF] = rd_allowed[LF] = 0x02 | 0x10 ; + for ( c='A' ; c<'Z' ; c++ ) { + rd_allowed[c] = 0x04 | 0x08 ; + rd_nexts[c] = 0x04 ; + } + rd_allowed[' '] = 0x08 ; +} + + +/* Get a modem response into buffer s, storing up to n bytes. + The response includes characters from the most recent control + character until the first LF following some text. Returns s + or null if times-out in t deciseconds or on i/o error. Trace + messages are buffered to reduce possible timing problems. */ + +char *tgets( TFILE *f, char *s, int len, int t ) +{ + int i, n, c ; + + for ( i=n=0 ; 1 ; i++ ) { + if ( ( c = tgetc ( f, t ) ) == EOF ) break ; + if ( i == 0 ) msg ( "M-+ .%03d [", time_ms ( ) ) ; + msg ( "M-+ %s", cname ( c ) ) ; + if ( n > 0 && c == LF ) break ; + if ( ! iscntrl ( c ) && n < len ) s[n++] = c ; + } + + if ( n >= len ) msg ( "W- modem response overflow" ) ; + s[ n < len ? n : len-1 ] = '\0' ; + if ( i > 0 ) { + if ( c == EOF ) msg ( "M- <...%.1f s>]", (float)t/10 ) ; + else msg ( "M- ]" ) ; + } + + return c == EOF ? 0 : s ; +} + + +/* Send bytes to the modem, doing bit-reversal and escaping DLEs. + Returns 0 or 2 on errors. */ + +int sendbuf ( TFILE *f, uchar *p, int n, int dcecps ) +{ + int err=0, c, over ; + uchar *order = f->obitorder ; + uchar buf [ MINWRITE+1 ] ; + int i ; + + for ( i=0 ; ! err && n > 0 ; n-- ) { + c = order [ *p++ ] ; + if ( c == DLE ) buf[i++] = DLE ; + buf[i++] = c ; + if ( i >= MINWRITE || n == 1 ) { + + /* ``virtual'' flow control */ + + if ( vfc && dcecps > 0 ) { + over = f->bytes - ( proc_ms ( ) - f->mstart ) * dcecps / 1000 + - MAXDCEBUF ; + if ( over > 0 ) msleep ( over * 1000 / dcecps ) ; + } + + if ( tput ( f, buf, i ) < 0 ) + err = msg ( "ES2fax device write error:" ) ; + + i = 0 ; + } + } + + return err ; +} + + +/* Scan responses since giving previous command (by cmd()) for a + match to string 's' at start of response. If a matching + response is found it finds the start of the data field which + is defined as the next non-space character in the current or + any subsequent responses. If ip is not null, reads one integer + (decimal format) into ip. [problem: Class 2.0 status responses + are in hex.] Returns pointer to start of data field of + response string or NULL if not found. */ + +char responses [ MAXRESPB ], *lresponse = responses ; + +char *sresponse ( char *s, int *ip ) +{ + char *p, *r = 0 ; + int lens, lenr ; + + lens = strlen ( s ) ; + for ( p=responses ; p 0 ) + msg ( "R read value %d from \"%s\"", *ip, r ) ; + } + + } + + return r ; +} + + +/* Search for the string s in responses since last command. + Skips lines beginning with "AT" (command echo) and removes + trailing spaces. Returns pointer to the string or NULL if not + found. */ + +char *strinresp ( char *s ) +{ + char *p, *r = 0 ; + + for ( p=responses ; p= CMDBUFSIZE-4 ) { + msg ( "E modem command \"%s\" too long", s ) ; + } else { + sprintf ( buf, "AT%s\r", s ) ; + tput ( f, buf, strlen(buf) ) ; + } + } + + if ( t ) { + + msg ( "C- waiting %.1f s", ((float) t)/10 ) ; + + while ( ( p = tgets ( f, buf, CMDBUFSIZE, t ) ) ) { + + if ( ( resplen += strlen ( buf ) + 1 ) <= MAXRESPB ) { + strcpy ( lresponse, buf ) ; + lresponse += strlen ( buf ) + 1 ; + } + + if ( ( p = strtabmatch ( (char**) prompts, buf ) ) ) { + msg ( "C- response \"%s\"", buf ) ; + break ; + } + + if ( ! strcmp ( buf, "RING" ) ) { msleep ( 100 ) ; goto retry ; } + } + } + + return p ? *p : EOF ; +} + + +/* Send command to modem and wait for reply after testing (and + possibly setting) current error status via err + pointer. Returns 0 if err is already set, command response, or + EOF on timeout. */ + +int ckcmd ( TFILE *f, int *err, char *s, int t, int r ) +{ + int c=0 ; + if ( ( ! err || ! *err ) && ( c = cmd ( f, s, t ) ) != r && r ) { + msg ( err ? "E %s %s %s" : "W %s %s %s", + c == EOF ? "timed out" : "wrong response", + s ? "after command: " : "after waiting", + s ? s : "" ) ; + if ( err ) *err = 3 ; + } + return c ; +} + + +/* Resynchronize modem from an unknown state. If no immediate + response, try pulsing DTR low (needs &D{2,3,4}), and then + cancelling data or fax data modes. In each case, discards any + responses for about 2 seconds and then tries command ATQ0V1 to + enable text responses. Returns 0 if OK or 4 if no response. + */ + +int modemsync ( TFILE *f ) +{ + int err=0, method=0 ; + + for ( method=0 ; ! err ; method++ ) { + switch ( method ) { + case 0 : + break ; + case 1 : + break ; + ttymode ( f, VOICECOMMAND ) ; + case 2 : + msg ("Isync: dropping DTR") ; + ttymode ( f, COMMAND ) ; msleep ( 200 ) ; + ttymode ( f, DROPDTR ) ; msleep ( 200 ) ; + ttymode ( f, COMMAND ) ; + break ; + case 3 : + msg ("Isync: sending escapes") ; + ttymode ( f, VOICECOMMAND ) ; + tput ( f, CAN_STR, 1 ) ; + tput ( f, DLE_ETX, 2 ) ; + msleep ( 100 ) ; + ttymode ( f, COMMAND ) ; + tput ( f, CAN_STR, 1 ) ; + tput ( f, DLE_ETX, 2 ) ; + msleep ( 1500 ) ; + tput ( f, "+++", 3 ) ; + break ; + case 4 : + err = msg ("E4sync: modem not responding") ; + continue ; + } + while ( method && cmd ( f, 0, 20 ) != EOF ) ; + if ( cmd ( f, "Q0V1", -20 ) == OK ) break ; + } + return err ; +} + + +/* Set up modem by sending initialization/reset commands. + Accepts either OK or CONNECT responses. Optionally changes + baud rate if a command begins with a number. Returns 0 if OK, + 3 on errors. */ + +int setup ( TFILE *f, char **cmds, int ignerr ) +{ + int err=0 ; + char c ; + + for ( ; ! err && *cmds ; cmds++ ) { +#if 0 + if ( *cmds && isdigit( **cmds ) ) { + + } +#endif + if ( ( c = cmd ( f, *cmds, -TO_RESET ) ) != OK && c != VCONNECT && + ! ignerr ) { + err = msg ( "E3modem command (%s) failed", *cmds ? *cmds : "none" ) ; + } + } + + return err ; +} + + +/* Terminate session. Makes sure modem is responding, sends + modem reset commands or hang-up command if none, removes lock + files. Returns 0 if OK, 3 on error.*/ + +int end_session ( TFILE *f, char **zcmd, char **lkfile, int sync ) +{ + int err = 0 ; + + if ( f && sync ) + err = modemsync ( f ) ; + if ( f && zcmd && ! err && sync ) + err = setup ( f, zcmd, 0 ) ; + if ( f ) + ttymode ( f, ORIGINAL ) ; + if ( lkfile ) + unlockall ( lkfile ) ; + return err ; +} + + +/* Initialize session. Try locking and opening fax device until opened or + get error. Then set tty modes, register signal handler, set up + modem. Returns 0 if OK, 2 on errors, 3 if initialization failed, 4 if no + modem response. */ + +int begin_session ( TFILE *f, char *fname, int reverse, int hwfc, + char **lkfile, ttymodes mode, void (*onsig) (int) ) +{ + int i, err=0, busy=0, minbusy=0 ; + + do { + err = lockall ( lkfile, busy >= minbusy ) ; + if ( ! err ) err = ttyopen ( f, fname, reverse, hwfc ) ; + if ( err == 1 ) { + if ( busy++ >= minbusy ) { + msg ( "W %s locked or busy. waiting.", fname ) ; + minbusy = minbusy ? minbusy*2 : 1 ; + } + msleep ( lockpolldelay ) ; + } + } while ( err == 1 ) ; + + if ( ! err ) msg ( "Iopened %s", fname ) ; + + if ( ! err ) err = ttymode ( f, mode ) ; + + if ( ! err ) { + rd_init ( ) ; + f->rd_state = RD_BEGIN ; + } + + for ( i=0 ; ! err && catch [ i ] ; i++ ) + if ( signal ( catch [ i ], onsig ) == SIG_ERR ) + err = msg ( "ES2can't set signal %d handler:", catch [ i ] ) ; + + if ( !err ) err = modemsync ( f ) ; + + return err ; +} diff --git a/efaxio.h b/efaxio.h new file mode 100644 index 0000000..257b8df --- /dev/null +++ b/efaxio.h @@ -0,0 +1,54 @@ +#ifndef _EFAXIO_H +#define _EFAXIO_H + +#include "efaxos.h" /* TFILE definition */ + +#ifndef uchar +#define uchar unsigned char +#endif + +#define CMDBUFSIZE 256 /* longest possible command or response */ +#define DLE_ETX "\020\003" /* DLE-ETX (end of data) string */ +#define CAN_STR "\030" /* CAN (cancel reception) string */ +#define TO_RESET 50 /* t/o for modem reset commands (>>2.6s) */ +#define T_CMD 1 /* pause before each modem command */ +#define MAXDCEBUF 32 /* max bytes allowed in buffer when write */ +#define MINWRITE 64 /* minimum bytes to write() to modem */ + +enum promptcodes { /* codes for modem prompts */ + BUSY = 'B', CONNECT = 'C', DATA='D', ERROR = 'E', + MODULATION='M', NO = 'N', OK = 'O', RING = 'R', VCONNECT = 'V' } ; + + /* Modem features */ + +extern int c1, c20 ; /* use class 1/class 2.0 */ +extern int c2 ; /* force class 2 */ +extern int cmdpause ; /* delay before each init command */ +extern int vfc ; /* virtual flow control */ +extern uchar startchar ; /* character to start reception */ +extern int lockpolldelay ; /* milliseconds between checks of lock files */ + + /* response detector lookup tables */ +extern uchar rd_allowed[], rd_nexts[] ; +#define RD_BEGIN 0x01 +#define RD_END 0x20 + + /* Modem interface routines */ + +int cmd ( TFILE *f, char *s , int t ) ; +int ckcmd ( TFILE *f, int *err, char *s, int t, int r ) ; +int modemsync ( TFILE *f ) ; +char *sresponse ( char *s, int *ip ) ; +char *strinresp ( char *s ) ; +int getresp ( char *s, char *buf, int len ) ; + +int setup ( TFILE *f, char **cmds, int ignerr ) ; + +int sendbuf ( TFILE *f, uchar *p, int n, int dcecps ) ; + +int begin_session ( TFILE *f, char *fname, int reverse, int hwfc, char **lkfile, + ttymodes mode, void (*onsig) (int) ) ; + +int end_session ( TFILE *f, char **zcmd, char **lkfile, int sync ) ; + +#endif diff --git a/efaxlib.c b/efaxlib.c new file mode 100644 index 0000000..4887cd9 --- /dev/null +++ b/efaxlib.c @@ -0,0 +1,2306 @@ +/* + efaxlib.c - utility routines for efax + Copyright 1995 Ed Casas +*/ + +#include +#include +#include + +#include "efaxmsg.h" +#include "efaxlib.h" + +#ifndef SEEK_SET +#define SEEK_SET 0 +#endif + +#define DEFXRES 204.145 /* fax x and y resolution in dpi */ +#define DEFYRES 195.58 + +#define DEFWIDTH 1728 /* 215x297 mm image at fax resolution */ +#define DEFHEIGHT 2287 + +extern t4tab wtab [ ( 64 + 27 + 13 ) + 1 ] ; /* T.4 coding tables */ +extern t4tab btab [ ( 64 + 27 + 13 ) + 1 ] ; + +short short256 = 256 ; /* for endian-ness detection */ + +/* Make sure printf strings have only %d escapes and n or fewer + of them. Returns 0 if OK, 1 on error. */ + +int ckfmt ( char *p, int n ) +{ + for ( ; *p ; p++ ) { + if ( p[0] == '%' ) { + if ( p[1] == 'd' ) n-- ; + else if ( p[1] == '%' ) p++ ; + else n=-1 ; + } + } + + return n < 0 ; +} + + +/* Initialize state of variable-length code word encoder. */ + +void newENCODER ( ENCODER *e ) +{ + e->x = 0 ; + e->shift = -8 ; +} + + +/* Store a code word `code' of length `bits' (<=24) in buffer pointed to by + `buf'. Bits that don't fit in complete bytes are saved between calls. + To flush the remaining bits call the function with code=0 and bits=0. + Returns pointer to next free element in output buffer. Calling function + must ensure at least bits/8 bytes are available in buffer. */ + +uchar *putcode ( ENCODER *e, short code, short bits, uchar *buf ) +{ + e->x = ( e->x << bits ) | code ; + e->shift += bits ? bits : -e->shift ; + + while ( e->shift >= 0 ) { + *buf++ = e->x >> e->shift ; + e->shift -= 8 ; + } + + return buf ; +} + + +/* Convert run lengths to 1-D T.4-codes. First run is white. Silently + truncates run lengths that are too long. After using this function EOLs + may need to be added and/or the putcode() buffer flushed. Returns + pointer to next free element in output buffer. */ + +uchar *runtocode ( ENCODER *e, short *runs, int nr, uchar *codes ) +{ + uchar col = 0, *maxcodes = codes + MAXCODES ; + t4tab *ctab = wtab, *p ; + short rlen ; + long x ; + short shift ; + +#define PUTCODE(p) { x = ( x << p->bits ) | p->code ; shift += p->bits ; \ + while ( shift >= 0 ) { *codes++ = x >> shift ; shift -= 8 ; } } + + x = e->x ; shift = e->shift ; + + while ( nr-- > 0 ) { + rlen = *runs++ ; + if ( rlen > 63 ) { /* make-up code */ + if ( rlen > MAXRUNLEN ) rlen = MAXRUNLEN ; + p = ctab + 63 + ( rlen >> 6 ) ; + if ( codes < maxcodes ) PUTCODE(p) ; + } + p = ctab + ( rlen & 0x3f ) ; /* terminating code */ + if ( codes < maxcodes ) PUTCODE(p) ; + ctab = ( col ^= 1 ) ? btab : wtab ; + } + + e->x = x ; e->shift = shift ; + + return codes ; +} + + +/* Pad/truncate run-length coded scan line 'runs' of 'nr' runs by 'pad' + pixels (truncate if negative). Returns the new number of runs. */ + +int xpad ( short *runs, int nr, int pad ) +{ + if ( pad < 0 ) { /* truncate */ + while ( pad < 0 ) pad += ( nr <= 0 ? -pad : runs [ --nr ] ) ; + runs [ nr++ ] = pad ; + } else { /* pad with white */ + if ( nr & 1 ) runs [ nr - 1 ] += pad ; + else runs [ nr++ ] = pad ; + } + return nr ; +} + + +/* Shift a run-length coded scan line right by s pixels (left if negative). + If necessary, zero-length runs are created to avoid copying. Returns + the pixel width change (+/-). */ + +int xshift ( short *runs, int nr, int s ) +{ + int i=0, n=0 ; + if ( s < 0 ) { + for ( i = 0 ; s < 0 && i < nr ; i++ ) { + s += runs [ i ] ; + n -= runs [ i ] ; + runs [ i ] = 0 ; + } + i-- ; + } + if ( i < nr ) { + runs [ i ] += s ; + n += s ; + } + return n ; +} + + +/* Scale nr run lengths in buffer pointed to by p to scale image + horizontally. The scaling factor is xs/256. Returns new line width in + pixels. */ + +int xscale ( short *p, int nr, int xs ) +{ + int inlen=0, outlen=0 ; + for ( ; nr-- > 0 ; p++ ) { + inlen += *p ; + *p = ( ( inlen * xs + 128 ) >> 8 ) - outlen ; + outlen += *p ; + } + return outlen ; +} + + +/* Zero-terminated lists of run lengths for each byte. */ + +uchar byteruns [ 1408 + 1 ] = + "8071061106205120511105210530413041210411110411204220421104310440" + "3140313103121103122031112031111103112103113032303221032111032120" + "3320331103410350215021410213110213202121202121110212210212302111" + "3021112102111111021111202112202112110211310211402240223102221102" + "2220221120221111022121022130233023210231110231202420241102510260" + "1160115101141101142011312011311101132101133011213011212101121111" + "0112112011222011221101123101124011114011113101111211011112201111" + "1120111111110111112101111130111230111221011121110111212011132011" + "1311011141011150125012410123110123201221201221110122210122301211" + "3012112101211111012111201212201212110121310121401340133101321101" + "3220131120131111013121013130143014210141110141201520151101610170" + "1701610151101520141201411101421014301313013121013111101311201322" + "0132110133101340121401213101212110121220121112012111110121121012" + "1130122301222101221110122120123201231101241012501115011141011131" + "1011132011121201112111011122101112301111130111112101111111101111" + "1120111122011112110111131011114011240112310112211011222011211201" + "1211110112121011213011330113210113111011312011420114110115101160" + "2602510241102420231202311102321023302213022121022111102211202222" + "0222110223102240211402113102112110211220211112021111110211121021" + "1130212302122102121110212120213202131102141021503503410331103320" + "3212032111032210323031130311210311111031112031220312110313103140" + "4404310421104220411204111104121041305305210511105120620611071080" ; + +/* Convert byte-aligned bit-mapped n-byte scan line into array of run + lengths. Run length array must have *more* than 8*n elements. First + run is white. Returns number of runs coded. */ + +int bittorun ( uchar *bits, int n, short *runs ) +{ + static uchar init=0, *rltab [ 256 ] ; + register uchar *p, c, lastc = 0x00 ; + short *runs0 = runs ; + + if ( ! init ) { /* initialize pointer and run tables */ + int i = 0 ; + for ( rltab[ 0 ] = p = byteruns ; *p ; p++ ) + if ( ! ( *p -= '0' ) && i < 255 ) + rltab [ ++i ] = p+1 ; + init = 1 ; + } + + *runs = 0 ; + for ( ; n > 0 ; n-- ) { + p = rltab [ c = *bits++ ] ; + if ( ( lastc & 0x01 ) ? ! ( c & 0x80 ) : ( c & 0x80 ) ) + *(++runs) = *p++ ; /* new run */ + else + *runs += *p++ ; /* continue run */ + while ( *p ) + *(++runs) = *p++ ; + lastc = c ; + } + + return runs - runs0 + 1 ; +} + + +/* Bitwise-OR two run-length coded scan lines. The run length + vectors a and b are OR-ed into c. If c is null, the result is + placed in a. The new image width is stored in pels if it is + not null. Returns the number of runs in the result. */ + +int runor ( short *a, int na, short *b, int nb, short *c, int *pels ) +{ + register short la, lb ; + int ia, ib, ic, np=0 ; + short tmp [ MAXRUNS ] ; + + if ( ! c ) c = tmp ; + + la = a [ ia = 0 ] ; + lb = b [ ib = 0 ] ; + c [ ic = 0 ] = 0 ; + + while ( 1 ) { + if ( la <= lb ) { /* select shorter sub-run */ + if ( ( ( ia | ib ) ^ ic ) & 1 ) /* OR of subruns same colour as c? */ + c [ ++ic ] = la ; /* no, new output run */ + else + c [ ic ] += la ; /* yes, add it */ + lb -= la ; /* align subruns */ + if ( ++ia >= na ) break ; /* done */ + la = a [ ia ] ; /* get new subrun */ + } else { /* same for line b ... */ + if ( ( ( ia | ib ) ^ ic ) & 1 ) + c [ ++ic ] = lb ; + else + c [ ic ] += lb ; + la -= lb ; + if ( ++ib >= nb ) break ; + lb = b [ ib ] ; + } + } + + if ( ia < na ) + while ( 1 ) { + if ( ( ia ^ ic ) & 1 ) + c [ ++ic ] = la ; + else + c [ ic ] += la ; + if ( ++ia >= na ) break ; + la = a [ ia ] ; + } + else + while ( 1 ) { + if ( ( ib ^ ic ) & 1 ) + c [ ++ic ] = lb ; + else + c [ ic ] += lb ; + if ( ++ib >= nb ) break ; + lb = b [ ib ] ; + } + + if ( c == tmp ) for ( ia=0 ; ia <= ic ; ia++ ) np += a[ia] = c[ia] ; + + if ( pels ) *pels = np ; + + return ic + 1 ; +} + + +/* Get a number from a PBM file header while skipping whitespace + and comments. Returns the number or 0 on EOF. Reads one more + byte than used by the number. */ + +int pbmdim ( IFILE *f ) +{ + int c, n=0 ; + + /* scan for first digit and skip comments */ + while ( ! isdigit ( c = fgetc ( f->f ) ) && c >= 0 ) + if ( c == '#' ) + while ( ( c = fgetc ( f->f ) ) != '\n' && c >= 0 ) ; + + /* get the number */ + if ( c >= 0 && isdigit( c ) ) { + n = c - '0' ; + while ( isdigit ( c = fgetc ( f->f ) ) && c >= 0 ) + n = n * 10 + c - '0' ; + } + + return n ; +} + + +/* Append nb bits from in[from] to bit-mapped scan line buffer + where `from' is a bit (not byte) index. Bits in bytes are + ordered from MS to LS bit. Initialize before each scan line by + calling with nb=0 and in pointing to output buffer. Flush + after each scan line by calling with nb=0 and in=NULL. */ + +#define putbits( c, b ) { x = ( x << (b) ) | (c) ; shift += (b) ; \ + if ( shift >= 0 ) { *out++ = x >> shift ; shift -= 8 ; } } + +void copybits ( uchar *in, int from, short nb ) +{ + uchar *f ; + short bits ; + static uchar *out ; + static short x, shift ; + static unsigned char right [ 9 ] = { + 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff } ; + + if ( ! nb ) { /* reset for new scan line */ + if ( in ) out = in ; + else putbits ( 0, -shift ) ; /* or flush bit buffer */ + x = 0 ; + shift = -8 ; + } else { + f = in + ( from >> 3 ) ; + bits = 8 - ( from & 7 ) ; + + if ( nb >= bits ) { + putbits ( *f++ & right [ bits ], bits ) ; + nb -= bits ; + } else { + putbits ( ( *f >> ( bits - nb ) ) & right [ bits ], nb ) ; + nb = 0 ; + } + + while ( nb >= 8 ) { putbits ( *f++, 8 ) ; nb -= 8 ; } + + if ( nb > 0 ) putbits ( *f >> ( 8 - nb ), nb ); + } +} + + +/* Generate scan line 'line' of string 'txt' using font `font' + and store the runs in 'runs'. The font is scaled so it + appears to have cells of width w and height h. lmargin pixels + of white space are added at the left margin. Sets 'pels' to + line width if not null. Returns number of runs coded. */ + +int texttorun ( uchar *txt, faxfont *font, short line, + int w, int h, int lmargin, + short *runs, int *ppels ) +{ + uchar *in, out [ MAXLINELEN * MAXFONTW / 8 + 1 ] ; + int i, nc = 0, cw, nr, pels ; + + line = ( line * font->h + h/2 ) / h ; + + cw = font->w ; + if ( line >= font->h ) line = font->h - 1 ; + in = font->buf + 256/8 * cw * line ; + + copybits ( out, 0, 0 ) ; + for ( i=0 ; txt[i] && i < MAXLINELEN ; i++ ) { + copybits ( in, font->offset [ txt[i] ], cw ) ; + nc++ ; + while ( ( txt[i] == HT ) && ( nc & 7 ) ) { /* tab */ + copybits ( in, font->offset [ ' ' ], cw ) ; + nc++ ; + } + } + copybits ( 0, 0, 0 ) ; + + nr = bittorun ( out, ( nc*cw + 7 )/8, runs ) ; + + if ( font->w == w ) + pels = nc*cw ; + else + pels = xscale ( runs, nr, ( w * 256 ) / font->w ) ; + + pels += xshift ( runs, nr, lmargin ) ; + + if ( ppels ) *ppels = pels ; + + return nr ; +} + + /* Image File Input Functions */ + + +/* Names of file formats */ + +char *iformatname [ NIFORMATS ] = IFORMATS ; +char *oformatname [ NOFORMATS ] = OFORMATS ; +char *pformatname [ NPFORMATS ] = PFORMATS ; + +/* Log the names of files still to be sent using the "msg()" + format string s. */ + +void logifnames ( IFILE *f, char *s ) +{ + PAGE *p ; + char *fn ; + + if ( f && f->page ) + for ( p = f->page ; p <= f->lastpage ; p++ ) { + fn = p->fname ; + if ( fn ) msg ( s, fn ) ; + } +} + + +/* Read run lengths for one scan line from T.4-coded IFILE f into buffer + runs. If pointer pels is not null it is used to save pixel count. + Returns number of runs stored, EOF on RTC, or -2 on EOF or other + error. */ + +int readruns ( IFILE *f, short *runs, int *pels ) +{ + int err=0, c=EOF, n ; + register int x ; + dtab *tab, *t ; + short shift ; + short *p, *maxp, *q, len=0, npad=0 ; + DECODER *d ; + uchar reverse=f->page->revbits ; + + maxp = ( p = runs ) + MAXRUNS ; + d = &f->d ; + + x = d->x ; shift = d->shift ; tab = d->tab ; /* restore decoder state */ + + do { + do { + while ( shift < 0 ) { + if ( ( c = fgetc ( f->f ) ) == EOF ) { + x = ( x << 15 ) | 1 ; shift += 15 ; /* EOL pad at EOF */ + npad++ ; + } else { + if ( reverse ) c = normalbits [ c & 0xff ] ; + x = ( x << 8 ) | c ; shift += 8 ; + } + } + t = tab + ( ( x >> shift ) & 0x1ff ) ; + tab = t->next ; + shift -= t->bits ; + } while ( ! t->code ) ; + if ( p < maxp ) *p++ = t->code ; + } while ( t->code != -1 ) ; + + d->x = x ; d->shift = shift ; d->tab = tab ; /* save state */ + + if ( npad > 1 ) msg ("W EOF before RTC" ) ; + + if ( p >= maxp ) msg ( "W run length buffer overflow" ) ; + + /* combine make-up and terminating codes and remove +1 offset + in run lengths */ + + n = p - runs - 1 ; + for ( p = q = runs ; n-- > 0 ; ) + if ( *p > 64 && n-- > 0 ) { + len += *q++ = p[0] + p[1] - 2 ; + p+=2 ; + } else { + len += *q++ = *p++ - 1 ; + } + n = q - runs ; + + /* check for RTC and errors */ + + if ( len ) + d->eolcnt = 0 ; + else + if ( ++(d->eolcnt) >= RTCEOL ) err = EOF ; + + if ( c == EOF ) { + if ( ferror ( f->f ) ) { + err = -msg ("ES2error reading fax file:") ; + } else { + err = -2 ; + } + } + + if ( pels ) *pels = len ; + + return err ? err : n ; +} + + +/* Read a PCX compressed bit-map */ + +int readpcx ( char *p, int len, IFILE *f ) +{ + int err=0, n, c ; + + while ( !err && len > 0 ) { + if ( ( c = fgetc ( f->f ) ) < 0 ) { + err = msg ( "ES2 PCX read failed" ) ; + } else { + if ( ( c & 0xc0 ) == 0xc0 ) { /* a run count */ + n = c & 0x3f ; + c = fgetc ( f->f ) ; + if ( ( c = fgetc ( f->f ) ) < 0 ) { + err = msg ( "ES2 PCX read failed" ) ; + } else { + memset ( p, c, n <= len ? n : len ) ; + p += n ; + len -= n ; + } + } else { /* bits 0 to 7 are image data */ + *p++ = c ; + len-- ; + } + } + } + + if ( len != 0 ) + msg ( "W PCX run-length coding error" ) ; + + return err ; +} + +/* Read a scan line from the current page of IFILE f. Stores + number of runs in runs and line width in pels if not null. + Pages ends at EOF. Text pages also end if a complete text line + would not fit or if the line contains a formfeed character. + PBM pages also end when all lines in the bitmap have been + read. Fax pages also end at RTC. Returns number of runs stored + or EOF at end of page. */ + +int readline ( IFILE *f, short *runs, int *pels ) +{ + int nr = 0, nb ; + uchar bits [ MAXBITS ] ; + + if ( f->lines != 0 ) { /* -1 allowed as well */ + switch ( f->page->format ) { + + case P_TEXT : + if ( f->txtlines <= 0 ) { /* need another text line */ + if ( fgets ( f->text, MAXLINELEN, f->f ) ) { + f->txtlines = f->charh ; + if ( strchr ( f->text, FF ) ) { + f->lines = 0 ; /* no more lines in this page */ + nr = EOF ; /* don't return any */ + } + } else { + nr = EOF ; + } + } + if ( nr != EOF ) { + nr = texttorun ( (uchar*) f->text, f->font, f->charh - f->txtlines, + f->charw, f->charh, f->lmargin, + runs, pels ) ; + f->txtlines-- ; + } + break ; + + case P_RAW: + case P_PBM: + if ( fread ( bits, 1, f->page->w/8, f->f ) != f->page->w/8 ) { + nr = EOF ; + } else { + nr = bittorun ( bits, f->page->w/8, runs ) ; + if ( pels ) *pels = f->page->w ; + } + break ; + + case P_FAX: + nr = readruns ( f, runs, pels ) ; + break ; + + case P_PCX: + nb = ( ( f->page->w + 15 ) / 16 ) * 2 ; /* round up */ + if ( readpcx ( bits, nb, f ) != 0 ) { + nr = EOF ; + } else { + nr = bittorun ( bits, nb, runs ) ; + if ( pels ) *pels = f->page->w ; + } + break ; + + } + } else { + nr = EOF ; + } + + if ( nr >= 0 && f->lines > 0 ) f->lines-- ; + + return nr ; +} + + +/* Deduce the file type by scanning buffer p of n bytes. */ + +int getformat ( uchar *p, int n ) +{ + int format = 0 ; + + /* figure out file type if not already set */ + + if ( ! format && n < 2 ) { + format = I_TEXT ; + msg ( "W only read %d byte(s) from input file, assuming text", n ) ; + } + + if ( ! format && ! p[0] && ! ( p[1] & 0xe0 ) ) { + format = I_FAX ; + } + + if ( ! format && ! strncmp ( p, "P4", 2 ) ) { + format = I_PBM ; + } + + if ( ! format && n >= 128 && p[0] == 0x0a && + strchr ("\02\03\05", p[1] ) && p[2] <= 1 ) { + if ( p[65] != 1 ) { + msg ( "E can't read colour PCX" ) ; + } else { + format = p[2] ? I_PCX : I_PCX ; + } + } + + if ( ! format && ! strncmp ( p, "%!", 2 ) ) { + msg ( "W Postscript input file will be treated as text" ) ; + } + + if ( ! format && ( p[0] == 'M' || p[1] == 'I' ) && ( p[1] == p[0] ) ) { + format = I_TIFF ; + } + + if ( ! format && + ( p[0] == 0x3a && p[1] == 0xde && + p[2] == 0x68 && p[3] == 0xb1) ) { + format = I_DCX ; + } + + if ( ! format && n ) { /* "90% printable" heuristic */ + int i, nprint = 0 ; + for ( i=0 ; i 0.90 ) { + format = I_TEXT ; + } + } + + return format ; +} + + +/* initialize page descriptor */ + +void page_init ( PAGE *p, char *fn ) +{ + p->fname = fn ; + p->offset = 0 ; + p->w = DEFWIDTH ; + p->h = DEFHEIGHT ; + p->xres = DEFXRES ; + p->yres = DEFYRES ; + p->format = P_FAX ; + p->revbits = 0 ; +} + + +void page_report ( PAGE *p, int fmt, int n ) +{ + msg ( "F page %d : %s + %ld : %dx%d @ %.fx%.f dpi %s/%s", + n, + p->fname, p->offset, p->w, p->h, + p->xres, p->yres, + iformatname [fmt], pformatname [p->format] ) ; +} + +/* File handling for undefined file types */ + +#define auto_first 0 +#define auto_next 0 + +/* File handling for TIFF files */ + +#define tiff_reset 0 + +/* Name of TIFF tag 'tag'. */ + +char *tagname ( int tag ) +{ + static struct tagnamestruct { int code ; char *name ; } + tagnames [] = { + { 256, "width" }, + { 257, "length" }, + { 258, "bits/sample" }, + { 259, "compresssion(g3=3)" }, + { 262, "photometric(0-min=white)" }, + { 266, "fill order(msb2lsb=1)" }, + { 273, "strip offsets" }, + { 274, "orientation(1=normal)" }, + { 277, "samples/pixel" }, + { 278, "rows/strip" }, + { 279, "strip byte counts" }, + { 282, "xresolution" }, + { 283, "yresolution" }, + { 284, "storage(1=single plane)" }, + { 292, "g3options" }, + { 296, "resolution units(2=in,3=cm)" }, + { 297, "page number" }, + { 327, "clean fax(0=clean/1=regen/2=errors)" }, + {0,0} }, + *p ; + + for ( p=tagnames ; p->code ; p++ ) + if ( tag == p->code ) break ; + return p->code ? p->name : "unknown tag" ; +} + +/* Read 2- or 4-byte integers from a file and correct for file + endianness. Returns 0 if OK, 1 on error. */ + +int fread2 ( unsigned short *p, IFILE *f ) +{ + int err=0 ; + uchar c[2] ; + + err = fread ( c, 1, 2, f->f ) == 2 ? 0 : 1 ; + + *p = f->bigend ? + c[0] << 8 | c[1] << 0 : + c[1] << 8 | c[0] << 0 ; + + return err ; +} + +int fread4 ( unsigned long *p, IFILE *f ) +{ + int err=0 ; + uchar c[4] ; + + err = fread ( c, 1, 4, f->f ) == 4 ? 0 : 1 ; + + *p = f->bigend ? + c[0] << 24 | c[1] << 16 | c[2] << 8 | c[3] << 0 : + c[3] << 24 | c[2] << 16 | c[1] << 8 | c[0] << 0 ; + + return err ; +} + + +/* Read a TIFF directory at current file offset, save image + format information and seek to next directory if any. Returns + 0 if OK, 2 on errors. */ + +int tiff_next ( IFILE *f ) +{ + int err=0 ; + unsigned short ntag, tag, type ; + unsigned long count, tv ; + double ftv ; + + msg ( "F+ TIFF directory at %ld", ftell ( f->f ) ) ; + + if ( fread2 ( &ntag, f ) ) { + err = msg ( "E2can't read TIFF tag count" ) ; + } else { + msg ( "F+ with %d tags", (int) ntag ) ; + } + + for ( ; ! err && ntag > 0 ; ntag-- ) { + + err = err || fread2 ( &tag, f ) ; + err = err || fread2 ( &type, f ) ; + err = err || fread4 ( &count, f ) ; + + if ( type == 3 ) { /* left-aligned short */ + unsigned short a, b ; + err = err || fread2 ( &a, f ) ; + err = err || fread2 ( &b, f ) ; + tv = a ; + } else { /* long or offset to data */ + err = err || fread4 ( &tv, f ) ; + } + + if ( type == 5 ) { /* float as ratio in directory data */ + long a, b, where=0 ; + err = err || ( ( where = ftell ( f->f ) ) < 0 ) ; + err = err || fseek ( f->f, tv, SEEK_SET ) ; + err = err || fread4 ( &a, f ) ; + err = err || fread4 ( &b, f ) ; + err = err || fseek ( f->f, where, SEEK_SET ) ; + ftv = (float) a / ( b ? b : 1 ) ; + } else { + ftv = 0.0 ; + } + + if ( err ) { + err = msg ( "ES2can't read TIFF tag" ) ; + continue ; + } + +#ifdef TIFF_DEBUG + { + char *tagtype[] = { "none", "byte", "ascii", "short", "long", "ratio" } ; + msg ( "F %3d %-5s tag %s %5ld (%3d:%s)", + count, + type <= 5 ? tagtype[type] : "other", + count > 1 ? "@" : "=", + type == 5 ? (long) ftv : tv, tag, tagname(tag) ) ; + } +#endif + + switch ( tag ) { + case 256 : /* width */ + f->page->w = tv ; + break ; + case 257 : /* height */ + f->page->h = tv ; + break ; + case 259 : /* compression: 1=none, 3=G3 */ + if ( tv == 1 ) { + f->page->format = P_RAW ; + } else if ( tv == 3 ) { + f->page->format = P_FAX ; + } else { + err = msg ( "E2can only read TIFF/G3 or TIFF/uncompressed" ) ; + } + break ; + case 266 : /* fill order */ + f->page->revbits = ( tv == 2 ? 1 : 0 ) ; + break ; + case 273 : /* data offset */ + if ( count != 1 ) + err = msg ( "E2can't read multi-strip TIFF files" ) ; + else + f->page->offset = tv ; + break ; + case 282 : /* x resolution */ + f->page->xres = ftv ; + break ; + case 283 : /* y resolution */ + f->page->yres = ftv ; + break ; + case 292 : /* T4 options: 1=2D, 2=uncompressed */ + if ( tv & 0x1 ) + err = msg ( "E2can't read 2D compressed TIFF-F file" ) ; + if ( tv & 0x2 ) + err = msg ( "E2can't read uncompressed TIFF-F file" ) ; + break ; + case 296 : /* units: 2=in, 3=cm */ + if ( tv == 3 ) { + f->page->xres *= 2.54 ; + f->page->yres *= 2.54 ; + } + break ; + } + + } /* end of tag reading loop */ + + + if ( f->page->format == I_AUTO ) { + msg ( "W missing TIFF compression format, set to raw" ) ; + f->page->format = P_RAW ; + } + + if ( ! err ) { + + if ( fread4 ( &(f->next), f ) ) { + err = msg ( "E2can't read offset to next TIFF directory" ) ; + } else { + if ( f->next ) { + msg ( "F , next directory at %ld.", f->next ) ; + if ( fseek ( f->f, f->next, SEEK_SET ) ) + err = msg ( "ES2 seek to next TIFF directory failed" ) ; + } else { + msg ( "F , last image." ) ; + } + } + + } + + if ( ! f->page->offset ) + err = msg ( "E2 missing offset to TIFF data" ) ; + + return err ; +} + + +int tiff_first ( IFILE *f ) +{ + short magic, version ; + + fread ( (uchar*) &magic, 1, 2, f->f ) ; + f->bigend = ( *(uchar*) &magic == 'M' ) ? 1 : 0 ; + fread2 ( &version, f ) ; + fread4 ( &(f->next), f ) ; + + msg ( "F TIFF version %d.%d file (%s-endian)", + version/10, version%10, f->bigend ? "big" : "little" ) ; + + fseek ( f->f, f->next, SEEK_SET ) ; + + return tiff_next ( f ) ; +} + + +/* File handling for text files. */ + +int text_next ( IFILE *f ) +{ + int err = 0, i, nc ; + char buf [ MAXLINELEN ] ; + + f->page->offset = ftell ( f->f ) ; + f->page->format = P_TEXT ; + + nc = ( f->page->w - f->lmargin ) / f->charw ; + + if ( nc > MAXLINELEN ) + nc = MAXLINELEN ; + + for ( i = 0 ; i < f->pglines && fgets ( buf, nc, f->f ) ; i++ ) ; + + f->next = ! feof(f->f) ? ftell ( f->f ) : 0 ; + + err = ferror(f->f) ? 2 : 0 ; + + return err ; +} + + +int text_first ( IFILE *f ) +{ + static faxfont defaultfont ; + + if ( ! f->font ) { + /* use default font scaled 2X, 1 inch margin, 66 lines/page */ + f->font = &defaultfont ; + readfont ( 0, f->font ) ; + if ( ! f->charw ) f->charw = 2 * f->font->w ; + if ( ! f->charh ) f->charh = 2 * f->font->h ; + if ( ! f->lmargin ) f->lmargin = 204 ; + if ( ! f->pglines ) f->pglines = DEFPGLINES ; + } + /* if not set, take default values from font */ + if ( ! f->charw ) f->charw = f->font->w ; + if ( ! f->charh ) f->charh = f->font->h ; + if ( ! f->lmargin ) f->lmargin = 0 ; + if ( ! f->pglines ) f->pglines = f->page->h / f->charh - 6 ; + + return text_next ( f ) ; +} + + +/* File handling for PBM files */ + +int pbm_first ( IFILE *f ) +{ + int err=0 ; + + fseek ( f->f, 2, SEEK_SET ) ; + + if ( ! ( f->page->w = pbmdim ( f ) ) || ! ( f->page->h = pbmdim ( f ) ) ) { + err = msg ( "E2 EOF or 0 dimension in PBM header" ) ; + } else if ( f->page->w % 8 ) { + err = msg ( "E2 PBM width must be multiple of 8" ) ; + } else { + msg ( "F read %dx%d PBM header", f->page->w, f->page->h ) ; + } + + f->page->offset = ftell ( f->f ) ; + f->page->format = P_PBM ; + f->next = 0 ; + + return err ; +} + +#define pbm_next 0 + + +/* File handling for FAX files */ + +#define fax_first 0 + +#define fax_next 0 + +/* File handling for PCX files */ + +/* get a 16-bit word in Intel byte order. */ + +int fgeti ( IFILE *f ) +{ + return fgetc ( f->f ) + fgetc ( f->f ) * 256 ; +} + +int pcx_first ( IFILE *f ) +{ + int err=0, xmin, xmax, ymin, ymax, nc, nb ; + long start ; + + start = ftell ( f->f ) ; + + fseek ( f->f, start+4, SEEK_SET ) ; + xmin = fgeti ( f ) ; + ymin = fgeti ( f ) ; + xmax = fgeti ( f ) ; + ymax = fgeti ( f ) ; + + f->page->w = xmax - xmin + 1 ; + f->page->h = ymax - ymin + 1 ; + + f->page->xres = fgeti ( f ) ; + f->page->yres = fgeti ( f ) ; + + fseek ( f->f, start+0x41, SEEK_SET ) ; + nc = fgetc ( f->f ) ; + nb = fgeti ( f ) ; + + if ( nc != 1 ) + msg ( "W mono PCX file has %d colour planes", nc ) ; + + if ( nb != ( f->page->w + 15 ) / 16 * 2 ) + msg ( "W PCX file has %d bytes per scan line for %d pels", + nb, f->page->w ) ; + + f->page->offset = start + 128 ; + f->page->format = P_PCX ; + + return err ; +} + +#define pcx_next 0 + +/* File handling for DCX files */ + +int dcx_next ( IFILE *f ) +{ + int err=0 ; + long thisp, nextp ; + + /* get this and next pages' offsets */ + + fseek ( f->f, f->next, SEEK_SET ) ; + fread4 ( &thisp, f ) ; + fread4 ( &nextp, f ) ; + + /* save address of next directory entry, if any */ + + f->next = nextp ? f->next + 4 : 0 ; + + if ( ! thisp ) + err = msg ( "E2 can't happen (dcx_next)" ) ; + else + fseek ( f->f, thisp, SEEK_SET ) ; + + return err ? err : pcx_first ( f ) ; +} + +int dcx_first ( IFILE *f ) +{ + f->bigend = 0 ; + f->next = 4 ; + return dcx_next ( f ) ; +} + + +#define raw_reset 0 +#define raw_first 0 +#define raw_next 0 + + +/* input file state reset for different compression methods */ + +#define pcx_reset 0 +#define pbm_reset 0 + +int text_reset ( IFILE *f ) +{ + int err=0 ; + + f->lines = ( ( f->pglines * f->charh ) / f->charh ) * f->charh ; + f->txtlines = 0 ; + f->page->yres = f->lines > 1078 ? 196 : 98 ; /* BOGUS */ + + return err ; +} + +int fax_reset ( IFILE *f ) +{ + int pels ; + short runs [ MAXRUNS ] ; + + newDECODER ( &f->d ) ; + if ( readruns ( f, runs, &pels ) < 0 || pels ) /* skip first EOL */ + msg ( "W first line has %d pixels: probably not fax data", pels ) ; + f->lines = -1 ; + + return 0 ; +} + + +/* Skip to start of same (dp=0) or next (dp=1) page image. + Returns 0 if OK, 1 if no more pages, 2 on errors. */ + +int nextipage ( IFILE *f, int dp ) +{ + int err=0 ; + + int ( *reset [NPFORMATS] ) ( IFILE * ) = { + raw_reset, fax_reset, pbm_reset, text_reset, pcx_reset + }, (*pf)(IFILE*) ; + + /* close current file if any and set to NULL */ + + if ( f->f ) { + fclose ( f->f ) ; + f->f = 0 ; + } + + /* if requested, point to next page and check if done */ + + if ( dp ) { + f->page++ ; + } + + if ( f->page > f->lastpage ) { + err = 1 ; + } + + /* open the file and seek to start of image data */ + + if ( ! err ) { + f->f = fopen ( f->page->fname, (f->page->format == P_TEXT) ? "r" : "rb" ) ; + if ( ! f->f ) + err = msg ( "ES2can't open %s:", f->page->fname ) ; + } + + if ( ! err && fseek ( f->f, f->page->offset, SEEK_SET ) ) + err = msg ( "ES2 seek failed" ) ; + + /* default initializations */ + + f->lines = f->page->h ; + + /* coding-specific initializations for this page */ + + if ( ! err ) { + pf = reset[f->page->format] ; + if ( pf ) + err = (*pf)(f) ; + } + + return err ; +} + + +/* Returns true if on last file. */ + +int lastpage ( IFILE *f ) +{ + return f->page >= f->lastpage ; +} + +#define dfax_first 0 +#define dfax_next 0 + +/* Initialize an input (IFILE) structure. This structure + collects the data about images to be processed to allow a + simple interface for functions that need to read image files. + + The IFILE is initialized by building an array of information + for each page (image) in all of the files. + + The page pointer index is initialized so that the first call + to nextipage with dp=1 actually opens the first file. + +*/ + + +int newIFILE ( IFILE *f, char **fnames ) +{ + int err=0, i, n, fformat=0 ; + char **p ; + uchar buf[128] ; + int ( *fun ) ( IFILE * ) ; + + int ( *first [NIFORMATS] ) ( IFILE * ) = { + auto_first, pbm_first, fax_first, text_first, tiff_first, + dfax_first, pcx_first, raw_first, dcx_first + } ; + + int ( *next [NIFORMATS] ) ( IFILE * ) = { + auto_next, pbm_next, fax_next, text_next, tiff_next, + dfax_next, pcx_next, raw_next, dcx_next + } ; + + f->page = f->pages ; + + /* get info for all pages in all files */ + + for ( p=fnames ; ! err && *p ; p++ ) { + + if ( ! ( f->f = fopen ( *p, "rb" ) ) ) + err = msg ( "ES2 can't open %s:", *p ) ; + + if ( ! err ) { + n = fread ( buf, 1, 128, f->f ) ; + if ( ferror ( f->f ) ) + err = msg ( "ES2 can't open %s:", *p ) ; + } + + if ( ! err ) { + fformat = getformat ( buf, n ) ; + if ( ! fformat ) + err = msg ( "E2 can't get format of %s", *p ) ; + } + + if ( ! err && fseek ( f->f, 0, SEEK_SET ) ) + err = msg ( "ES2 can't rewind %s:", *p ) ; + + /* get format information for all pages in this file */ + + for ( i=0 ; ! err ; i++ ) { + + page_init ( f->page, *p ) ; + + if ( ( fun = i ? next[fformat] : first[fformat] ) ) + err = (*fun)(f) ; + + if ( ! err ) { + + page_report ( f->page, fformat, f->page - f->pages + 1 ) ; + + f->page++ ; + + if ( f->page >= f->pages + MAXPAGE ) + err = msg ( "E2 too many pages (max is %d)", MAXPAGE ) ; + } + + if ( ! f->next ) break ; + } + + if ( f->f ) { + fclose ( f->f ) ; + f->f = 0 ; + } + + } + + f->lastpage = f->page - 1 ; + + f->page = f->pages ; + + if ( ! normalbits[1] ) initbittab() ; /* bit-reverse table initialization */ + + return err ; +} + + /* Image File Output Functions */ + +/* Strings and function to write a bit map in HP-PCL format. The only + compression is removal of trailing zeroes. Margins and resolution are + set before first write. */ + +char *PCLBEGIN = + "\033E" /* Printer reset. */ + "\033&l0E" /* top margin = 0 */ + "\033&a0L" /* left margin = 0 */ + "\033*t%dR" /* Set raster graphics resolution */ + "\033*r1A" ; /* Start raster graphics, rel. adressing */ + +char *PCLEND = + "\033*rB" /* end raster graphics */ + "\014" /* form feed */ + "\033E" ; /* Printer reset. */ + +void pclwrite ( OFILE *f, unsigned char *buf, int n ) +{ + while ( n > 0 && buf [ n-1 ] == 0 ) n-- ; + fprintf( f->f, "\033*b%dW", n ) ; + fwrite ( buf, n, 1, f->f ) ; +} + + +/* Write a bit map as (raw) Portable Gray Map (PGM) format after + decimating by a factor of 4. Sums bits in each 4x4-pel square + to compute sample value. This function reduces each dimension + of a bit map by 4 (it writes n*8/4 pixels per scan line and + one scan line for every 4 in). The 17 possible sample values + are spread linearly over the range 0-255. */ + +void pgmwrite ( OFILE *f, uchar *buf, int n ) +{ + static uchar gval [ MAXBITS * 8 / 4 ] ; + static int init=0, lines=0 ; + static uchar hbits [ 256 ], lbits [ 256 ] ; + static int nybblecnt [ 16 ] = { 0,1,1,2, 1,2,2,3, 1,2,2,3, 2,3,3,4 } ; + static uchar corr [ 17 ] = { 255, 239, 223, 207, 191, 175, 159, 143, 127, + 111, 95, 79, 63, 47, 31, 15, 0 } ; + int m ; + uchar *p, *q ; + + if ( ! init ) { /* build table of bit counts in each nybble */ + short i ; + for ( i=0 ; i<256 ; i++ ) { + hbits [ i ] = nybblecnt [ i >> 4 & 0x0f ] ; + lbits [ i ] = nybblecnt [ i & 0x0f ] ; + } + init = 1 ; + } + + for ( m=n, p=gval, q=buf ; m-- > 0 ; q++ ) { + *p++ += hbits [ *q ] ; + *p++ += lbits [ *q ] ; + } + + if ( ( lines++ & 0x03 ) == 0x03 ) { + for ( p=gval, m=2*n ; m-- > 0 ; p++ ) *p = corr [ *p ] ; + fwrite ( gval, 1, 2*n, f->f ) ; + memset ( gval, 0, 2*n ) ; + } +} + + +/* Postscript image data is differentially coded vertically and + run-length coded horizontally. A leading byte (n) defines the type + of coding for subsequent data: + + 0 repeat previous line + 1-127 n data bytes follow + 128-254 copy n-127 bytes from previous line + 255 n repeat the next character 'n' times + + The overhead for coding a copy is 2 bytes (copy count, data count), + so copies > 2 bytes should be so coded. The overhead for coding a + run is 4 bytes (255, count, byte, data count), so runs > 4 bytes + should be so coded. Copies decode/execute faster and code more + compactly so are preferred over runs. + +*/ + +const char PSBEGIN [] = /* start of file */ + "%%!PS-Adobe-2.0 EPSF-2.0 \n" + "%%%%Creator: efax (Copyright 1995 Ed Casas) \n" + "%%%%Title: efix output\n" + "%%%%Pages: (atend) \n" + "%%%%BoundingBox: 0 0 %d %d \n" + "%%%%BeginComments \n" + "%%%%EndComments \n" + "/val 1 string def \n" + "/buf %d string def \n" + "/getval { \n" + " currentfile val readhexstring pop 0 get \n" + "} bind def \n" + "/readbuf { \n" + " 0 %% => index \n" + " { \n" + " dup buf length ge { exit } if \n" + " getval %% => index run_length \n" + " dup 127 le { \n" + " dup 0 eq { \n" + " pop buf length \n" + " } { \n" + " currentfile buf 3 index 3 index getinterval readhexstring pop pop\n" + " } ifelse \n" + " } { \n" + " dup 255 eq { \n" + " pop getval getval %% => index run_length value \n" + " 2 index 1 3 index 2 index add 1 sub %% => ... start 1 end \n" + " { buf exch 2 index put } for \n" + " pop \n" + " } { \n" + " 127 sub \n" + " } ifelse \n" + " } ifelse \n" + " add %% => index \n" + " } loop \n" + " pop \n" + " buf \n" + "} bind def \n" + "%%%%EndProlog \n" ; + +const char PSPAGE [] = /* start of page */ + "%%%%Page: %d %d \n" + "gsave \n" + "%f %f translate \n" + "%f %f scale \n" + "%d %d %d [ %d %d %d %d %d %d ] { readbuf } image \n" ; + +const char PSPAGEEND [] = /* end of page */ + "\n" + "grestore \n" + "showpage \n" ; + +const char PSEND [] = /* end of file */ + "%%Trailer \n" + "%%%%Pages: %d \n" ; + + +void psinit ( OFILE *f, int newfile, int page, int w, int h, int n ) +{ + float ptw, pth ; + + if ( ! f ) { + msg ( "E2 can't happen (psinit)" ) ; + return ; + } + + ptw = w/f->xres * 72.0 ; /* convert to points */ + pth = h/f->yres * 72.0 ; + + if ( newfile ) + fprintf ( f->f, PSBEGIN, + (int) ptw, (int) pth, /* Bounding Box */ + n ) ; /* buffer string length */ + + fprintf ( f->f, PSPAGE, + page, page, /* page number */ + 0.0, 0.0, /* shift */ + ptw, pth, /* scaling */ + w, h, 1, /* image size */ + w, 0, 0, -h, 0, h ) ; /* CTM */ + f->pslines = 0 ; + f->lastpageno = page ; +} + + +char nhexout = 0, hexchars [ 16 ] = "0123456789abcdef" ; + +#define hexputc( f, c ) ( \ + putc ( hexchars [ (c) >> 4 ], f ), \ + putc ( hexchars [ (c) & 0x0f ], f ), \ + ( ( ( nhexout++ & 31 ) == 31 ) ? putc ( '\n', f ) : 0 ) ) + +void hexputs ( FILE *f, uchar *p, int n ) +{ + uchar c ; + if ( n > 0 ) { + hexputc ( f, n ) ; + while ( n-- ) { c = *p++ ^ 0xff ; hexputc ( f, c ) ; } + } +} + +/* Encode into postscript. If not a repeated line, test (using + index j) from current position (i) for possible encodings as: + copy of > 2 bytes, runs of > 4 or data >=127. Otherwise the + byte is skipped. Uncoded bytes are output from the last + uncoded byte (l) before output of runs/copies. */ + +void pswrite ( OFILE *f, unsigned char *buf, int n ) +{ + int i, j, l ; + static unsigned char last [ MAXBITS ] ; + + l=i=0 ; + + if ( ! f || ! buf || n<0 ) { + msg ( "E2 can't happen (pswrite)" ) ; + return ; + } + + for ( j=0 ; jpslines ; j++ ) ; + if ( j == n ) { /* repeat line */ + hexputc ( f->f, 0 ) ; + l=i=n ; + } + + while ( ipslines ; j++ ) ; + if ( j-i > 2 ) { /* skip */ + hexputs ( f->f, buf+l, i-l ) ; + hexputc ( f->f, j-i + 127 ) ; + l=i=j ; + } else { + for ( j=i ; j 4 ) { /* run */ + hexputs ( f->f, buf+l, i-l ) ; + hexputc ( f->f, 255 ) ; + hexputc ( f->f, j-i ) ; + hexputc ( f->f, buf[i] ^ 0xff ) ; + l=i=j ; + } else { + if ( i-l >= 127 ) { /* maximum data length */ + hexputs ( f->f, buf+l, i-l ) ; + l=i ; + } else { /* data */ + i++ ; + } + } + } + + } + hexputs ( f->f, buf+l, i-l ) ; + + if ( n >= 0 ) + memcpy ( last, buf, n ) ; + + f->pslines++ ; +} + + +/* Write 2- and 4-byte integers to an image output file. Return + as for fwrite. */ + +int fwrite2 ( short s, OFILE *f ) +{ + uchar *p = (void*) &s ; + return fwrite ( bigendian ? p + sizeof(short) - 2 : p, 2, 1, f->f ) ; +} + +int fwrite4 ( long l, OFILE *f ) +{ + uchar *p = (void*) &l ; + return fwrite ( bigendian ? p + sizeof(long ) - 4 : p, 4, 1, f->f ) ; +} + + +/* Write a TIFF directory tag. Returns 0 if OK, 1 on errors. */ + +int wtag ( OFILE *f, int lng, short tag, short type, long count, long offset ) +{ + int err=0 ; + + err = err || ! fwrite2 ( tag, f ) ; + err = err || ! fwrite2 ( type, f ) ; + err = err || ! fwrite4 ( count, f ) ; + if ( lng ) { + err = err || ! fwrite4 ( offset, f ) ; + } else { + err = err || ! fwrite2 ( offset, f ) ; + err = err || ! fwrite2 ( 0, f ) ; + } + + if ( err ) msg ( "ES2 can't write TIFF tag" ) ; + return err ; +} + + +/* Write TIFF header and directory. File format based on Sam + Leffler's tiff.h. Can only be used for single-image TIFFs + because always seeks to start of file to re-write the + header. */ + +#define NTAGS 17 /* number of tags in directory */ +#define NRATIO 2 /* number of floats (as ratios) */ + +int tiffinit ( OFILE *f ) +{ + int err=0, compr=1 ; + long tdoff, doff ; + + fseek ( f->f, 0, SEEK_SET ) ; + + /* 0 ==> (start of TIFF file) */ + + /* write magic, TIFF version and offset to directory */ + + fwrite2 ( bigendian ? 0x4d4d : 0x4949, f ) ; + fwrite2 ( 42, f ) ; + fwrite4 ( 8, f ) ; + + /* 8 ==> directory */ + + fwrite2 ( NTAGS, f ) ; + + /* figure out offsets within file and compression code */ + + tdoff = 8 + 2 + NTAGS*12 + 4 ; /* offset to directory data */ + doff = tdoff + NRATIO*8 ; /* offset to image data */ + + switch ( f->format ) { + case O_TIFF_RAW: compr = 1 ; break ; + case O_TIFF_FAX: compr = 3 ; break ; + default: err = msg ( "E2can't happen(tiffinit)" ) ; break ; + } + + /* write directory tags, 12 bytes each */ + + wtag( f, 1, 256, 4, 1, f->w ) ; /* width long */ + wtag( f, 1, 257, 4, 1, f->h ) ; /* length long */ + wtag( f, 0, 258, 3, 1, 1 ) ; /* bits/sample short */ + + wtag( f, 0, 259, 3, 1, compr ) ; /* compresssion(g3=3) short */ + wtag( f, 0, 262, 3, 1, 0 ) ; /* photometric(0-min=white) short */ + wtag( f, 0, 266, 3, 1, 1 ) ; /* fill order(msb2lsb=1) short */ + wtag( f, 1, 273, 4, 1, doff ) ; /* strip offsets long */ + + wtag( f, 0, 274, 3, 1, 1 ) ; /* orientation(1=normal) short */ + wtag( f, 0, 277, 3, 1, 1 ) ; /* samples/pixel short */ + wtag( f, 1, 278, 4, 1, f->h ) ; /* rows/strip long */ + wtag( f, 1, 279, 4, 1, f->bytes ) ; /* strip byte counts long */ + + wtag( f, 1, 282, 5, 1, tdoff+0 ) ; /* xresolution ratio */ + wtag( f, 1, 283, 5, 1, tdoff+8 ) ; /* yresolution ratio */ + wtag( f, 0, 284, 3, 1, 1 ) ; /* storage(1=single plane) short */ + wtag( f, 1, 292, 4, 1, 0 ) ; /* g3options long */ + + wtag( f, 0, 296, 3, 1, 2 ) ; /* resolution units(2=in,3=cm) short */ + wtag( f, 0, 327, 3, 1, 0 ) ; /* clean fax(0=clean) short */ + + fwrite4 ( 0, f ) ; /* offset to next dir (no more) */ + + /* ==> tdoff (tag data offset), write ratios for floats here */ + + fwrite4 ( f->xres+0.5, f ) ; + fwrite4 ( 1, f ) ; + fwrite4 ( f->yres+0.5, f ) ; + fwrite4 ( 1, f ) ; + + /* ==> doff (strip data offset), image data goes here */ + + return err ; +} + + +/* Convert array 'runs' of 'nr' run lengths into a bit map 'buf'. Returns + the number of bytes filled. */ + +int runtobit ( short *runs, int nr, uchar *buf ) +{ + static uchar zerofill [ 9 ] = { + 0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00 } ; + static uchar onefill [ 9 ] = { + 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff } ; + + uchar col=0, *buf0 = buf ; + register short len, b=8, bytes ; + + while ( nr-- > 0 ) { + len = *runs++ ; + if ( col ) *buf |= onefill [ b ] ; /* right bits of cur. byte */ + else *buf &= zerofill [ b ] ; + if ( b > len ) { /* done fill */ + b -= len ; + } else { /* continue to next byte */ + len -= b ; + buf++ ; + b = 8 ; + if ( ( bytes = len>>3 ) > 0 ) { /* fill >1 byte */ + memset ( buf, col, bytes ) ; + len -= bytes*8; + buf += bytes ; + } + *buf = col ; /* flood the rest */ + b -= len ; + } + col ^= 0xff ; + } + + return buf - buf0 + ( b < 8 ) ; +} + + +/* Write a PCX file header. */ + +int fputi ( int i, OFILE *f ) +{ + putc ( i & 0xff, f->f ) ; + putc ( ( i >> 8 ) & 0xff, f->f ) ; + return 0 ; +} + +void pcxinit ( OFILE *f ) +{ + uchar buf [ 60 ] = { 0x0a, 3, 1, 1 } ; /* magic, version, compr, BPP */ + + fwrite ( buf, 1, 4, f->f ) ; /* 4 */ + fputi ( 0, f ) ; /* 8 xmin, ymin, xmax, ymax */ + fputi ( 0, f ) ; + fputi ( f->w-1, f ) ; + fputi ( f->h-1, f ) ; + fputi ( f->xres, f ) ; /* 4 x and y dpi */ + fputi ( f->yres, f ) ; + memset ( buf, 0, 48 ) ; /* 48 palette */ + fwrite ( buf, 1, 48, f->f ) ; + putc ( 0, f->f ) ; /* 1 reserved */ + putc ( 1, f->f ) ; /* 1 planes per pixel */ + fputi ( (f->w+15)/16*2, f ) ; /* 2 bytes per line */ + memset ( buf, 0, 60 ) ; /* 60 zero */ + fwrite ( buf, 1, 60, f->f ) ; +} + +/* Write a PCX-compressed scan line. */ + +void pcxwrite ( OFILE *of, uchar *p, int nb ) +{ + int c, n, runc ; + FILE *f = of->f ; + + runc = *p++ ; + n = 1 ; + + for ( nb-- ; nb > 0 ; nb-- ) { + c = *p++ ; + if ( c == runc && n < 63 ) { /* continue run */ + n++ ; + } else { /* terminate run */ + if ( n > 1 || ( ( runc & 0xc0 ) == 0xc0 ) ) /* output as run */ + putc ( n | 0xc0, f ) ; + putc ( runc, f ) ; + runc = c ; /* start new run */ + n = 1 ; + } + } + + /* last run */ + + if ( n > 1 || ( ( runc & 0xc0 ) == 0xc0 ) ) /* output as run */ + putc ( n | 0xc0, f ) ; + putc ( runc, f ) ; + +} + + +/* Begin/end output pages. If not starting first page (0), terminate + previous page. If output filename pattern is defined, [re-]opens that + file. If not terminating last page (page==EOF), writes file header. + Returns 0 or 2 on errors. */ + +int nextopage ( OFILE *f, int page ) +{ + int err = 0 ; + int i, nb=0 ; + uchar *p, codes [ ( RTCEOL * EOLBITS ) / 8 + 3 ] ; + + if ( f->f ) { /* terminate previous page */ + + switch ( f->format ) { + case O_PBM: + break ; + case O_PGM: + break ; + case O_FAX: + case O_TIFF_FAX: + for ( p = codes, i=0 ; ie, EOLCODE, EOLBITS, p ) ; + nb = putcode ( &f->e, 0, 0, p ) - codes ; + fwrite ( codes, 1, nb, f->f ) ; + f->bytes += nb ; + if ( f->format == O_TIFF_FAX ) tiffinit ( f ) ; + break ; + case O_TIFF_RAW: + tiffinit(f) ; /* rewind & update TIFF header */ + break ; + case O_PCL: + fprintf ( f->f, PCLEND ) ; + break ; + case O_PS: + fprintf ( f->f, PSPAGEEND ) ; + if ( f->fname || page<0 ) fprintf ( f->f, PSEND, f->lastpageno ) ; + break ; + case O_PCX: + case O_PCX_RAW: + fseek ( f->f, 0, SEEK_SET ) ; + pcxinit ( f ) ; + break ; + } + + if ( ferror ( f->f ) ) { + err = msg ("ES2output error:" ) ; + } else { + msg ( "F+ wrote %s as %dx%d pixel %.fx%.f dpi %s page", + f->cfname, f->w, f->h, f->xres, f->yres, + oformatname [f->format] ) ; + + switch ( f->format ) { + case O_PS: + msg ( "F (%d lines)", f->pslines ) ; + break ; + case O_TIFF_RAW: + case O_TIFF_FAX: + msg ( "F (%d bytes)", f->bytes ) ; + break ; + default: + msg ( "F " ) ; + break ; + } + + } + + } + + if ( ! err && page >= 0 ) { /* open new file */ + if ( f->fname ) { + sprintf ( f->cfname, f->fname, page+1, page+1, page+1 ) ; + + if ( ! f->f ) + f->f = fopen ( f->cfname, ( f->format == O_PS ) ? "w" : "wb+" ) ; + else + f->f = freopen ( f->cfname, ( f->format == O_PS ) ? "w" : "wb+", f->f ) ; + + if ( ! f->f ) { + err = msg ("ES2can't open output file %s:", f->cfname ) ; + } + } else { + f->f = stdout ; + strcpy ( f->cfname, "standard output" ) ; + } + } + + /* start new page */ + + if ( ! err && page >= 0 ) { + switch ( f->format ) { + case O_PBM: + fprintf ( f->f, "P4 %d %d\n", f->w, f->h ) ; + break ; + case O_PGM: + fprintf ( f->f, "P5 %d %d %d\n", f->w/4, f->h/4, 255 ) ; + break ; + case O_FAX: + case O_TIFF_FAX: + if ( f->format == O_TIFF_FAX ) tiffinit ( f ) ; + p = putcode ( &f->e, EOLCODE, EOLBITS, codes ) ; + nb = p - codes ; + fwrite ( codes, 1, nb, f->f ) ; + break ; + case O_TIFF_RAW: + tiffinit ( f ) ; + break ; + case O_PCL: + fprintf ( f->f, PCLBEGIN, (int) f->xres ) ; + break ; + case O_PS: + psinit ( f, ( f->fname || page==0 ), page+1, f->w, f->h, f->w/8 ) ; + break ; + case O_PCX: + case O_PCX_RAW: + fseek ( f->f, 0, SEEK_SET ) ; + pcxinit ( f ) ; + break ; + } + + if ( ferror ( f->f ) ) err = msg ("ES2output error:" ) ; + } + + /* only count lines/bytes for those formats that don't have + headers or where we will update the headers on closing */ + + switch ( f->format ) { + case O_FAX: + case O_TIFF_FAX: + case O_PCX: + case O_PCX_RAW: + f->h = 0 ; + f->bytes = nb ; + break ; + } + + return err ; +} + + +/* Output scan line of nr runs no times to output file f. */ + +void writeline ( OFILE *f, short *runs, int nr, int no ) +{ + int nb = 0 ; + uchar *p, buf [ MAXCODES ] ; + + /* if line to be output, convert to right format */ + + if ( no > 0 ) + switch ( f->format ) { + case O_PBM: + case O_PGM: + case O_PCL: + case O_PS: + case O_TIFF_RAW: + case O_PCX: + case O_PCX_RAW: + nb = runtobit ( runs, nr, buf ) ; + break ; + case O_FAX: + case O_TIFF_FAX: + break ; + } + + /* output `no' times. */ + + while ( no-- > 0 ) { + switch ( f->format ) { + case O_PCX_RAW: + case O_TIFF_RAW: + case O_PBM: + fwrite ( buf, 1, nb, f->f ) ; + break ; + case O_PGM: + pgmwrite ( f, buf, nb ) ; + break ; + case O_TIFF_FAX: + case O_FAX: + p = runtocode ( &f->e, runs, nr, buf ) ; + p = putcode ( &f->e, EOLCODE, EOLBITS, p ) ; + nb = p - buf ; + fwrite ( buf, 1, nb, f->f ) ; + break ; + case O_PCL: + pclwrite ( f, buf, nb ) ; + break ; + case O_PS: + pswrite ( f, buf, nb ) ; + break ; + case O_PCX: + pcxwrite ( f, buf, nb ) ; + break ; + } + + /* only count lines/bytes for those formats that don't have + headers or where we will update the headers on closing */ + + switch ( f->format ) { + case O_FAX: + case O_TIFF_FAX: + case O_TIFF_RAW: + case O_PCX: + case O_PCX_RAW: + f->h++ ; + f->bytes += nb ; + break ; + } + + } +} + + +/* Initialize new output file. If fname is NULL, stdout will be used for + all images. */ + +void newOFILE ( OFILE *f, int format, char *fname, + float xres, float yres, int w, int h ) +{ + f->f = 0 ; + f->format = format ; + f->fname = fname ; + f->xres = xres ; + f->yres = yres ; + f->w = w ; + f->h = h ; + f->bytes = 0 ; + newENCODER ( &f->e ) ; +} + +/* Read a bitmap to use as a font and fill in the font data. If + the file name is null, empty, or there are errors, the font is + initialized to the built-in font. Returns 0 if OK, 2 on + errors. */ + +int readfont ( char *fname, faxfont *font ) +{ + int err=0, i, j, n=0, nr, nb, fontok=0, pels ; + char *fnames [2] = { 0, 0 } ; + short runs [ MAXRUNS ] ; + IFILE f; + + if ( fname && *fname ) { + + fnames[0] = fname ; + + newIFILE ( &f, fnames ) ; + + if ( nextipage ( &f, 0 ) ) { + err = msg ( "E2 can't open font file %s", fnames[0] ) ; + } + + nb = 0 ; + while ( ! err && ( nr = readline ( &f, runs, &pels ) ) >= 0 ) { + if ( nb+pels/8 < MAXFONTBUF ) { + nb += runtobit ( runs, nr, font->buf+nb ) ; + } else { + err = msg ("E2font file %s too large (max %d bytes)", + fnames[0], MAXFONTBUF ) ; + } + } + + if ( ! err && nb != f.page->w * f.page->h / 8 ) + err = msg ( "E2 read %d bytes of font data for %dx%d bitmap", + nb, f.page->w, f.page->h ) ; + + if ( ! err && ( f.page->w / 256 > MAXFONTW || f.page->h > MAXFONTH ) ) { + err = msg ( "E2font size (%dx%d) too large", f.page->w, f.page->h ) ; + } + + if ( err ) { + font->w = font->h = 0 ; + } else { + font->w = f.page->w / 256 ; + font->h = f.page->h ; + for ( i=0 ; i<256 ; i++ ) font->offset[i] = i*font->w ; + msg ("Iread %dx%d font %s (%d bytes)", font->w, font->h, fname, nb ) ; + fontok = 1 ; + } + + if ( f.f ) { + fclose ( f.f ) ; + f.f = 0 ; + } + } + + if ( ! fontok ) { /* use built-in font */ + + font->w = STDFONTW ; + font->h = STDFONTH ; + + for ( i=j=0 ; j 0 ; n-- ) + font->buf [ j++ ] = 0 ; + else + font->buf [ j++ ] = stdfont [ i ] ; + + if ( i != 1980 ) err = msg ( "E2can't happen(readfont)" ) ; + + for ( i=0 ; i<256 ; i++ ) font->offset[i] = i*font->w ; + } + + return err ; +} + + +/* Initialize bit reversal lookup tables (note that the + `normalbits' array is the one actually used for the bit + reversal. */ + +void initbittab ( void ) +{ + int i ; + for ( i=0 ; i<256 ; i++ ) + normalbits [ reversebits [ i ] = i ] = + ( i& 1 ? 128:0 ) | ( i& 2 ? 64:0 ) | ( i& 4 ? 32:0 ) | ( i& 8 ? 16:0 ) | + ( i&16 ? 8:0 ) | ( i&32 ? 4:0 ) | ( i&64 ? 2:0 ) | ( i&128 ? 1:0 ) ; +} + + + /* T.4 Encoding/Decoding */ + +/* Table-lookup decoder for variable-bit-length codewords. The table index + is the N most recently undecoded bits with the first (oldest) undecoded + bit as the MS bit. If the N bits uniquely identify a codeword then the + indexed 'code' member identifies the code, otherwise it is zero. The + 'bits' member gives the number of bits to be considered decoded (to be + removed from the bit stream) and the 'next' element is a pointer to the + table to use for decoding the next part of the bit sequence. + + For T.4 decoding the longest T.4 codeword is 13 bits. The implementation + below uses two tables of 512 elements (N=9 bits) for each colour. + Codewords longer than 9 bits require a second lookup. Since all + codewords longer than than 9 bits have a 4-bit zero prefix it is + possible to use only one secondary 9-bit lookup table by dropping only + the first 4 bits after the first lookup. The code indentifier is the run + length + 1. A separate table is used for decoding the variable-length + FILL patterns. + + For undefined codewords, one bit is skipped and decoding continues at + the white code table. */ + +/* the lookup tables for each colour and the fill lookup table */ + +dtab tw1 [ 512 ], tw2 [ 512 ], tb1 [ 512 ], tb2 [ 512 ], fill [ 512 ] ; +char tabinit=0 ; + +/* Add code cword shifted left by shift to decoding table tab. */ + +void addcode ( dtab *tab, int cword, int shift, + short code, short bits, dtab *next ) +{ + int i, n = 1 << shift ; + + for ( i = cword << shift ; n-- > 0 ; i++ ) { + tab[i].code = code ; + tab[i].bits = bits ; + tab[i].next = next ; + } +} + +/* Initialize the decoding table for one colour using the codes in the T.4 + table p0. t1 and t2 are the two decoding tables and ot is the first + table of the other colour. */ + +void init1dtab ( t4tab *p0, dtab *t1, dtab *t2, dtab *ot ) +{ + t4tab *p ; + for ( p = p0 ; p->code ; p++ ) + if ( p->bits <= 9 ) { + addcode ( t1, p->code, 9 - p->bits, p->rlen + 1, p->bits, + ( p - p0 ) > 63 ? t1 : ot ) ; + } else { + addcode ( t1, p->code >> ( p->bits - 9 ), 0, 0, 4, t2 ) ; + addcode ( t2, p->code, 13 - p->bits, p->rlen + 1, p->bits - 4, + ( p - p0 ) > 63 ? t1 : ot ) ; + } +} + + +/* Initialize a T.4 decoder. */ + +void newDECODER ( DECODER *d ) +{ + int i ; + + if ( ! tabinit ) { + + /* undefined codes */ + + addcode ( tw1, 0, 9, 0, 1, tw1 ) ; + addcode ( tw2, 0, 9, 0, 1, tw1 ) ; + addcode ( tb1, 0, 9, 0, 1, tw1 ) ; + addcode ( tb2, 0, 9, 0, 1, tw1 ) ; + addcode ( fill, 0, 9, 0, 1, tw1 ) ; + + /* fill and EOL */ + + addcode ( tw1, 0, 0, 0, 4, tw2 ) ; + addcode ( tw2, 0, 2, 0, 7, fill ) ; + addcode ( tb1, 0, 0, 0, 4, tb2 ) ; + addcode ( tb2, 0, 2, 0, 7, fill ) ; + + addcode ( fill, 0, 0, 0, 9, fill ) ; + for ( i=0 ; i<=8 ; i++ ) + addcode ( fill, 1, i, -1, 9-i, tw1 ) ; + + /* white and black runs */ + + init1dtab ( wtab, tw1, tw2, tb1 ) ; + init1dtab ( btab, tb1, tb2, tw1 ) ; + + tabinit=1 ; + } + + /* initialize decoder to starting state */ + + d->x = 0 ; + d->shift = -9 ; + d->tab = tw1 ; + d->eolcnt = 0 ; +} + + /* T.4 coding table and default font for efax/efix */ + +/* T.4 1-D run-length coding tables. codes must be in run length + order for runtocode(). */ + +t4tab wtab [ ( 64 + 27 + 13 ) + 1 ] = { /* runs of white */ + +/* Terminating White Codes */ + +{53,8,0}, {7,6,1}, {7,4,2}, {8,4,3}, {11,4,4}, {12,4,5}, +{14,4,6}, {15,4,7}, {19,5,8}, {20,5,9}, {7,5,10}, {8,5,11}, +{8,6,12}, {3,6,13}, {52,6,14}, {53,6,15}, {42,6,16}, {43,6,17}, +{39,7,18}, {12,7,19}, {8,7,20}, {23,7,21}, {3,7,22}, {4,7,23}, +{40,7,24}, {43,7,25}, {19,7,26}, {36,7,27}, {24,7,28}, {2,8,29}, +{3,8,30}, {26,8,31}, {27,8,32}, {18,8,33}, {19,8,34}, {20,8,35}, +{21,8,36}, {22,8,37}, {23,8,38}, {40,8,39}, {41,8,40}, {42,8,41}, +{43,8,42}, {44,8,43}, {45,8,44}, {4,8,45}, {5,8,46}, {10,8,47}, +{11,8,48}, {82,8,49}, {83,8,50}, {84,8,51}, {85,8,52}, {36,8,53}, +{37,8,54}, {88,8,55}, {89,8,56}, {90,8,57}, {91,8,58}, {74,8,59}, +{75,8,60}, {50,8,61}, {51,8,62}, {52,8,63}, + +/* Make Up White Codes */ + +{27,5,64}, {18,5,128}, {23,6,192}, {55,7,256}, {54,8,320}, {55,8,384}, +{100,8,448}, {101,8,512}, {104,8,576}, {103,8,640}, {204,9,704}, {205,9,768}, +{210,9,832}, {211,9,896}, {212,9,960}, {213,9,1024},{214,9,1088},{215,9,1152}, +{216,9,1216},{217,9,1280},{218,9,1344},{219,9,1408},{152,9,1472},{153,9,1536}, +{154,9,1600},{24,6,1664}, {155,9,1728}, + +/* Extended Make Up Codes (Black and White) */ + +{8,11,1792}, {12,11,1856},{13,11,1920},{18,12,1984},{19,12,2048},{20,12,2112}, +{21,12,2176},{22,12,2240},{23,12,2304},{28,12,2368},{29,12,2432},{30,12,2496}, +{31,12,2560}, + +{0,0,0} } ; + +t4tab btab [ ( 64 + 27 + 13 ) + 1 ] = { /* runs of black */ + +/* Terminating Black Codes */ + +{55,10,0}, {2,3,1}, {3,2,2}, {2,2,3}, {3,3,4}, {3,4,5}, +{2,4,6}, {3,5,7}, {5,6,8}, {4,6,9}, {4,7,10}, {5,7,11}, +{7,7,12}, {4,8,13}, {7,8,14}, {24,9,15}, {23,10,16}, {24,10,17}, +{8,10,18}, {103,11,19}, {104,11,20}, {108,11,21}, {55,11,22}, {40,11,23}, +{23,11,24}, {24,11,25}, {202,12,26}, {203,12,27}, {204,12,28}, {205,12,29}, +{104,12,30}, {105,12,31}, {106,12,32}, {107,12,33}, {210,12,34}, {211,12,35}, +{212,12,36}, {213,12,37}, {214,12,38}, {215,12,39}, {108,12,40}, {109,12,41}, +{218,12,42}, {219,12,43}, {84,12,44}, {85,12,45}, {86,12,46}, {87,12,47}, +{100,12,48}, {101,12,49}, {82,12,50}, {83,12,51}, {36,12,52}, {55,12,53}, +{56,12,54}, {39,12,55}, {40,12,56}, {88,12,57}, {89,12,58}, {43,12,59}, +{44,12,60}, {90,12,61}, {102,12,62}, {103,12,63}, + +/* Make Up Black Codes */ + +{15,10,64}, {200,12,128},{201,12,192},{91,12,256}, {51,12,320}, {52,12,384}, +{53,12,448}, {108,13,512},{109,13,576},{74,13,640}, {75,13,704}, {76,13,768}, +{77,13,832}, {114,13,896},{115,13,960},{116,13,1024},{117,13,1088}, +{118,13,1152}, +{119,13,1216},{82,13,1280},{83,13,1344},{84,13,1408},{85,13,1472},{90,13,1536}, +{91,13,1600},{100,13,1664},{101,13,1728}, + +/* Extended Make Up Codes (Black and White) */ + +{8,11,1792}, {12,11,1856},{13,11,1920},{18,12,1984},{19,12,2048},{20,12,2112}, +{21,12,2176},{22,12,2240},{23,12,2304},{28,12,2368},{29,12,2432},{30,12,2496}, +{31,12,2560}, + +{0,0,0} } ; + + +/* The built-in 8x16 font. Runs of zeroes are coded as 0 + followed by the repetition count. */ + +uchar stdfont [ 1980 ] = { +0,255,0,255,0,194,8,4,12,10,18,0,3,16,4,8,20,8,4,8,20,0,1,10,8,4, +4,10,18,0,2,16,4,8,20,4,0,68,20,0,1,8,0,2,12,6,48,0,5,2,0,43,14,32, +56,0,2,12,0,1,32,0,1,2,0,1,14,0,1,32,8,4,32,56,0,14,6,8,48,0,40,8, +0,1,18,0,6,30,0,4,4,0,11,4,8,18,20,18,12,0,2,8,8,20,20,4,8,20,20, +0,1,20,4,8,10,20,18,0,2,8,8,20,20,8,0,1,24,8,4,8,10,20,12,0,2,8,4, +8,20,16,8,8,20,54,10,8,4,8,10,20,0,2,16,4,8,20,4,0,1,20,0,33,12,20, +18,28,48,12,12,8,8,8,0,4,2,28,8,28,28,4,62,28,62,28,28,0,5,60,28, +12,60,14,56,62,30,14,34,62,62,33,16,33,34,12,60,12,60,30,127,34,33, +65,34,34,62,8,32,8,8,0,1,24,0,1,32,0,1,2,0,1,16,0,1,32,8,4,32,8,0, +7,16,0,6,8,8,8,0,36,4,14,0,1,34,8,12,18,28,24,0,3,28,0,1,24,0,1,28, +28,8,0,1,30,0,2,8,28,0,1,100,100,98,0,6,18,31,14,0,8,56,0,7,13,0, +5,32,36,4,8,20,20,20,18,0,2,4,8,20,20,8,16,20,20,8,20,4,8,20,20,20, +0,2,8,8,20,20,8,32,20,0,33,12,20,18,42,73,18,24,8,8,42,8,0,3,4,34, +24,34,34,12,32,34,2,34,34,0,2,2,0,1,16,2,34,12,34,18,36,32,16,18, +34,8,8,34,16,51,50,18,34,18,34,32,8,34,33,73,34,34,2,8,16,8,8,0,1, +24,0,1,32,0,1,2,0,1,16,0,1,32,0,2,32,8,0,7,16,0,6,8,8,8,0,36,15,16, +65,34,8,18,0,1,34,4,0,3,34,0,1,36,8,2,2,0,2,58,0,2,56,34,0,1,36,36, +18,0,1,12,12,12,12,12,12,24,18,62,62,62,62,62,62,62,62,36,34,12,12, +12,12,12,0,1,18,34,34,34,34,34,32,36,0,5,12,0,10,52,0,6,8,0,6,32, +0,34,12,0,1,63,40,74,18,0,1,16,4,20,8,0,3,4,34,40,2,2,20,32,32,2, +34,34,24,24,4,0,1,8,2,78,18,34,32,34,32,16,32,34,8,8,36,16,51,50, +33,34,33,34,32,8,34,33,73,20,34,4,8,16,8,20,0,2,28,44,14,30,28,62, +30,44,56,60,34,8,82,44,28,44,30,22,30,62,34,34,65,34,34,62,8,8,8, +0,35,12,20,16,62,34,8,16,0,1,77,4,0,3,93,0,1,24,8,2,12,0,1,34,58, +0,2,8,34,0,1,40,40,100,4,12,12,12,12,12,12,40,32,32,32,32,32,8,8, +8,8,34,50,18,18,18,18,18,34,35,34,34,34,34,34,60,40,28,28,28,28,28, +28,54,14,28,28,28,28,56,56,56,56,2,44,28,28,28,28,28,8,29,34,34,34, +34,34,44,34,0,33,12,0,1,18,24,52,12,0,1,16,4,42,8,0,3,8,34,8,2,2, +36,60,32,4,34,34,24,24,8,127,4,2,82,18,34,32,34,32,16,32,34,8,8,40, +16,45,42,33,34,33,34,48,8,34,33,73,20,20,4,8,8,8,20,0,2,34,50,16, +34,34,16,34,50,8,4,36,8,109,50,34,50,34,24,32,16,34,34,73,34,34,2, +4,8,16,57,0,34,12,36,16,34,20,0,1,40,0,1,81,28,18,127,0,1,89,0,2, +127,12,2,0,1,34,58,28,0,1,8,34,36,40,40,24,4,18,18,18,18,18,18,40, +32,32,32,32,32,8,8,8,8,34,50,33,33,33,33,33,20,37,34,34,34,34,20, +34,40,34,34,34,34,34,34,9,16,34,34,34,34,8,8,8,8,30,50,34,34,34,34, +34,0,1,34,34,34,34,34,34,50,34,0,33,12,0,1,18,12,8,25,0,1,16,4,8, +127,0,1,127,0,1,8,34,8,4,12,68,2,60,8,28,30,0,2,16,0,1,2,28,82,18, +60,32,34,60,30,32,62,8,8,56,16,45,42,33,34,33,60,28,8,34,18,85,8, +20,8,8,8,8,34,0,2,2,34,32,34,34,16,34,34,8,4,40,8,73,34,34,34,34, +16,32,16,34,34,73,20,34,4,24,8,12,78,0,35,36,60,34,62,0,1,36,0,1, +81,36,36,1,28,85,0,2,8,16,2,0,1,34,26,28,0,1,8,34,18,18,22,106,0, +1,18,18,18,18,18,18,47,32,60,60,60,60,8,8,8,8,122,42,33,33,33,33, +33,8,45,34,34,34,34,20,34,36,2,2,2,2,2,2,9,32,34,34,34,34,8,8,8,8, +34,34,34,34,34,34,34,127,38,34,34,34,34,34,34,34,0,33,8,0,1,63,10, +22,37,0,1,16,4,0,1,8,0,3,8,34,8,8,2,126,2,34,8,34,2,0,2,8,127,4,16, +86,63,34,32,34,32,16,34,34,8,8,36,16,45,38,33,60,33,36,6,8,34,18, +54,20,8,16,8,8,8,34,0,2,30,34,32,34,62,16,34,34,8,4,56,8,73,34,34, +34,34,16,28,16,34,20,85,8,20,8,4,8,16,0,35,8,36,16,34,8,8,18,0,1, +81,26,72,1,0,1,34,0,2,8,30,28,0,1,34,10,28,0,1,8,28,9,22,17,22,4, +63,63,63,63,63,63,120,32,32,32,32,32,8,8,8,8,34,42,33,33,33,33,33, +20,41,34,34,34,34,8,34,34,30,30,30,30,30,30,63,32,62,62,62,62,8,8, +8,8,34,34,34,34,34,34,34,0,1,42,34,34,34,34,20,34,20,0,35,18,10,41, +34,0,1,16,4,0,1,8,0,3,16,34,8,16,2,4,2,34,16,34,2,0,2,4,0,1,8,0,1, +73,33,34,32,34,32,16,34,34,8,8,34,16,33,38,33,32,33,34,2,8,34,18, +34,20,8,16,8,4,8,0,3,34,34,32,34,32,16,38,34,8,4,36,8,73,34,34,34, +34,16,2,16,34,20,34,20,20,16,8,8,8,0,35,12,20,16,62,62,8,10,0,1,77, +0,1,36,1,0,1,28,0,6,34,10,0,4,18,42,34,42,28,33,33,33,33,33,33,72, +32,32,32,32,32,8,8,8,8,34,38,33,33,33,33,33,34,49,34,34,34,34,8,60, +34,34,34,34,34,34,34,72,32,32,32,32,32,8,8,8,8,34,34,34,34,34,34, +34,8,50,34,34,34,34,20,34,20,0,33,12,0,1,18,42,73,34,0,1,8,8,0,1, +8,12,0,1,24,16,34,8,32,34,4,34,34,16,34,34,24,24,2,0,1,16,16,32,33, +34,16,36,32,16,18,34,8,8,33,16,33,34,18,32,18,34,2,8,34,12,34,34, +8,32,8,4,8,0,3,34,34,16,38,34,16,26,34,8,4,34,8,73,34,34,34,38,16, +2,16,38,8,34,34,8,32,8,8,8,0,35,12,15,16,65,8,8,4,0,1,34,0,1,18,0, +5,127,0,3,54,10,0,4,36,79,68,79,32,33,33,33,33,33,33,72,16,32,32, +32,32,8,8,8,8,36,38,18,18,18,18,18,0,1,18,34,34,34,34,8,32,34,34, +34,34,34,34,34,72,16,34,34,34,34,8,8,8,8,34,34,34,34,34,34,34,8,34, +38,38,38,38,8,34,8,0,33,12,0,1,18,28,6,29,0,1,8,8,0,2,12,0,1,24,32, +28,8,62,28,4,28,28,16,28,28,24,24,0,3,16,28,33,60,14,56,62,16,14, +34,62,112,33,30,33,34,12,32,12,34,60,8,28,12,34,34,8,62,8,2,8,0,3, +29,60,14,26,28,16,2,34,8,4,33,8,73,34,28,60,26,16,60,14,26,8,34,34, +8,62,8,8,8,0,35,12,4,62,0,1,8,8,36,0,1,28,0,11,42,10,0,5,66,71,66, +32,33,33,33,33,33,33,79,14,62,62,62,62,62,62,62,62,56,34,12,12,12, +12,12,0,1,44,28,28,28,28,8,32,36,29,29,29,29,29,29,55,14,28,28,28, +28,8,8,8,8,28,34,28,28,28,28,28,0,1,92,26,26,26,26,8,60,8,0,36,8, +0,3,6,48,0,2,24,0,2,32,0,11,48,0,21,6,0,9,14,2,56,0,1,127,0,7,2,0, +2,4,0,5,32,2,0,7,16,0,1,6,8,48,0,35,12,0,4,8,24,0,13,32,10,0,1,4, +0,6,32,0,7,4,0,31,4,0,21,16,32,16,0,81,3,0,21,28,0,2,56,0,5,32,2, +0,7,48,0,39,12,0,19,32,0,2,24,0,6,30,0,7,24,0,31,24,0,21,48,32,48, +0,255,0,1 +} ; diff --git a/efaxlib.h b/efaxlib.h new file mode 100644 index 0000000..9c725ae --- /dev/null +++ b/efaxlib.h @@ -0,0 +1,218 @@ +#ifndef _EFAXLIB_H +#define _EFAXLIB_H + +#include + +#define EFAX_PATH_MAX 1024 + + /* T.4 fax encoding/decoding */ + +#ifndef uchar +#define uchar unsigned char +#endif + +#define DEFPGLINES 66 /* default lines per page */ + + /* Buffer sizes. */ + +/* The maximum scan line width, MAXRUNS, is conservatively + set at 8k pels. This is enough for the longest standard T.4 coding line + width (2432 pels), the longest encodeable run (2623 pels) and with + 32-pel-wide characters allows up to 256 characters per line. Converted + to T.4 codes, each pair of runs takes up to 25 bits to code. MAXCODES + must also be at least the maximum minimum line length (1200 cps*40 ms ~= + 48 bytes). */ + +#define MAXRUNS 8192 +#define MAXBITS (MAXRUNS/8+1) +#define MAXCODES (MAXRUNS*25/8/2+1) + +/* Line/font size limits */ + +#define MAXLINELEN 256 /* maximum length of string */ +#define MAXFONTW 32 /* maximum char width */ +#define MAXFONTH 48 /* maximum char height */ +#define MAXFONTBUF (MAXFONTW*MAXFONTH/8*256) /* PBM font buffer size */ + +/* Longest run encodeable by the T.4 encoding tables used. */ + +#define MAXRUNLEN (2560+63) + + /* Codes for EOL and number of EOLs required for RTC */ + +#define EOLCODE 1 +#define EOLBITS 12 +#define RTCEOL 5 + /* Fonts */ + +#define STDFONTW 8 /* the built-in font width, height & size */ +#define STDFONTH 16 +#define STDFONTBUF 4096 + +typedef struct fontstruct { + int h, w ; + uchar buf [ MAXFONTBUF ] ; + short offset [ 256 ] ; +} faxfont ; + +extern uchar stdfont [ ] ; /* compressed bit map for built-in font */ + +int readfont ( char *fname, faxfont *font ) ; + + /* T.4 Encoding/Decoding */ + +typedef struct t4tabstruct { + short code, bits, rlen ; /* code, bits, run length */ +} t4tab ; + +extern t4tab wtab [ ( 64 + 27 + 13 ) + 1 ] ; /* white runs */ +extern t4tab btab [ ( 64 + 27 + 13 ) + 1 ] ; /* black runs */ + +typedef struct dtabstruct { /* decoder table entry */ + struct dtabstruct *next ; + short bits, code ; +} dtab ; + + /* Image Input */ + +#define bigendian ( * (uchar*) &short256 ) +extern short short256 ; + +/* input, output and page file formats */ + +#define NIFORMATS 9 +#define NOFORMATS 14 +#define NPFORMATS 5 + +enum iformats { I_AUTO=0, I_PBM=1, I_FAX=2, I_TEXT=3, I_TIFF=4, + I_DFAX=5, I_PCX=6, I_RAW=7, I_DCX=8 } ; + +#define IFORMATS { "AUTO", "PBM", "FAX", "TEXT", "TIFF", \ + "DFAX", "PCX", "RAW", "DCX" } ; + +enum oformats { O_AUTO=0, O_PBM=1, O_FAX=2, O_PCL=3, O_PS=4, + O_PGM=5, O_TEXT=6, O_TIFF_FAX=7, O_TIFF_RAW=8, O_DFAX=9, + O_TIFF=10, O_PCX=11, O_PCX_RAW=12, O_DCX=13 } ; + +#define OFORMATS { "AUTO", "PBM", "FAX", "PCL", "PS", \ + "PGM", "TEXT", "TIFF", "TIFF", "DFAX", \ + "TIFF", "PCX", "PCX", "DCX" } + +enum pformats { P_RAW=0, P_FAX=1, P_PBM=2, P_TEXT=3, P_PCX=4 } ; + +#define PFORMATS { "RAW", "FAX", "PBM", "TEXT", "PCX" } + + +extern char *iformatname [ NIFORMATS ] ; +extern char *oformatname [ NOFORMATS ] ; +extern char *pformatname [ NPFORMATS ] ; + +typedef struct decoderstruct { + long x ; /* undecoded bits */ + short shift ; /* number of unused bits - 9 */ + dtab *tab ; /* current decoding table */ + int eolcnt ; /* EOL count for detecting RTC */ +} DECODER ; + +void newDECODER ( DECODER *d ) ; + +#define IFILEBUFSIZE 512 + +#define MAXPAGE 360 /* number of A4 pages in a 100m roll */ + +typedef struct PAGEstruct { /* page data */ + char *fname ; /* file name */ + long offset ; /* location of data within file */ + int w, h ; /* pel and line counts */ + float xres, yres ; /* x and y resolution, dpi */ + uchar format ; /* image coding */ + uchar revbits ; /* fill order is LS to MS bit */ +} PAGE ; + +typedef struct ifilestruct { /* input image file */ + + /* data for each pages */ + + PAGE *page, *lastpage ; /* pointers to current and last page */ + PAGE pages [ MAXPAGE ] ; /* page data */ + + long next ; /* offset to next page (while scanning only) */ + + /* data for current input page */ + + FILE *f ; /* current file pointer */ + int lines ; /* scan lines remaining in page */ + + uchar bigend ; /* TIFF: big-endian byte order */ + + DECODER d ; /* FAX: T.4 decoder state */ + + faxfont *font ; /* TEXT: font to use */ + int pglines ; /* TEXT: text lines per page */ + char text [ MAXLINELEN ] ; /* TEXT: current string */ + int txtlines ; /* TEXT: scan lines left in text l. */ + int charw, charh, lmargin ; /* TEXT: desired char w, h & margin */ + +} IFILE ; + +int newIFILE ( IFILE *f, char **fname ) ; +void logifnames ( IFILE *f, char *s ) ; +int nextipage ( IFILE *f, int dp ) ; +int lastpage ( IFILE *f ) ; +int readline ( IFILE *f, short *runs, int *pels ) ; + + /* Image Output */ + +typedef struct encoderstruct { + long x ; /* unused bits */ + short shift ; /* number of unused bits - 8 */ +} ENCODER ; + +void newENCODER ( ENCODER *e ) ; + +typedef struct ofilestruct { /* input image file state */ + FILE *f ; /* file pointer */ + int format ; /* file format */ + char *fname ; /* file name pattern */ + float xres, yres ; /* x and y resolution, dpi */ + int w, h ; /* width & height, pixels */ + int lastpageno ; /* PS: last page number this file */ + int pslines ; /* PS: scan lines written to file */ + int bytes ; /* TIFF: data bytes written */ + ENCODER e ; /* T.4 encoder state */ + char cfname [ EFAX_PATH_MAX + 1 ] ; /* current file name */ +} OFILE ; + +void newOFILE ( OFILE *f, int format, char *fname, + float xres, float yres, int w, int h ) ; +int nextopage ( OFILE *f, int page ) ; +void writeline ( OFILE *f, short *runs, int nr, int no ) ; + + /* Scan Line Processing */ + +uchar *putcode ( ENCODER *e, short code , short bits , uchar *buf ) ; +uchar *runtocode ( ENCODER *e, short *runs, int nr, uchar *buf ) ; + +/* int bittorun ( uchar *buf, int n, short *runs ) ; */ +int texttorun ( uchar *txt, faxfont *font, short line, + int w, int h, int lmargin, + short *runs, int *pels ) ; + +int xpad ( short *runs, int nr, int pad ) ; +int xscale ( short *runs, int nr, int xs ) ; +int xshift ( short *runs, int nr, int s ) ; + +int runor ( short *a, int na, short *b, int nb, short *c, int *pels ) ; + +/* Bit reversal lookup tables (note that the `normalbits' array + is the one actually used for the bit reversal. */ + +uchar reversebits [ 256 ], normalbits [ 256 ] ; + +void initbittab(void) ; + +/* Other Stuff */ + +int ckfmt ( char *p, int n ) ; + +#endif diff --git a/efaxmsg.c b/efaxmsg.c new file mode 100644 index 0000000..068f5f1 --- /dev/null +++ b/efaxmsg.c @@ -0,0 +1,180 @@ +#include /* ANSI C */ +#include +#include +#include +#include +#include + +#include "efaxmsg.h" + +#define MAXTSTAMP 80 /* maximum length of a time stamp */ +#define MAXMSGBUF 4096 /* maximum status/error message bytes held */ + +#define NLOG 2 + +char *verb[NLOG] = { "ewin", "" } ; +char *argv0 = "" ; + +int nxtoptind = 1 ; /* for communication with nextopt() */ +char *nxtoptarg ; + + +/* For systems without strerror(3) */ + +#ifdef NO_STRERROR +extern int sys_nerr; +extern char *sys_errlist[]; + +extern char *strerror( int i ) +{ + return ( i >= 0 && i < sys_nerr ) ? sys_errlist[i] : "Unknown Error" ; +} +#endif + + +/* Print time stamp. */ + +time_t tstamp ( time_t last, FILE *f ) +{ + time_t now ; + char tbuf [ MAXTSTAMP ] ; + + now = time ( 0 ) ; + + strftime ( tbuf, MAXTSTAMP, ( now - last > 600 ) ? "%c" : "%M:%S", + localtime( &now ) ) ; + fputs ( tbuf, f ) ; + + return now ; +} + + +/* Return string corresponding to character c. */ + +char *cname ( uchar c ) +{ +#define CNAMEFMT "<0x%02x>" +#define CNAMELEN 6+1 + static char *cnametab [ 256 ] = { /* character names */ + "","","","", "","","","", + "", "", "", "", "", "", "", "", + "","","","","","","","", + "","", "","", "", "", "", "" } ; + static char names[ (127-32)*2 + 129*(CNAMELEN) ] ; + char *p=names ; + static int i=0 ; + + if ( ! i ) { + for ( i=32 ; i<256 ; i++ ) { + cnametab [ i ] = p ; + sprintf ( p, i<127 ? "%c" : CNAMEFMT, i ) ; + p += strlen ( p ) + 1 ; + } + } + + return cnametab [ c ] ; +} + +/* Print a message with a variable number of printf()-type + arguments if the first character appears in the global + verb[ose] string. Other leading characters and digits do + additional actions: + allows the message to be continued on + the same line, '-' buffers the message instead of printing it, + E, and W expand into strings, S prints the error message for + the most recent system error, a digit sets the return value, a + space ends prefix but isn't printed. Returns 0 if no prefix + digit. */ + +enum msgflags { E=0x01, W=0x02, S=0x04, NOFLSH=0x08, NOLF=0x10 } ; + +int msg ( char *fmt, ... ) +{ + static int init=0 ; + static FILE *logfile [ NLOG ] ; + static char msgbuf [ NLOG ] [ MAXMSGBUF ] ; + static time_t logtime [ NLOG ] = { 0, 0 } ; + static int atcol1 [ NLOG ] = { 1, 1 } ; + + int err=0, i, flags=0 ; + char *p ; + + va_list ap ; + va_start ( ap, fmt ) ; + + if ( ! init ) { + logfile[0] = stderr ; + logfile[1] = stdout ; + for ( i=0 ; i= argc || *(a = argv[nxtoptind]) != '-' ) return -1 ; + nxtoptind++ ; + + if ( ! *(a+1) || ( ( p = strchr ( args, *(a+1) ) ) == 0 ) ) + return msg ( "Eunknown option (%s)", a ), '?' ; + + if ( *(p+1) != ':' ) nxtoptarg = 0 ; + else + if ( *(a+2) ) nxtoptarg = a+2 ; + else + if ( nxtoptind >= argc ) return msg ( "Eno argument for (%s)", a ), '?' ; + else nxtoptarg = argv [ nxtoptind++ ] ; + return *(a+1) ; +} + diff --git a/efaxmsg.h b/efaxmsg.h new file mode 100644 index 0000000..d148bbb --- /dev/null +++ b/efaxmsg.h @@ -0,0 +1,31 @@ +#ifndef _EFAXMSG_H +#define _EFAXMSG_H + +#include + +#ifndef uchar +#define uchar unsigned char +#endif + + /* Messages & Program Arguments */ + +enum cchar { /* control characters */ + NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS, HT, LF, + VT, FF, CR, SO, SI, DLE, XON, DC2, XOFF,DC4, NAK, + SYN, ETB, CAN, EM, SUB, ESC, FS, GS, RS, US } ; + +extern char *verb[] ; /* types of messages to print */ +extern char *argv0 ; /* program name */ + +char *cname ( unsigned char c ) ; +time_t tstamp ( time_t last, FILE *f ) ; +int msg ( char *fmt, ... ) ; + +extern int nxtoptind ; +extern char *nxtoptarg ; + +int nextopt( int argc, char **argv, char *args ) ; + +#endif + + diff --git a/efaxos.c b/efaxos.c new file mode 100644 index 0000000..6b346d6 --- /dev/null +++ b/efaxos.c @@ -0,0 +1,510 @@ +/* + efaxos.c - O/S-dependent routines + Copyright 1995, Ed Casas +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef FD_SET +#include /* for AIX */ +#endif + +#include "efaxlib.h" +#include "efaxmsg.h" +#include "efaxos.h" + +#ifdef USE_TERMIO +#include +#include +#define termios termio +#define tcgetattr(fd, pt) ioctl(fd, TCGETA, pt) +#define tcsetattr(fd, x, pt) ioctl(fd, TCSETAW, pt) +#define cfsetospeed(pt, b) ((pt)->c_cflag = ((pt)->c_cflag & ~CBAUD) | b) +#define cfsetispeed(pt, b) +#define tcdrain(fd) +#else +#include +#endif + +#ifdef TIOCSSOFTCAR +#include +#endif + +#ifndef CRTSCTS +#define CRTSCTS 0 +#endif + + +/* The milliseconds portion of the current time. If your system + does not provide gettimeofday(3) you can safely substitute a + dummy function that returns 0. This will cause the + milliseconds portion of time stamps to be printed as 0. */ + +int time_ms ( void ) +{ + struct timeval tv ; + gettimeofday ( &tv, NULL ) ; + return (int) ( tv.tv_usec / 1000 ) ; +} + + +/* Process elapsed time in milliseconds. This is used for + ``virtual flow control'' only. */ + +long proc_ms ( void ) +{ + struct timeval t ; + static int init=0 ; + static struct timeval s ; + if ( ! init ) { + gettimeofday ( &s, 0 ) ; + init = 1 ; + } + gettimeofday ( &t, 0 ) ; + return ( t.tv_sec - s.tv_sec ) * 1000 + ( t.tv_usec - s.tv_usec ) / 1000 ; +} + + +/* Wait for t millisecond (for systems without usleep). */ + +void msleep ( int t ) +{ + struct timeval timeout ; + timeout.tv_sec = t / 1000 ; + timeout.tv_usec = ( t % 1000 ) * 1000 ; + if ( select ( 1 , 0 , 0 , 0 , &timeout ) < 0 ) + msg ("ES2select failed in msleep:") ; +} + + +/* Return number of characters ready to read or < 0 on error. t + is tenths of a second of idle time before timing out. If t is + negative, waits forever. */ + +int tdata ( TFILE *f, int t ) +{ + int n, err=0 ; + fd_set fds ; + + struct timeval timeout ; + + if ( f->fd < 0 ) msg ( "Ecan't happen (faxdata)" ) ; + + timeout.tv_sec = t / 10 ; + timeout.tv_usec = ( t % 10 ) * 100000 ; + + FD_ZERO ( &fds ) ; + FD_SET ( f->fd, &fds ) ; + + do { + n = select ( f->fd + 1, &fds, 0, 0, t<0 ? 0 : &timeout ) ; + if ( n < 0 ) { + if ( errno == EINTR ) { + msg ( "W0 select() interrupted in tdata()" ) ; + } else { + err = msg ( "ES2 select() failed in tdata():" ) ; + } + } + } while ( n < 0 && ! err ) ; + + return n ; +} + + +/* tundrflw is called only by the tgetc() macro when the buffer + is empty. t is maximum idle time before giving up. Returns + number of characters read or EOF on timeout or errors. */ + +int tundrflw ( TFILE *f, int t ) +{ + int n ; + + n = tdata ( f, t ) ; + + if ( n > 0 ) + if ( ( n = read( f->fd, f->ibuf, IBUFSIZE ) ) < 0 ) + msg ( "ES2fax device read:" ) ; + + f->iq = ( f->ip = f->ibuf ) + ( n > 0 ? n : 0 ) ; + + return n > 0 ? n : EOF ; +} + + +/* tgetd returns the next data character after removing DLE + escapes, DLE-ETX terminators and fixing bit order. Evaluates + to the next character, EOF on error/timeout, or -2 on DLE-ETX. */ + +int tgetd ( TFILE *f, int t ) +{ + int c ; + + if ( ( c = tgetc(f,t) ) < 0 ) + c = EOF ; + else + if ( c != DLE ) + c = f->ibitorder[c] ; + else { /* escape sequence */ + c = tgetc(f,t) ; + if ( c == ETX ) + c = -2 ; + else + if ( c == DLE || c == SUB ) + c = f->ibitorder [ DLE ] ; + else + c = msg ( "W0invalid escape sequence (DLE-%s) in data", cname(c) ) ; + } + + return c ; +} + +/* Write buffer to modem. Returns 0 or EOF on error. */ + +int tput ( TFILE *f, uchar *p, int n ) +{ + int m=0 ; + + while ( n > 0 && ( m = write( f->fd, p, n ) ) > 0 ) { + if ( m != n ) + msg ( "Wonly wrote %d of %d bytes", m, n ) ; + n -= m ; + p += m ; + } + + if ( m < 0 ) + msg ( "ES2fax device write:" ) ; + + return m ; +} + + +/* Compare current termios state with termios struct t. Returns 0 if equal, + 1 otherwise. */ + +int ckfld ( char *field, int set, int get ) +{ + return set == get ? + 0 : msg ( "W1 termios.%s is 0%08o, not 0%08o", field, get, set ) ; +} + +int checktermio ( struct termios *t, TFILE *f ) +{ + struct termios s ; + int err=0 ; + s.c_iflag=s.c_oflag=s.c_lflag=s.c_cflag=s.c_cc[VMIN]=s.c_cc[VTIME]=0 ; + if ( tcgetattr ( f->fd , &s ) ) + err = msg ("ES2tcgetattr failed:") ; + + if ( ! err ) return + ckfld ( "iflag" , t->c_iflag, s.c_iflag ) || + ckfld ( "oflag" , t->c_oflag , s.c_oflag ) || + ckfld ( "lflag" , t->c_lflag , s.c_lflag ) || + ckfld ( "cflag" , t->c_cflag , s.c_cflag ) || + ckfld ( "START" , t->c_cc[VSTART] , s.c_cc[VSTART] ) || + ckfld ( "STOP" , t->c_cc[VSTOP] , s.c_cc[VSTOP] ) || + ckfld ( "MIN" , t->c_cc[VMIN] , s.c_cc[VMIN] ) || + ckfld ( "TIME" , t->c_cc[VTIME] , s.c_cc[VTIME] ) ; + return err ; +} + + +/* Set serial port mode. Sets raw, 8-bit, 19.2 kbps mode with no + flow control or as required. Break and parity errors are + ignored. CLOCAL means DCD is ignored since some modems + apparently drop it during the fax session. Flow control is + only used when sending. Returns 0 or 2 on error. */ + +int ttymode ( TFILE *f, enum ttymodes mode ) +{ + int err=0, i ; + static struct termios t, oldt, *pt ; + static int saved=0 ; + + if ( ! saved ) { + if ( tcgetattr ( f->fd, &oldt ) ) + err = msg ( "ES2tcgetattr on fd=%d failed:", f->fd ) ; + else + saved=1 ; + } + + t.c_iflag = IGNBRK | IGNPAR ; + t.c_oflag = 0 ; + t.c_cflag = CS8 | CREAD | CLOCAL ; + t.c_lflag = 0 ; + + for ( i=0 ; ihwfc ? CRTSCTS : 0 ; + case VOICECOMMAND : + cfsetospeed ( pt, B38400 ) ; + cfsetispeed ( pt, B38400 ) ; + break ; + case SEND : + t.c_iflag |= IXON ; + t.c_cflag |= f->hwfc ? CRTSCTS : 0 ; + case COMMAND : + cfsetospeed ( pt, B19200 ) ; + cfsetispeed ( pt, B19200 ) ; + break ; + case DROPDTR : + cfsetospeed ( pt, B0 ) ; + break ; + case ORIGINAL : + if ( saved ) pt = &oldt ; + break ; + default : + err = msg ("E2can't happen(ttymode)") ; + break ; + } + + if ( ! err && tcsetattr ( f->fd, TCSADRAIN, pt ) ) + err = msg ( "ES2tcsetattr on fd=%d failed:", f->fd ) ; + + if ( ! err && checktermio ( pt, f ) ) + msg ( "Wterminal mode not set properly" ) ; + + tcflow ( f->fd, TCOON ) ; /* in case XON got lost */ + + return err ; +} + + +/* Initialize TFILE data structure. Bit ordering: serial devices + transmit LS bit first. T.4/T.30 says MS bit is sent + first. `Normal' order therefore reverses bit order. */ + +void tinit ( TFILE *f, int fd, int reverse, int hwfc ) +{ + f->ip = f->iq = f->ibuf ; + f->obitorder = normalbits ; + f->ibitorder = reverse ? reversebits : normalbits ; + f->fd = fd ; + f->hwfc = hwfc ; + if ( ! normalbits[1] ) initbittab () ; +} + + +/* Open a serial fax device as a TFILE. Returns 0 if OK, 1 if + busy, 2 on error. */ + +int ttyopen ( TFILE *f, char *fname, int reverse, int hwfc ) +{ + int flags, err=0 ; + + tinit ( f, open ( fname, O_RDWR | O_NDELAY | O_NOCTTY ), reverse, hwfc ) ; + + if ( f->fd < 0 ) { + if ( errno == EBUSY ) { + err = 1 ; + } else { + err = msg ( "ES2can't open serial port %s:", fname ) ; + } + } + + if ( ! err ) { + if ( ( flags = fcntl( f->fd, F_GETFL, 0 ) ) < 0 || + fcntl( f->fd, F_SETFL, ( flags & ~O_NDELAY ) ) < 0 ) + err = msg ( "ES2fax device fcntl failed %s:", fname ) ; + } + +#ifdef TIOCSSOFTCAR + { + int arg = 1 ; + if ( ! err ) + if ( ioctl ( f->fd, TIOCSSOFTCAR, &arg ) ) + msg ("WS unable to set software carrier:" ) ; + } +#endif + + return err ; +} + + /* UUCP-style device locking using lock files */ + +/* Test for UUCP lock file & remove stale locks. Returns 0 on null file + name or if no longer locked, 1 if locked by another pid, 2 on error, 3 + if locked by us. */ + +int ttlocked ( char *fname, int log ) +{ + int err=0, ipid ; + FILE *f ; + pid_t pid = 0 ; + char buf [ EFAX_PATH_MAX ] = "" ; + + if ( fname && *fname == BINLKFLAG ) fname++ ; + + if ( fname && ( f = fopen ( fname , "r" ) ) ) { + + if ( fread ( buf, sizeof(char), EFAX_PATH_MAX-1, f ) == sizeof(pid_t) || + sscanf ( buf, "%d" , &ipid ) != 1 ) { + pid = * (pid_t *) buf ; + if ( log ) msg ("X+ read binary pid %d from %s", (int) pid, fname ) ; + } else { + char *p ; + pid = (pid_t) ipid ; + if ( log ) { + msg ( "X+ read HDB pid %d [", (int) pid ) ; + for ( p=buf ; *p ; p++ ) msg ( "X+ %s", cname ( *p ) ) ; + msg ( "X+ ] from %s", fname ) ; + } + } + + if ( kill ( pid, 0 ) && errno == ESRCH ) { + if ( log ) msg ("X - stale" ) ; + if ( remove ( fname ) ) + err = msg ( "ES2can't remove stale lock %s from pid %d:", + fname, pid ) ; + else + err = msg ( "I0removed stale lock %s from pid %d", fname, pid ) ; + } else { + if ( pid != getpid() ) { + err = 1 ; + if ( log ) msg ( "X1 (not our pid)" ) ; + } else { + err = 3 ; + if ( log ) msg ( "X3 (our pid)" ) ; + } + } + fclose ( f ) ; + } + return err ; +} + + +/* Create UUCP (text or binary) lock file. Returns 0 on null + file name or if created, 1 if locked by another pid, 2 on + error, 3 if locked by us. */ + +int ttlock ( char *fname, int log ) +{ + int err=0, dirlen, bin=0 ; + FILE *f=0 ; + pid_t pid = getpid ( ) ; + char *p , buf [ EFAX_PATH_MAX ] = "" ; + + if ( fname && *fname == BINLKFLAG ) { + fname++ ; + bin = 1 ; + } + + err = ttlocked ( fname, log ) ; + + if ( ! err ) { + dirlen = ( p = strrchr( fname , '/' ) ) ? p-fname+1 : strlen ( fname ) ; + sprintf ( buf , "%.*sTMP..%05d" , dirlen , fname , (int) pid ) ; + if ( ! ( f = fopen( buf, "w" ) ) ) + err = msg ( "ES2can't open pre-lock file %s:", buf ) ; + } + + if ( ! err && f ) { + if ( bin ) { + if ( fwrite ( &pid, sizeof(pid_t), 1, f ) < 1 ) + err = msg ( "ES2can't write pre-lock file %s:", buf ) ; + } else { + if ( fprintf ( f, "%10d\n", (int) pid ) < 0 ) + err = msg ( "ES2can't write pre-lock file %s:", buf ) ; + } + } + + if ( ! err && f ) { + if ( rename ( buf , fname ) == 0 ) { + chmod ( fname , 0444 ) ; + msg ( "Xcreated %s lock file %s", bin ? "binary" : "text", fname ) ; + } else { + err = ttlocked ( fname, log ) ; + if ( ! err ) + err = msg ( "ES2can't rename lock file %s to %s:", buf, fname ) ; + } + } + + if ( f ) { + fclose ( f ) ; + if ( err ) remove ( buf ) ; + } + + return err ; +} + + +/* Remove lock file. Returns 0 on null file name, doesn't exist, or was + removed, 1 if the lock is to another pid, 2 on errors. */ + +int ttunlock ( char *fname ) +{ + int err = 0 ; + + if ( fname && *fname == BINLKFLAG ) fname++ ; + + switch ( ttlocked ( fname, 1 ) ) { + case 0: break ; + case 1: err = msg ( "E1won't remove lock %s (not ours)" , fname ) ; break ; + case 2: err = 2 ; break ; + case 3: + if ( remove ( fname ) ) { + err = msg ( "ES2can't remove lock %s:", fname ) ; + } else { + err = msg ( "X0removed lock file %s", fname ) ; + } + break ; + default: + err = msg ( "E2can't happen (ttunlock)" ) ; + break ; + } + return err ; +} + + +/* Lock all lock files and possibly log attempt if log=1. + Returns 0 if all locks [already] applied, 1 if any are locked + to other pids, 2 on any errors. */ + +int lockall ( char **lkfiles, int log ) +{ + int err = 0 ; + char **p = lkfiles ; + while ( *p && ! err ) + if ( ( err = ttlock ( *p++, log ) ) == 3 ) err = 0 ; + return err ; +} + + +/* Remove all lock files. Returns 0 if all locks removed, 2 on + errors. */ + +int unlockall ( char **lkfiles ) +{ + int err = 0, i ; + char **p = lkfiles ; + while ( *p ) + if ( ( i = ttunlock ( *p++ ) ) != 0 ) err = i ; + return err ; +} + +/* Return basename of the argument or the whole thing if can't + find it. */ + +char *efaxbasename ( char *p ) +{ + return strrchr ( p , '/' ) ? strrchr ( p , '/' ) + 1 : p ; +} + diff --git a/efaxos.h b/efaxos.h new file mode 100644 index 0000000..8696993 --- /dev/null +++ b/efaxos.h @@ -0,0 +1,86 @@ +#ifndef _EFAXOS_H +#define _EFAXOS_H + +#include + +#include "efaxlib.h" + +/* signals to be caught */ + +#define ANSISIGS SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM +#define UNIXSIGS SIGHUP, SIGQUIT, SIGIOT, SIGALRM +#define CATCHSIGS ANSISIGS, UNIXSIGS + +/* Bit order reversal table. */ + +extern unsigned char normalbits [ ] ; + +typedef enum ttymodes /* serial port modes: */ +{ + COMMAND, /* 19200 8N1, no f/c, DTR high */ + SEND, /* 19200 send-only XON/XOFF f/c */ + VOICECOMMAND, /* 38400 8N1, no f/c, DTR high */ + VOICESEND, /* 38400 send-only XON/XOFF f/c*/ + DROPDTR, /* ", DTR low */ + ORIGINAL /* restore original settings */ +} ttymodes ; + +/* OS-specific i/o & delay functions */ + +/* We define new stream i/o macros because it's not possible to + do non-blocking reads/writes with C stream i/o [UNIX select() + gives the status of the file, not the stream buffer].*/ + +#define IBUFSIZE 1024 /* read up to this many bytes at a time from fax */ +#define OBUFSIZE 1024 /* maximum bytes to write at a time to fax */ + +typedef struct tfilestruct { + int fd ; + unsigned char *ip, *iq ; + unsigned char ibuf [ IBUFSIZE ] ; + unsigned char *ibitorder, *obitorder ; + int bytes, pad, lines ; + int hwfc ; + time_t start ; + long mstart ; + int rd_state ; +} TFILE ; + +/* tgetc() is a macro like getc(). It evaluates to the next + character from the fax device or EOF after idle time t. */ + +#define tgetc(f,t) ( (f)->ip >= (f)->iq && tundrflw(f,t) == EOF ? EOF : \ + *(unsigned char*)(f)->ip++ ) + +int tundrflw ( TFILE *f, int t ) ; +int tgetd ( TFILE *f, int t ) ; +int tput ( TFILE *f, unsigned char *p, int n ) ; +int tdata ( TFILE *f, int t ) ; +void tinit ( TFILE *f, int fd, int reverse, int hwfc ) ; +int ttyopen ( TFILE *f, char *fname, int reverse, int hwfc ) ; +int ttymode ( TFILE *f, ttymodes mode ) ; +void msleep ( int t ) ; +long proc_ms ( void ) ; +int time_ms ( void ) ; + +/* POSIX execl */ + +extern int execl ( const char *path, const char *arg , ... ) ; + +/* UUCP-style device locks */ + +#define BINLKFLAG '#' /* prefix to force binary lock files */ + + /* [un]lock serial port using named files */ +int lockall ( char **lkfiles, int log ) ; +int unlockall ( char **lkfiles ) ; + +/* extract program name to be used in messages from argv0 */ + +char *efaxbasename ( char *p ) ; + +/* default fax modem device */ + +#define FAXFILE "/dev/modem" + +#endif diff --git a/efix.1 b/efix.1 new file mode 100644 index 0000000..6d0bffb --- /dev/null +++ b/efix.1 @@ -0,0 +1,264 @@ +.TH EFIX 1 "February 1999" "" "" +.UC 1 +.SH NAME +efix \- convert between fax, text, bit-map and gray-scale formats +.SH SYNOPSIS + +.B efix +[ +.I options +] +.I file... + +.SH OPTIONS + +Where \fIoptions\fP are: + +.TP 9 +.B -i \fIf\fP +the input image is in format \fIf\fP. Default is to automatically +determine the input type from its contents. + +.TP 9 +.B + fax +fax ("Group3") 1-D coded image + +.TP 9 +.B + text +text. Line feeds separate lines, form feeds cause page breaks +and tabs are expanded assuming tabs every 8 columns. + +.TP 9 +.B + pbm +raw PBM (portable bit map) + +.TP 9 +.B + tiffg3 +TIFF format with Group 3 (fax) compression. + +.TP 9 +.B + tiffraw +TIFF format with no compression. + +.TP 9 +.B -o \fIf\fP +write the output in format \fIf\fP. Default is tiffg3. + +.TP 9 +.B + fax +fax ("Group3") 1-D coded image + +.TP 9 +.B + pbm +raw PBM + +.TP 9 +.B + pgm +raw PGM (Portable Gray Map). Gray-scale values are produced by +summing pixels in 4x4 pixel blocks. The output file is 1/4 of +the size given by -p. The resulting image has 17 discrete values +between 0 and 255. + +.TP 9 +.B + pcl +HP-PCL (e.g. HP LaserJet). + +.TP 9 +.B + ps +encapsulated Postscript (e.g. Apple Laserwriter). The file is +compressed using differential coding vertically and run-length +coding horizontally. There is no provision for positioning the +image within the page and so the image will appear at the lower +left corner of the page when printed. + +.TP 9 +.B + tiffg3 +TIFF format with Group 3 (fax) compression. + +.TP 9 +.B + tiffraw +TIFF format with no compression. + +.TP 9 +.B -n \fIpat\fP +use the printf(3) pattern \fIpath\fP to generate the output file +name. Up to three %d escapes will be replaced by the page number +starting with 1 (e.g. -n order.%03d will create file names +order.001, order.002, etc.) + +.TP 9 +.B -v \fIlvl\fP +print messages of type in string \fIlvl\fP. Each +\fIlower-case\fP letter in \fIlvl\fP enables one type of message: + +.RS 12 +.B +e - +errors +.br +.B +w - +warnings +.br +.B +i - +information messages +.br +.B +a - +program arguments +.br +.B +f - +file format details +.RE + +.RS 9 +The default is "ewi". +.RE + +.TP 9 +.B -f \fIfnt\fP +use font file \fIfnt\fP for text. The font file for an WxH font +should be a bit map of an image of H rows and 256*W columns. +Each successive WxH cell contains the bit map for characters with +codes from 0 to 255. The default is to use a built-in 8x16 font. + +.TP 9 +.B -s \fIX\fP\fRx\fP\fIY\fP +scale the input by a factor of X horizontally and Y vertically. +Scaling does not change the size of the output (use -p). If Y is +not specified it is assumed to be the same as X. Any floating +point value may be used for X and Y. The default is 1,1. + +.TP 9 +.B -d \fIR\fP\fR,\fP\fID\fP +displace the output right by R and down by D (opposite if +negative). See below for units. Default is 0,0. + +.TP 9 +.B -p \fIW\fP\fRx\fP\fIH\fP +truncate or pad the output to generate an image of width W and +height H. This does not scale the input. See below for units. +The default is the size of the input image if it can be +determined or A4 (215x297mm) if it can't. + +.TP 9 +.B -r \fIX\fP\fRx\fP\fIY\fP +assume an output device resolution of X by Y dots per inch. If Y +is not specified it is assumed to be the same as X. The default +is the input resolution if it can be determined or the fax +resolution of 204.1x195.6 dpi if it can't. + +.TP 9 +.B -R \fIX\fP\fRx\fP\fIY\fP +assume an input device resolution of X by Y dots per inch. If Y +is not specified it is assumed to be the same as X. The default +is the input resolution if it can be determined or the fax +resolution of 204.1x195.6 dpi if it can't. + +.TP 9 +.B -l \fIn\fP +place n lines per page during text input. Default is 66. + +.TP 9 +.B -O \fIf\fP +overlay (logical OR) the image from file f into the output. Use +'-' for standard input (-O-). Default is no overlay file. + +.TP 9 +.B -M +ignore all other options and copy the standard input to the +standard output while applying base64 (MIME) encoding as +specified by RFC 1521. + + +.SH FILES + +If no -n options are given, output is written to the standard +output. + +.SH UNITS + +The units of the W, H, R, and D values above are in inches by +default. Any floating point value may be used. Units of inches, +centimetres, millimetres or points (72 per inch) can be used +instead by appending one of the strings `in', `cm', `mm', or `pt' +to the argument (e.g. -d2,4cm). + +.SH CUT AND PASTE + +The -d and -p options allow efix to cut out images from received +faxes for use in other faxes or documents. The -d option specifies +the top left portion of the desired image and the -p option gives +the size of the cut image. For example, the command +.RS +.nf +.ft CW + efix -d-5,-8 -p2,1 sample.001 >sig.001 +.ft P +.fi +.RE +would cut out part of the input with its top left corner 5 inches +from the left edge and 8 inches from top of the input image. The +output image would be 2 inches wide and 1 inch high. + +The -O option allows efix to superimpose two or more images. The +overlay image must be in fax format and cannot be scaled, +truncated or shifted. However, multiple efix commands may be +pipelined to transform images before combining them. For +example, the command +.RS +.nf +.ft CW + efix -d4,8 signature | \\ + efix -O- letterhead | \\ + efix -O- letter.002 >letter.002.new +.ft P +.fi +.RE +will shift the image in the file signature down 8 inches and +right 4 inches and combine (overlay) it with the images in the +files letterhead and letter.002. + +.SH REFERENCES + +Gunter Born, "The File Formats Handbook", International Thompson +Computer Press, 1995. + +.SH COPYRIGHT + +efix is copyright 1994 -- 1999 by Ed Casas. It may be used, +copied and modified under the terms of the GNU Public License. + +.SH DISCLAIMER + +Although \fBefix\fP has been tested it may have errors that will +prevent it from working correctly on your system. Some of these +errors may cause serious problems including loss of data. + +.SH SEE ALSO + +.BR efax(1), +.BR ghostscript(1), +.BR pbm(5), +.BR pgm(5). + +.SH BUGS + +Only reads two types of TIFF compression formats. + +Does not write multi-page TIFF files (a feature). + diff --git a/efix.c b/efix.c new file mode 100644 index 0000000..57f96ff --- /dev/null +++ b/efix.c @@ -0,0 +1,447 @@ +#define Copyright "Copyright 1999 Ed Casas" + +#define Version "efix v 0.3" + +/* + Copyright (C) 1999 Ed Casas + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Please contact the author if you wish to use efax or efix in + ways not covered by the GNU GPL. + + You may contact the author by e-mail at: edc@cce.com, by mail + at: 2629 West 3rd Ave, Vancouver, BC, Canada, V6K 1M4, or by + fax at: +1 604 734 5291. + +*/ + +const char *Usage = + "Usage:\n" + " %s [ option ]... file... \n" +"Options (defaults):\n" + " -i f input format (auto):\n" + " fax fax (\"Group3\") 1-D coded image\n" + " text text\n" + " pbm raw PBM (portable bit map)\n" + " tiffg3 TIFF, Group 3 fax compression\n" + " tiffraw TIFF, no compression\n" + " pcx mono PCX\n" + " dcx mono DCX\n" + " -o f output format (tiffg3):\n" + " fax fax (\"Group3\") 1-D coded image\n" + " pbm Portable Bit Map\n" + " pgm Portable Gray Map (decimated by 4)\n" + " pcl HP-PCL (e.g. HP LaserJet)\n" + " ps Postscript (e.g. Apple Laserwriter)\n" + " tiffg3 TIFF, Group 3 fax compression\n" + " tiffraw TIFF, no compression\n" + " pcx mono PCX\n" + " dcx mono DCX\n" + " -n pat printf() pattern for output file name (ofile)\n" + " -f fnt use PBM font file fnt for text (built-in)\n" + " -l n lines per text page (66)\n" + " -v lvl print messages of type in string lvl (ewi)\n" + " -s XxY scale input by X and Y (Y optional) (1x1)\n" + " -r XxY resolution of output is X by Y (dpi, Y optional) (204x196)\n" + " -R XxY resolution of input is X by Y (dpi, Y optional) (204x196)\n" + " -p WxH pad/truncate output to width W by height H (215x297mm)\n" + " -d R,D displace output right R, down D (opposite if -ve) (0,0)\n" + " -O f overlay file f (none)\n" + " -M ignore other options and base64 (MIME) encode stdin to stdout\n" + "\n" + "Add 'in', 'cm', 'mm', or 'pt' to -p and -d arguments (default in[ches]).\n" + "Default output size and resolution is same as input (if known).\n" + ; + +#include /* ANSI C */ +#include +#include +#include +#include + +#include "efaxlib.h" +#include "efaxmsg.h" + +#ifndef INT_MAX +#define INT_MAX 32767 +#endif + +/* Allowed input and output formats. *** MUST match enum *** */ + +char *iformatstr[] = { " 3text", " 1pbm", " 2fax", " 4tiffg3", " 4tiffraw", + " 6pcx", " 6pcxraw", " 8dcx", 0 } ; + +char *oformatstr[] = { " 1pbm" , " 2fax", " 3pcl", " 4ps", " 5pgm", + " 7tiffg3", " 8tiffraw", + "11pcx", "12pcxraw", "13dcx", 0 } ; + +/* Look up a string in a NULL-delimited table where the first + character of each string is the digit to return if the rest of + the string matches. Returns the value of the digit for the + matching string or -1 if no matches. */ + +int lookup ( char **tab, char *s ) +{ + char **p ; + for ( p=tab ; p && *p && strcmp ( *p+2, s ) ; p++ ) ; + return p && *p ? atoi ( *p ) : -1 ; +} + + +/* Extract pair of values from string. If it's a `dim'ension, + two values are required and they are converted to inches, else + the y value is optional. Returns 0 or 2 on error. */ + +int getxy ( char *arg, float *x, float *y, int dim ) +{ + int i, n, nc=0, err=0 ; + char c ; + static char *unitstr[] = { " 0in", " 1cm", " 2mm", " 3pt", 0 } ; + static float unitval[] = { 1.0, 2.54, 25.4, 72.0, 1.0 } ; + + if ( ! arg ) + err = msg ( "E2 missing argument" ) ; + + if ( !x || !y ) + err = msg ( "E2 can't happen (getxy)" ) ; + + if ( ! err ) { + n = sscanf ( arg , "%f%c%f%n", x, &c, y, &nc ) ; + switch ( n ) { + case 0 : err = msg ( "E2bad X value in (%s)", arg ) ; break ; + case 2 : err = msg ( "E2bad Y value in (%s)", arg ) ; break ; + } + } + + if ( ! err ) { + if ( dim ) { + if ( n != 3 ) { + err = msg ( "Emissing Y dimension in (%s)", arg ) ; + } else { + while ( arg [ nc ] && isspace ( arg [ nc ] ) ) nc++ ; + if ( arg [ nc ] ) { + if ( ( i = lookup ( unitstr, arg+nc ) ) >= 0 ) { + *x /= unitval [ i ] ; + *y /= unitval [ i ] ; + } else { + err = msg ( "E2bad units: `%s'", arg+nc ) ; + } + } + } + } else { + if ( n == 1 ) *y = *x ; + } + } + + if ( ! err ) + msg ( "Aconverted (%s) into %f x %f", arg, *x, *y ) ; + + return err ; +} + + +/* Copy stdin to stdout while applying base64 (RFC 1521) + encoding. This encoding must be applied after the file is + complete since some output formats (e.g. TIFF) require seeking + backwards within the (binary) file. */ + +int base64encode ( void ) +{ + int err=0, c ; + uchar n=0, m=0, bits=0 ; + + static uchar chartab[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/" ; + + while ( ( c = fgetc ( stdin ) ) >= 0 ) { + switch ( n ) { + case 0: + putc ( chartab [ c >> 2 ], stdout ) ; + bits = c & 0x3 ; + n = 1 ; + break ; + case 1: + putc ( chartab [ (bits << 4) | ( c >> 4 ) ], stdout ) ; + bits = c & 0xf ; + n = 2 ; + break ; + case 2: + putc ( chartab [ (bits << 2) | ( c >> 6 ) ], stdout ) ; + putc ( chartab [ c & 0x3f ], stdout ) ; + n = 0 ; + if ( ++m >= 18 ) { + putc ( '\n', stdout ) ; + m = 0 ; + } + break ; + } + + } + + switch ( n ) { + case 0: + break ; + case 1: + putc ( chartab [ (bits << 4) | ( 0 >> 4 ) ], stdout ) ; + putc ( '=', stdout ) ; + putc ( '=', stdout ) ; + break ; + case 2 : + putc ( chartab [ (bits << 2) | ( 0 >> 6 ) ], stdout ) ; + putc ( '=', stdout ) ; + break ; + } + + putc ( '\n', stdout ) ; + + return err ; +} + + +int main( int argc, char **argv) +{ + int err=0, done=0, i, c ; + int nr, pels, ovnr, ovpels, no ; /* run/pixel/repeat counts */ + int linesout ; + int page, ilines, olines ; /* page & line counts */ + int xs, ys, w, h, ixsh, iysh ; /* integer scale, size & shift */ + short runs [ MAXRUNS ] , ovruns [ MAXRUNS ] ; + + float /* defaults: */ + xsc=1.0, ysc=1.0, /* scale */ + xsh=0.0, ysh=0.0, /* shift */ + dxres = 204.145, /* o/p res'n: 1728/215mm * 25.4 x */ + dyres = 195.58, /* 7.7 * 25.4 */ + dxsz = 215 / 25.4, /* o/p size: 8.5" x A4 */ + dysz = 297 / 25.4 ; + + float /* arguments: */ + axres = 0, ayres = 0, axsz = 0, aysz = 0, ainxres=0, ainyres=0 ; + + float /* values used: */ + xres = 0, yres = 0, xsz = 0, ysz = 0 ; + + IFILE ifile, ovfile ; + OFILE ofile ; + + char **ifnames, *ovfnames [ 2 ] = { 0, 0 } ; + + int iformat=I_AUTO, oformat=O_TIFF_FAX, pglines=0 ; + char *ofname=0 ; + + faxfont font, *pfont=0 ; /* text font */ + + /* initialize */ + + argv0 = argv[0] ; + + /* process arguments */ + + while ( !err && (c=nextopt(argc,argv,"n:i:o:O:v:l:f:r:s:p:d:R:M") ) != -1) { + switch ( c ) { + case 'n': + ofname = nxtoptarg ; + break ; + case 'i': + if ( ( iformat = lookup ( iformatstr, nxtoptarg ) ) < 0 ) + err = msg ( "E2invalid input type (%s)", nxtoptarg ) ; + break ; + case 'o': + if ( ( oformat = lookup ( oformatstr, nxtoptarg ) ) < 0 ) + err = msg ( "E2invalid output type (%s)", nxtoptarg ) ; + break ; + case 'O': + ovfnames[0] = nxtoptarg ; + break ; + case 'v': + verb[0] = nxtoptarg ; + msg ( "A " Version ) ; + for ( i=0 ; i 0 ) ifile.page->xres = ainxres ; + if ( ainyres > 0 ) ifile.page->yres = ainyres ; + + if ( ifile.page->xres <= 0 ) ifile.page->xres = dxres ; + if ( ifile.page->yres <= 0 ) ifile.page->yres = dyres ; + + xres = axres > 0 ? axres : ifile.page->xres ; + yres = ayres > 0 ? ayres : ifile.page->yres ; + + xsz = axsz > 0 ? axsz : ( ifile.page->w > 0 ? + ifile.page->w / ifile.page->xres : dxsz ) ; + ysz = aysz > 0 ? aysz : ( ifile.page->h > 0 ? + ifile.page->h / ifile.page->yres : dysz ) ; + + + w = xsz * xres + 0.5 ; /* output dimensions in pixels */ + h = ysz * yres + 0.5 ; + + ixsh = xsh * xres ; /* x/y shifts in pixels/lines */ + iysh = ysh * yres ; + + if ( ( w & 7 ) != 0 ) /* just about everything requires... */ + msg ("Iimage width rounded to %d pixels", + w = ( w + 7 ) & ~7 ) ; + + if ( ofile.format == O_PGM && h & 3 ) /* PGM x4 decimation requires... */ + msg ("I PGM image height rounded up to %d lines", + h = ( h + 3 ) & ~3 ) ; + + if ( w <= 0 || h <= 0 || xres < 0 || yres < 0 ) + err = msg ( "E2negative/zero scaling/size/resolution" ) ; + + if ( ofile.format == O_PCL && /* check for strange PCL resolutions */ + ( xres != yres || ( xres != 300 && xres != 150 && xres != 75 ) ) ) + msg ( "Wstrange PCL resolution (%.0fx%.0f)", xres, yres ) ; + + if ( w > MAXBITS*8 ) /* make sure output will fit... */ + err = msg( "E2requested output width too large (%d pixels)", w ) ; + + ofile.w = w ; + ofile.h = h ; + ofile.xres = xres ; + ofile.yres = yres ; + + /* scale according to input file resolution */ + + xs = 256 * xsc * xres / ifile.page->xres + 0.5 ; + ys = 256 * ysc * yres / ifile.page->yres + 0.5 ; + + if ( xs <= 0 || ys <= 0 ) + err = msg ( "E2negative/zero scaling" ) ; + + if ( *ovfnames ) /* [re-]open overlay file */ + if ( nextipage ( &ovfile , 0 ) ) { + err=2 ; + continue ; + } + + if ( nextopage ( &ofile, page ) ) { + err=2 ; + continue ; + } + linesout=0 ; + + /* y-shift */ + + if ( iysh > 0 ) { + writeline ( &ofile, ( ( *runs = w ), runs ), 1, iysh ) ; + linesout += iysh ; + } else { + for ( i=0 ; i < -iysh ; i++ ) + readline ( &ifile, runs, 0 ) ; + } + + /* copy input to output */ + + olines = ilines = 0 ; + + while ( linesout < h ) { + + if ( ( nr = readline ( &ifile, runs, &pels ) ) < 0 ) + break ; + else + ilines++ ; + + if ( *ovfnames ) { + if ( ( ovnr = readline ( &ovfile, ovruns, &ovpels ) ) >= 0 ) + nr = runor ( runs, nr, ovruns, ovnr, 0, &pels ) ; + } + + /* x-scale, x-shift & x-pad input line */ + + pels = ( xs == 256 ) ? pels : xscale ( runs, nr, xs ) ; + pels += ( ixsh == 0 ) ? 0 : xshift ( runs, nr, ixsh ) ; + nr = ( pels == w ) ? nr : xpad ( runs, nr, w - pels ) ; + + /* y-scale by deleting/duplicating lines. */ + + no = ( ( ilines * ys ) >> 8 ) - olines ; + + if ( linesout + no > h ) no = h - linesout ; + olines += no ; + + writeline ( &ofile, runs, nr, no ) ; + linesout += no ; + } + + /* y-pad */ + + if ( linesout < h ) + writeline ( &ofile, ( ( *runs = w ), runs ), 1, h - linesout ) ; + + if ( ferror ( ifile.f ) ) err = msg ( "ES2input error:" ) ; + } + + nextopage ( &ofile, EOF ) ; + + return err ; +} diff --git a/fax b/fax new file mode 100755 index 0000000..b8ea77a --- /dev/null +++ b/fax @@ -0,0 +1,1040 @@ +#!/bin/sh +# +# fax - script to make, send, receive, view or print a fax +# Copyright 1993-1999 by Ed Casas +# +# --- Start of user configuration section --- +# +# Notes: +# +# - do not put spaces before or after the equal (=) signs. +# +# - variables can also be set on the command line, for example: +# fax DEV=cua0 send file.ps +# or in a configuration file (see CONFIGFILES below) +# + +# The names of the fax script, efax and efix, including full path +# if necessary. + +FAX=fax +EFAX=efax +EFIX=efix + +# The device to which the fax modem is connected (e.g. ttya for +# /dev/ttya). Use a dial-out (cua) device if available. If +# there are links to this device then all programs must use same +# name or the UUCP locking mechanism will fail. For example, if +# /dev/modem is a link to /dev/cua1, then getty, uucp, kermit, +# pppd, dip, etc. must *all* use either /dev/modem or /dev/cua1. + +DEV=cua1 + +# Your fax number in international format, 20 characters maximum. +# Use only digits, spaces, and the "+" character. + +FROM="+1 800 555 5555" + +# Your name as it should appear on the page header. + +NAME="Put Your Name Here" + +# The preferred page size for creating and printing faxes. +# Allowed values are "letter", "legal", and "a4". + +PAGE=letter +# PAGE=legal +# PAGE=a4 + +# The type of printer. Use 'pcl' for HP-PCL or 'ps' for +# Postscript. See definition of PRINT (below) for more options. + +PRTYPE=ps # Postscript (e.g. Apple LaserWriter) +# PRTYPE=pcl # HP-PCL (e.g. HP LaserJet) + +# The command to print image files from standard input. Typically +# this is "lpr" or "lp". + +PRCMD="lpr" + +# The command to view a Portable Gray Map (PGM) image from the +# standard input. Typically "xv -" or "xloadimage stdin". + +VIEWCMD="xloadimage stdin" # best +# VIEWCMD="pnmtoxwd | xwud" # slower alternative +# VIEWCMD="xv -" # much slower alternative + +# The name of the Ghostscript executable including full path if +# necessary. Only required if faxing Postscript files. + +GS=gs + +# Dial string prefix and suffix such as T for tone dialing, P for +# pulse dialing, 9 to get an external line, commas for delays or +# W to wait for dial tone. See definition of TELCVT below if you +# have more complex requirements. + +DIALPREFIX="T" +DIALSUFFIX="" + +# The name(s) of lock file(s) according to your system's +# conventions. Protect with single quotes for delayed evaluation. +# Add a leading '#' to the file name to use binary format. + +LOCK='-x /var/lock/LCK..$DEV' # modern systems +# LOCK='-x /usr/spool/uucp/LCK..$DEV' # older systems +# LOCK='-x /var/lock/LCK..$DEV -x /var/spool/uucp/LCK..$DEV' # both +# LOCK='-x #/usr/spool/uucp/LCK..$DEV' # binary format +# LOCK='-x /usr/spool/locks/LK.047.040.011' # SysV style names +# LOCK='' # no lock file + +# Uncomment one of the following lines to force xon/xoff flow +# control only if you have one of the types of modems listed. + +# FCINIT='-j\Q4' # AT&T (Dataport, Paradyne) +# FCINIT='-j\Q1' # Motorola (Power Modem, 3400 Pro,...) +# FCINIT='-j*F1' # QuickComm (Spirit II) +# FCINIT='-j&H2' # USR (Courier, Sportster) +# FCINIT='-or' # Multi-Tech (for bit reversal) + +# **************************************************************** +# The remaining options probably won't need to be changed. +# **************************************************************** + +# Configuration files that are sourced if they exist. Comment +# out if you don't need to use config files. Warning: any type of +# shell command in these files will be executed. + +CONFIGFILES="/etc/efax.rc ${HOME:-~}/.efaxrc ./.efaxrc" + +# A command that will generate unique names for logs and received +# files. 'date +%m%d%H%M%S' works on most systems. Protect with +# single quotes. + +TSTAMP='date +%m%d%H%M%S' +# TSTAMP='echo $$' # alternative - use process number + +# Shell command to convert aliases to phone numbers when sending +# faxes. When executed $1 will be the alias and $f the file name +# to search. The example below uses a directory file where alias +# lines start with the keyword "fax" followed by the alias in +# parentheses and a colon. The remainder of the line is taken to +# be the phone number. Other lines are ignored. For example, if +# one of the files in DIRFILES (defined below) contained the line +# "fax(kpmg): 691-3031", you could use the command "fax send kpmg +# invoice.24". Protect with single quotes. + +LOOKUP='eval sed -n -e "/^fax($1):/{" -e "s/^[^:]*://p" -eq -e"}" $f' + +# List of telephone directory file(s) to be searched. The +# default is the file .faxdir in the user's home directory. + +DIRFILES="${HOME:-.}/.faxdir" + +# Shell command to convert phone numbers to dial strings. This +# lets you to store numbers without the long distance or +# alternate carrier access codes, passwords, accounting digits, +# etc. In the examples below this is used to convert numbers +# beginning with '+'; the first substitution handles same-country +# calls and the second handles international calls. + +TELCVT='sed -e s/+1/1/ -e s/+/011/' # North America +# TELCVT='sed -e s/+61/0/ -e s/+/0011/' # Australia +# TELCVT='sed -e s/+44/0/ -e s/+/00/' # UK +# TELCVT='sed -e s/+49/0/ -e s/+/00/' # Germany +# TELCVT='sed -e s/+852// -e s/+/001/' # Hong Kong +# TELCVT='sed -e s/+33// -e s/+/19W/' # France (?) +# TELCVT='sed -e s/+34/0/ -e s/+/07W/' # Spain +# TELCVT='sed -e s/+1/10288/' # use AT&T +# TELCVT='sed -e s/+/T82W1682W9W/' # get out of PBX + +# efix options to use a bitmap font for text-to-fax conversion. +# The option -l66 puts 66 lines of text per page, -d1,1 sets 1 +# inch top & left margin. Comment these out to use the built-in +# font. Use "fax makefont" to make bitmap fonts from Postscript +# fonts. + +# TEXTFONT="-l66 -d1,1 -f /usr/bin/efaxfont" + +# efax options to specify a different font for headers. Generate +# using "fax makefont." + +# HDRFONT="-f /usr/bin/efaxfont" + +# Dimensions of page sizes. + +PAGE_letter="8.465x11in" # fax width x letter length +PAGE_legal="8.465x14in" # fax width x legal length +PAGE_a4="21x29.7cm" # ISO A4 + +# Default resolution for converting to fax format. Can only be +# 204x196 or 204x98. + +RES=204x196 # default "Fine" resolution (196 lpi) +# RES=204x98 # standard resolution (98 lpi) + +# When the print and view commands below are executed, $f will be +# the input file name and $PAGEDIM will be one of the above page +# dimensions. Protect with single quotes. + +# PRINT: A command to convert fax files to a printable format. +# For printers other than Postscript or PCL you can use efix's +# PBM output and an appropriate pbm filter (such as pbmtoepson) +# or efix's Postsript output and Ghostscript as a filter. Change +# the scaling (-s) and displacement (-d) options as required to +# fit the image onto the area your printer can print. + +PRINT='$EFIX -ve -p$PAGEDIM -r300 -s0.98 -d0,0.125 -o$PRTYPE $f' + +# example using pbm utilities: +# PRINT='$EFIX -ve -p$PAGEDIM -r60x72 -opbm $f | pbmtoepson' + +# example using Ghostscript: +# PRINT='$EFIX -ve -p$PAGEDIM -r120x144 -ops $f | \ +# $GS -q -sPAPERSIZE=$PAGE -sDEVICE=epson -r120x144 \ +# -dNOPAUSE -dSAFER -sOutputFile=- - ' + +# VIEW: A command to convert fax files to PGM format for +# previewing. efix's pgm output reduces image dimensions by 4X. + +# VIEW='$EFIX -ve -p$PAGEDIM -r200 -opgm $f' # 50dpi: fast, whole-page view +VIEW='$EFIX -ve -p$PAGEDIM -r300 -opgm $f' # 75dpi: slower, readable size + +# Commands to set up modem. "-iZ -i&FE&D2S7=120 -i&C0" +# works with almost all modems. See the efax(1) man page for +# details. + +INIT="-iZ -i&FE&D2S7=120 -i&C0" + +# Command(s) to reset modem when efax finishes. "-kZ" works in +# almost all cases. + +RESET="-kZ" +# RESET="-kZ -k&F+FCLASS=0" # for modems that stay in fax mode after reset + +# Speaker mode(M) and loudness(L). Mn sets speaker mode where n +# means: 0:never, 1:until carrier, 2:always, 3:on receive only. + +SPKR="-iM1L0" + +# Options to use a particular command sets. Normally efax +# selects the command set based on the modem's capabilities. Use +# -o1 to force Class 1, -o2 for Class 2 and -o0 for Class 2.0. + +# CLASSINIT="-o1" # Class 1 +# CLASSINIT="" # Class 2 +# CLASSINIT="-o0" # Class 2.0 + +# The modem's capabilities for sending faxes. Normally efax +# chooses these by querying the modem. "-c 1,3,0,0,0,0,0,0" +# forces 9600 bps maximum speed. See the efax(1) man page for a +# description of the fields. + +# TXCAP="-c 1,3,0,2,0,0,0,0" + +# Capabilities for receiving faxes. Usually the same as TXCAP. +# If your modem only receives at 4800 bps use "-c 1,1,0,0,0,0,0,0". + +# RXCAP="$TXCAP" + +# Additional options required only for transmit or only for +# receive. None normally required. + +RXINIT="" +TXINIT="" + +# Command to make a date for the page header. Protect with single +# quotes. 'date "+%Y/%m/%d %H:%M"' works on most systems. + +DATECMD='date "+%Y/%m/%d %H:%M"' # YYYY/MM/DD HH:MM (24hour) +# DATECMD='date' # longer, more readable + +# Page header format. You may use $DATE, $NAME, $FROM, $TO, and +# "%d/%d" (for page number and count). Protect with single +# quotes. Example: '$DATE $FROM $NAME p. %d/%d'. + +HDR='$DATE $FROM $NAME p. %d/%d' + +# BUSYRETRIES is a list of delays in seconds between attempts to +# redial busy numbers. Comment out if you don't want to retry +# busy numbers. + +BUSYRETRIES="30 60 120 300 60 600 60 60 1200 60 60" + +# FAILRETRIES is a list of delays in seconds between attempts to +# retry failed transmissions. Retries are only attempted if at +# least one page was sent in the previous attempt. Retries +# include only pages not already sent. Comment out if you don't +# want to retry failed transmissions. + +FAILRETRIES="300 300" # try two more times at 5 minute intervals + +# Command to run another program (efax) at a higher-than-normal +# scheduling priority. This command isn't used if it fails +# (e.g. because the current user isn't privileged). Comment this +# out if it causes problems. + +NICE="nice -n -10" + +# Standard versions of commands that are often aliased. + +RM="/bin/rm -f" +LS="/bin/ls" + +# Messages to display. VERB sets the messages displayed (stderr) +# and VERBLOG the messages written to log files (stdout). + +VERB="ewin" # show errors, warnings, progress & negotiation +VERBLOG="chewmainrxtf" # log everything + +# **************************************************************** +# The remaining configuration options apply only to the `fax +# answer' command. You can ignore these if you will only be +# running efax manually. See "USING INIT TO RUN EFAX" in the +# efax man page for more information. +# **************************************************************** + +# device or file where fatal error messages should be written + +CONSOLE=/dev/console + +# The directory to store incoming faxes and log files. This directory +# should already exist and be writable by the user(s) of this script. + +FAXDIR=/var/spool/fax +LOGDIR=/var/log/fax + +# The strftime(3) pattern that generates the file name for +# received files. For example, at 10:45:36 on February 25, +# "%m%d%H%M%S" would produce 0225104536, "%j-%H%M" would produce +# 056-1045, and %d%b%H%M 25Feb1045. + +ANSFNAME="%m%d%H%M%S" + +# umask for received files. Use 022 to allow anyone to retrieve faxes. + +UMASK=022 + +# The user to be sent mail when a fax is received. + +FAXMGR=root + +# The sendmail executable including full path if necessary. Only +# required if forwarding received faxes by e-mail in $NOTIFY. + +SENDMAIL=/usr/sbin/sendmail + +# The command to execute when a fax is received. Normally this +# sends FAXMGR e-mail or prints the received fax. The variable +# $f will be the name of the log file, $FILES will contain the +# names of the received files, and $REMID will have the remote ID +# string or '?' if none. The faxmail function will e-mail the fax +# as MIME image/tiff attachments. Comment this out to do +# nothing. Protect with single quotes. + +NOTIFY='faxmail "$REMID" "$f" $FILES | $SENDMAIL $FAXMGR' +# NOTIFY='mail -s "fax/message from $REMID: $FILES" $FAXMGR <$f' +# NOTIFY='lpr $f ; $FAX print $OPT $FILES' + +# The number of rings to wait before answering. + +ANSRINGS=1 + +# If you want to enable fax/data adaptive answer (AA) read the +# efax man page and define DATAINIT to be the options that enable +# AA. Note: AA does not work properly on some (2400/9600) modems +# unless the modem initialization is done at 2400 bps (not +# possible with efax). USR modems do not support modem adaptive +# answer (+FAE=) in Class 1. &C1 enables most modems' DCD line +# so a signal can be sent to shells when a call is dropped. You +# must also define DCMD (see below). + +DATAOPT="-j&C1 -j+FCLASS=0 -jS7=30" +# DATAINIT="$DATAOPT -j+FAE=1" # Class 1 modem adaptive answer +# DATAINIT="$DATAOPT -j+FAA=1" # Class 2[.0] modem adaptive answer +# DATAINIT="$DATAOPT -oa" # software adaptive answer +# DATAINIT="$DATAOPT" # data-only answer + +# If you have a voice modem and want to answer in voice mode +# define VOICEINIT to be the options that enable voice mode. You +# must also set VCMD below. Voice support is not yet available. + +# VOICEINIT="-j#CLS=8" # Rockwell voice modems +# VOICEINIT="-jM2L2#CLS=8#VLS=4" # with speaker on + +# Argument to exec(2) of "/bin/sh -c" for incoming data calls. +# This command will usually exec getty(8) but can include other +# commands to set up the serial port, etc. Up to 6 %d arguments +# are replaced by the baud rate following the CONNECT response +# from the modem or 19200 if none. If using getty_ps ensure +# /etc/gettydefs has entries for all possible %d values +# (e.g. 19200). Use 'nice' if required to reduce any special +# priority set by NICE. + +DCMD="exec /sbin/getty -h $DEV %d vt100" # for getty_ps (Linux) +# DCMD="exec /sbin/agetty -h $DEV %d vt100" # for agetty (Linux) +# DCMD="exec pppd $DEV %d" # start PPP server + +# Argument to exec(2) of "/bin/sh -c" for incoming voice calls. +# This command will usually be a shell script that interacts with +# the caller by using efone to play/record audio and detect DTMF +# tones. Up to 6 %d arguments are replaced by the modem file +# descriptor. VCMD can "exec fax reanswer" to switch to fax or +# data mode if required. + +FONE=/usr/bin/fone # minimal voice mail +VCMD="exec $FONE %d" + +# The owner.group and mode to which "fax answer" sets the serial +# device. This allows non-root processes to grab the device from +# efax even if a previous process (e.g. login) has changed it. +# Comment out if you don't need to reset device ownership. + +OWNER=root.tty # typical +MODE=666 # anybody +# MODE=660 # only owner & group + +# Regular expression for efax exit codes in log files that will +# *not* be saved. For example, use [145] to ignore exits due to +# `locked' (1), `no modem' (4), and `signal' (5) conditions + +NOLOG='[145]' + +# **************************************************************** +# --- End of user configuration section --- +# **************************************************************** + +# --- source configuration files + +for f in $CONFIGFILES +do + if [ -r $f ] + then + eval "`cat $f`" + fi +done + +# --- set any variables given on command line + +while : ; do + case $# in 0) break ;; esac + case "$1" in [A-Z]*=*) eval $1 ; shift ;; *) break ;; esac +done + +# -------- initialize + +ERR=0 + +$NICE true 2>/dev/null ; case $? in 0) ;; *) NICE="" ;; esac + +# -------- resolve dependencies on command-line arguments + +eval LOCK=\"$LOCK\" # depends on DEV + +# make device name w/o directories + +case $DEV in + */*) DEVN=`echo $DEV|sed -e s./._.g` ;; + *) DEVN=$DEV ;; +esac + +case $PAGE in + letter) PAGEDIM="$PAGE_letter" ;; + legal) PAGEDIM="$PAGE_legal" ;; + a4) PAGEDIM="$PAGE_a4" ;; + *) echo "Error: PAGE=\"${PAGE}\" not valid." ; exit 2 ;; +esac + +# --- check for a command or alias and optional flags + +cmd="" +case $0 in +*/faxlpr|faxlpr) cmd=faxlpr ;; +*) + while : ; do + case $# in 0) case $cmd in '') cmd=receive ;; esac ; break ;; esac + case $1 in + -l) OPT="$OPT -l" ; RES=204x98 ; shift ;; + -h) OPT="$OPT -h" ; RES=204x196 ; shift ;; + -v) OPT="$OPT -v" ; VERB=$VERBLOG ; shift ;; + *) + case $cmd in '') cmd=$1 ; shift ;; *) break ;; esac + ;; + esac + done +;; +esac + +# -------- functions + +faxmail () { + echo "Subject: fax/message from $1" + shift + echo "Content-Type: multipart/mixed; boundary=EFAX_MAIL" + echo "" + echo "--EFAX_MAIL" + echo "Content-Type: text/plain; charset=\"us-ascii\"" + echo "Content-Transfer-Encoding: 7bit" + echo "" + cat $1 + shift + for f in $* + do + echo "--EFAX_MAIL" + echo "Content-Type: image/tiff" + echo "Content-Transfer-Encoding: base64" + echo "" + $EFIX -M <$f + done +} + +# -------- export variables for fone script + +export DEV TSTAMP + +# -------- do the appropriate command + +while : ; do # so we can use `break' to get to the end of the script + +case $cmd in + +# fax answer : clean up logs and exec efax. normally run by init(8). + + answer) + + if cd $FAXDIR ; then : + else + echo "Error: $FAX cannot cd to $FAXDIR" >>$CONSOLE + sleep 30 + break + fi + + while [ -f ${DEVN}.stop ] ; do sleep 15 ; done + + umask $UMASK + case $OWNER in '') ;; *) chown $OWNER /dev/$DEV ;; esac + case $MODE in '') ;; *) chmod $MODE /dev/$DEV ;; esac + + for f in ${DEVN}.[0-9]* # clean up old log files + do + egrep "done, returning $NOLOG|exec'ing" $f >/dev/null 2>/dev/null + case $? in + 0) + $RM $f + ;; + 1) FILES=`sed -n -e '/received ->/s/^.*-> \(.*\)$/\1/p' $f` + FILES=`echo $FILES` + REMID=`sed -n -e '/remote ID ->/s/^.*-> \(.*\)$/\1/p' \ + -e tok -e b -e ':ok' -e q $f` + case $REMID in '') REMID='?' ;; esac + eval $NOTIFY + echo >>${LOGDIR}/${DEVN}.log + cat $f >>${LOGDIR}/${DEVN}.log + $RM $f + ;; + esac + done + + exec $NICE $EFAX -v "" -v "$VERBLOG" -d/dev/$DEV $INIT $SPKR \ + $CLASSINIT $FCINIT $RXINIT $LOCK \ + $RXCAP -l "$FROM" $RESET \ + $DATAINIT -g "$DCMD" $VOICEINIT -e "$VCMD" \ + -jS0=$ANSRINGS -w -s -r "$ANSFNAME" 2>$CONSOLE >${DEVN}.$$ + + echo ERROR: $FAX answer exec failed >>$CONSOLE ; sleep 30 + + break + ;; + + +# fax reanswer : switch from voice mode to fax[/data] mode + + reanswer) + + # we should already be in the fax spool directory, the + # device locked, the modem answered and initialized in + # voice mode and stdout/stderr redirected appropriately + + umask $UMASK + + exec $NICE $EFAX -v "" -v "$VERBLOG" -d/dev/$DEV '-i#CLS=0' \ + $CLASSINIT $FCINIT $RXINIT \ + $RXCAP -l "$FROM" $RESET \ + $DATAINIT -g "$DCMD" \ + -r "$ANSFNAME" + + echo ERROR: $FAX reanswer exec failed >>$CONSOLE ; sleep 30 + + break + ;; + +# fax queue : list received fax files + + q*) + + cd $FAXDIR + case $? in 0) ;; *) echo "cannot cd to $FAXDIR" ; break ;; esac + + for f in [0-9]*.[0-9][0-9][0-9] [0-9]*.v + do + if [ -r $f ] + then + echo + echo Fax files in `pwd` : + echo + $LS -l [0-9]*.[0-9][0-9][0-9] [0-9]*.v + echo + break + fi + done + + break + ;; + +# faxlpr : get phone number and user from current cf* file and run fax send + + faxlpr) + + cd $FAXDIR # the lpr spool directory for printer 'fax' + case $? in 0) ;; *) echo "$0: cannot cd to $FAXDIR" ; break ;; esac + + test -r lock + case $? in 0) ;; *) echo "$0: can't read lock file" ; break ;; esac + + cfile=`tail -1 lock` + + test -r $cfile + case $? in 0) ;; *) echo "$0: can't read control file" ; break ;; esac + + cfile=`cat $cfile` + + num=` echo "$cfile" | sed -e /^[^J]/d -e s/.//` + host=`echo "$cfile" | sed -e /^[^H]/d -e s/.//` + user=`echo "$cfile" | sed -e /^[^P]/d -e s/.//` + + test "$num" + case $? in 0) ;; *) echo "$0: can't read phone number" ; break ;; esac + + cat - >> fax$$ # save in a file + + l=`$FAX send "$num" fax$$` + + case $? in + 0) echo "$l" | mail -s "fax to $num succeeded" $user@$host ;; + *) echo "$l" | mail -s "fax to $num failed " $user@$host ;; + esac + + $RM fax$$ fax$$.??? + + break + ;; + +# fax start/stop/status : manage fax receive daemon + + start|stop|st*) # common section + + cd $FAXDIR ; + case $? in 0) ;; *) echo "cannot cd to $FAXDIR" ; break ;; esac + + n= ; for f in ${DEVN}.[0-9]* ; do logfile="$f" ; n=x$n ; done + + case $n in + xx*) echo Warning: multiple logs for $DEV : ; ls ${DEVN}.[0-9]* ;; + esac + + case $logfile in + *\*) echo no fax answer process for device $DEV ; break ;; + esac + + efaxpid=`echo $logfile | sed -e "s/${DEVN}\.//g"` + + case $cmd in + +# fax start - remove stop file so fax answer will continue + + start) + + if [ ! -w . ] ; then echo "can't write `pwd`" ; break ; fi + $RM ${DEVN}.stop + break + ;; + +# fax stop - make a stop file and kill current fax answer daemon + + stop) + + if [ ! -w . ] ; then echo "can't write `pwd`" ; break ; fi + touch ${DEVN}.stop + echo stopping fax daemon for ${DEV}, pid=$efaxpid + kill -HUP $efaxpid + break + ;; + + +# fax status - display pid and log file for current daemon + + st*) + + if [ -f ${DEVN}.stop ] ; then stat="(set to stop)" ; fi + + if ps -u $efaxpid 2>/dev/null ; then : + else + echo "NOT ACTIVE (last daemon was $efaxpid)" + fi + + echo + echo from: $FAXDIR/$logfile + echo + + egrep "Warning|Error|starts|activity|opened|received -|done" $logfile + + case $# in + 0) ;; *) echo "---------------" ; sleep $1 ; exec $FAX status $1 ;; + esac + + break + ;; + + esac # common section + ;; + +# fax makefont : rasterize a PS font into a 256-character-wide bitmap + + makefont) + + if [ $# -lt 5 ] + then + echo Usage: fax makefont fontname fontsize \ + cellwidth cellheight filename + echo "(cellwidth and cellheight in pixels, fontsize in points)" + echo "Example: fax makefont Courier-Bold 8 16 24 efaxfont" + echo "will make an 8pt font (there are about 3 pixels per pt)" + exit 1 + fi + + FNTFMT=pbmraw # format for font files + # FNTFMT=tiffg3 # smaller, available with Ghostscript 3.x or later + + pelwidth=`expr 256 \* $3` + gs -q -sDEVICE=$FNTFMT -r204x196 -g${pelwidth}x$4 \ + -sOutputFile=$5 - </dev/null ) + ;; + II*|MM*|P4*) + echo "$1 is an image file..." + $EFIX -ve -otiffg3 -p$PAGEDIM -r$RES -n $1.%03d $1 + ;; + *) echo "$1 is text..." + $EFIX -ve -otiffg3 -p$PAGEDIM -r$RES -n $1.%03d $TEXTFONT $1 + ;; + esac + + break + ;; + +# fax send : fax files to given number, converting first if necessary + + s*) + + case $# in + 0) echo "missing phone number to call" ; ERR=2 ; break ;; + esac + + # look up names + + case $1 in + [A-Za-z]*) + for f in $DIRFILES ; do + if [ -r $f ] ; then TELNO=`$LOOKUP` ; fi + case "$TELNO" in '') continue ;; *) break ;; esac + done + case "$TELNO" in + '') echo "Name lookup for $1 failed" ; ERR=2 ; break ;; + *) echo "Lookup: $1 = $TELNO" ;; + esac + ;; + *) TELNO="$1" ;; + esac + + shift + + case "$TO" in '') TO="$TELNO" ;; *) ;; esac + + TELNO=`echo $TELNO|sed "s/[ ()][ ()]*//g"` + + # handle manual dialing and number->dial string conversions + + case "$TELNO" in + -m*) MANINIT="-jX3" ; TELNO="" ;; + +*) TELNO=`echo $TELNO | $TELCVT` ;; + esac + + case $TELNO in + '') ;; + *) TELNO="${DIALPREFIX}${TELNO}${DIALSUFFIX}" ;; + esac + + # use `fax make' to convert files if they need to be updated + + FILES="" + for f in $* ; do + case $f in -) FILES="$FILES -" ; continue ;; esac + if [ ! -r $f ] ; then + echo "can't read file $f" ; ERR=2 ; break 2 + fi + case $f in + *.[0-9][0-9][0-9]) FILES="$FILES $f" ;; # skip image files + *) if echo ${f}.001: $f \; x | make -r -q -f - ; then + echo ${f}.nnn is up-to-date + else + $RM ${f}.[0-9][0-9][0-9] + $FAX make $OPT $f + fi + if [ -r $f.001 ] ; then + FILES="$FILES $f.[0-9][0-9][0-9]" + else # something's wrong, catch it later + FILES="$FILES $f.001" + fi + ;; + esac + done + + # check that all files are OK + + for f in $FILES ; do + case $f in -) continue ;; esac + if [ ! -r $f ] ; then + echo "can't read file $f" ; ERR=2 ; break 2 + fi + done + + # send it + + for s in 0 $FAILRETRIES ; do + + case $s in + 0) ;; *) echo "Will try again in $s seconds" ; sleep $s ;; + esac + +# logfile=`$TSTAMP`.log + logfile="$TELNO".log + + for t in 0 $BUSYRETRIES ; do + + case $t in + 0) ;; *) echo "Will try again in $t seconds" ; sleep $t ;; + esac + + DATE=`eval "$DATECMD"` + eval HDR=\"$HDR\" + + $NICE $EFAX -v "$VERB" -v "$VERBLOG" \ + -d/dev/$DEV $LOCK $INIT $SPKR \ + $CLASSINIT $FCINIT $TXINIT \ + $TXCAP -l "$FROM" $RESET $HDRFONT -h "$HDR" \ + $MANINIT -t "$TELNO" $FILES >$logfile + + ERR=$? + + case $ERR in + 0) $RM $logfile ; break 2 ;; + 1) echo Busy... ;; + *) echo "There were errors (see ${logfile})." ; break ;; + esac + + done + + SENT=` sed -n -e '/sent ->/s/^.*-> \([^ ]*\).*/\1/p' $logfile` + FILES=`sed -n -e '/failed ->/s/^.*-> \([^ ]*\).*/\1/p' $logfile` + case $SENT in '') break ;; esac + case $FILES in '') break ;; esac + echo Failed... + + done + + break + ;; + +# fax hangup : hang up the phone + + hangup*) + $NICE exec $EFAX -v $VERB -d/dev/$DEV $LOCK -iZ -T + break + ;; + +# fax fone : open modem device and exec fone script + + fone) + + cd $FAXDIR + case $? in 0) ;; *) echo "cannot cd to $FAXDIR" ; break ;; esac + + $NICE exec $EFAX -v $VERB -d/dev/$DEV $LOCK $INIT \ + -j#CLS=8 -a#VLS=1 -e "$VCMD $*" + + break + ;; + +# fax (rm|print|view) commands possibly on files in spool directory + + rm|p*|v*) # common code + + # switch to spool directory if first file is found there + + for f in $FAXDIR/$1 ; do + if test -r $f ; then cd $FAXDIR ; break ; fi + done + + for f in $* ; do + case $cmd in + + rm) # fax rm : delete files + if $RM $f ; then echo deleted $f ; fi + ;; + + p*) # fax print : print files + echo "$f ... " + eval "$PRINT | $PRCMD" + ;; + + v*) # fax view : display images + echo "$f ... " + eval "$VIEW | $VIEWCMD" + ;; + + esac + done + + break + ;; + +# fax [receive] : answer phone now and receive fax files + + r*) + + case $1 in '') file=`$TSTAMP` ;; *) file=$1 ;; esac + logfile=${file}.log + + $NICE $EFAX -v "$VERB" -v "$VERBLOG" -d/dev/$DEV $LOCK $INIT $SPKR \ + $CLASSINIT $FCINIT $RXINIT \ + $RXCAP -l "$FROM" $RESET \ + -r $file >$logfile + + ERR=$? + + case $ERR in + 0) $RM $logfile ; break ;; + 1) echo Busy... ;; + *) echo "There were errors (see ${logfile})." ; break ;; + esac + + break + ;; + +# fax new : create a cover page for a fax (needs work) + + new) + + fname=${1-new.fax} + DATE=`date "+%B %d %Y"` + cat >$fname 2>/dev/null << EOF + + ________________________________________________ + + FAX COVER PAGE + ________________________________________________ + + To: x + fax: + + ________________________________________________ + + From: $NAME + fax: $FROM + + Date: $DATE + + Pages: 1 (including this page) + + ________________________________________________ + +EOF + ${VISUAL-${EDITOR-vi}} $fname + + break + ;; + +# fax help : show command arguments + + -\?|\?|-h*|h*) + +cat 1>&2 <