diff spandsp-0.0.3/spandsp-0.0.3/src/t4.c @ 5:f762bf195c4b

import spandsp-0.0.3
author Peter Meerwald <pmeerw@cosy.sbg.ac.at>
date Fri, 25 Jun 2010 16:00:21 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spandsp-0.0.3/spandsp-0.0.3/src/t4.c	Fri Jun 25 16:00:21 2010 +0200
@@ -0,0 +1,1999 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t4.c - ITU T.4 FAX image processing
+ * This depends on libtiff (see <http://www.libtiff.org>)
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 Steve Underwood
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Id: t4.c,v 1.67 2006/12/01 18:00:48 steveu Exp $
+ */
+
+/*
+ * Much of this file is based on the T.4 and T.6 support in libtiff, which requires
+ * the following notice in any derived source code:
+ *
+ * Copyright (c) 1990-1997 Sam Leffler
+ * Copyright (c) 1991-1997 Silicon Graphics, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and 
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that (i) the above copyright notices and this permission notice appear in
+ * all copies of the software and related documentation, and (ii) the names of
+ * Sam Leffler and Silicon Graphics may not be used in any advertising or
+ * publicity relating to the software without the specific, prior written
+ * permission of Sam Leffler and Silicon Graphics.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
+ * 
+ * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
+ * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
+ * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
+ * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
+ * OF THIS SOFTWARE.
+ *
+ * Decoder support is derived from code in Frank Cringle's viewfax program;
+ *      Copyright (C) 1990, 1995  Frank D. Cringle.
+ */
+
+/*! \file */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <time.h>
+#include <memory.h>
+#include <string.h>
+#if defined(HAVE_TGMATH_H)
+#include <tgmath.h>
+#endif
+#if defined(HAVE_MATH_H)
+#include <math.h>
+#endif
+#include <tiffio.h>
+
+#include "spandsp/telephony.h"
+#include "spandsp/logging.h"
+#include "spandsp/bit_operations.h"
+#include "spandsp/async.h"
+#include "spandsp/t4.h"
+
+/* Finite state machine state codes */
+#define S_Null                  0
+#define S_Pass                  1
+#define S_Horiz                 2
+#define S_V0                    3
+#define S_VR                    4
+#define S_VL                    5
+#define S_Ext                   6
+#define S_TermW                 7
+#define S_TermB                 8
+#define S_MakeUpW               9
+#define S_MakeUpB               10
+#define S_MakeUp                11
+#define S_EOL                   12
+
+#include "faxfont.h"
+
+#if 1
+#define STATE_TRACE(...) /**/
+#else
+void STATE_TRACE(char *format, ...)
+{
+    va_list arg_ptr;
+
+    va_start(arg_ptr, format);
+    vprintf(format, arg_ptr);
+    va_end(arg_ptr);
+}
+/*- End of function --------------------------------------------------------*/
+#endif
+
+/* Finite state machine state table entry */
+typedef struct
+{
+    uint8_t state;          /* see above */
+    uint8_t width;          /* width of code in bits */
+    uint32_t param;         /* run length in bits */
+} T4_tab_entry;
+
+#include "t4states.h"
+
+/* From the T.4 spec:
+
+    a0	The reference or starting changing element on the coding line. At
+        the start of the coding line, a0 is set on an imaginary white
+        changing element situated just before the first element on the
+        line. During the coding of the coding line, the position of a0
+        is defined by the previous coding mode. (See 4.2.1.3.2.)
+    a1	The next changing element to the right of a0 on the coding line.
+    a2	The next changing element to the right of a1 on the coding line.
+    b1	The first changing element on the reference line to the right of
+        a0 and of opposite colour to a0.
+    b2	The next changing element to the right of b1 on the reference line.
+*/
+
+/*
+ * ITU T.4 1D Huffman run length codes and
+ * related definitions.  Given the small sizes
+ * of these tables it does not seem
+ * worthwhile to make code & length 8 bits.
+ */
+typedef struct
+{
+    uint16_t length;        /* length of T.4 code, in bits */
+    uint16_t code;          /* T.4 code */
+    int16_t runlen;         /* run length, in bits */
+} T4_table_entry;
+
+#define is_aligned(p,t)  ((((intptr_t)(p)) & (sizeof(t) - 1)) == 0)
+
+/* Status values returned instead of a run length */
+#define        T4CODE_EOL        -1        /* NB: ACT_EOL - ACT_WRUNT */
+#define        T4CODE_INVALID    -2        /* NB: ACT_INVALID - ACT_WRUNT */
+#define        T4CODE_EOF        -3        /* end of input data */
+#define        T4CODE_INCOMP     -4        /* incomplete run code */
+
+/*
+ * Note that these tables are ordered such that the
+ * index into the table is known to be either the
+ * run length, or (run length / 64) + a fixed offset.
+ *
+ * NB: The T4CODE_INVALID entries are only used
+ *     during state generation (see mkg3states.c).
+ */
+const T4_table_entry t4_white_codes[] =
+{
+    {  8, 0x35,    0 },        /* 0011 0101 */
+    {  6, 0x07,    1 },        /* 0001 11 */
+    {  4, 0x07,    2 },        /* 0111 */
+    {  4, 0x08,    3 },        /* 1000 */
+    {  4, 0x0B,    4 },        /* 1011 */
+    {  4, 0x0C,    5 },        /* 1100 */
+    {  4, 0x0E,    6 },        /* 1110 */
+    {  4, 0x0F,    7 },        /* 1111 */
+    {  5, 0x13,    8 },        /* 1001 1 */
+    {  5, 0x14,    9 },        /* 1010 0 */
+    {  5, 0x07,   10 },        /* 0011 1 */
+    {  5, 0x08,   11 },        /* 0100 0 */
+    {  6, 0x08,   12 },        /* 0010 00 */
+    {  6, 0x03,   13 },        /* 0000 11 */
+    {  6, 0x34,   14 },        /* 1101 00 */
+    {  6, 0x35,   15 },        /* 1101 01 */
+    {  6, 0x2A,   16 },        /* 1010 10 */
+    {  6, 0x2B,   17 },        /* 1010 11 */
+    {  7, 0x27,   18 },        /* 0100 111 */
+    {  7, 0x0C,   19 },        /* 0001 100 */
+    {  7, 0x08,   20 },        /* 0001 000 */
+    {  7, 0x17,   21 },        /* 0010 111 */
+    {  7, 0x03,   22 },        /* 0000 011 */
+    {  7, 0x04,   23 },        /* 0000 100 */
+    {  7, 0x28,   24 },        /* 0101 000 */
+    {  7, 0x2B,   25 },        /* 0101 011 */
+    {  7, 0x13,   26 },        /* 0010 011 */
+    {  7, 0x24,   27 },        /* 0100 100 */
+    {  7, 0x18,   28 },        /* 0011 000 */
+    {  8, 0x02,   29 },        /* 0000 0010 */
+    {  8, 0x03,   30 },        /* 0000 0011 */
+    {  8, 0x1A,   31 },        /* 0001 1010 */
+    {  8, 0x1B,   32 },        /* 0001 1011 */
+    {  8, 0x12,   33 },        /* 0001 0010 */
+    {  8, 0x13,   34 },        /* 0001 0011 */
+    {  8, 0x14,   35 },        /* 0001 0100 */
+    {  8, 0x15,   36 },        /* 0001 0101 */
+    {  8, 0x16,   37 },        /* 0001 0110 */
+    {  8, 0x17,   38 },        /* 0001 0111 */
+    {  8, 0x28,   39 },        /* 0010 1000 */
+    {  8, 0x29,   40 },        /* 0010 1001 */
+    {  8, 0x2A,   41 },        /* 0010 1010 */
+    {  8, 0x2B,   42 },        /* 0010 1011 */
+    {  8, 0x2C,   43 },        /* 0010 1100 */
+    {  8, 0x2D,   44 },        /* 0010 1101 */
+    {  8, 0x04,   45 },        /* 0000 0100 */
+    {  8, 0x05,   46 },        /* 0000 0101 */
+    {  8, 0x0A,   47 },        /* 0000 1010 */
+    {  8, 0x0B,   48 },        /* 0000 1011 */
+    {  8, 0x52,   49 },        /* 0101 0010 */
+    {  8, 0x53,   50 },        /* 0101 0011 */
+    {  8, 0x54,   51 },        /* 0101 0100 */
+    {  8, 0x55,   52 },        /* 0101 0101 */
+    {  8, 0x24,   53 },        /* 0010 0100 */
+    {  8, 0x25,   54 },        /* 0010 0101 */
+    {  8, 0x58,   55 },        /* 0101 1000 */
+    {  8, 0x59,   56 },        /* 0101 1001 */
+    {  8, 0x5A,   57 },        /* 0101 1010 */
+    {  8, 0x5B,   58 },        /* 0101 1011 */
+    {  8, 0x4A,   59 },        /* 0100 1010 */
+    {  8, 0x4B,   60 },        /* 0100 1011 */
+    {  8, 0x32,   61 },        /* 0011 0010 */
+    {  8, 0x33,   62 },        /* 0011 0011 */
+    {  8, 0x34,   63 },        /* 0011 0100 */
+    {  5, 0x1B,   64 },        /* 1101 1 */
+    {  5, 0x12,  128 },        /* 1001 0 */
+    {  6, 0x17,  192 },        /* 0101 11 */
+    {  7, 0x37,  256 },        /* 0110 111 */
+    {  8, 0x36,  320 },        /* 0011 0110 */
+    {  8, 0x37,  384 },        /* 0011 0111 */
+    {  8, 0x64,  448 },        /* 0110 0100 */
+    {  8, 0x65,  512 },        /* 0110 0101 */
+    {  8, 0x68,  576 },        /* 0110 1000 */
+    {  8, 0x67,  640 },        /* 0110 0111 */
+    {  9, 0xCC,  704 },        /* 0110 0110 0 */
+    {  9, 0xCD,  768 },        /* 0110 0110 1 */
+    {  9, 0xD2,  832 },        /* 0110 1001 0 */
+    {  9, 0xD3,  896 },        /* 0110 1001 1 */
+    {  9, 0xD4,  960 },        /* 0110 1010 0 */
+    {  9, 0xD5, 1024 },        /* 0110 1010 1 */
+    {  9, 0xD6, 1088 },        /* 0110 1011 0 */
+    {  9, 0xD7, 1152 },        /* 0110 1011 1 */
+    {  9, 0xD8, 1216 },        /* 0110 1100 0 */
+    {  9, 0xD9, 1280 },        /* 0110 1100 1 */
+    {  9, 0xDA, 1344 },        /* 0110 1101 0 */
+    {  9, 0xDB, 1408 },        /* 0110 1101 1 */
+    {  9, 0x98, 1472 },        /* 0100 1100 0 */
+    {  9, 0x99, 1536 },        /* 0100 1100 1 */
+    {  9, 0x9A, 1600 },        /* 0100 1101 0 */
+    {  6, 0x18, 1664 },        /* 0110 00 */
+    {  9, 0x9B, 1728 },        /* 0100 1101 1 */
+    { 11, 0x08, 1792 },        /* 0000 0001 000 */
+    { 11, 0x0C, 1856 },        /* 0000 0001 100 */
+    { 11, 0x0D, 1920 },        /* 0000 0001 101 */
+    { 12, 0x12, 1984 },        /* 0000 0001 0010 */
+    { 12, 0x13, 2048 },        /* 0000 0001 0011 */
+    { 12, 0x14, 2112 },        /* 0000 0001 0100 */
+    { 12, 0x15, 2176 },        /* 0000 0001 0101 */
+    { 12, 0x16, 2240 },        /* 0000 0001 0110 */
+    { 12, 0x17, 2304 },        /* 0000 0001 0111 */
+    { 12, 0x1C, 2368 },        /* 0000 0001 1100 */
+    { 12, 0x1D, 2432 },        /* 0000 0001 1101 */
+    { 12, 0x1E, 2496 },        /* 0000 0001 1110 */
+    { 12, 0x1F, 2560 },        /* 0000 0001 1111 */
+    { 12, 0x01, T4CODE_EOL },           /* 0000 0000 0001 */
+    {  9, 0x01, T4CODE_INVALID },       /* 0000 0000 1 */
+    { 10, 0x01, T4CODE_INVALID },       /* 0000 0000 01 */
+    { 11, 0x01, T4CODE_INVALID },       /* 0000 0000 001 */
+    { 12, 0x00, T4CODE_INVALID },       /* 0000 0000 0000 */
+};
+
+const T4_table_entry t4_black_codes[] =
+{
+    { 10, 0x37,    0 },        /* 0000 1101 11 */
+    {  3, 0x02,    1 },        /* 010 */
+    {  2, 0x03,    2 },        /* 11 */
+    {  2, 0x02,    3 },        /* 10 */
+    {  3, 0x03,    4 },        /* 011 */
+    {  4, 0x03,    5 },        /* 0011 */
+    {  4, 0x02,    6 },        /* 0010 */
+    {  5, 0x03,    7 },        /* 0001 1 */
+    {  6, 0x05,    8 },        /* 0001 01 */
+    {  6, 0x04,    9 },        /* 0001 00 */
+    {  7, 0x04,   10 },        /* 0000 100 */
+    {  7, 0x05,   11 },        /* 0000 101 */
+    {  7, 0x07,   12 },        /* 0000 111 */
+    {  8, 0x04,   13 },        /* 0000 0100 */
+    {  8, 0x07,   14 },        /* 0000 0111 */
+    {  9, 0x18,   15 },        /* 0000 1100 0 */
+    { 10, 0x17,   16 },        /* 0000 0101 11 */
+    { 10, 0x18,   17 },        /* 0000 0110 00 */
+    { 10, 0x08,   18 },        /* 0000 0010 00 */
+    { 11, 0x67,   19 },        /* 0000 1100 111 */
+    { 11, 0x68,   20 },        /* 0000 1101 000 */
+    { 11, 0x6C,   21 },        /* 0000 1101 100 */
+    { 11, 0x37,   22 },        /* 0000 0110 111 */
+    { 11, 0x28,   23 },        /* 0000 0101 000 */
+    { 11, 0x17,   24 },        /* 0000 0010 111 */
+    { 11, 0x18,   25 },        /* 0000 0011 000 */
+    { 12, 0xCA,   26 },        /* 0000 1100 1010 */
+    { 12, 0xCB,   27 },        /* 0000 1100 1011 */
+    { 12, 0xCC,   28 },        /* 0000 1100 1100 */
+    { 12, 0xCD,   29 },        /* 0000 1100 1101 */
+    { 12, 0x68,   30 },        /* 0000 0110 1000 */
+    { 12, 0x69,   31 },        /* 0000 0110 1001 */
+    { 12, 0x6A,   32 },        /* 0000 0110 1010 */
+    { 12, 0x6B,   33 },        /* 0000 0110 1011 */
+    { 12, 0xD2,   34 },        /* 0000 1101 0010 */
+    { 12, 0xD3,   35 },        /* 0000 1101 0011 */
+    { 12, 0xD4,   36 },        /* 0000 1101 0100 */
+    { 12, 0xD5,   37 },        /* 0000 1101 0101 */
+    { 12, 0xD6,   38 },        /* 0000 1101 0110 */
+    { 12, 0xD7,   39 },        /* 0000 1101 0111 */
+    { 12, 0x6C,   40 },        /* 0000 0110 1100 */
+    { 12, 0x6D,   41 },        /* 0000 0110 1101 */
+    { 12, 0xDA,   42 },        /* 0000 1101 1010 */
+    { 12, 0xDB,   43 },        /* 0000 1101 1011 */
+    { 12, 0x54,   44 },        /* 0000 0101 0100 */
+    { 12, 0x55,   45 },        /* 0000 0101 0101 */
+    { 12, 0x56,   46 },        /* 0000 0101 0110 */
+    { 12, 0x57,   47 },        /* 0000 0101 0111 */
+    { 12, 0x64,   48 },        /* 0000 0110 0100 */
+    { 12, 0x65,   49 },        /* 0000 0110 0101 */
+    { 12, 0x52,   50 },        /* 0000 0101 0010 */
+    { 12, 0x53,   51 },        /* 0000 0101 0011 */
+    { 12, 0x24,   52 },        /* 0000 0010 0100 */
+    { 12, 0x37,   53 },        /* 0000 0011 0111 */
+    { 12, 0x38,   54 },        /* 0000 0011 1000 */
+    { 12, 0x27,   55 },        /* 0000 0010 0111 */
+    { 12, 0x28,   56 },        /* 0000 0010 1000 */
+    { 12, 0x58,   57 },        /* 0000 0101 1000 */
+    { 12, 0x59,   58 },        /* 0000 0101 1001 */
+    { 12, 0x2B,   59 },        /* 0000 0010 1011 */
+    { 12, 0x2C,   60 },        /* 0000 0010 1100 */
+    { 12, 0x5A,   61 },        /* 0000 0101 1010 */
+    { 12, 0x66,   62 },        /* 0000 0110 0110 */
+    { 12, 0x67,   63 },        /* 0000 0110 0111 */
+    { 10, 0x0F,   64 },        /* 0000 0011 11 */
+    { 12, 0xC8,  128 },        /* 0000 1100 1000 */
+    { 12, 0xC9,  192 },        /* 0000 1100 1001 */
+    { 12, 0x5B,  256 },        /* 0000 0101 1011 */
+    { 12, 0x33,  320 },        /* 0000 0011 0011 */
+    { 12, 0x34,  384 },        /* 0000 0011 0100 */
+    { 12, 0x35,  448 },        /* 0000 0011 0101 */
+    { 13, 0x6C,  512 },        /* 0000 0011 0110 0 */
+    { 13, 0x6D,  576 },        /* 0000 0011 0110 1 */
+    { 13, 0x4A,  640 },        /* 0000 0010 0101 0 */
+    { 13, 0x4B,  704 },        /* 0000 0010 0101 1 */
+    { 13, 0x4C,  768 },        /* 0000 0010 0110 0 */
+    { 13, 0x4D,  832 },        /* 0000 0010 0110 1 */
+    { 13, 0x72,  896 },        /* 0000 0011 1001 0 */
+    { 13, 0x73,  960 },        /* 0000 0011 1001 1 */
+    { 13, 0x74, 1024 },        /* 0000 0011 1010 0 */
+    { 13, 0x75, 1088 },        /* 0000 0011 1010 1 */
+    { 13, 0x76, 1152 },        /* 0000 0011 1011 0 */
+    { 13, 0x77, 1216 },        /* 0000 0011 1011 1 */
+    { 13, 0x52, 1280 },        /* 0000 0010 1001 0 */
+    { 13, 0x53, 1344 },        /* 0000 0010 1001 1 */
+    { 13, 0x54, 1408 },        /* 0000 0010 1010 0 */
+    { 13, 0x55, 1472 },        /* 0000 0010 1010 1 */
+    { 13, 0x5A, 1536 },        /* 0000 0010 1101 0 */
+    { 13, 0x5B, 1600 },        /* 0000 0010 1101 1 */
+    { 13, 0x64, 1664 },        /* 0000 0011 0010 0 */
+    { 13, 0x65, 1728 },        /* 0000 0011 0010 1 */
+    { 11, 0x08, 1792 },        /* 0000 0001 000 */
+    { 11, 0x0C, 1856 },        /* 0000 0001 100 */
+    { 11, 0x0D, 1920 },        /* 0000 0001 101 */
+    { 12, 0x12, 1984 },        /* 0000 0001 0010 */
+    { 12, 0x13, 2048 },        /* 0000 0001 0011 */
+    { 12, 0x14, 2112 },        /* 0000 0001 0100 */
+    { 12, 0x15, 2176 },        /* 0000 0001 0101 */
+    { 12, 0x16, 2240 },        /* 0000 0001 0110 */
+    { 12, 0x17, 2304 },        /* 0000 0001 0111 */
+    { 12, 0x1C, 2368 },        /* 0000 0001 1100 */
+    { 12, 0x1D, 2432 },        /* 0000 0001 1101 */
+    { 12, 0x1E, 2496 },        /* 0000 0001 1110 */
+    { 12, 0x1F, 2560 },        /* 0000 0001 1111 */
+    { 12, 0x01, T4CODE_EOL },            /* 0000 0000 0001 */
+    {  9, 0x01, T4CODE_INVALID },        /* 0000 0000 1 */
+    { 10, 0x01, T4CODE_INVALID },        /* 0000 0000 01 */
+    { 11, 0x01, T4CODE_INVALID },        /* 0000 0000 001 */
+    { 12, 0x00, T4CODE_INVALID },        /* 0000 0000 0000 */
+};
+
+#if defined(__i386__)  ||  defined(__x86_64__)   
+static __inline__ int run_length(unsigned int bits)
+{
+    return 7 - top_bit(bits);
+}
+/*- End of function --------------------------------------------------------*/
+#else
+static __inline__ int run_length(unsigned int bits)
+{
+    static const uint8_t run_len[256] =
+    {
+        8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x00 - 0x0f */
+        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 - 0x1f */
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x20 - 0x2f */
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x30 - 0x3f */
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 - 0x4f */
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x50 - 0x5f */
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 - 0x6f */
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x70 - 0x7f */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf0 - 0xff */
+    };
+
+    return run_len[bits];
+}
+/*- End of function --------------------------------------------------------*/
+#endif
+
+static __inline__ int flush_bits_to_image_buffer(t4_state_t *s)
+{
+    uint8_t *t;
+
+    s->bit = 8;
+    if (s->image_size >= s->image_buffer_size)
+    {
+        if ((t = realloc(s->image_buffer, s->image_buffer_size + 10000)) == NULL)
+            return -1;
+        s->image_buffer_size += 10000;
+        s->image_buffer = t;
+    }
+    s->image_buffer[s->image_size++] = (uint8_t) s->data;
+    s->data = 0;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void put_run(t4_state_t *s, int black)
+{
+    int i;
+
+    s->row_len += s->run_length;
+    /* Ignore anything before the first EOL */
+    /* Don't allow rows to grow too long, and overflow the buffers */
+    if (s->row_len <= s->image_width)
+    {
+        *s->pa++ = s->run_length;
+        for (i = 0;  i < s->run_length;  i++)
+        {
+            s->data = (s->data << 1) | black;
+            if (--s->bit == 0)
+                flush_bits_to_image_buffer(s);
+        }
+    }
+    s->run_length = 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void put_eol(t4_state_t *s)
+{
+    uint32_t *x;
+    uint8_t *t;
+    
+    if (s->run_length)
+        put_run(s, 0);
+    if (s->row_len != s->image_width)
+    {
+        STATE_TRACE("%d Bad row - %d %d\n", s->row, s->row_len, s->row_is_2d);
+        /* Clean up the bad runs */
+        while (s->a0 > s->image_width  &&  s->pa > s->cur_runs)
+            s->a0 -= *--s->pa;
+        if (s->a0 < s->image_width)
+        {
+            if (s->a0 < 0)
+                s->a0 = 0;
+            if ((s->pa - s->cur_runs) & 1)
+                put_run(s, 0);
+            s->run_length = s->image_width - s->a0;
+            put_run(s, 0);
+        }
+        else if (s->a0 > s->image_width)
+        {
+            s->run_length = s->image_width;
+            put_run(s, 0);
+            s->run_length = 0;
+            put_run(s, 0);
+        }
+        if (s->row_starts_at != s->last_row_starts_at)
+        {
+            /* Copy the previous row over this one */
+            if (s->row_starts_at + s->bytes_per_row >= s->image_buffer_size)
+            {
+                if ((t = realloc(s->image_buffer, s->image_buffer_size + 10000)) == NULL)
+                {
+                    /* TODO: take some action to report the allocation failure */
+                    return;
+                }
+                s->image_buffer_size += 10000;
+                s->image_buffer = t;
+            }
+            memcpy(s->image_buffer + s->row_starts_at, s->image_buffer + s->last_row_starts_at, s->bytes_per_row);
+            s->image_size = s->row_starts_at + s->bytes_per_row;
+        }
+        s->bad_rows++;
+        s->curr_bad_row_run++;
+    }
+    else
+    {
+        if (s->curr_bad_row_run)
+        {
+            if (s->curr_bad_row_run > s->longest_bad_row_run)
+                s->longest_bad_row_run = s->curr_bad_row_run;
+            s->curr_bad_row_run = 0;
+        }
+        STATE_TRACE("%d Good row - %d %d\n", s->row, s->row_len, s->row_is_2d);
+    }
+    
+#if 0
+    /* Dump the runs of black and white for analysis */
+    {
+        int total;
+
+        span_log(&s->logging, SPAN_LOG_DEBUG_2, "Ref ");
+        total = 0;
+        for (x = s->ref_runs;  x < s->pb;  x++)
+        {
+            total += *x;
+            span_log(&s->logging, SPAN_LOG_DEBUG_2, "%d ", *x);
+        }
+        span_log(&s->logging, SPAN_LOG_DEBUG_2, " total = %d\n", total);
+        span_log(&s->logging, SPAN_LOG_DEBUG_2, "Cur ");
+        total = 0;
+        for (x = s->cur_runs;  x < s->pa;  x++)
+        {
+            total += *x;
+            span_log(&s->logging, SPAN_LOG_DEBUG_2, "%d ", *x);
+        }
+        span_log(&s->logging, SPAN_LOG_DEBUG_2, "total = %d\n", total);
+    }
+#endif
+
+    /* Prepare the buffers for the next row. */
+    s->image_length++;
+    s->last_row_starts_at = s->row_starts_at;
+    s->row_starts_at = s->image_size;
+    x = s->cur_runs;
+    s->cur_runs = s->ref_runs;
+    s->ref_runs = x;
+
+    s->pa = s->cur_runs;
+    s->pb = s->ref_runs;
+    
+    s->a0 = 0;
+    s->b1 = *s->pb++;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_rx_end_page(t4_state_t *s)
+{
+    int row;
+    int i;
+    time_t now;
+    struct tm *tm;
+    char buf[256 + 1];
+    uint16_t resunit;
+    float x_resolution;
+    float y_resolution;
+
+    if (s->line_encoding == T4_COMPRESSION_ITU_T6)
+    {
+        /* Push enough zeros through the decoder to flush out any remaining codes */
+        for (i = 0;  i < 13;  i++)
+            t4_rx_put_bit(s, 0);
+    }
+    if (s->curr_bad_row_run)
+    {
+        if (s->curr_bad_row_run > s->longest_bad_row_run)
+            s->longest_bad_row_run = s->curr_bad_row_run;
+        s->curr_bad_row_run = 0;
+    }
+
+    if (s->image_size == 0)
+        return -1;
+
+    /* Prepare the directory entry fully before writing the image, or libtiff complains */
+    TIFFSetField(s->tiff_file, TIFFTAG_COMPRESSION, s->output_compression);
+    if (s->output_compression == COMPRESSION_CCITT_T4)
+    {
+        TIFFSetField(s->tiff_file, TIFFTAG_T4OPTIONS, s->output_t4_options);
+        TIFFSetField(s->tiff_file, TIFFTAG_FAXMODE, FAXMODE_CLASSF);
+    }
+    TIFFSetField(s->tiff_file, TIFFTAG_IMAGEWIDTH, s->image_width);
+    TIFFSetField(s->tiff_file, TIFFTAG_BITSPERSAMPLE, 1);
+    TIFFSetField(s->tiff_file, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
+    TIFFSetField(s->tiff_file, TIFFTAG_SAMPLESPERPIXEL, 1);
+    if (s->output_compression == COMPRESSION_CCITT_T4
+        ||
+        s->output_compression == COMPRESSION_CCITT_T6)
+    {
+        TIFFSetField(s->tiff_file, TIFFTAG_ROWSPERSTRIP, -1L);
+    }
+    else
+    {
+        TIFFSetField(s->tiff_file,
+                     TIFFTAG_ROWSPERSTRIP,
+                     TIFFDefaultStripSize(s->tiff_file, 0));
+    }
+    TIFFSetField(s->tiff_file, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
+    TIFFSetField(s->tiff_file, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
+    TIFFSetField(s->tiff_file, TIFFTAG_FILLORDER, FILLORDER_LSB2MSB);
+
+    x_resolution = s->x_resolution/100.0f;
+    y_resolution = s->y_resolution/100.0f;
+    /* Metric seems the sane things to use in the 21st century, but a lot of lousy software
+       gets FAX resolutions wrong, and more get it wrong using metric than using inches. */
+#if 0
+    TIFFSetField(s->tiff_file, TIFFTAG_XRESOLUTION, x_resolution);
+    TIFFSetField(s->tiff_file, TIFFTAG_YRESOLUTION, y_resolution);
+    resunit = RESUNIT_CENTIMETER;
+    TIFFSetField(s->tiff_file, TIFFTAG_RESOLUTIONUNIT, resunit);
+#else
+    TIFFSetField(s->tiff_file, TIFFTAG_XRESOLUTION, floorf(x_resolution*2.54f + 0.5f));
+    TIFFSetField(s->tiff_file, TIFFTAG_YRESOLUTION, floorf(y_resolution*2.54f + 0.5f));
+    resunit = RESUNIT_INCH;
+    TIFFSetField(s->tiff_file, TIFFTAG_RESOLUTIONUNIT, resunit);
+#endif
+
+    /* TODO: add the version of spandsp */
+    TIFFSetField(s->tiff_file, TIFFTAG_SOFTWARE, "spandsp");
+    if (gethostname(buf, sizeof(buf)) == 0)
+        TIFFSetField(s->tiff_file, TIFFTAG_HOSTCOMPUTER, buf);
+
+    //TIFFSetField(s->tiff_file, TIFFTAG_FAXRECVPARAMS, ???);
+    //TIFFSetField(s->tiff_file, TIFFTAG_FAXMODE, ???);
+    if (s->sub_address)
+        TIFFSetField(s->tiff_file, TIFFTAG_FAXSUBADDRESS, s->sub_address);
+    if (s->far_ident)
+        TIFFSetField(s->tiff_file, TIFFTAG_IMAGEDESCRIPTION, s->far_ident);
+    if (s->vendor)
+        TIFFSetField(s->tiff_file, TIFFTAG_MAKE, s->vendor);
+    if (s->model)
+        TIFFSetField(s->tiff_file, TIFFTAG_MODEL, s->model);
+
+    time(&now);
+    tm = localtime(&now);
+    sprintf(buf,
+    	    "%4d/%02d/%02d %02d:%02d:%02d",
+            tm->tm_year + 1900,
+            tm->tm_mon + 1,
+            tm->tm_mday,
+            tm->tm_hour,
+            tm->tm_min,
+            tm->tm_sec);
+    TIFFSetField(s->tiff_file, TIFFTAG_DATETIME, buf);
+    TIFFSetField(s->tiff_file, TIFFTAG_FAXRECVTIME, now - s->page_start_time);
+
+    TIFFSetField(s->tiff_file, TIFFTAG_IMAGELENGTH, s->image_length);
+    /* Set the total pages to 1. For any one page document we will get this
+       right. For multi-page documents we will need to come back and fill in
+       the right answer when we know it. */
+    TIFFSetField(s->tiff_file, TIFFTAG_PAGENUMBER, s->pages_transferred++, 1);
+    if (s->output_compression == COMPRESSION_CCITT_T4)
+    {
+        if (s->bad_rows)
+        {
+            TIFFSetField(s->tiff_file, TIFFTAG_BADFAXLINES, s->bad_rows);
+            TIFFSetField(s->tiff_file, TIFFTAG_CLEANFAXDATA, CLEANFAXDATA_REGENERATED);
+            TIFFSetField(s->tiff_file, TIFFTAG_CONSECUTIVEBADFAXLINES, s->longest_bad_row_run);
+        }
+        else
+        {
+            TIFFSetField(s->tiff_file, TIFFTAG_CLEANFAXDATA, CLEANFAXDATA_CLEAN);
+        }
+    }
+    TIFFSetField(s->tiff_file, TIFFTAG_IMAGEWIDTH, s->image_width);
+
+    /* Write the image first.... */
+    for (row = 0;  row < s->image_length;  row++)
+    {
+        if (TIFFWriteScanline(s->tiff_file, s->image_buffer + row*s->bytes_per_row, row, 0) < 0)
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "%s: Write error at row %d.\n", s->file, row);
+            break;
+        }
+    }
+    /* ....then the directory entry, and libtiff is happy. */
+    TIFFWriteDirectory(s->tiff_file);
+
+    s->bits = 0;
+    s->bits_to_date = 0;
+    s->consecutive_eols = 0;
+
+    s->image_size = 0;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_rx_put_bit(t4_state_t *s, int bit)
+{
+    int bits;
+
+    /* We decompress bit by bit, as the data stream is received. We need to
+       scan continuously for EOLs, so we might as well work this way. */
+    s->bits_to_date = (s->bits_to_date >> 1) | ((bit & 1) << 12);
+    if (++s->bits < 13)
+        return FALSE;
+    if (!s->first_eol_seen)
+    {
+        /* Do not let anything through to the decoder, until an EOL arrives. */
+        if ((s->bits_to_date & 0xFFF) != 0x800)
+            return FALSE;
+        s->bits = (s->line_encoding == T4_COMPRESSION_ITU_T4_1D)  ?  1  :  0;
+        s->first_eol_seen = TRUE;
+        return FALSE;
+    }
+    /* Check if the image has already terminated */
+    if (s->consecutive_eols >= 5)
+        return TRUE;
+    if (s->row_is_2d  &&  s->black_white == 0)
+    {
+        switch (T4_black_table[s->bits_to_date & 0x1FFF].state)
+        {
+        case S_EOL:
+            STATE_TRACE("EOL\n");
+            put_eol(s);
+            s->row_is_2d = !(s->bits_to_date & 0x1000);
+            s->bits -= (T4_black_table[s->bits_to_date & 0x1FFF].width + 1);
+            s->its_black = FALSE;
+            s->row_len = 0;
+            break;
+        default:
+            bits = s->bits_to_date & 0x7F;
+            STATE_TRACE("State %d, %d\n",
+                        T4_common_table[bits].state,
+                        T4_common_table[bits].width);
+            switch (T4_common_table[bits].state)
+            {
+            case S_Pass:
+                STATE_TRACE("Pass\n");
+                if (s->row_len < s->image_width)
+                {
+                    if (s->pa != s->cur_runs)
+                    {
+                        while (s->b1 <= s->a0  &&  s->b1 < s->image_width)
+                        {
+                            s->b1 += s->pb[0] + s->pb[1];
+                            s->pb += 2;
+                        }
+                    }
+                    s->b1 += *s->pb++;
+                    s->run_length += (s->b1 - s->a0);
+                    s->a0 = s->b1;
+                    s->b1 += *s->pb++;
+                }
+                break;
+            case S_Horiz:
+                STATE_TRACE("Horiz\n");
+                s->its_black = ((int) (s->pa - s->cur_runs)) & 1;
+                s->black_white = 2;
+                break;
+            case S_V0:
+                STATE_TRACE("V0 %d %d %d %d\n",
+                            s->a0,
+                            s->b1,
+                            s->image_width,
+                            s->run_length);
+                if (s->row_len < s->image_width)
+                {
+                    if (s->pa != s->cur_runs)
+                    {
+                        while (s->b1 <= s->a0  &&  s->b1 < s->image_width)
+                        {
+                            s->b1 += s->pb[0] + s->pb[1];
+                            s->pb += 2;
+                        }
+                    }
+                    s->run_length += (s->b1 - s->a0);
+                    s->a0 = s->b1;
+                    put_run(s, ((int) (s->pa - s->cur_runs)) & 1);
+                    s->b1 += *s->pb++;
+                }
+                break;
+            case S_VR:
+                STATE_TRACE("VR[%d] %d %d %d %d\n",
+                            T4_common_table[bits].param,
+                            s->a0,
+                            s->b1,
+                            s->image_width,
+                            s->run_length);
+                if (s->row_len < s->image_width)
+                {
+                    if (s->pa != s->cur_runs)
+                    {
+                        while (s->b1 <= s->a0  &&  s->b1 < s->image_width)
+                        {
+                            s->b1 += s->pb[0] + s->pb[1];
+                            s->pb += 2;
+                        }
+                    }
+                    s->run_length += (s->b1 + T4_common_table[bits].param - s->a0);
+                    s->a0 = s->b1 + T4_common_table[bits].param;
+                    put_run(s, ((int) (s->pa - s->cur_runs)) & 1);
+                    s->b1 += *s->pb++;
+                }
+                break;
+            case S_VL:
+                STATE_TRACE("VL[%d] %d %d %d %d\n",
+                            T4_common_table[bits].param,
+                            s->a0,
+                            s->b1,
+                            s->image_width,
+                            s->run_length);
+                if (s->row_len < s->image_width)
+                {
+                    if (s->pa != s->cur_runs)
+                    {
+                        while (s->b1 <= s->a0  &&  s->b1 < s->image_width)
+                        {
+                            s->b1 += s->pb[0] + s->pb[1];
+                            s->pb += 2;
+                        }
+                    }
+                    s->run_length += (s->b1 - T4_common_table[bits].param - s->a0);
+                    s->a0 = s->b1 - T4_common_table[bits].param;
+                    put_run(s, ((int) (s->pa - s->cur_runs)) & 1);
+                    s->b1 -= *--s->pb;
+                }
+                break;
+            case S_Ext:
+                STATE_TRACE("Ext %d 0x%x\n",
+                            ((s->bits_to_date >> T4_common_table[bits].width) & 0x7),
+                            s->bits_to_date);
+                if (s->row_len < s->image_width)
+                    *s->pa++ = s->image_width - s->a0;
+                break;
+            case S_Null:
+                break;
+            default:
+                span_log(&s->logging, SPAN_LOG_WARNING, "Unexpected T.4 state %d\n", T4_common_table[bits].state);
+                break;
+            }
+            s->bits -= T4_common_table[bits].width;
+            break;
+        }
+    }
+    else
+    {
+        if (s->its_black)
+        {
+            bits = s->bits_to_date & 0x1FFF;
+            STATE_TRACE("Black state %d %d\n", T4_black_table[bits].state, T4_black_table[bits].param);
+            switch (T4_black_table[bits].state)
+            {
+            case S_MakeUpB:
+            case S_MakeUp:
+                if (s->row_len < s->image_width)
+                {
+                    s->run_length += T4_black_table[bits].param;
+                    s->a0 += T4_black_table[bits].param;
+                }
+                break;
+            case S_TermB:
+                if (s->row_len < s->image_width)
+                {
+                    s->run_length += T4_black_table[bits].param;
+                    s->a0 += T4_black_table[bits].param;
+                    put_run(s, 1);
+                    if (s->black_white)
+                    {
+                        if (s->black_white == 1)
+                        {
+                            if (s->pa != s->cur_runs)
+                            {
+                                while (s->b1 <= s->a0  &&  s->b1 < s->image_width)
+                                {
+                                    s->b1 += s->pb[0] + s->pb[1];
+                                    s->pb += 2;
+                                }
+                            }
+                        }
+                        s->black_white--;
+                    }
+                }
+                s->its_black = FALSE;
+                break;
+            case S_EOL:
+                STATE_TRACE("EOL\n");
+                if (s->row_len == 0)
+                {
+                    if (++s->consecutive_eols >= 5)
+                        return TRUE;
+                }
+                else
+                {
+                    s->consecutive_eols = 0;
+                    put_eol(s);
+                }
+                if (s->line_encoding != T4_COMPRESSION_ITU_T4_1D)
+                {
+                    s->row_is_2d = !(s->bits_to_date & 0x1000);
+                    s->bits--;
+                }
+                s->its_black = FALSE;
+                s->row_len = 0;
+                break;
+            default:
+                /* Bad black */
+                s->black_white = 0;
+                break;
+            }
+            s->bits -= T4_black_table[bits].width;
+        }
+        else
+        {
+            bits = s->bits_to_date & 0xFFF;
+            STATE_TRACE("White state %d %d\n", T4_white_table[bits].state, T4_white_table[bits].param);
+            switch (T4_white_table[bits].state)
+            {
+            case S_MakeUpW:
+            case S_MakeUp:
+                if (s->row_len < s->image_width)
+                {
+                    s->run_length += T4_white_table[bits].param;
+                    s->a0 += T4_white_table[bits].param;
+                }
+                break;
+            case S_TermW:
+                if (s->row_len < s->image_width)
+                {
+                    s->run_length += T4_white_table[bits].param;
+                    s->a0 += T4_white_table[bits].param;
+                    put_run(s, 0);
+                    if (s->black_white)
+                    {
+                        if (s->black_white == 1)
+                        {
+                            if (s->pa != s->cur_runs)
+                            {
+                                while (s->b1 <= s->a0  &&  s->b1 < s->image_width)
+                                {
+                                    s->b1 += s->pb[0] + s->pb[1];
+                                    s->pb += 2;
+                                }
+                            }
+                        }
+                        s->black_white--;
+                    }
+                }
+                s->its_black = TRUE;
+                break;
+            case S_EOL:
+                STATE_TRACE("EOL\n");
+                if (s->row_len == 0)
+                {
+                    if (++s->consecutive_eols >= 5)
+                        return TRUE;
+                }
+                else
+                {
+                    s->consecutive_eols = 0;
+                    put_eol(s);
+                }
+                if (s->line_encoding != T4_COMPRESSION_ITU_T4_1D)
+                {
+                    s->row_is_2d = !(s->bits_to_date & 0x1000);
+                    s->bits--;
+                }
+                s->its_black = FALSE;
+                s->row_len = 0;
+                break;
+            default:
+                /* Bad white */
+                s->black_white = 0;
+                break;
+            }
+            s->bits -= T4_white_table[bits].width;
+        }
+    }
+
+    if (s->line_encoding == T4_COMPRESSION_ITU_T6  &&  s->row_len >= s->image_width)
+    {
+        /* T.6 has no EOL markers. We sense the end of a line by its length alone. */
+        STATE_TRACE("EOL T.6\n");
+        put_eol(s);
+        s->its_black = FALSE;
+        s->row_len = 0;
+    }
+    return FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+t4_state_t *t4_rx_create(const char *file, int output_encoding)
+{
+    t4_state_t *s;
+    
+    if ((s = (t4_state_t *) malloc(sizeof(t4_state_t *))))
+    {
+        if (t4_rx_init(s, file, output_encoding))
+        {
+            free(s);
+            return NULL;
+        }
+    }
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_rx_init(t4_state_t *s, const char *file, int output_encoding)
+{
+    memset(s, 0, sizeof(*s));
+    span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
+    span_log_set_protocol(&s->logging, "T.4");
+
+    span_log(&s->logging, SPAN_LOG_FLOW, "Start rx document\n");
+
+    if ((s->tiff_file = TIFFOpen(file, "w")) == NULL)
+        return -1;
+
+    /* Save the file name for logging reports. */
+    s->file = strdup(file);
+    /* Only provide for one form of coding throughout the file, even though the
+       coding on the wire could change between pages. */
+    switch (output_encoding)
+    {
+    case T4_COMPRESSION_ITU_T4_1D:
+        s->output_compression = COMPRESSION_CCITT_T4;
+        s->output_t4_options = GROUP3OPT_FILLBITS;
+        break;
+    case T4_COMPRESSION_ITU_T4_2D:
+        s->output_compression = COMPRESSION_CCITT_T4;
+        s->output_t4_options = GROUP3OPT_FILLBITS | GROUP3OPT_2DENCODING;
+        break;
+    case T4_COMPRESSION_ITU_T6:
+        s->output_compression = COMPRESSION_CCITT_T6;
+        s->output_t4_options = 0;
+        break;
+    }
+
+    /* Until we have a valid figure for the bytes per row, we need it to be set to a suitable
+       value to ensure it will be seen as changing when the real value is used. */
+    s->bytes_per_row = 0;
+
+    s->pages_transferred = 0;
+
+    s->image_buffer = NULL;
+    s->image_buffer_size = 0;
+
+    /* Set some default values */
+    s->x_resolution = T4_X_RESOLUTION_R8;
+    s->y_resolution = T4_Y_RESOLUTION_FINE;
+    s->image_width = 1728;
+
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_rx_start_page(t4_state_t *s)
+{
+    int bytes_per_row;
+    int run_space;
+    uint32_t *bufptr;
+
+    span_log(&s->logging, SPAN_LOG_FLOW, "Start rx page - compression %d\n", s->line_encoding);
+    if (s->tiff_file == NULL)
+        return -1;
+
+    /* Calculate the scanline/tile width. */
+    bytes_per_row = s->image_width/8;
+    run_space = 2*((s->image_width + 31) & ~31);
+    run_space = (run_space + 3)*sizeof(uint32_t);
+    if (bytes_per_row != s->bytes_per_row)
+    {
+        /* Allocate the space required for decoding the new row length. */
+        s->bytes_per_row = bytes_per_row;
+        if ((bufptr = (uint32_t *) realloc(s->cur_runs, run_space)) == NULL)
+            return -1;
+        s->cur_runs = bufptr;
+        if ((bufptr = (uint32_t *) realloc(s->ref_runs, run_space)) == NULL)
+            return -1;
+        s->ref_runs = bufptr;
+    }
+    memset(s->cur_runs, 0, run_space);
+    memset(s->ref_runs, 0, run_space);
+
+    s->bits = 0;
+    s->bits_to_date = 0;
+
+    s->row_is_2d = (s->line_encoding == T4_COMPRESSION_ITU_T6);
+    s->first_eol_seen = (s->line_encoding == T4_COMPRESSION_ITU_T6);
+
+    s->bad_rows = 0;
+    s->longest_bad_row_run = 0;
+    s->curr_bad_row_run = 0;
+    s->image_length = 0;
+    s->consecutive_eols = 0;
+    s->data = 0;
+    s->bit = 8;
+    s->image_size = 0;
+    s->row_starts_at = 0;
+    s->last_row_starts_at = 0;
+
+    s->row_len = 0;
+    s->its_black = FALSE;
+    s->black_white = 0;
+
+    s->pa = s->cur_runs;
+    s->pb = s->ref_runs;
+
+    /* Initialise the reference line to all white */
+    s->ref_runs[0] = s->image_width;
+    s->ref_runs[1] = 0;
+    s->a0 = 0;
+    s->b1 = s->image_width;
+    s->run_length = 0;
+
+    time (&s->page_start_time);
+
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_rx_delete(t4_state_t *s)
+{
+    if (t4_rx_end(s))
+        return -1;
+    free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_rx_end(t4_state_t *s)
+{
+    int i;
+
+    if (s->tiff_file)
+    {
+        if (s->pages_transferred > 1)
+        {
+            /* We need to edit the TIFF directories. Until now we did not know
+               the total page count, so the TIFF file currently says one. Now we
+               need to set the correct total page count associated with each page. */
+            for (i = 0;  i < s->pages_transferred;  i++)
+            {
+                TIFFSetDirectory(s->tiff_file, (tdir_t) i);
+                TIFFSetField(s->tiff_file, TIFFTAG_PAGENUMBER, i, s->pages_transferred);
+                TIFFWriteDirectory(s->tiff_file);
+            }
+        }
+        TIFFClose(s->tiff_file);
+        s->tiff_file = NULL;
+        if (s->file)
+            free((char *) s->file);
+        s->file = NULL;
+    }
+    if (s->image_buffer)
+    {
+        free(s->image_buffer);
+        s->image_buffer = NULL;
+        s->image_buffer_size = 0;
+    }
+    if (s->cur_runs)
+    {
+        free(s->cur_runs);
+        s->cur_runs = NULL;
+    }
+    if (s->ref_runs)
+    {
+        free(s->ref_runs);
+        s->ref_runs = NULL;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t4_rx_set_rx_encoding(t4_state_t *s, int encoding)
+{
+    s->line_encoding = encoding;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t4_rx_set_image_width(t4_state_t *s, int width)
+{
+    s->image_width = width;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t4_rx_set_y_resolution(t4_state_t *s, int resolution)
+{
+    s->y_resolution = resolution;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t4_rx_set_x_resolution(t4_state_t *s, int resolution)
+{
+    s->x_resolution = resolution;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t4_rx_set_sub_address(t4_state_t *s, const char *sub_address)
+{
+    s->sub_address = (sub_address  &&  sub_address[0])  ?  sub_address  :  NULL;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t4_rx_set_far_ident(t4_state_t *s, const char *ident)
+{
+    s->far_ident = (ident  &&  ident[0])  ?  ident  :  NULL;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t4_rx_set_vendor(t4_state_t *s, const char *vendor)
+{
+    s->vendor = vendor;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t4_rx_set_model(t4_state_t *s, const char *model)
+{
+    s->model = model;
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void put_bits(t4_state_t *s, int bits, int length)
+{
+    static const int msbmask[9] =
+    {
+        0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff
+    };
+
+    s->row_bits += length;
+    while (length > s->bit)
+    {
+        s->data |= (bits >> (length - s->bit));
+        length -= s->bit;
+        flush_bits_to_image_buffer(s);
+    }
+    s->data |= ((bits & msbmask[length]) << (s->bit - length));
+    s->bit -= length;
+    if (s->bit == 0)
+        flush_bits_to_image_buffer(s);
+}
+/*- End of function --------------------------------------------------------*/
+
+/*
+ * Write the sequence of codes that describes
+ * the specified span of zero's or one's.  The
+ * appropriate table that holds the make-up and
+ * terminating codes is supplied.
+ */
+static __inline__ void put_span(t4_state_t *s, int32_t span, const T4_table_entry *tab)
+{
+    const T4_table_entry *te;
+
+    te = &tab[63 + (2560 >> 6)];
+    while (span >= 2560 + 64)
+    {
+        put_bits(s, te->code, te->length);
+        span -= te->runlen;
+    }
+    te = &tab[63 + (span >> 6)];
+    if (span >= 64)
+    {
+        put_bits(s, te->code, te->length);
+        span -= te->runlen;
+    }
+    put_bits(s, tab[span].code, tab[span].length);
+}
+/*- End of function --------------------------------------------------------*/
+
+/*
+ * Find a span of ones or zeros using the supplied
+ * table.  The 'base' of the bit string is supplied
+ * along with the start and end bit indices.
+ */
+static __inline__ int find0span(uint8_t *bp, int bs, int be)
+{
+    int bits;
+    int n;
+    int span;
+    unsigned int *lp;
+
+    bits = be - bs;
+    bp += bs >> 3;
+    /* Check partial byte on LHS. */
+    if (bits > 0  &&  (n = (bs & 7)))
+    {
+        span = run_length((*bp << n) & 0xFF);
+        if (span > 8 - n)       /* Value too generous */
+            span = 8 - n;
+        if (span > bits)        /* Constrain span to bit range */
+            span = bits;
+        if (n + span < 8)       /* Doesn't extend to edge of byte */
+            return span;
+        bits -= span;
+        bp++;
+    }
+    else
+    {
+        span = 0;
+    }
+    if (bits >= (int) (2*8*sizeof(unsigned int)))
+    {
+        /* Align to natural integer boundary and check integers. */
+        while (!is_aligned(bp, unsigned int))
+        {
+            if (*bp)
+                return span + run_length(*bp);
+            span += 8;
+            bits -= 8;
+            bp++;
+        }
+        lp = (unsigned int *) bp;
+        while (bits >= (int) (8*sizeof(unsigned int))  &&  *lp == 0)
+        {
+            span += 8*sizeof(unsigned int);
+            bits -= 8*sizeof(unsigned int);
+            lp++;
+        }
+        bp = (uint8_t *) lp;
+    }
+    /* Scan full bytes for all 0's. */
+    while (bits >= 8)
+    {
+        if (*bp)
+            return span + run_length(*bp);
+        span += 8;
+        bits -= 8;
+        bp++;
+    }
+    /* Check partial byte on RHS. */
+    if (bits > 0)
+    {
+        n = run_length(*bp);
+        span += ((n > bits)  ?  bits  :  n);
+    }
+    return span;
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ int find1span(uint8_t *bp, int bs, int be)
+{
+    int bits;
+    int n;
+    int span;
+    unsigned int *lp;
+
+    bits = be - bs;
+    bp += bs >> 3;
+    /* Check partial byte on LHS. */
+    if (bits > 0  &&  (n = (bs & 7)))
+    {
+        span = run_length(((*bp << n) & 0xFF) ^ 0xFF);
+        if (span > 8 - n)       /* Value too generous */
+            span = 8 - n;
+        if (span > bits)        /* Constrain span to bit range */
+            span = bits;
+        if (n + span < 8)       /* Doesn't extend to edge of byte */
+            return span;
+        bits -= span;
+        bp++;
+    }
+    else
+    {
+        span = 0;
+    }
+    if (bits >= (int) (2*8*sizeof(unsigned int)))
+    {
+        /* Align to natural integer boundary and check integers. */
+        while (!is_aligned(bp, unsigned int))
+        {
+            if (*bp != 0xFF)
+                return span + run_length(*bp ^ 0xFF);
+            span += 8;
+            bits -= 8;
+            bp++;
+        }
+        lp = (unsigned int *) bp;
+        while (bits >= (int) (8*sizeof(unsigned int))  &&  *lp == (unsigned int) ~0)
+        {
+            span += 8*sizeof(unsigned int);
+            bits -= 8*sizeof(unsigned int);
+            lp++;
+        }
+        bp = (uint8_t *) lp;
+    }
+    /* Scan full bytes for all 1's. */
+    while (bits >= 8)
+    {
+        if (*bp != 0xFF)
+            return span + run_length(*bp ^ 0xFF);
+        span += 8;
+        bits -= 8;
+        bp++;
+    }
+    /* Check partial byte on RHS. */
+    if (bits > 0)
+    {
+        n = run_length(*bp ^ 0xFF);
+        span += ((n > bits)  ?  bits  :  n);
+    }
+    return span;
+}
+/*- End of function --------------------------------------------------------*/
+
+/*
+ * Write an EOL code to the output stream.  We also handle writing the tag
+ * bit for the next scanline when doing 2D encoding.
+ */
+static void t4_encode_eol(t4_state_t *s)
+{
+    unsigned int code;
+    int length;
+
+    if (s->line_encoding == T4_COMPRESSION_ITU_T4_1D)
+    {
+        code = 0x001;
+        length = 12;
+    }
+    else
+    {
+        code = 0x0002 | (!s->row_is_2d);
+        length = 13;
+    }
+    /* We may need to pad the row to a minimum length. */
+    if (s->row_bits + length < s->min_row_bits)
+        put_bits(s, 0, s->min_row_bits - (s->row_bits + length));
+    put_bits(s, code, length);
+    s->row_bits = 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+/*
+ * 2D-encode a row of pixels.  Consult ITU specification T.4 for the algorithm.
+ */
+static void t4_encode_2d_row(t4_state_t *s, uint8_t *bp)
+{
+    int a0;
+    int a1;
+    int b1;
+    int a2;
+    int b2;
+    int d;
+    static const T4_table_entry codes[] =
+    {
+        { 7, 0x03, 0 },         /* VR3          0000 011 */
+        { 6, 0x03, 0 },         /* VR2          0000 11 */
+        { 3, 0x03, 0 },         /* VR1          011 */
+        { 1, 0x01, 0 },         /* V0           1 */
+        { 3, 0x02, 0 },         /* VL1          010 */
+        { 6, 0x02, 0 },         /* VL2          0000 10 */
+        { 7, 0x02, 0 },         /* VL3          0000 010 */
+        { 3, 0x01, 0 },         /* horizontal   001 */
+        { 4, 0x01, 0 }          /* pass         0001 */
+    };
+    
+    a0 = 0;
+    a1 = (bp[0] & 0x80)  ?  0  :  find0span(bp, 0, s->image_width);
+    b1 = (s->ref_row_buf[0] & 0x80)  ?  0  :  find0span(s->ref_row_buf, 0, s->image_width);
+    for (;;)
+    {
+        b2 = (b1 < s->image_width)  ?  (b1 + (((s->ref_row_buf[b1 >> 3] << (b1 & 7)) & 0x80)  ?  find1span(s->ref_row_buf, b1, s->image_width)  :  find0span(s->ref_row_buf, b1, s->image_width)))  :  s->image_width;
+        if (b2 >= a1)
+        {
+            d = b1 - a1;
+            if (-3 <= d  &&  d <= 3)
+            {
+                /* Vertical mode */
+                put_bits(s, codes[d + 3].code, codes[d + 3].length);
+                a0 = a1;
+            }
+            else
+            {
+                /* Horizontal mode */
+                a2 = (a1 < s->image_width)  ?  (a1 + (((bp[a1 >> 3] << (a1 & 7)) & 0x80)  ?  find1span(bp, a1, s->image_width)  :  find0span(bp, a1, s->image_width)))  :  s->image_width;
+                put_bits(s, codes[7].code, codes[7].length);
+                if (a0 + a1 == 0  ||  ((bp[a0 >> 3] << (a0 & 7)) & 0x80) == 0)
+                {
+                    put_span(s, a1 - a0, t4_white_codes);
+                    put_span(s, a2 - a1, t4_black_codes);
+                }
+                else
+                {
+                    put_span(s, a1 - a0, t4_black_codes);
+                    put_span(s, a2 - a1, t4_white_codes);
+                }
+                a0 = a2;
+            }
+        }
+        else
+        {
+            /* Pass mode */
+            put_bits(s, codes[8].code, codes[8].length);
+            a0 = b2;
+        }
+        if (a0 >= s->image_width)
+            break;
+        a1 = a0 + (((bp[a0 >> 3] << (a0 & 7)) & 0x80)  ?  find1span(bp, a0, s->image_width)  :  find0span(bp, a0, s->image_width));
+        b1 = a0 + (((bp[a0 >> 3] << (a0 & 7)) & 0x80)  ?  find0span(s->ref_row_buf, a0, s->image_width)  :  find1span(s->ref_row_buf, a0, s->image_width));
+        b1 = b1 + (((bp[a0 >> 3] << (a0 & 7)) & 0x80)  ?  find1span(s->ref_row_buf, b1, s->image_width)  :  find0span(s->ref_row_buf, b1, s->image_width));
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+/*
+ * 1D-encode a row of pixels.  The encoding is
+ * a sequence of all-white or all-black spans
+ * of pixels encoded with Huffman codes.
+ */
+static void t4_encode_1d_row(t4_state_t *s, uint8_t *bp)
+{
+    int span;
+    int bs;
+
+    bs = 0;
+    for (;;)
+    {
+        span = find0span(bp, bs, s->image_width);                /* white span */
+        put_span(s, span, t4_white_codes);
+        bs += span;
+        if (bs >= s->image_width)
+            break;
+        span = find1span(bp, bs, s->image_width);                /* black span */
+        put_span(s, span, t4_black_codes);
+        bs += span;
+        if (bs >= s->image_width)
+            break;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static int t4_encode_row(t4_state_t *s, uint8_t *bp)
+{
+    switch (s->line_encoding)
+    {
+    case T4_COMPRESSION_ITU_T6:
+        /* T.6 compression is a trivial step up from T.4 2D, so we just
+           throw it in here. T.6 is only used with error correction,
+           so it does not need independantly compressed (i.e. 1D) lines
+           to recover from data errors. It doesn't need EOLs, either. */
+        t4_encode_2d_row(s, bp);
+        memcpy(s->ref_row_buf, bp, s->bytes_per_row);
+        break;
+    case T4_COMPRESSION_ITU_T4_2D:
+        t4_encode_eol(s);
+        if (s->row_is_2d)
+        {
+            t4_encode_2d_row(s, bp);
+            s->rows_to_next_1d_row--;
+        }
+        else
+        {
+            t4_encode_1d_row(s, bp);
+            s->row_is_2d = TRUE;
+        }
+        if (s->rows_to_next_1d_row <= 0)
+        {
+            /* Insert a row of 1D encoding */
+            s->row_is_2d = FALSE;
+            s->rows_to_next_1d_row = s->max_rows_to_next_1d_row - 1;
+        }
+        else
+        {
+            memcpy(s->ref_row_buf, bp, s->bytes_per_row);
+        }
+        break;
+    default:
+    case T4_COMPRESSION_ITU_T4_1D:
+        t4_encode_eol(s);
+        t4_encode_1d_row(s, bp);
+        break;
+    }
+    bp += s->bytes_per_row;
+    s->row++;
+    return 1;
+}
+/*- End of function --------------------------------------------------------*/
+
+t4_state_t *t4_tx_create(const char *file, int start_page, int stop_page)
+{
+    t4_state_t *s;
+    
+    if ((s = (t4_state_t *) malloc(sizeof(t4_state_t *))))
+    {
+        if (t4_tx_init(s, file, start_page, stop_page))
+        {
+            free(s);
+            return NULL;
+        }
+    }
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_tx_init(t4_state_t *s, const char *file, int start_page, int stop_page)
+{
+    float x_resolution;
+    float y_resolution;
+    uint16_t res_unit;
+    uint32_t parm;
+
+    memset(s, 0, sizeof(*s));
+    span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
+    span_log_set_protocol(&s->logging, "T.4");
+
+    span_log(&s->logging, SPAN_LOG_FLOW, "Start tx document\n");
+
+    if ((s->tiff_file = TIFFOpen(file, "r")) == NULL)
+        return -1;
+
+    s->file = strdup(file);
+    s->start_page = (start_page >= 0)  ?  start_page  :  0;
+    s->stop_page = (stop_page >= 0)  ?  stop_page : INT_MAX;
+    TIFFGetField(s->tiff_file, TIFFTAG_IMAGEWIDTH, &parm);
+    s->image_width = parm;
+    s->bytes_per_row = (s->image_width + 7)/8;
+    TIFFGetField(s->tiff_file, TIFFTAG_XRESOLUTION, &x_resolution);
+    TIFFGetField(s->tiff_file, TIFFTAG_YRESOLUTION, &y_resolution);
+    TIFFGetField(s->tiff_file, TIFFTAG_RESOLUTIONUNIT, &res_unit);
+
+    /* Allow a little range for the X resolution in centimeters. The spec doesn't pin down the
+       precise value. The other value should be exact. */
+    if ((res_unit == RESUNIT_CENTIMETER  &&  fabsf(x_resolution - 160.74f) < 2.0f)
+        ||
+        (res_unit == RESUNIT_INCH  &&  fabs(x_resolution - 408.0f) < 2.0f))
+    {
+        s->x_resolution = T4_X_RESOLUTION_R16;
+    }
+    else if ((res_unit == RESUNIT_CENTIMETER  &&  fabsf(x_resolution - 40.19f) < 2.0f)
+            ||
+            (res_unit == RESUNIT_INCH  &&  fabs(x_resolution - 102.0f) < 2.0f))
+    {
+        s->x_resolution = T4_X_RESOLUTION_R4;
+    }
+    else
+    {
+        /* Treat everything else as R8. Most FAXes are this resolution anyway. */
+        s->x_resolution = T4_X_RESOLUTION_R8;
+    }
+
+    if ((res_unit == RESUNIT_CENTIMETER  &&  fabsf(y_resolution - 154.0f) < 2.0f)
+        ||
+        (res_unit == RESUNIT_INCH  &&  fabsf(y_resolution - 392.0f) < 2.0f))
+    {
+        s->y_resolution = T4_Y_RESOLUTION_SUPERFINE;
+        s->max_rows_to_next_1d_row = 8;
+    }
+    else if ((res_unit == RESUNIT_CENTIMETER  &&  fabsf(y_resolution - 77.0f) < 2.0f)
+             ||
+             (res_unit == RESUNIT_INCH  &&  fabsf(y_resolution - 196.0f) < 2.0f))
+    {
+        s->y_resolution = T4_Y_RESOLUTION_FINE;
+        s->max_rows_to_next_1d_row = 4;
+    }
+    else
+    {
+        s->y_resolution = T4_Y_RESOLUTION_STANDARD;
+        s->max_rows_to_next_1d_row = 2;
+    }
+    s->rows_to_next_1d_row = s->max_rows_to_next_1d_row - 1;
+
+    s->pages_transferred = s->start_page;
+    if ((s->row_buf = malloc(s->bytes_per_row)) == NULL)
+        return -1;
+    if ((s->ref_row_buf = malloc(s->bytes_per_row)) == NULL)
+    {
+        free(s->row_buf);
+        s->row_buf = NULL;
+        return -1;
+    }
+    s->image_buffer_size = 0;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void make_header(t4_state_t *s, char *header)
+{
+    time_t now;
+    struct tm tm;
+    static const char *months[] =
+    {
+        "Jan",
+        "Feb",
+        "Mar",
+        "Apr",
+        "May",
+        "Jun",
+        "Jul",
+        "Aug",
+        "Sep",
+        "Oct",
+        "Nov",
+        "Dec"
+    };
+
+    time(&now);
+    tm = *localtime(&now);
+    snprintf(header,
+             132,
+             "  %2d-%s-%d  %02d:%02d    %-50s %-21s   p.%d",
+             tm.tm_mday,
+             months[tm.tm_mon],
+             tm.tm_year + 1900,
+             tm.tm_hour,
+             tm.tm_min,
+             s->header_info,
+             s->local_ident,
+             s->pages_transferred + 1);
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_tx_start_page(t4_state_t *s)
+{
+    int row;
+    int ok;
+    int i;
+    int pattern;
+    int row_bufptr;
+    int parm;
+    char *t;
+    char header[132 + 1];
+
+    span_log(&s->logging, SPAN_LOG_FLOW, "Start tx page %d\n", s->pages_transferred);
+    if (s->pages_transferred > s->stop_page)
+        return -1;
+    if (s->tiff_file == NULL)
+        return -1;
+    if (!TIFFSetDirectory(s->tiff_file, (tdir_t) s->pages_transferred))
+        return -1;
+    s->image_size = 0;
+    s->bit = 8;
+    s->row_is_2d = (s->line_encoding == T4_COMPRESSION_ITU_T6);
+    s->rows_to_next_1d_row = s->max_rows_to_next_1d_row - 1;
+
+    /* Allow for pages being of different width */
+    TIFFGetField(s->tiff_file, TIFFTAG_IMAGEWIDTH, &parm);
+    if (parm != s->image_width)
+    {
+        s->image_width = parm;
+        s->bytes_per_row = (s->image_width + 7)/8;
+        if ((s->row_buf = realloc(s->row_buf, s->bytes_per_row)) == NULL)
+            return -1;
+        if ((s->ref_row_buf = realloc(s->ref_row_buf, s->bytes_per_row)) == NULL)
+        {
+            free(s->row_buf);
+            s->row_buf = NULL;
+            return -1;
+        }
+    }
+    memset(s->ref_row_buf, 0, s->bytes_per_row);
+
+    if (s->header_info  &&  s->header_info[0])
+    {
+        /* Modify the resulting image to include a header line, typical of hardware FAX machines */
+        make_header(s, header);
+        for (row = 0;  row < 16;  row++)
+        {
+            t = header;
+            row_bufptr = 0;
+            for (t = header;  *t  &&  row_bufptr <= s->bytes_per_row - 2;  t++)
+            {
+                pattern = header_font[(uint8_t) *t][row];
+                s->row_buf[row_bufptr++] = (uint8_t) (pattern >> 8);
+                s->row_buf[row_bufptr++] = (uint8_t) (pattern & 0xFF);
+            }
+            for (  ;  row_bufptr <= s->bytes_per_row;  )
+                s->row_buf[row_bufptr++] = 0;
+            switch (s->y_resolution)
+            {
+            case T4_Y_RESOLUTION_SUPERFINE:
+                if ((ok = t4_encode_row(s, s->row_buf)) <= 0)
+                    return -1;
+                if ((ok = t4_encode_row(s, s->row_buf)) <= 0)
+                    return -1;
+                /* Fall through */
+            case T4_Y_RESOLUTION_FINE:
+                if ((ok = t4_encode_row(s, s->row_buf)) <= 0)
+                    return -1;
+                /* Fall through */
+            default:
+                if ((ok = t4_encode_row(s, s->row_buf)) <= 0)
+                    return -1;
+                break;
+            }
+        }
+    }
+    TIFFGetField(s->tiff_file, TIFFTAG_IMAGELENGTH, &s->image_length);
+    for (row = 0;  row < s->image_length;  row++)
+    {
+        if ((ok = TIFFReadScanline(s->tiff_file, s->row_buf, row, 0)) <= 0)
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "%s: Write error at row %d.\n", s->file, row);
+            break;
+        }
+        if ((ok = t4_encode_row(s, s->row_buf)) <= 0)
+            return -1;
+    }
+
+    if (s->line_encoding != T4_COMPRESSION_ITU_T6)
+    {
+        /* Attach a return to control (RTC == 6 x EOLs) to the end of the page */
+        s->row_is_2d = FALSE;
+        for (i = 0;  i < 6;  i++)
+        {
+            t4_encode_eol(s);
+            /* Suppress row padding between these EOLs */
+            s->row_bits = INT_MAX - 1000;
+        }
+    }
+    put_bits(s, 0, 7);
+    s->bit_pos = 7;
+    s->bit_ptr = 0;
+    s->row_bits = 0;
+
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_tx_more_pages(t4_state_t *s)
+{
+    span_log(&s->logging, SPAN_LOG_FLOW, "Checking for the existance of page %d\n", s->pages_transferred + 1);
+    if (s->pages_transferred > s->stop_page)
+        return -1;
+    if (s->tiff_file == NULL)
+        return -1;
+    if (!TIFFSetDirectory(s->tiff_file, (tdir_t) s->pages_transferred + 1))
+        return -1;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_tx_restart_page(t4_state_t *s)
+{
+    s->bit_pos = 7;
+    s->bit_ptr = 0;
+    s->row_bits = 0;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_tx_end_page(t4_state_t *s)
+{
+    s->pages_transferred++;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_tx_get_bit(t4_state_t *s)
+{
+    int bit;
+
+    if (s->bit_ptr >= s->image_size)
+        return PUTBIT_END_OF_DATA;
+    bit = (s->image_buffer[s->bit_ptr] >> s->bit_pos) & 1;
+    if (--s->bit_pos < 0)
+    {
+        s->bit_pos = 7;
+        s->bit_ptr++;
+    }
+    return bit;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_tx_check_bit(t4_state_t *s)
+{
+    int bit;
+
+    if (s->bit_ptr >= s->image_size)
+        return PUTBIT_END_OF_DATA;
+    bit = (s->image_buffer[s->bit_ptr] >> s->bit_pos) & 1;
+    return bit;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_tx_delete(t4_state_t *s)
+{
+    if (t4_tx_end(s))
+        return -1;
+    free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_tx_end(t4_state_t *s)
+{
+    if (s->tiff_file)
+    {
+        TIFFClose(s->tiff_file);
+        s->tiff_file = NULL;
+        if (s->file)
+            free((char *) s->file);
+        s->file = NULL;
+    }
+    if (s->image_buffer)
+    {
+        free(s->image_buffer);
+        s->image_buffer = NULL;
+        s->image_buffer_size = 0;
+    }
+    if (s->row_buf)
+    {
+        free(s->row_buf);
+        s->row_buf = NULL;
+    }
+    if (s->ref_row_buf)
+    {
+        free(s->ref_row_buf);
+        s->ref_row_buf = NULL;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t4_tx_set_tx_encoding(t4_state_t *s, int encoding)
+{
+    s->line_encoding = encoding;
+    s->rows_to_next_1d_row = s->max_rows_to_next_1d_row - 1;
+    s->row_is_2d = FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t4_tx_set_min_row_bits(t4_state_t *s, int bits)
+{
+    s->min_row_bits = bits;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t4_tx_set_local_ident(t4_state_t *s, const char *ident)
+{
+    s->local_ident = (ident  &&  ident[0])  ?  ident  :  NULL;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t4_tx_set_header_info(t4_state_t *s, const char *info)
+{
+    s->header_info = (info  &&  info[0])  ?  info  :  NULL;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_tx_get_y_resolution(t4_state_t *s)
+{
+    return s->y_resolution;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_tx_get_x_resolution(t4_state_t *s)
+{
+    return s->x_resolution;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t4_tx_get_image_width(t4_state_t *s)
+{
+    return s->image_width;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t4_get_transfer_statistics(t4_state_t *s, t4_stats_t *t)
+{
+    t->pages_transferred = s->pages_transferred;
+    t->width = s->image_width;
+    t->length = s->image_length;
+    t->bad_rows = s->bad_rows;
+    t->longest_bad_row_run = s->longest_bad_row_run;
+    t->x_resolution = s->x_resolution;
+    t->y_resolution = s->y_resolution;
+    t->encoding = s->line_encoding;
+    t->image_size = s->image_size;
+}
+/*- End of function --------------------------------------------------------*/
+
+const char *t4_encoding_to_str(int encoding)
+{
+    switch (encoding)
+    {
+    case T4_COMPRESSION_ITU_T4_1D:
+        return "T.4 1-D";
+    case T4_COMPRESSION_ITU_T4_2D:
+        return "T.4 2-D";
+    case T4_COMPRESSION_ITU_T6:
+        return "T.6";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/

Repositories maintained by Peter Meerwald, pmeerw@pmeerw.net.