package uk.co.mmscomputing.imageio.sff; import java.io.*; import uk.co.mmscomputing.io.ModHuffmanOutputStream; public class SFFInputStream extends FilterInputStream{ /* Input: sff image file data Output: modified huffman code read(),read(byte[]),read(byte[],int,int): signal [-1] means 'end of page'. start/resumes reading if hasImage() returns true. */ private int resVert,resHorz; private int width,height,offset; private byte[] data; private byte[] lineData=null; private int index,length; private boolean eop; // end of page private byte[] whiteLine; private int whiteLines; private boolean needToDuplicateLine,duplicateLine; private int maxIllegalLineCodings=-1; // -1 => don't throw IllegalLineCoding exception public SFFInputStream(InputStream in)throws IOException{ super(in); readHeader(); if(in.read()!=254){ // 0: page signature 254 throw new IOException(getClass().getName()+".readHeader\n\tMissing page signature in sff fax document"); } } public int getWidth(){return width;} public int getHeight(){return height;} public boolean isEndOfPage(){return eop;} public void setMaxAllowedIllegalLineCodings(int max){maxIllegalLineCodings=max;} public int getVerticalResolution(){ switch(resVert){ // case 0: return 98; case 0: // duplicate lines case 1: return 196; default: return 0; // unknown,error } } public int getHorizontalResolution(){ switch(resHorz){ case 0: return 203; default: return 0; // unknown,error } } public boolean hasImage()throws IOException{ data=null;index=0;length=0; height=0;eop=false; whiteLines=0;whiteLine=null; duplicateLine=true; return readImageHeader(); } public int read(byte[] b)throws IOException{ return read(b,0,b.length); } public int read(byte[] b,int off,int len)throws IOException{ // eop=false; for(int i=0;i<len;i++){ int c=read(); if(c<0){ // if end of page if(i>0){return i;} // have some data in buffer return -1; // end of page } b[off+i]=(byte)c; } return len; } public int read()throws IOException{ if(index>=length){ // get next scan line if(0<whiteLines){ // if white lines whiteLines--; }else{ if(eop){return -1;} // end of page length=readLine(); if(length==-1){return -1;} // end of stream } height++;index=0; } return data[index++]&0x000000FF; } private int readUnsignedShort() throws IOException{ int i; i =(((int)in.read())&0x000000FF); i|=(((int)in.read())&0x000000FF)<<8; return i; } private int readInt() throws IOException{ int i =((int)in.read()&0x000000FF); i|=((int)in.read()&0x000000FF)<<8; i|=((int)in.read()&0x000000FF)<<16; i|=((int)in.read()&0x000000FF)<<24; return i; } private void skipBytes(int count) throws IOException{ byte[] buf=new byte[count];in.read(buf); } private int readLine()throws IOException{ eop=false; if(needToDuplicateLine){ // if vertical resolution is 96 dpi duplicateLine=!duplicateLine; // output every line twice if(duplicateLine){return length;} } int len=in.read(); if(len==-1){ eop=true; System.err.println(getClass().getName()+".readLine:\n\tMissing page header."); return -1; // end of stream (we shouldn't be here) }else if(len==0){ // long lines; modified huffman len = readUnsignedShort(); data=lineData; in.read(data,0,len); data[len]=0x00; // for end of line synchronization, in case of coding errors data[len+1]=(byte)0x80; return len+2; }else if(len<=216){ // normal lines; modified huffman data=lineData; in.read(data,0,len); data[len]=0x00; // for end of line synchronization, in case of coding errors data[len+1]=(byte)0x80; return len+2; }else if(len<=253){ // skip white space lines if(whiteLine==null){ // get modified huffman codes for white line whiteLine=getWhiteLineCode(width); } whiteLines=(len-216-1); // -1 : because we are setting first white line buffer here already data=whiteLine; return whiteLine.length; }else if(len==254){ // new page header eop=true;return -1; // signal end of page }else /*if(len==255)*/{ len=in.read(); if(len==0){ // 'illegal line coding' if(maxIllegalLineCodings==0){ // System.err.println(getClass().getName()+".readLine :\n\tToo many illegal line codings."); throw new IllegalLineCodingException(getClass().getName()+".readLine :\n\tToo many illegal line codings."); } maxIllegalLineCodings--; if(whiteLine==null){ // get modified huffman codes for white line whiteLine=getWhiteLineCode(width); } data=whiteLine; // interpret as white line return whiteLine.length; }else{ System.err.println(getClass().getName()+".readLine:\n\tReceived additional user information."); byte[] aui=new byte[len&0x00FF]; in.read(aui); // additional user information for(int i=0;i<aui.length;i++){ System.err.println("info["+i+"] = 0x"+Integer.toHexString(aui[i])); } return readLine(); } } } private byte[] getWhiteLineCode(int width)throws IOException{ ByteArrayOutputStream baos=new ByteArrayOutputStream(); ModHuffmanOutputStream mhos=new ModHuffmanOutputStream(baos); mhos.write(width); mhos.flush(); baos.write(0x00);baos.write(0x80); // for end of line synchronization mhos.close(); return baos.toByteArray(); // 1728 white standard G3 fax line => B2 59 01 [00 80] } // Common-ISDN-API Part I : Annex B // http://www.capi.org/download/capi20-1.pdf private void readHeader()throws IOException{ byte[] Sfff = new byte[4]; in.read(Sfff); // 0: 'Sfff' if((Sfff[0]!=(byte)'S')||(Sfff[1]!=(byte)'f')||(Sfff[2]!=(byte)'f')||(Sfff[3]!=(byte)'f')){ throw new IOException(getClass().getName()+".readHeader:\n\tInvalid sff fax document : Magic value is missing."); } int version=readUnsignedShort(); // 4: version 0x01 5: 0x00 if(version!=1){ throw new IOException(getClass().getName()+".readHeader:\n\tUnknown file version ["+version+"]"); } int userInfo=readUnsignedShort(); // 6: not used by capi 0x0000 int pageCount=readUnsignedShort(); // 8: 0x0000 if not known int firstPageHeader=readUnsignedShort(); // 10: capi : 0x14 but may be more int lastPageHeader=readInt(); // 12: 0x00000000 if not known int offDocEnd=readInt(); // 16: 0x00000000 if not known skipBytes(firstPageHeader-0x14); // 20: header length (0x14) } private boolean readImageHeader()throws IOException{ // int pageSignature=in.read(); // 0: 254 (read this in readLine) int pageHeaderLen=in.read(); // 1: offset from after this byte to data (usually 0x10) if(pageHeaderLen<=0){ if(pageHeaderLen==-1){ System.err.println(getClass().getName()+".readHeader:\n\tMissing page header."); }else{ // need to read until -1 so that capi system does not moan: // Disconnected during transfer (local abort) int b; if((b=in.read())!=-1){ System.err.println(getClass().getName()+".readHeader:\n\tAdditional data after end of document."); do{b=in.read();}while(b>0); } } return false; // end of stream,end of document } resVert=in.read(); // 2: 0x00 => 98lpi 0x01 => 196lpi if(resVert==255){return false;} // end of document (should not be done like that) needToDuplicateLine=(resVert==0); resHorz=in.read(); // 3: 0x00 => 203dpi int coding=in.read(); // 4: 0x00 => modified huffman if(coding!=0){throw new IOException(getClass().getName()+".readPageHeader:\n\tDo not know how to handle this sff page coding ["+coding+"].");} /*reserved=*/in.read(); // 5: 0x00 width=readUnsignedShort(); // 6: line length if((lineData==null)||(lineData.length<width)){// create a buffer for modified huffman codes of current row lineData=new byte[width]; } int height=readUnsignedShort(); // 8: 0x0000 if not known int offPreviousPage=readInt(); // 10: 0x0000 if not known int offNextPage=readInt(); // 14: 0x0000 if not known skipBytes(pageHeaderLen-0x10); // 18: page header length return true; // next document } static public class IllegalLineCodingException extends IOException{ public IllegalLineCodingException(String msg){ super(msg); } } }