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);
}
}
}