Re-engineering of terminal emulator, phase 1.
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 13 Oct 2004 11:50:16 +0000 (11:50 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 13 Oct 2004 11:50:16 +0000 (11:50 +0000)
The active terminal screen is no longer an array of `unsigned long'
encoding 16-bit Unicode plus 16 attribute bits. Now it's an array of
`termchar' structures, which currently have 32-bit Unicode and 32
attribute bits but which will probably expand further in future.

To prevent bloat of the memory footprint, I've introduced a mostly
RLE-like compression scheme for storing scrollback: each line is
compressed into a compact (but hard to modify) form when it moves
into the term->scrollback tree, and is temporarily decompressed when
the user wants to scroll back over it. My initial tests suggest that
this compression averages about 1/4 of the previous (32 bits per
character cell) data size in typical output, which means this is an
improvement even without counting the new ability to extend the
information stored in each character cell.

Another beneficial side effect is that the insane format in which
Unicode was passed to front ends through do_text() has now been
rendered sane.

Testing is incomplete; this _may_ still have instabilities. Windows
and Unix front ends both seem to work as far as I've looked, but I
haven't yet looked very hard. The Mac front end I've edited (it
seemed obvious how to change it) but I can't compile or test it.

As an immediate functional effect, the terminal emulator now
supports full 32-bit Unicode to whatever extent the host platform
allows it to. For example, if you output a 4-or-more-byte UTF-8
character in Unix pterm, it will not display it properly, but it
will correctly paste it back out in a UTF8_STRING selection. Windows
is more restricted, sadly.

git-svn-id: svn://svn.tartarus.org/sgt/putty@4609 cda61777-01e9-0310-a592-d414129be87e

mac/macterm.c
putty.h
terminal.c
terminal.h
unicode.c
unix/pterm.c
window.c

index 7766c7b..1ff64ac 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: macterm.c,v 1.76 2004/06/20 17:07:37 jacob Exp $ */
+/* $Id: macterm.c,v 1.77 2004/10/13 11:50:16 simon Exp $ */
 /*
  * Copyright (c) 1999 Simon Tatham
  * Copyright (c) 1999, 2002 Ben Harris
@@ -1139,7 +1139,7 @@ struct do_text_args {
  *
  * x and y are text row and column (zero-based)
  */
-void do_text(Context ctx, int x, int y, char *text, int len,
+void do_text(Context ctx, int x, int y, wchar_t *text, int len,
             unsigned long attr, int lattr)
 {
     Session *s = ctx;
@@ -1150,7 +1150,6 @@ void do_text(Context ctx, int x, int y, char *text, int len,
     RgnHandle visrgn;
 #endif
     char mactextbuf[1024];
-    UniChar unitextbuf[1024];
     wchar_t *unitextptr;
     int i, fontwidth;
     ByteCount iread, olen;
@@ -1185,20 +1184,16 @@ void do_text(Context ctx, int x, int y, char *text, int len,
        return;
 #endif
 
-    /* Unpack Unicode from the mad format we get passed */
-    for (i = 0; i < len; i++)
-       unitextbuf[i] = (unsigned char)text[i] | (attr & CSET_MASK);
-
     if (s->uni_to_font != NULL) {
        err = ConvertFromUnicodeToText(s->uni_to_font, len * sizeof(UniChar),
-                                      unitextbuf, kUnicodeUseFallbacksMask,
+                                      text, kUnicodeUseFallbacksMask,
                                       0, NULL, NULL, NULL,
                                       1024, &iread, &olen, mactextbuf);
        if (err != noErr && err != kTECUsedFallbacksStatus)
            olen = 0;
     } else  if (s->font_charset != CS_NONE) {
        /* XXX this is bogus if wchar_t and UniChar are different sizes. */
-       unitextptr = (wchar_t *)unitextbuf;
+       unitextptr = (wchar_t *)text;
        olen = charset_from_unicode(&unitextptr, &len, mactextbuf, 1024,
                                    s->font_charset, NULL, ".", 1);
     } else
diff --git a/putty.h b/putty.h
index 93a39e4..780b4eb 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -40,35 +40,35 @@ typedef struct terminal_tag Terminal;
  * ATTR_INVALID is an illegal colour combination.
  */
 
-#define TATTR_ACTCURS      0x4UL      /* active cursor (block) */
-#define TATTR_PASCURS      0x2UL      /* passive cursor (box) */
-#define TATTR_RIGHTCURS            0x1UL      /* cursor-on-RHS */
+#define TATTR_ACTCURS      0x40000000UL      /* active cursor (block) */
+#define TATTR_PASCURS      0x20000000UL      /* passive cursor (box) */
+#define TATTR_RIGHTCURS            0x10000000UL      /* cursor-on-RHS */
 
 #define LATTR_NORM   0x00000000UL
-#define LATTR_WIDE   0x01000000UL
-#define LATTR_TOP    0x02000000UL
-#define LATTR_BOT    0x03000000UL
-#define LATTR_MODE   0x03000000UL
-#define LATTR_WRAPPED 0x10000000UL
-#define LATTR_WRAPPED2 0x20000000UL
+#define LATTR_WIDE   0x00000001UL
+#define LATTR_TOP    0x00000002UL
+#define LATTR_BOT    0x00000003UL
+#define LATTR_MODE   0x00000003UL
+#define LATTR_WRAPPED 0x00000010UL
+#define LATTR_WRAPPED2 0x00000020UL
 
-#define ATTR_INVALID 0x03FF0000UL
+#define ATTR_INVALID 0x03FFU
 
 /* Like Linux use the F000 page for direct to font. */
-#define ATTR_OEMCP   0x0000F000UL      /* OEM Codepage DTF */
-#define ATTR_ACP     0x0000F100UL      /* Ansi Codepage DTF */
+#define CSET_OEMCP   0x0000F000UL      /* OEM Codepage DTF */
+#define CSET_ACP     0x0000F100UL      /* Ansi Codepage DTF */
 
 /* These are internal use overlapping with the UTF-16 surrogates */
-#define ATTR_ASCII   0x0000D800UL      /* normal ASCII charset ESC ( B */
-#define ATTR_LINEDRW 0x0000D900UL      /* line drawing charset ESC ( 0 */
-#define ATTR_SCOACS  0x0000DA00UL      /* SCO Alternate charset */
-#define ATTR_GBCHR   0x0000DB00UL      /* UK variant   charset ESC ( A */
-#define CSET_MASK    0x0000FF00UL      /* Character set mask; MUST be 0xFF00 */
+#define CSET_ASCII   0x0000D800UL      /* normal ASCII charset ESC ( B */
+#define CSET_LINEDRW 0x0000D900UL      /* line drawing charset ESC ( 0 */
+#define CSET_SCOACS  0x0000DA00UL      /* SCO Alternate charset */
+#define CSET_GBCHR   0x0000DB00UL      /* UK variant   charset ESC ( A */
+#define CSET_MASK    0xFFFFFF00UL      /* Character set mask */
 
-#define DIRECT_CHAR(c) ((c&0xFC00)==0xD800)
-#define DIRECT_FONT(c) ((c&0xFE00)==0xF000)
+#define DIRECT_CHAR(c) ((c&0xFFFFFC00)==0xD800)
+#define DIRECT_FONT(c) ((c&0xFFFFFE00)==0xF000)
 
-#define UCSERR      (ATTR_LINEDRW|'a') /* UCS Format error character. */
+#define UCSERR      (CSET_LINEDRW|'a') /* UCS Format error character. */
 /*
  * UCSWIDE is a special value used in the terminal data to signify
  * the character cell containing the right-hand half of a CJK wide
@@ -79,27 +79,24 @@ typedef struct terminal_tag Terminal;
  */
 #define UCSWIDE             0xDFFF
 
-#define ATTR_NARROW  0x80000000UL
-#define ATTR_WIDE    0x40000000UL
-#define ATTR_BOLD    0x04000000UL
-#define ATTR_UNDER   0x08000000UL
-#define ATTR_REVERSE 0x10000000UL
-#define ATTR_BLINK   0x20000000UL
-#define ATTR_FGMASK  0x001F0000UL
-#define ATTR_BGMASK  0x03E00000UL
-#define ATTR_COLOURS 0x03FF0000UL
-#define ATTR_FGSHIFT 16
-#define ATTR_BGSHIFT 21
-
-#define ATTR_DEFAULT 0x01280000UL      /* bg 9, fg 8 */
-#define ATTR_DEFFG   0x00080000UL
-#define ATTR_DEFBG   0x01200000UL
-#define ERASE_CHAR   (ATTR_DEFAULT | ATTR_ASCII | ' ')
-#define ATTR_MASK    0xFFFFFF00UL
-#define CHAR_MASK    0x000000FFUL
+#define ATTR_NARROW  0x8000U
+#define ATTR_WIDE    0x4000U
+#define ATTR_BOLD    0x0400U
+#define ATTR_UNDER   0x0800U
+#define ATTR_REVERSE 0x1000U
+#define ATTR_BLINK   0x2000U
+#define ATTR_FGMASK  0x001FU
+#define ATTR_BGMASK  0x03E0U
+#define ATTR_COLOURS 0x03FFU
+#define ATTR_FGSHIFT 0
+#define ATTR_BGSHIFT 5
+
+#define ATTR_DEFAULT 0x0128U          /* bg 9, fg 8 */
+#define ATTR_DEFFG   0x0008U
+#define ATTR_DEFBG   0x0120U
 
 #define ATTR_CUR_AND (~(ATTR_BOLD|ATTR_REVERSE|ATTR_BLINK|ATTR_COLOURS))
-#define ATTR_CUR_XOR 0x016A0000UL
+#define ATTR_CUR_XOR 0x016AU
 
 struct sesslist {
     int nsessions;
@@ -530,8 +527,8 @@ struct RSAKey;                             /* be a little careful of scope */
  * Exports from window.c.
  */
 void request_resize(void *frontend, int, int);
-void do_text(Context, int, int, char *, int, unsigned long, int);
-void do_cursor(Context, int, int, char *, int, unsigned long, int);
+void do_text(Context, int, int, wchar_t *, int, unsigned long, int);
+void do_cursor(Context, int, int, wchar_t *, int, unsigned long, int);
 int char_width(Context ctx, int uc);
 #ifdef OPTIMISE_SCROLL
 void do_scroll(Context, int, int, int);
index 00fd7c8..d091162 100644 (file)
@@ -68,14 +68,25 @@ const wchar_t sel_nl[] = SEL_NL;
  * then we must look one space further to the left.
  */
 #define UCSGET(a, x) \
-    ( (x)>0 && ((a)[(x)] & (CHAR_MASK | CSET_MASK)) == UCSWIDE ? \
-       (a)[(x)-1] : (a)[(x)] )
+    ( (x)>0 && (a)[(x)].chr == UCSWIDE ? (a)[(x)-1].chr : (a)[(x)].chr )
+
+/*
+ * Detect the various aliases of U+0020 SPACE.
+ */
+#define IS_SPACE_CHR(chr) \
+       ((chr) == 0x20 || (DIRECT_CHAR(chr) && ((chr) & 0xFF) == 0x20))
+
+/*
+ * Spot magic CSETs.
+ */
+#define CSET_OF(chr) (DIRECT_CHAR(chr)||DIRECT_FONT(chr) ? (chr)&CSET_MASK : 0)
 
 /*
  * Internal prototypes.
  */
-static unsigned long *resizeline(unsigned long *, int);
-static unsigned long *lineptr(Terminal *, int, int);
+static void resizeline(Terminal *, termline *, int);
+static termline *lineptr(Terminal *, int, int, int);
+static void unlineptr(termline *);
 static void do_paint(Terminal *, Context, int);
 static void erase_lots(Terminal *, int, int, int);
 static void swap_screen(Terminal *, int, int, int);
@@ -86,29 +97,511 @@ static void term_print_finish(Terminal *);
 static void scroll_display(Terminal *, int, int, int);
 #endif /* OPTIMISE_SCROLL */
 
+static termline *newline(Terminal *term, int cols, int bce)
+{
+    termline *line;
+    int j;
+
+    line = snew(termline);
+    line->chars = snewn(cols, termchar);
+    for (j = 0; j < cols; j++)
+       line->chars[j] = (bce ? term->erase_char : term->basic_erase_char);
+    line->cols = cols;
+    line->lattr = LATTR_NORM;
+    line->temporary = FALSE;
+
+    return line;
+}
+
+static void freeline(termline *line)
+{
+    sfree(line->chars);
+    sfree(line);
+}
+
+static void unlineptr(termline *line)
+{
+    if (line->temporary)
+       freeline(line);
+}
+
+/*
+ * Compress and decompress a termline into an RLE-based format for
+ * storing in scrollback. (Since scrollback almost never needs to
+ * be modified and exists in huge quantities, this is a sensible
+ * tradeoff, particularly since it allows us to continue adding
+ * features to the main termchar structure without proportionally
+ * bloating the terminal emulator's memory footprint unless those
+ * features are in constant use.)
+ */
+struct buf {
+    unsigned char *data;
+    int len, size;
+};
+static void add(struct buf *b, unsigned char c)
+{
+    if (b->len >= b->size) {
+       b->size = (b->len * 3 / 2) + 512;
+       b->data = sresize(b->data, b->size, unsigned char);
+    }
+    b->data[b->len++] = c;
+}
+static int get(struct buf *b)
+{
+    return b->data[b->len++];
+}
+static void makerle(struct buf *b, termline *ldata,
+                   void (*makeliteral)(struct buf *b, termchar *c,
+                                       unsigned long *state))
+{
+    int hdrpos, hdrsize, n, prevlen, prevpos, thislen, thispos, prev2;
+    termchar *c = ldata->chars;
+    unsigned long state = 0, oldstate;
+
+    n = ldata->cols;
+
+    hdrpos = b->len;
+    hdrsize = 0;
+    add(b, 0);
+    prevlen = prevpos = 0;
+    prev2 = FALSE;
+
+    while (n-- > 0) {
+       thispos = b->len;
+       makeliteral(b, c++, &state);
+       thislen = b->len - thispos;
+       if (thislen == prevlen &&
+           !memcmp(b->data + prevpos, b->data + thispos, thislen)) {
+           /*
+            * This literal precisely matches the previous one.
+            * Turn it into a run if it's worthwhile.
+            * 
+            * With one-byte literals, it costs us two bytes to
+            * encode a run, plus another byte to write the header
+            * to resume normal output; so a three-element run is
+            * neutral, and anything beyond that is unconditionally
+            * worthwhile. With two-byte literals or more, even a
+            * 2-run is a win.
+            */
+           if (thislen > 1 || prev2) {
+               int runpos, runlen;
+
+               /*
+                * It's worth encoding a run. Start at prevpos,
+                * unless hdrsize==0 in which case we can back up
+                * another one and start by overwriting hdrpos.
+                */
+
+               hdrsize--;             /* remove the literal at prevpos */
+               if (prev2) {
+                   assert(hdrsize > 0);
+                   hdrsize--;
+                   prevpos -= prevlen;/* and possibly another one */
+               }
+
+               if (hdrsize == 0) {
+                   assert(prevpos == hdrpos + 1);
+                   runpos = hdrpos;
+                   b->len = prevpos+prevlen;
+               } else {
+                   memmove(b->data + prevpos+1, b->data + prevpos, prevlen);
+                   runpos = prevpos;
+                   b->len = prevpos+prevlen+1;
+                   /*
+                    * Terminate the previous run of ordinary
+                    * literals.
+                    */
+                   assert(hdrsize >= 1 && hdrsize <= 128);
+                   b->data[hdrpos] = hdrsize - 1;
+               }
+
+               runlen = prev2 ? 3 : 2;
+
+               while (n > 0 && runlen < 129) {
+                   int tmppos, tmplen;
+                   tmppos = b->len;
+                   oldstate = state;
+                   makeliteral(b, c, &state);
+                   tmplen = b->len - tmppos;
+                   b->len = tmppos;
+                   if (tmplen != thislen ||
+                       memcmp(b->data + runpos+1, b->data + tmppos, tmplen)) {
+                       state = oldstate;
+                       break;         /* run over */
+                   }
+                   n--, c++, runlen++;
+               }
+
+               assert(runlen >= 2 && runlen <= 129);
+               b->data[runpos] = runlen + 0x80 - 2;
+
+               hdrpos = b->len;
+               hdrsize = 0;
+               add(b, 0);
+
+               continue;
+           } else {
+               /*
+                * Just flag that the previous two literals were
+                * identical, in case we find a third identical one
+                * we want to turn into a run.
+                */
+               prev2 = TRUE;
+               prevlen = thislen;
+               prevpos = thispos;
+           }
+       } else {
+           prev2 = FALSE;
+           prevlen = thislen;
+           prevpos = thispos;
+       }
+
+       /*
+        * This character isn't (yet) part of a run. Add it to
+        * hdrsize.
+        */
+       hdrsize++;
+       if (hdrsize == 128) {
+           b->data[hdrpos] = hdrsize - 1;
+           hdrpos = b->len;
+           hdrsize = 0;
+           add(b, 0);
+           prevlen = prevpos = 0;
+           prev2 = FALSE;
+       }
+    }
+
+    /*
+     * Clean up.
+     */
+    if (hdrsize > 0) {
+       assert(hdrsize <= 128);
+       b->data[hdrpos] = hdrsize - 1;
+    } else {
+       b->len = hdrpos;
+    }
+}
+static void makeliteral_chr(struct buf *b, termchar *c, unsigned long *state)
+{
+    /*
+     * My encoding for characters is UTF-8-like, in that it stores
+     * 7-bit ASCII in one byte and uses high-bit-set bytes as
+     * introducers to indicate a longer sequence. However, it's
+     * unlike UTF-8 in that it doesn't need to be able to
+     * resynchronise, and therefore I don't want to waste two bits
+     * per byte on having recognisable continuation characters.
+     * Also I don't want to rule out the possibility that I may one
+     * day use values 0x80000000-0xFFFFFFFF for interesting
+     * purposes, so unlike UTF-8 I need a full 32-bit range.
+     * Accordingly, here is my encoding:
+     * 
+     * 00000000-0000007F: 0xxxxxxx (but see below)
+     * 00000080-00003FFF: 10xxxxxx xxxxxxxx
+     * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx
+     * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
+     * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
+     * 
+     * (`Z' is like `x' but is always going to be zero since the
+     * values I'm encoding don't go above 2^32. In principle the
+     * five-byte form of the encoding could extend to 2^35, and
+     * there could be six-, seven-, eight- and nine-byte forms as
+     * well to allow up to 64-bit values to be encoded. But that's
+     * completely unnecessary for these purposes!)
+     * 
+     * The encoding as written above would be very simple, except
+     * that 7-bit ASCII can occur in several different ways in the
+     * terminal data; sometimes it crops up in the D800 page
+     * (CSET_ASCII) but at other times it's in the 0000 page (real
+     * Unicode). Therefore, this encoding is actually _stateful_:
+     * the one-byte encoding of 00-7F actually indicates `reuse the
+     * upper three bytes of the last character', and to encode an
+     * absolute value of 00-7F you need to use the two-byte form
+     * instead.
+     */
+    if ((c->chr & ~0x7F) == *state) {
+       add(b, (unsigned char)(c->chr & 0x7F));
+    } else if (c->chr < 0x4000) {
+       add(b, (unsigned char)(((c->chr >> 8) & 0x3F) | 0x80));
+       add(b, (unsigned char)(c->chr & 0xFF));
+    } else if (c->chr < 0x200000) {
+       add(b, (unsigned char)(((c->chr >> 16) & 0x1F) | 0xC0));
+       add(b, (unsigned char)((c->chr >> 8) & 0xFF));
+       add(b, (unsigned char)(c->chr & 0xFF));
+    } else if (c->chr < 0x10000000) {
+       add(b, (unsigned char)(((c->chr >> 24) & 0x0F) | 0xE0));
+       add(b, (unsigned char)((c->chr >> 16) & 0xFF));
+       add(b, (unsigned char)((c->chr >> 8) & 0xFF));
+       add(b, (unsigned char)(c->chr & 0xFF));
+    } else {
+       add(b, 0xF0);
+       add(b, (unsigned char)((c->chr >> 24) & 0xFF));
+       add(b, (unsigned char)((c->chr >> 16) & 0xFF));
+       add(b, (unsigned char)((c->chr >> 8) & 0xFF));
+       add(b, (unsigned char)(c->chr & 0xFF));
+    }
+    *state = c->chr & ~0xFF;
+}
+static void makeliteral_attr(struct buf *b, termchar *c, unsigned long *state)
+{
+    /*
+     * My encoding for attributes is 16-bit-granular and assumes
+     * that the top bit of the word is never required. I either
+     * store a two-byte value with the top bit clear (indicating
+     * just that value), or a four-byte value with the top bit set
+     * (indicating the same value with its top bit clear).
+     */
+    if (c->attr < 0x8000) {
+       add(b, (unsigned char)((c->attr >> 8) & 0xFF));
+       add(b, (unsigned char)(c->attr & 0xFF));
+    } else {
+       add(b, (unsigned char)(((c->attr >> 24) & 0x7F) | 0x80));
+       add(b, (unsigned char)((c->attr >> 16) & 0xFF));
+       add(b, (unsigned char)((c->attr >> 8) & 0xFF));
+       add(b, (unsigned char)(c->attr & 0xFF));
+    }
+}
+
+static termline *decompressline(unsigned char *data, int *bytes_used);
+
+static unsigned char *compressline(termline *ldata)
+{
+    struct buf buffer = { NULL, 0, 0 }, *b = &buffer;
+
+    /*
+     * First, store the column count, 7 bits at a time, least
+     * significant `digit' first, with the high bit set on all but
+     * the last.
+     */
+    {
+       int n = ldata->cols;
+       while (n >= 128) {
+           add(b, (unsigned char)((n & 0x7F) | 0x80));
+           n >>= 7;
+       }
+       add(b, (unsigned char)(n));
+    }
+
+    /*
+     * Next store the lattrs; same principle.
+     */
+    {
+       int n = ldata->lattr;
+       while (n >= 128) {
+           add(b, (unsigned char)((n & 0x7F) | 0x80));
+           n >>= 7;
+       }
+       add(b, (unsigned char)(n));
+    }
+
+    /*
+     * Now we store a sequence of separate run-length encoded
+     * fragments, each containing exactly as many symbols as there
+     * are columns in the ldata.
+     * 
+     * All of these have a common basic format:
+     * 
+     *  - a byte 00-7F indicates that X+1 literals follow it
+     *         - a byte 80-FF indicates that a single literal follows it
+     *           and expects to be repeated (X-0x80)+2 times.
+     * 
+     * The format of the `literals' varies between the fragments.
+     */
+    makerle(b, ldata, makeliteral_chr);
+    makerle(b, ldata, makeliteral_attr);
+
+    /*
+     * Diagnostics: ensure that the compressed data really does
+     * decompress to the right thing.
+     */
+#ifndef CHECK_SB_COMPRESSION
+    {
+       int dused;
+       termline *dcl;
+
+#ifdef DIAGNOSTIC_SB_COMPRESSION
+       int i;
+       for (i = 0; i < b->len; i++) {
+           printf(" %02x ", b->data[i]);
+       }
+       printf("\n");
+#endif
+
+       dcl = decompressline(b->data, &dused);
+       assert(b->len == dused);
+       assert(ldata->cols == dcl->cols);
+       assert(ldata->lattr == dcl->lattr);
+       assert(!memcmp(ldata->chars, dcl->chars, ldata->cols * TSIZE));
+
+#ifdef DIAGNOSTIC_SB_COMPRESSION
+       printf("%d cols (%d bytes) -> %d bytes (factor of %g)\n",
+              ldata->cols, 4 * ldata->cols, dused,
+              (double)dused / (4 * ldata->cols));
+#endif
+
+       freeline(dcl);
+    }
+#endif
+
+    /*
+     * Trim the allocated memory so we don't waste any, and return.
+     */
+    return sresize(b->data, b->len, unsigned char);
+}
+
+static void readrle(struct buf *b, termline *ldata,
+                   void (*readliteral)(struct buf *b, termchar *c,
+                                       unsigned long *state))
+{
+    int n = 0;
+    unsigned long state = 0;
+
+    while (n < ldata->cols) {
+       int hdr = get(b);
+
+       if (hdr >= 0x80) {
+           /* A run. */
+
+           int pos = b->len, count = hdr + 2 - 0x80;
+           while (count--) {
+               assert(n < ldata->cols);
+               b->len = pos;
+               readliteral(b, ldata->chars + n, &state);
+               n++;
+           }
+       } else {
+           /* Just a sequence of consecutive literals. */
+
+           int count = hdr + 1;
+           while (count--) {
+               assert(n < ldata->cols);
+               readliteral(b, ldata->chars + n, &state);
+               n++;
+           }
+       }
+    }
+
+    assert(n == ldata->cols);
+}
+static void readliteral_chr(struct buf *b, termchar *c, unsigned long *state)
+{
+    int byte;
+
+    /*
+     * 00000000-0000007F: 0xxxxxxx
+     * 00000080-00003FFF: 10xxxxxx xxxxxxxx
+     * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx
+     * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
+     * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
+     */
+
+    byte = get(b);
+    if (byte < 0x80) {
+       c->chr = byte | *state;
+    } else if (byte < 0xC0) {
+       c->chr = (byte &~ 0xC0) << 8;
+       c->chr |= get(b);
+    } else if (byte < 0xE0) {
+       c->chr = (byte &~ 0xE0) << 16;
+       c->chr |= get(b) << 8;
+       c->chr |= get(b);
+    } else if (byte < 0xF0) {
+       c->chr = (byte &~ 0xF0) << 24;
+       c->chr |= get(b) << 16;
+       c->chr |= get(b) << 8;
+       c->chr |= get(b);
+    } else {
+       assert(byte == 0xF0);
+       c->chr = get(b) << 24;
+       c->chr |= get(b) << 16;
+       c->chr |= get(b) << 8;
+       c->chr |= get(b);
+    }
+    *state = c->chr & ~0xFF;
+}
+static void readliteral_attr(struct buf *b, termchar *c, unsigned long *state)
+{
+    int val;
+
+    val = get(b) << 8;
+    val |= get(b);
+
+    if (val >= 0x8000) {
+       val <<= 16;
+       val |= get(b) << 8;
+       val |= get(b);
+    }
+
+    c->attr = val;
+}
+
+static termline *decompressline(unsigned char *data, int *bytes_used)
+{
+    int ncols, byte, shift;
+    struct buf buffer, *b = &buffer;
+    termline *ldata;
+
+    b->data = data;
+    b->len = 0;
+
+    /*
+     * First read in the column count.
+     */
+    ncols = shift = 0;
+    do {
+       byte = get(b);
+       ncols |= (byte & 0x7F) << shift;
+       shift += 7;
+    } while (byte & 0x80);
+
+    /*
+     * Now create the output termline.
+     */
+    ldata = snew(termline);
+    ldata->chars = snewn(ncols, termchar);
+    ldata->cols = ncols;
+    ldata->temporary = TRUE;
+
+    /*
+     * Now read in the lattr.
+     */
+    ldata->lattr = shift = 0;
+    do {
+       byte = get(b);
+       ldata->lattr |= (byte & 0x7F) << shift;
+       shift += 7;
+    } while (byte & 0x80);
+
+    /*
+     * Now we read in each of the RLE streams in turn.
+     */
+    readrle(b, ldata, readliteral_chr);
+    readrle(b, ldata, readliteral_attr);
+
+    /* Return the number of bytes read, for diagnostic purposes. */
+    if (bytes_used)
+       *bytes_used = b->len;
+
+    return ldata;
+}
+
 /*
  * Resize a line to make it `cols' columns wide.
  */
-static unsigned long *resizeline(unsigned long *line, int cols)
+static void resizeline(Terminal *term, termline *line, int cols)
 {
     int i, oldlen;
-    unsigned long lineattrs;
 
-    if (line[0] != (unsigned long)cols) {
+    if (line->cols != cols) {
        /*
         * This line is the wrong length, which probably means it
         * hasn't been accessed since a resize. Resize it now.
         */
-       oldlen = line[0];
-       lineattrs = line[oldlen + 1];
-       line = sresize(line, 2 + cols, TTYPE);
-       line[0] = cols;
+       oldlen = line->cols;
+       line->chars = sresize(line->chars, cols, TTYPE);
+       line->cols = cols;
        for (i = oldlen; i < cols; i++)
-           line[i + 1] = ERASE_CHAR;
-       line[cols + 1] = lineattrs & LATTR_MODE;
+           line->chars[i] = term->basic_erase_char;
     }
-
-    return line;
 }
 
 /*
@@ -129,9 +622,9 @@ static int sblines(Terminal *term)
  * whether the y coordinate is non-negative or negative
  * (respectively).
  */
-static unsigned long *lineptr(Terminal *term, int y, int lineno)
+static termline *lineptr(Terminal *term, int y, int lineno, int screen)
 {
-    unsigned long *line, *newline;
+    termline *line;
     tree234 *whichtree;
     int treeindex;
 
@@ -140,6 +633,9 @@ static unsigned long *lineptr(Terminal *term, int y, int lineno)
        treeindex = y;
     } else {
        int altlines = 0;
+
+       assert(!screen);
+
        if (term->cfg.erase_to_scrollback &&
            term->alt_which && term->alt_screen) {
            altlines = term->alt_sblines;
@@ -153,22 +649,24 @@ static unsigned long *lineptr(Terminal *term, int y, int lineno)
            /* treeindex = y + count234(term->alt_screen); */
        }
     }
-    line = index234(whichtree, treeindex);
+    if (whichtree == term->scrollback) {
+       unsigned char *cline = index234(whichtree, treeindex);
+       line = decompressline(cline, NULL);
+    } else {
+       line = index234(whichtree, treeindex);
+    }
 
     /* We assume that we don't screw up and retrieve something out of range. */
     assert(line != NULL);
 
-    newline = resizeline(line, term->cols);
-    if (newline != line) {
-       delpos234(whichtree, treeindex);
-       addpos234(whichtree, newline, treeindex);
-        line = newline;
-    }
+    resizeline(term, line, term->cols);
+    /* FIXME: should we sort the compressed scrollback out here? */
 
-    return line + 1;
+    return line;
 }
 
-#define lineptr(x) lineptr(term,x,__LINE__)
+#define lineptr(x) (lineptr)(term,x,__LINE__,FALSE)
+#define scrlineptr(x) (lineptr)(term,x,__LINE__,TRUE)
 
 /*
  * Set up power-on settings for the terminal.
@@ -196,7 +694,7 @@ static void power_on(Terminal *term)
     term->alt_utf = term->utf = term->save_utf = 0;
     term->utf_state = 0;
     term->alt_sco_acs = term->sco_acs = term->save_sco_acs = 0;
-    term->cset_attr[0] = term->cset_attr[1] = term->save_csattr = ATTR_ASCII;
+    term->cset_attr[0] = term->cset_attr[1] = term->save_csattr = CSET_ASCII;
     term->rvideo = 0;
     term->in_vbell = FALSE;
     term->cursor_on = 1;
@@ -207,7 +705,7 @@ static void power_on(Terminal *term)
     term->app_keypad_keys = term->cfg.app_keypad;
     term->use_bce = term->cfg.bce;
     term->blink_is_real = term->cfg.blinktext;
-    term->erase_char = ERASE_CHAR;
+    term->erase_char = term->basic_erase_char;
     term->alt_which = 0;
     term_print_finish(term);
     {
@@ -242,7 +740,7 @@ void term_update(Terminal *term)
        if (!term->cfg.arabicshaping || !term->cfg.bidi)
        {
            term->wcFrom = sresize(term->wcFrom, term->cols, bidi_char);
-           term->ltemp = sresize(term->ltemp, term->cols+1, unsigned long);
+           term->ltemp = sresize(term->ltemp, term->cols+1, termchar);
            term->wcTo = sresize(term->wcTo, term->cols, bidi_char);
        }
 
@@ -299,6 +797,14 @@ void term_pwron(Terminal *term)
     term_update(term);
 }
 
+static void set_erase_char(Terminal *term)
+{
+    term->erase_char = term->basic_erase_char;
+    if (term->use_bce)
+       term->erase_char.attr = (term->curr_attr &
+                                (ATTR_FGMASK | ATTR_BGMASK));
+}
+
 /*
  * When the user reconfigures us, we need to check the forbidden-
  * alternate-screen config option, disable raw mouse mode if the
@@ -334,12 +840,7 @@ void term_reconfig(Terminal *term, Config *cfg)
        term->alt_om = term->dec_om = term->cfg.dec_om;
     if (reset_bce) {
        term->use_bce = term->cfg.bce;
-       if (term->use_bce)
-           term->erase_char = (' ' | ATTR_ASCII |
-                               (term->curr_attr &
-                                (ATTR_FGMASK | ATTR_BGMASK)));
-       else
-           term->erase_char = ERASE_CHAR;
+       set_erase_char(term);
     }
     if (reset_blink)
        term->blink_is_real = term->cfg.blinktext;
@@ -354,7 +855,7 @@ void term_reconfig(Terminal *term, Config *cfg)
        set_raw_mouse_mode(term->frontend, 0);
     }
     if (term->cfg.no_remote_charset) {
-       term->cset_attr[0] = term->cset_attr[1] = ATTR_ASCII;
+       term->cset_attr[0] = term->cset_attr[1] = CSET_ASCII;
        term->sco_acs = term->alt_sco_acs = 0;
        term->utf = 0;
     }
@@ -368,10 +869,10 @@ void term_reconfig(Terminal *term, Config *cfg)
  */
 void term_clrsb(Terminal *term)
 {
-    unsigned long *line;
+    termline *line;
     term->disptop = 0;
     while ((line = delpos234(term->scrollback, 0)) != NULL) {
-       sfree(line);
+       sfree(line);            /* this is compressed data, not a termline */
     }
     term->tempsblines = 0;
     term->alt_sblines = 0;
@@ -421,7 +922,8 @@ Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata,
     term->tempsblines = 0;
     term->alt_sblines = 0;
     term->disptop = 0;
-    term->disptext = term->dispcurs = NULL;
+    term->disptext = NULL;
+    term->dispcursx = term->dispcursy = -1;
     term->tabs = NULL;
     deselect(term);
     term->rows = term->cols = -1;
@@ -444,24 +946,31 @@ Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata,
     term->bidi_cache_size = 0;
     term->pre_bidi_cache = term->post_bidi_cache = NULL;
 
+    term->basic_erase_char.chr = CSET_ASCII | ' ';
+    term->basic_erase_char.attr = ATTR_DEFAULT;
+
     return term;
 }
 
 void term_free(Terminal *term)
 {
-    unsigned long *line;
+    termline *line;
     struct beeptime *beep;
     int i;
 
     while ((line = delpos234(term->scrollback, 0)) != NULL)
-       sfree(line);
+       sfree(line);                   /* compressed data, not a termline */
     freetree234(term->scrollback);
     while ((line = delpos234(term->screen, 0)) != NULL)
-       sfree(line);
+       freeline(line);
     freetree234(term->screen);
     while ((line = delpos234(term->alt_screen, 0)) != NULL)
-       sfree(line);
+       freeline(line);
     freetree234(term->alt_screen);
+    if (term->disptext) {
+       for (i = 0; i < term->rows; i++)
+           freeline(term->disptext[i]);
+    }
     sfree(term->disptext);
     while (term->beephead) {
        beep = term->beephead;
@@ -493,7 +1002,7 @@ void term_free(Terminal *term)
 void term_size(Terminal *term, int newrows, int newcols, int newsavelines)
 {
     tree234 *newalt;
-    unsigned long *newdisp, *line;
+    termline **newdisp, *line;
     int i, j;
     int sblen;
     int save_alt_which = term->alt_which;
@@ -539,20 +1048,20 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines)
     assert(term->rows == count234(term->screen));
     while (term->rows < newrows) {
        if (term->tempsblines > 0) {
+           unsigned char *cline;
            /* Insert a line from the scrollback at the top of the screen. */
            assert(sblen >= term->tempsblines);
-           line = delpos234(term->scrollback, --sblen);
+           cline = delpos234(term->scrollback, --sblen);
+           line = decompressline(cline, NULL);
+           sfree(cline);
+           line->temporary = FALSE;   /* reconstituted line is now real */
            term->tempsblines -= 1;
            addpos234(term->screen, line, 0);
            term->curs.y += 1;
            term->savecurs.y += 1;
        } else {
            /* Add a new blank line at the bottom of the screen. */
-           line = snewn(newcols + 2, TTYPE);
-           line[0] = newcols;
-           for (j = 0; j < newcols; j++)
-               line[j + 1] = ERASE_CHAR;
-            line[newcols + 1] = LATTR_NORM;
+           line = newline(term, newcols, FALSE);
            addpos234(term->screen, line, count234(term->screen));
        }
        term->rows += 1;
@@ -565,7 +1074,8 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines)
        } else {
            /* push top row to scrollback */
            line = delpos234(term->screen, 0);
-           addpos234(term->scrollback, line, sblen++);
+           addpos234(term->scrollback, compressline(line), sblen++);
+           freeline(line);
            term->tempsblines += 1;
            term->curs.y -= 1;
            term->savecurs.y -= 1;
@@ -588,26 +1098,29 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines)
     term->disptop = 0;
 
     /* Make a new displayed text buffer. */
-    newdisp = snewn(newrows * (newcols + 1), TTYPE);
-    for (i = 0; i < newrows * (newcols + 1); i++)
-       newdisp[i] = ATTR_INVALID;
+    newdisp = snewn(newrows, termline *);
+    for (i = 0; i < newrows; i++) {
+       newdisp[i] = newline(term, newcols, FALSE);
+       for (j = 0; j < newcols; j++)
+           newdisp[i]->chars[i].attr = ATTR_INVALID;
+    }
+    if (term->disptext) {
+       for (i = 0; i < term->rows; i++)
+           freeline(term->disptext[i]);
+    }
     sfree(term->disptext);
     term->disptext = newdisp;
-    term->dispcurs = NULL;
+    term->dispcursx = term->dispcursy = -1;
 
     /* Make a new alternate screen. */
     newalt = newtree234(NULL);
     for (i = 0; i < newrows; i++) {
-       line = snewn(newcols + 2, TTYPE);
-       line[0] = newcols;
-       for (j = 0; j < newcols; j++)
-           line[j + 1] = term->erase_char;
-        line[newcols + 1] = LATTR_NORM;
+       line = newline(term, newcols, TRUE);
        addpos234(newalt, line, i);
     }
     if (term->alt_screen) {
        while (NULL != (line = delpos234(term->alt_screen, 0)))
-           sfree(line);
+           freeline(line);
        freetree234(term->alt_screen);
     }
     term->alt_screen = newalt;
@@ -669,13 +1182,13 @@ static int find_last_nonempty_line(Terminal * term, tree234 * screen)
 {
     int i;
     for (i = count234(screen) - 1; i >= 0; i--) {
-       unsigned long *line = index234(screen, i);
+       termline *line = index234(screen, i);
        int j;
-       int cols = line[0];
-       for (j = 0; j < cols; j++) {
-           if (line[j + 1] != term->erase_char) break;
+       for (j = 0; j < line->cols; j++) {
+           if (line->chars[j].chr != term->erase_char.chr ||
+               line->chars[j].attr != term->erase_char.attr) break;
        }
-       if (j != cols) break;
+       if (j != line->cols) break;
     }
     return i;
 }
@@ -786,7 +1299,7 @@ static void check_selection(Terminal *term, pos from, pos to)
  */
 static void scroll(Terminal *term, int topline, int botline, int lines, int sb)
 {
-    unsigned long *line, *line2;
+    termline *line;
     int i, seltop, olddisptop, shift;
 
     if (topline != 0 || term->alt_which != 0)
@@ -797,10 +1310,10 @@ static void scroll(Terminal *term, int topline, int botline, int lines, int sb)
     if (lines < 0) {
        while (lines < 0) {
            line = delpos234(term->screen, botline);
-            line = resizeline(line, term->cols);
+            resizeline(term, line, term->cols);
            for (i = 0; i < term->cols; i++)
-               line[i + 1] = term->erase_char;
-           line[term->cols + 1] = 0;
+               line->chars[i] = term->erase_char;
+           line->lattr = LATTR_NORM;
            addpos234(term->screen, line, topline);
 
            if (term->selstart.y >= topline && term->selstart.y <= botline) {
@@ -827,19 +1340,21 @@ static void scroll(Terminal *term, int topline, int botline, int lines, int sb)
                int sblen = count234(term->scrollback);
                /*
                 * We must add this line to the scrollback. We'll
-                * remove a line from the top of the scrollback to
-                * replace it, or allocate a new one if the
-                * scrollback isn't full.
+                * remove a line from the top of the scrollback if
+                * the scrollback is full.
                 */
                if (sblen == term->savelines) {
-                   sblen--, line2 = delpos234(term->scrollback, 0);
-               } else {
-                   line2 = snewn(term->cols + 2, TTYPE);
-                   line2[0] = term->cols;
+                   unsigned char *cline;
+
+                   sblen--;
+                   cline = delpos234(term->scrollback, 0);
+                   sfree(cline);
+               } else
                    term->tempsblines += 1;
-               }
-               addpos234(term->scrollback, line, sblen);
-               line = line2;
+
+               addpos234(term->scrollback, compressline(line), sblen);
+
+               line = newline(term, term->cols, TRUE);
 
                /*
                 * If the user is currently looking at part of the
@@ -858,10 +1373,10 @@ static void scroll(Terminal *term, int topline, int botline, int lines, int sb)
                if (term->disptop > -term->savelines && term->disptop < 0)
                    term->disptop--;
            }
-            line = resizeline(line, term->cols);
+            resizeline(term, line, term->cols);
            for (i = 0; i < term->cols; i++)
-               line[i + 1] = term->erase_char;
-           line[term->cols + 1] = LATTR_NORM;
+               line->chars[i] = term->erase_char;
+           line->lattr = LATTR_NORM;
            addpos234(term->screen, line, botline);
 
            /*
@@ -948,26 +1463,34 @@ static void save_scroll(Terminal *term, int topline, int botline, int lines)
  */
 static void scroll_display(Terminal *term, int topline, int botline, int lines)
 {
-    unsigned long *start, *end;
-    int distance, size, i;
+    int distance, nlines, i;
 
-    start = term->disptext + topline * (term->cols + 1);
-    end = term->disptext + (botline + 1) * (term->cols + 1);
-    distance = (lines > 0 ? lines : -lines) * (term->cols + 1);
-    size = end - start - distance;
+    distance = lines > 0 ? lines : -lines;
+    nlines = botline - topline + 1 - distance;
     if (lines > 0) {
-       memmove(start, start + distance, size * TSIZE);
-       if (term->dispcurs >= start + distance &&
-           term->dispcurs <= start + distance + size)
-           term->dispcurs -= distance;
+       for (i = 0; i < nlines; i++)
+           memcpy(term->disptext[start+i]->chars,
+                  term->disptext[start+i+distance]->chars,
+                  term->cols * TSIZE);
+       if (term->dispcursy >= 0 &&
+           term->dispcursy >= topline + distance &&
+           term->dispcursy < topline + distance + nlines)
+           term->dispcursy -= distance;
        for (i = 0; i < distance; i++)
-           (start + size)[i] |= ATTR_INVALID;
+           for (j = 0; j < term->cols; j++)
+               term->disptext[start+nlines+i]->chars[j].attr |= ATTR_INVALID;
     } else {
-       memmove(start + distance, start, size * TSIZE);
-       if (term->dispcurs >= start && term->dispcurs <= start + size)
-           term->dispcurs += distance;
+       for (i = nlines; i-- ;)
+           memcpy(term->disptext[start+i+distance]->chars,
+                  term->disptext[start+i]->chars,
+                  term->cols * TSIZE);
+       if (term->dispcursy >= 0 &&
+           term->dispcursy >= topline &&
+           term->dispcursy < topline + nlines)
+           term->dispcursy += distance;
        for (i = 0; i < distance; i++)
-           start[i] |= ATTR_INVALID;
+           for (j = 0; j < term->cols; j++)
+               term->disptext[start+i]->chars[j].attr |= ATTR_INVALID;
     }
     save_scroll(term, topline, botline, lines);
 }
@@ -1037,10 +1560,7 @@ static void save_cursor(Terminal *term, int save)
        term->cset_attr[term->cset] = term->save_csattr;
        term->sco_acs = term->save_sco_acs;
        fix_cpos;
-       if (term->use_bce)
-           term->erase_char = (' ' | ATTR_ASCII |
-                               (term->curr_attr &
-                                (ATTR_FGMASK | ATTR_BGMASK)));
+       set_erase_char(term);
     }
 }
 
@@ -1065,19 +1585,19 @@ static void save_cursor(Terminal *term, int save)
  */
 static void check_boundary(Terminal *term, int x, int y)
 {
-    unsigned long *ldata;
+    termline *ldata;
 
     /* Validate input coordinates, just in case. */
     if (x == 0 || x > term->cols)
        return;
 
-    ldata = lineptr(y);
+    ldata = scrlineptr(y);
     if (x == term->cols) {
-       ldata[x] &= ~LATTR_WRAPPED2;
+       ldata->lattr &= ~LATTR_WRAPPED2;
     } else {
-       if ((ldata[x] & (CHAR_MASK | CSET_MASK)) == UCSWIDE) {
-           ldata[x-1] = ldata[x] =
-               (ldata[x-1] &~ (CHAR_MASK | CSET_MASK)) | ATTR_ASCII | ' ';
+       if (ldata->chars[x].chr == UCSWIDE) {
+           ldata->chars[x-1].chr = ' ' | CSET_ASCII;
+           ldata->chars[x] = ldata->chars[x-1];
        }
     }
 }
@@ -1139,18 +1659,19 @@ static void erase_lots(Terminal *term,
            scroll(term, 0, scrolllines - 1, scrolllines, TRUE);
        fix_cpos;
     } else {
-       unsigned long *ldata = lineptr(start.y);
+       termline *ldata = scrlineptr(start.y);
        while (poslt(start, end)) {
            if (start.x == term->cols) {
                if (!erase_lattr)
-                   ldata[start.x] &= ~(LATTR_WRAPPED | LATTR_WRAPPED2);
+                   ldata->lattr &= ~(LATTR_WRAPPED | LATTR_WRAPPED2);
                else
-                   ldata[start.x] = LATTR_NORM;
+                   ldata->lattr = LATTR_NORM;
            } else {
-               ldata[start.x] = term->erase_char;
+               ldata->chars[start.x] = term->erase_char;
+           }
+           if (incpos(start) && start.y < term->rows) {
+               ldata = scrlineptr(start.y);
            }
-           if (incpos(start) && start.y < term->rows)
-               ldata = lineptr(start.y);
        }
     }
 
@@ -1170,7 +1691,7 @@ static void insch(Terminal *term, int n)
     int dir = (n < 0 ? -1 : +1);
     int m;
     pos cursplus;
-    unsigned long *ldata;
+    termline *ldata;
 
     n = (n < 0 ? -n : n);
     if (n > term->cols - term->curs.x)
@@ -1182,15 +1703,17 @@ static void insch(Terminal *term, int n)
     check_boundary(term, term->curs.x, term->curs.y);
     if (dir < 0)
        check_boundary(term, term->curs.x + n, term->curs.y);
-    ldata = lineptr(term->curs.y);
+    ldata = scrlineptr(term->curs.y);
     if (dir < 0) {
-       memmove(ldata + term->curs.x, ldata + term->curs.x + n, m * TSIZE);
+       memmove(ldata->chars + term->curs.x, ldata->chars + term->curs.x + n,
+               m * TSIZE);
        while (n--)
-           ldata[term->curs.x + m++] = term->erase_char;
+           ldata->chars[term->curs.x + m++] = term->erase_char;
     } else {
-       memmove(ldata + term->curs.x + n, ldata + term->curs.x, m * TSIZE);
+       memmove(ldata->chars + term->curs.x + n, ldata->chars + term->curs.x,
+               m * TSIZE);
        while (n--)
-           ldata[term->curs.x + n] = term->erase_char;
+           ldata->chars[term->curs.x + n] = term->erase_char;
     }
 }
 
@@ -1412,7 +1935,8 @@ static void term_print_finish(Terminal *term)
  */
 void term_out(Terminal *term)
 {
-    int c, unget;
+    unsigned long c;
+    int unget;
     unsigned char localbuf[256], *chars;
     int nchars = 0;
 
@@ -1491,7 +2015,7 @@ void term_out(Terminal *term)
                        /* UTF-8 must be stateless so we ignore iso2022. */
                        if (term->ucsdata->unitab_ctrl[c] != 0xFF) 
                             c = term->ucsdata->unitab_ctrl[c];
-                       else c = ((unsigned char)c) | ATTR_ASCII;
+                       else c = ((unsigned char)c) | CSET_ASCII;
                        break;
                    } else if ((c & 0xe0) == 0xc0) {
                        term->utf_size = term->utf_state = 1;
@@ -1567,9 +2091,6 @@ void term_out(Terminal *term)
                    if (c == 0xFFFE || c == 0xFFFF)
                        c = UCSERR;
 
-                   /* Oops this is a 16bit implementation */
-                   if (c >= 0x10000)
-                       c = 0xFFFD;
                    break;
            }
            /* Are we in the nasty ACS mode? Note: no sco in utf mode. */
@@ -1577,7 +2098,7 @@ void term_out(Terminal *term)
                    (c!='\033' && c!='\012' && c!='\015' && c!='\b'))
            {
               if (term->sco_acs == 2) c |= 0x80;
-              c |= ATTR_SCOACS;
+              c |= CSET_SCOACS;
            } else {
                switch (term->cset_attr[term->cset]) {
                    /* 
@@ -1586,27 +2107,27 @@ void term_out(Terminal *term)
                     * range, make sure we use the same font as well as
                     * the same encoding.
                     */
-                 case ATTR_LINEDRW:
+                 case CSET_LINEDRW:
                    if (term->ucsdata->unitab_ctrl[c] != 0xFF)
                        c = term->ucsdata->unitab_ctrl[c];
                    else
-                       c = ((unsigned char) c) | ATTR_LINEDRW;
+                       c = ((unsigned char) c) | CSET_LINEDRW;
                    break;
 
-                 case ATTR_GBCHR:
+                 case CSET_GBCHR:
                    /* If UK-ASCII, make the '#' a LineDraw Pound */
                    if (c == '#') {
-                       c = '}' | ATTR_LINEDRW;
+                       c = '}' | CSET_LINEDRW;
                        break;
                    }
-                 /*FALLTHROUGH*/ case ATTR_ASCII:
+                 /*FALLTHROUGH*/ case CSET_ASCII:
                    if (term->ucsdata->unitab_ctrl[c] != 0xFF)
                        c = term->ucsdata->unitab_ctrl[c];
                    else
-                       c = ((unsigned char) c) | ATTR_ASCII;
+                       c = ((unsigned char) c) | CSET_ASCII;
                    break;
-               case ATTR_SCOACS:
-                   if (c>=' ') c = ((unsigned char)c) | ATTR_SCOACS;
+               case CSET_SCOACS:
+                   if (c>=' ') c = ((unsigned char)c) | CSET_SCOACS;
                    break;
                }
            }
@@ -1626,11 +2147,14 @@ void term_out(Terminal *term)
                term->curs.x--;
            term->wrapnext = FALSE;
            fix_cpos;
-           if (!term->cfg.no_dbackspace)    /* destructive bksp might be disabled */
-               *term->cpos = (' ' | term->curr_attr | ATTR_ASCII);
+           /* destructive backspace might be disabled */
+           if (!term->cfg.no_dbackspace) {
+               term->cpos->chr = ' ' | CSET_ASCII;
+               term->cpos->attr = term->curr_attr;
+           }
        } else
            /* Or normal C0 controls. */
-       if ((c & -32) == 0 && term->termstate < DO_CTRLS) {
+       if ((c & ~0x1F) == 0 && term->termstate < DO_CTRLS) {
            switch (c) {
              case '\005':             /* ENQ: terminal type query */
                /* Strictly speaking this is VT100 but a VT100 defaults to
@@ -1797,14 +2321,14 @@ void term_out(Terminal *term)
              case '\t':              /* HT: Character tabulation */
                {
                    pos old_curs = term->curs;
-                   unsigned long *ldata = lineptr(term->curs.y);
+                   termline *ldata = scrlineptr(term->curs.y);
 
                    do {
                        term->curs.x++;
                    } while (term->curs.x < term->cols - 1 &&
                             !term->tabs[term->curs.x]);
 
-                   if ((ldata[term->cols] & LATTR_MODE) != LATTR_NORM) {
+                   if ((ldata->lattr & LATTR_MODE) != LATTR_NORM) {
                        if (term->curs.x >= term->cols / 2)
                            term->curs.x = term->cols / 2 - 1;
                    } else {
@@ -1824,7 +2348,7 @@ void term_out(Terminal *term)
                /* Only graphic characters get this far;
                 * ctrls are stripped above */
                if (term->wrapnext && term->wrap) {
-                   term->cpos[1] |= LATTR_WRAPPED;
+                   scrlineptr(term->curs.y)->lattr |= LATTR_WRAPPED;
                    if (term->curs.y == term->marg_b)
                        scroll(term, term->marg_t, term->marg_b, 1, TRUE);
                    else if (term->curs.y < term->rows - 1)
@@ -1840,7 +2364,7 @@ void term_out(Terminal *term)
                    incpos(cursplus);
                    check_selection(term, term->curs, cursplus);
                }
-               if (((c & CSET_MASK) == ATTR_ASCII || (c & CSET_MASK) == 0) &&
+               if (((c & CSET_MASK) == CSET_ASCII || (c & CSET_MASK) == 0) &&
                    term->logctx)
                    logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
                {
@@ -1873,8 +2397,10 @@ void term_out(Terminal *term)
                        check_boundary(term, term->curs.x, term->curs.y);
                        check_boundary(term, term->curs.x+2, term->curs.y);
                        if (term->curs.x == term->cols-1) {
-                           *term->cpos++ = ATTR_ASCII | ' ' | term->curr_attr;
-                           *term->cpos |= LATTR_WRAPPED | LATTR_WRAPPED2;
+                           term->cpos->chr = CSET_ASCII | ' ';
+                           term->cpos->attr = term->curr_attr;
+                           scrlineptr(term->curs.y)->lattr |=
+                               LATTR_WRAPPED | LATTR_WRAPPED2;
                            if (term->curs.y == term->marg_b)
                                scroll(term, term->marg_t, term->marg_b,
                                       1, TRUE);
@@ -1886,14 +2412,19 @@ void term_out(Terminal *term)
                            check_boundary(term, term->curs.x, term->curs.y);
                            check_boundary(term, term->curs.x+2, term->curs.y);
                        }
-                       *term->cpos++ = c | term->curr_attr;
-                       *term->cpos++ = UCSWIDE | term->curr_attr;
+                       term->cpos->chr = c;
+                       term->cpos->attr = term->curr_attr;
+                       term->cpos++;
+                       term->cpos->chr = UCSWIDE;
+                       term->cpos->attr = term->curr_attr;
                        term->curs.x++;
                        break;
                      case 1:
                        check_boundary(term, term->curs.x, term->curs.y);
                        check_boundary(term, term->curs.x+1, term->curs.y);
-                       *term->cpos++ = c | term->curr_attr;
+                       term->cpos->chr = c;
+                       term->cpos->attr = term->curr_attr;
+                       term->cpos++;
                        break;
                      default:
                        continue;
@@ -1905,7 +2436,7 @@ void term_out(Terminal *term)
                    term->curs.x--;
                    term->wrapnext = TRUE;
                    if (term->wrap && term->vt52_mode) {
-                       term->cpos[1] |= LATTR_WRAPPED;
+                       scrlineptr(term->curs.y)->lattr |= LATTR_WRAPPED;
                        if (term->curs.y == term->marg_b)
                            scroll(term, term->marg_t, term->marg_b, 1, TRUE);
                        else if (term->curs.y < term->rows - 1)
@@ -2028,15 +2559,17 @@ void term_out(Terminal *term)
                  case ANSI('8', '#'):  /* DECALN: fills screen with Es :-) */
                    compatibility(VT100);
                    {
-                       unsigned long *ldata;
+                       termline *ldata;
                        int i, j;
                        pos scrtop, scrbot;
 
                        for (i = 0; i < term->rows; i++) {
-                           ldata = lineptr(i);
-                           for (j = 0; j < term->cols; j++)
-                               ldata[j] = ATTR_DEFAULT | 'E';
-                           ldata[term->cols] = 0;
+                           ldata = scrlineptr(i);
+                           for (j = 0; j < term->cols; j++) {
+                               ldata->chars[j] = term->basic_erase_char;
+                               ldata->chars[j].chr = 'E';
+                           }
+                           ldata->lattr = LATTR_NORM;
                        }
                        term->disptop = 0;
                        term->seen_disp_event = TRUE;
@@ -2053,8 +2586,8 @@ void term_out(Terminal *term)
                  case ANSI('6', '#'):
                    compatibility(VT100);
                    {
-                       unsigned long nlattr;
-                       unsigned long *ldata;
+                       int nlattr;
+
                        switch (ANSI(c, term->esc_query)) {
                          case ANSI('3', '#'): /* DECDHL: 2*height, top */
                            nlattr = LATTR_TOP;
@@ -2069,52 +2602,50 @@ void term_out(Terminal *term)
                            nlattr = LATTR_WIDE;
                            break;
                        }
-                       ldata = lineptr(term->curs.y);
-                       ldata[term->cols] &= ~LATTR_MODE;
-                       ldata[term->cols] |= nlattr;
+                       scrlineptr(term->curs.y)->lattr = nlattr;
                    }
                    break;
                  /* GZD4: G0 designate 94-set */
                  case ANSI('A', '('):
                    compatibility(VT100);
                    if (!term->cfg.no_remote_charset)
-                       term->cset_attr[0] = ATTR_GBCHR;
+                       term->cset_attr[0] = CSET_GBCHR;
                    break;
                  case ANSI('B', '('):
                    compatibility(VT100);
                    if (!term->cfg.no_remote_charset)
-                       term->cset_attr[0] = ATTR_ASCII;
+                       term->cset_attr[0] = CSET_ASCII;
                    break;
                  case ANSI('0', '('):
                    compatibility(VT100);
                    if (!term->cfg.no_remote_charset)
-                       term->cset_attr[0] = ATTR_LINEDRW;
+                       term->cset_attr[0] = CSET_LINEDRW;
                    break;
                  case ANSI('U', '('): 
                    compatibility(OTHER);
                    if (!term->cfg.no_remote_charset)
-                       term->cset_attr[0] = ATTR_SCOACS; 
+                       term->cset_attr[0] = CSET_SCOACS; 
                    break;
                  /* G1D4: G1-designate 94-set */
                  case ANSI('A', ')'):
                    compatibility(VT100);
                    if (!term->cfg.no_remote_charset)
-                       term->cset_attr[1] = ATTR_GBCHR;
+                       term->cset_attr[1] = CSET_GBCHR;
                    break;
                  case ANSI('B', ')'):
                    compatibility(VT100);
                    if (!term->cfg.no_remote_charset)
-                       term->cset_attr[1] = ATTR_ASCII;
+                       term->cset_attr[1] = CSET_ASCII;
                    break;
                  case ANSI('0', ')'):
                    compatibility(VT100);
                    if (!term->cfg.no_remote_charset)
-                       term->cset_attr[1] = ATTR_LINEDRW;
+                       term->cset_attr[1] = CSET_LINEDRW;
                    break;
                  case ANSI('U', ')'): 
                    compatibility(OTHER);
                    if (!term->cfg.no_remote_charset)
-                       term->cset_attr[1] = ATTR_SCOACS; 
+                       term->cset_attr[1] = CSET_SCOACS; 
                    break;
                  /* DOCS: Designate other coding system */
                  case ANSI('8', '%'):  /* Old Linux code */
@@ -2522,11 +3053,7 @@ void term_out(Terminal *term)
                                    break;
                                }
                            }
-                           if (term->use_bce)
-                               term->erase_char = (' ' | ATTR_ASCII |
-                                                   (term->curr_attr & 
-                                                    (ATTR_FGMASK |
-                                                     ATTR_BGMASK)));
+                           set_erase_char(term);
                        }
                        break;
                      case 's':       /* save cursor */
@@ -2725,7 +3252,7 @@ void term_out(Terminal *term)
                        {
                            int n = def(term->esc_args[0], 1);
                            pos cursplus;
-                           unsigned long *p = term->cpos;
+                           termchar *p = term->cpos;
                            if (n > term->cols - term->curs.x)
                                n = term->cols - term->curs.x;
                            cursplus = term->curs;
@@ -2838,11 +3365,7 @@ void term_out(Terminal *term)
                      case ANSI('L', '='):
                        compatibility(SCOANSI);
                        term->use_bce = (term->esc_args[0] <= 0);
-                       term->erase_char = ERASE_CHAR;
-                       if (term->use_bce)
-                           term->erase_char = (' ' | ATTR_ASCII |
-                                               (term->curr_attr & 
-                                                (ATTR_FGMASK | ATTR_BGMASK)));
+                       set_erase_char(term);
                        break;
                      case ANSI('p', '"'): /* DECSCL: set compat level */
                        /*
@@ -2994,17 +3517,17 @@ void term_out(Terminal *term)
                } else if (c == '\033')
                    term->termstate = OSC_MAYBE_ST;
                else if (term->osc_strlen < OSC_STR_MAX)
-                   term->osc_string[term->osc_strlen++] = c;
+                   term->osc_string[term->osc_strlen++] = (char)c;
                break;
              case SEEN_OSC_P:
                {
                    int max = (term->osc_strlen == 0 ? 21 : 16);
                    int val;
-                   if (c >= '0' && c <= '9')
+                   if ((int)c >= '0' && (int)c <= '9')
                        val = c - '0';
-                   else if (c >= 'A' && c <= 'A' + max - 10)
+                   else if ((int)c >= 'A' && (int)c <= 'A' + max - 10)
                        val = c - 'A' + 10;
-                   else if (c >= 'a' && c <= 'a' + max - 10)
+                   else if ((int)c >= 'a' && (int)c <= 'a' + max - 10)
                        val = c - 'a' + 10;
                    else {
                        term->termstate = TOPLEVEL;
@@ -3097,10 +3620,10 @@ void term_out(Terminal *term)
                     *
                     */
                  case 'F':
-                   term->cset_attr[term->cset = 0] = ATTR_LINEDRW;
+                   term->cset_attr[term->cset = 0] = CSET_LINEDRW;
                    break;
                  case 'G':
-                   term->cset_attr[term->cset = 0] = ATTR_ASCII;
+                   term->cset_attr[term->cset = 0] = CSET_ASCII;
                    break;
                  case 'H':
                    move(term, 0, 0, 0);
@@ -3236,10 +3759,7 @@ void term_out(Terminal *term)
                    /* compatibility(OTHER) */
                    term->vt52_bold = FALSE;
                    term->curr_attr = ATTR_DEFAULT;
-                   if (term->use_bce)
-                       term->erase_char = (' ' | ATTR_ASCII |
-                                           (term->curr_attr & 
-                                            (ATTR_FGMASK | ATTR_BGMASK)));
+                   set_erase_char(term);
                    break;
                  case 'S':
                    /* compatibility(VI50) */
@@ -3280,10 +3800,7 @@ void term_out(Terminal *term)
                if ((c & 0x8) || term->vt52_bold)
                    term->curr_attr |= ATTR_BOLD;
 
-               if (term->use_bce)
-                   term->erase_char = (' ' | ATTR_ASCII |
-                                       (term->curr_attr &
-                                        (ATTR_FGMASK | ATTR_BGMASK)));
+               set_erase_char(term);
                break;
              case VT52_BG:
                term->termstate = TOPLEVEL;
@@ -3295,10 +3812,7 @@ void term_out(Terminal *term)
                if (c & 0x8)
                    term->curr_attr |= ATTR_BLINK;
 
-               if (term->use_bce)
-                   term->erase_char = (' ' | ATTR_ASCII |
-                                       (term->curr_attr &
-                                        (ATTR_FGMASK | ATTR_BGMASK)));
+               set_erase_char(term);
                break;
 #endif
              default: break;          /* placate gcc warning about enum use */
@@ -3314,29 +3828,13 @@ void term_out(Terminal *term)
     logflush(term->logctx);
 }
 
-#if 0
-/*
- * Compare two lines to determine whether they are sufficiently
- * alike to scroll-optimise one to the other. Return the degree of
- * similarity.
- */
-static int linecmp(Terminal *term, unsigned long *a, unsigned long *b)
-{
-    int i, n;
-
-    for (i = n = 0; i < term->cols; i++)
-       n += (*a++ == *b++);
-    return n;
-}
-#endif
-
 /*
  * To prevent having to run the reasonably tricky bidi algorithm
  * too many times, we maintain a cache of the last lineful of data
  * fed to the algorithm on each line of the display.
  */
 static int term_bidi_cache_hit(Terminal *term, int line,
-                              unsigned long *lbefore, int width)
+                              termchar *lbefore, int width)
 {
     if (!term->pre_bidi_cache)
        return FALSE;                  /* cache doesn't even exist yet! */
@@ -3347,26 +3845,24 @@ static int term_bidi_cache_hit(Terminal *term, int line,
     if (!term->pre_bidi_cache[line])
        return FALSE;                  /* cache doesn't contain _this_ line */
 
-    if (!memcmp(term->pre_bidi_cache[line], lbefore,
-               width * sizeof(unsigned long)))
+    if (!memcmp(term->pre_bidi_cache[line], lbefore, width * TSIZE))
        return TRUE;                   /* aha! the line matches the cache */
 
     return FALSE;                     /* it didn't match. */
 }
 
-static void term_bidi_cache_store(Terminal *term, int line,
-                                 unsigned long *lbefore,
-                                 unsigned long *lafter, int width)
+static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore,
+                                 termchar *lafter, int width)
 {
     if (!term->pre_bidi_cache || term->bidi_cache_size <= line) {
        int j = term->bidi_cache_size;
        term->bidi_cache_size = line+1;
        term->pre_bidi_cache = sresize(term->pre_bidi_cache,
                                       term->bidi_cache_size,
-                                      unsigned long *);
+                                      termchar *);
        term->post_bidi_cache = sresize(term->post_bidi_cache,
                                        term->bidi_cache_size,
-                                       unsigned long *);
+                                       termchar *);
        while (j < term->bidi_cache_size) {
            term->pre_bidi_cache[j] = term->post_bidi_cache[j] = NULL;
            j++;
@@ -3376,11 +3872,11 @@ static void term_bidi_cache_store(Terminal *term, int line,
     sfree(term->pre_bidi_cache[line]);
     sfree(term->post_bidi_cache[line]);
 
-    term->pre_bidi_cache[line] = snewn(width, unsigned long);
-    term->post_bidi_cache[line] = snewn(width, unsigned long);
+    term->pre_bidi_cache[line] = snewn(width, termchar);
+    term->post_bidi_cache[line] = snewn(width, termchar);
 
-    memcpy(term->pre_bidi_cache[line], lbefore, width * sizeof(unsigned long));
-    memcpy(term->post_bidi_cache[line], lafter, width * sizeof(unsigned long));
+    memcpy(term->pre_bidi_cache[line], lbefore, width * TSIZE);
+    memcpy(term->post_bidi_cache[line], lafter, width * TSIZE);
 }
 
 /*
@@ -3390,15 +3886,17 @@ static void term_bidi_cache_store(Terminal *term, int line,
 static void do_paint(Terminal *term, Context ctx, int may_optimise)
 {
     int i, it, j, our_curs_y, our_curs_x;
-    unsigned long rv, cursor;
+    int rv, cursor;
     pos scrpos;
-    char ch[1024];
-    long cursor_background = ERASE_CHAR;
+    wchar_t ch[1024];
+    termchar cursor_background;
     unsigned long ticks;
 #ifdef OPTIMISE_SCROLL
     struct scrollregion *sr;
 #endif /* OPTIMISE_SCROLL */
 
+    cursor_background = term->basic_erase_char;
+
     /*
      * Check the visual bell state.
      */
@@ -3439,26 +3937,35 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
         * to display the cursor covering the _whole_ character,
         * exactly as if it were one space to the left.
         */
-       unsigned long *ldata = lineptr(term->curs.y);
+       termline *ldata = lineptr(term->curs.y);
        our_curs_x = term->curs.x;
        if (our_curs_x > 0 &&
-           (ldata[our_curs_x] & (CHAR_MASK | CSET_MASK)) == UCSWIDE)
+           ldata->chars[our_curs_x].chr == UCSWIDE)
            our_curs_x--;
+       unlineptr(ldata);
     }
 
-    if (term->dispcurs && (term->curstype != cursor ||
-                          term->dispcurs !=
-                          term->disptext + our_curs_y * (term->cols + 1) +
-                          our_curs_x)) {
-       if (term->dispcurs > term->disptext && 
-           (*term->dispcurs & (CHAR_MASK | CSET_MASK)) == UCSWIDE)
-           term->dispcurs[-1] |= ATTR_INVALID;
-       if ( (term->dispcurs[1] & (CHAR_MASK | CSET_MASK)) == UCSWIDE)
-           term->dispcurs[1] |= ATTR_INVALID;
-       *term->dispcurs |= ATTR_INVALID;
+    /*
+     * If the cursor is not where it was last time we painted, and
+     * its previous position is visible on screen, invalidate its
+     * previous position.
+     */
+    if (term->dispcursy >= 0 &&
+       (term->curstype != cursor ||
+        term->dispcursy != our_curs_y ||
+        term->dispcursx != our_curs_x)) {
+       termchar *dispcurs = term->disptext[term->dispcursy]->chars +
+           term->dispcursx;
+
+       if (term->dispcursx > 0 && dispcurs->chr == UCSWIDE)
+           dispcurs[-1].attr |= ATTR_INVALID;
+       if (term->dispcursx < term->cols-1 && dispcurs[1].chr == UCSWIDE)
+           dispcurs[1].attr |= ATTR_INVALID;
+       dispcurs->attr |= ATTR_INVALID;
+
        term->curstype = 0;
     }
-    term->dispcurs = NULL;
+    term->dispcursx = term->dispcursy = -1;
 
 #ifdef OPTIMISE_SCROLL
     /* Do scrolls */
@@ -3474,10 +3981,10 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
 
     /* The normal screen data */
     for (i = 0; i < term->rows; i++) {
-       unsigned long *ldata;
-       int lattr;
-       int idx, dirty_line, dirty_run, selected;
-       unsigned long attr = 0;
+       termline *ldata;
+       termchar *lchars;
+       int dirty_line, dirty_run, selected;
+       unsigned long attr = 0, cset = 0;
        int updated_line = 0;
        int start = 0;
        int ccount = 0;
@@ -3485,45 +3992,44 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
 
        scrpos.y = i + term->disptop;
        ldata = lineptr(scrpos.y);
-       lattr = (ldata[term->cols] & LATTR_MODE);
 
-       idx = i * (term->cols + 1);
-       dirty_run = dirty_line = (ldata[term->cols] !=
-                                 term->disptext[idx + term->cols]);
-       term->disptext[idx + term->cols] = ldata[term->cols];
+       dirty_run = dirty_line = (ldata->lattr !=
+                                 term->disptext[i]->lattr);
+       term->disptext[i]->lattr = ldata->lattr;
 
        /* Do Arabic shaping and bidi. */
        if(!term->cfg.bidi || !term->cfg.arabicshaping) {
 
-           if (!term_bidi_cache_hit(term, i, ldata, term->cols)) {
+           if (!term_bidi_cache_hit(term, i, ldata->chars, term->cols)) {
 
                for(it=0; it<term->cols ; it++)
                {
-                   int uc = (ldata[it] & 0xFFFF);
+                   unsigned long uc = (ldata->chars[it].chr);
 
                    switch (uc & CSET_MASK) {
-                     case ATTR_LINEDRW:
+                     case CSET_LINEDRW:
                        if (!term->cfg.rawcnp) {
                            uc = term->ucsdata->unitab_xterm[uc & 0xFF];
                            break;
                        }
-                     case ATTR_ASCII:
+                     case CSET_ASCII:
                        uc = term->ucsdata->unitab_line[uc & 0xFF];
                        break;
-                     case ATTR_SCOACS:
+                     case CSET_SCOACS:
                        uc = term->ucsdata->unitab_scoacs[uc&0xFF];
                        break;
                    }
                    switch (uc & CSET_MASK) {
-                     case ATTR_ACP:
+                     case CSET_ACP:
                        uc = term->ucsdata->unitab_font[uc & 0xFF];
                        break;
-                     case ATTR_OEMCP:
+                     case CSET_OEMCP:
                        uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
                        break;
                    }
 
-                   term->wcFrom[it].origwc = term->wcFrom[it].wc = uc;
+                   term->wcFrom[it].origwc = term->wcFrom[it].wc =
+                       (wchar_t)uc;
                    term->wcFrom[it].index = it;
                }
 
@@ -3540,42 +4046,41 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
 
                for(it=0; it<term->cols ; it++)
                {
-                   term->ltemp[it] = ldata[term->wcTo[it].index];
+                   term->ltemp[it] = ldata->chars[term->wcTo[it].index];
 
                    if (term->wcTo[it].origwc != term->wcTo[it].wc)
-                       term->ltemp[it] = ((term->ltemp[it] & 0xFFFF0000) |
-                                          term->wcTo[it].wc);
+                       term->ltemp[it].chr = term->wcTo[it].wc;
                }
-               term_bidi_cache_store(term, i, ldata, term->ltemp, term->cols);
-               ldata = term->ltemp;
+               term_bidi_cache_store(term, i, ldata->chars,
+                                     term->ltemp, term->cols);
+               lchars = term->ltemp;
            } else {
-               ldata = term->post_bidi_cache[i];
+               lchars = term->post_bidi_cache[i];
            }
-       }
+       } else
+           lchars = ldata->chars;
 
-       for (j = 0; j < term->cols; j++, idx++) {
+       for (j = 0; j < term->cols; j++) {
            unsigned long tattr, tchar;
-           unsigned long *d = ldata + j;
+           termchar *d = lchars + j;
            int break_run;
            scrpos.x = j;
 
-           tchar = (*d & (CHAR_MASK | CSET_MASK));
-           tattr = (*d & (ATTR_MASK ^ CSET_MASK));
+           tchar = d->chr;
+           tattr = d->attr;
+
            switch (tchar & CSET_MASK) {
-             case ATTR_ASCII:
+             case CSET_ASCII:
                tchar = term->ucsdata->unitab_line[tchar & 0xFF];
                break;
-             case ATTR_LINEDRW:
+             case CSET_LINEDRW:
                tchar = term->ucsdata->unitab_xterm[tchar & 0xFF];
                break;
-             case ATTR_SCOACS:  
+             case CSET_SCOACS:  
                tchar = term->ucsdata->unitab_scoacs[tchar&0xFF]; 
                break;
            }
-           tattr |= (tchar & CSET_MASK);
-           tchar &= CHAR_MASK;
-           if (j < term->cols-1 &&
-               (d[1] & (CHAR_MASK | CSET_MASK)) == UCSWIDE)
+           if (j < term->cols-1 && d[1].chr == UCSWIDE)
                tattr |= ATTR_WIDE;
 
            /* Video reversing things */
@@ -3603,31 +4108,42 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
             * Check the font we'll _probably_ be using to see if 
             * the character is wide when we don't want it to be.
             */
-           if ((tchar | tattr) != (term->disptext[idx]& ~ATTR_NARROW)) {
-               if ((tattr & ATTR_WIDE) == 0 && 
-                   char_width(ctx, (tchar | tattr) & 0xFFFF) == 2)
+           if (tchar != term->disptext[i]->chars[j].chr ||
+               tattr != (term->disptext[i]->chars[j].attr &~
+                         ATTR_NARROW)) {
+               if ((tattr & ATTR_WIDE) == 0 && char_width(ctx, tchar) == 2)
                    tattr |= ATTR_NARROW;
-           } else if (term->disptext[idx]&ATTR_NARROW)
+           } else if (term->disptext[i]->chars[j].attr & ATTR_NARROW)
                tattr |= ATTR_NARROW;
 
            /* Cursor here ? Save the 'background' */
            if (i == our_curs_y && j == our_curs_x) {
-               cursor_background = tattr | tchar;
-               term->dispcurs = term->disptext + idx;
+               cursor_background.chr = tchar;
+               cursor_background.attr = tattr;
+               term->dispcursx = j;
+               term->dispcursy = i;
            }
 
-           if ((term->disptext[idx] ^ tattr) & ATTR_WIDE)
+           if ((term->disptext[i]->chars[j].attr ^ tattr) & ATTR_WIDE)
                dirty_line = TRUE;
 
            break_run = (((tattr ^ attr) & term->attr_mask) ||
                j - start >= sizeof(ch));
 
            /* Special hack for VT100 Linedraw glyphs */
-           if ((attr & CSET_MASK) == 0x2300 && tchar >= 0xBA
-               && tchar <= 0xBD) break_run = TRUE;
+           if (tchar >= 0x23BA && tchar <= 0x23BD)
+               break_run = TRUE;
+
+           /*
+            * Separate out sequences of characters that have the
+            * same CSET, if that CSET is a magic one.
+            */
+           if (CSET_OF(tchar) != cset)
+               break_run = TRUE;
 
            if (!term->ucsdata->dbcs_screenfont && !dirty_line) {
-               if ((tchar | tattr) == term->disptext[idx])
+               if (term->disptext[i]->chars[j].chr == tchar &&
+                   term->disptext[i]->chars[j].attr == tattr)
                    break_run = TRUE;
                else if (!dirty_run && ccount == 1)
                    break_run = TRUE;
@@ -3635,26 +4151,28 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
 
            if (break_run) {
                if ((dirty_run || last_run_dirty) && ccount > 0) {
-                   do_text(ctx, start, i, ch, ccount, attr, lattr);
+                   do_text(ctx, start, i, ch, ccount, attr, ldata->lattr);
                    updated_line = 1;
                }
                start = j;
                ccount = 0;
                attr = tattr;
+               cset = CSET_OF(tchar);
                if (term->ucsdata->dbcs_screenfont)
                    last_run_dirty = dirty_run;
                dirty_run = dirty_line;
            }
 
-           if ((tchar | tattr) != term->disptext[idx])
+           if (term->disptext[i]->chars[j].chr != tchar ||
+               term->disptext[i]->chars[j].attr != tattr)
                dirty_run = TRUE;
-           ch[ccount++] = (char) tchar;
-           term->disptext[idx] = tchar | tattr;
+           ch[ccount++] = (wchar_t) tchar;
+           term->disptext[i]->chars[j].chr = tchar;
+           term->disptext[i]->chars[j].attr = tattr;
 
            /* If it's a wide char step along to the next one. */
            if (tattr & ATTR_WIDE) {
                if (++j < term->cols) {
-                   idx++;
                    d++;
                    /*
                     * By construction above, the cursor should not
@@ -3662,24 +4180,27 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
                     * Ever.
                     */
                    assert(!(i == our_curs_y && j == our_curs_x));
-                   if (term->disptext[idx] != *d)
+                   if (memcmp(&term->disptext[i]->chars[j],
+                              d, sizeof(*d)))
                        dirty_run = TRUE;
-                   term->disptext[idx] = *d;
+                   term->disptext[i]->chars[j] = *d;
                }
            }
        }
        if (dirty_run && ccount > 0) {
-           do_text(ctx, start, i, ch, ccount, attr, lattr);
+           do_text(ctx, start, i, ch, ccount, attr, ldata->lattr);
            updated_line = 1;
        }
 
        /* Cursor on this line ? (and changed) */
        if (i == our_curs_y && (term->curstype != cursor || updated_line)) {
-           ch[0] = (char) (cursor_background & CHAR_MASK);
-           attr = (cursor_background & ATTR_MASK) | cursor;
-           do_cursor(ctx, our_curs_x, i, ch, 1, attr, lattr);
+           ch[0] = (wchar_t) cursor_background.chr;
+           attr = cursor_background.attr | cursor;
+           do_cursor(ctx, our_curs_x, i, ch, 1, attr, ldata->lattr);
            term->curstype = cursor;
        }
+
+       unlineptr(ldata);
     }
 }
 
@@ -3721,10 +4242,11 @@ void term_blink(Terminal *term, int flg)
  */
 void term_invalidate(Terminal *term)
 {
-    int i;
+    int i, j;
 
-    for (i = 0; i < term->rows * (term->cols + 1); i++)
-       term->disptext[i] = ATTR_INVALID;
+    for (i = 0; i < term->rows; i++)
+       for (j = 0; j < term->cols; j++)
+           term->disptext[i]->chars[j].attr = ATTR_INVALID;
 }
 
 /*
@@ -3740,13 +4262,12 @@ void term_paint(Terminal *term, Context ctx,
     if (bottom >= term->rows) bottom = term->rows-1;
 
     for (i = top; i <= bottom && i < term->rows; i++) {
-       if ((term->disptext[i * (term->cols + 1) + term->cols] &
-            LATTR_MODE) == LATTR_NORM)
+       if (term->disptext[i]->lattr == LATTR_NORM)
            for (j = left; j <= right && j < term->cols; j++)
-               term->disptext[i * (term->cols + 1) + j] = ATTR_INVALID;
+               term->disptext[i]->chars[j].attr = ATTR_INVALID;
        else
            for (j = left / 2; j <= right / 2 + 1 && j < term->cols; j++)
-               term->disptext[i * (term->cols + 1) + j] = ATTR_INVALID;
+               term->disptext[i]->chars[j].attr = ATTR_INVALID;
     }
 
     /* This should happen soon enough, also for some reason it sometimes 
@@ -3801,7 +4322,7 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel)
 
     while (poslt(top, bottom)) {
        int nl = FALSE;
-       unsigned long *ldata = lineptr(top.y);
+       termline *ldata = lineptr(top.y);
        pos nlpos;
 
        /*
@@ -3818,15 +4339,13 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel)
         * because in normal selection mode this means we need a
         * newline at the end)...
         */
-       if (!(ldata[term->cols] & LATTR_WRAPPED)) {
-           while (((ldata[nlpos.x - 1] & 0xFF) == 0x20 ||
-                   (DIRECT_CHAR(ldata[nlpos.x - 1]) &&
-                    (ldata[nlpos.x - 1] & CHAR_MASK) == 0x20))
-                  && poslt(top, nlpos))
+       if (!(ldata->lattr & LATTR_WRAPPED)) {
+           while (IS_SPACE_CHR(ldata->chars[nlpos.x - 1].chr) &&
+                  poslt(top, nlpos))
                decpos(nlpos);
            if (poslt(nlpos, bottom))
                nl = TRUE;
-       } else if (ldata[term->cols] & LATTR_WRAPPED2) {
+       } else if (ldata->lattr & LATTR_WRAPPED2) {
            /* Ignore the last char on the line in a WRAPPED2 line. */
            decpos(nlpos);
        }
@@ -3850,7 +4369,7 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel)
            sprintf(cbuf, "<U+%04x>", (ldata[top.x] & 0xFFFF));
 #else
            wchar_t cbuf[16], *p;
-           int uc = (ldata[top.x] & 0xFFFF);
+           int uc = ldata->chars[top.x].chr;
            int set, c;
 
            if (uc == UCSWIDE) {
@@ -3859,29 +4378,29 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel)
            }
 
            switch (uc & CSET_MASK) {
-             case ATTR_LINEDRW:
+             case CSET_LINEDRW:
                if (!term->cfg.rawcnp) {
                    uc = term->ucsdata->unitab_xterm[uc & 0xFF];
                    break;
                }
-             case ATTR_ASCII:
+             case CSET_ASCII:
                uc = term->ucsdata->unitab_line[uc & 0xFF];
                break;
-             case ATTR_SCOACS:  
+             case CSET_SCOACS:  
                uc = term->ucsdata->unitab_scoacs[uc&0xFF]; 
                break;
            }
            switch (uc & CSET_MASK) {
-             case ATTR_ACP:
+             case CSET_ACP:
                uc = term->ucsdata->unitab_font[uc & 0xFF];
                break;
-             case ATTR_OEMCP:
+             case CSET_OEMCP:
                uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
                break;
            }
 
            set = (uc & CSET_MASK);
-           c = (uc & CHAR_MASK);
+           c = (uc & ~CSET_MASK);
            cbuf[0] = uc;
            cbuf[1] = 0;
 
@@ -3892,7 +4411,7 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel)
                    int rv;
                    if (is_dbcs_leadbyte(term->ucsdata->font_codepage, (BYTE) c)) {
                        buf[0] = c;
-                       buf[1] = (char) (0xFF & ldata[top.x + 1]);
+                       buf[1] = (char) (0xFF & ldata->chars[top.x + 1].chr);
                        rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 2, wbuf, 4);
                        top.x++;
                    } else {
@@ -3929,6 +4448,8 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel)
        }
        top.y++;
        top.x = rect ? old_top_x : 0;
+
+       unlineptr(ldata);
     }
 #if SELECTION_NUL_TERMINATED
     wblen++;
@@ -4026,29 +4547,27 @@ static int wordtype(Terminal *term, int uc)
     };
     const struct ucsword *wptr;
 
-    uc &= (CSET_MASK | CHAR_MASK);
-
     switch (uc & CSET_MASK) {
-      case ATTR_LINEDRW:
+      case CSET_LINEDRW:
        uc = term->ucsdata->unitab_xterm[uc & 0xFF];
        break;
-      case ATTR_ASCII:
+      case CSET_ASCII:
        uc = term->ucsdata->unitab_line[uc & 0xFF];
        break;
-      case ATTR_SCOACS:  
+      case CSET_SCOACS:  
        uc = term->ucsdata->unitab_scoacs[uc&0xFF]; 
        break;
     }
     switch (uc & CSET_MASK) {
-      case ATTR_ACP:
+      case CSET_ACP:
        uc = term->ucsdata->unitab_font[uc & 0xFF];
        break;
-      case ATTR_OEMCP:
+      case CSET_OEMCP:
        uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
        break;
     }
 
-    /* For DBCS font's I can't do anything usefull. Even this will sometimes
+    /* For DBCS fonts I can't do anything useful. Even this will sometimes
      * fail as there's such a thing as a double width space. :-(
      */
     if (term->ucsdata->dbcs_screenfont &&
@@ -4071,7 +4590,7 @@ static int wordtype(Terminal *term, int uc)
  */
 static pos sel_spread_half(Terminal *term, pos p, int dir)
 {
-    unsigned long *ldata;
+    termline *ldata;
     short wvalue;
     int topy = -sblines(term);
 
@@ -4083,14 +4602,14 @@ static pos sel_spread_half(Terminal *term, pos p, int dir)
         * In this mode, every character is a separate unit, except
         * for runs of spaces at the end of a non-wrapping line.
         */
-       if (!(ldata[term->cols] & LATTR_WRAPPED)) {
-           unsigned long *q = ldata + term->cols;
-           while (q > ldata && (q[-1] & CHAR_MASK) == 0x20)
+       if (!(ldata->lattr & LATTR_WRAPPED)) {
+           termchar *q = ldata->chars + term->cols;
+           while (q > ldata->chars && IS_SPACE_CHR(q[-1].chr))
                q--;
-           if (q == ldata + term->cols)
+           if (q == ldata->chars + term->cols)
                q--;
-           if (p.x >= q - ldata)
-               p.x = (dir == -1 ? q - ldata : term->cols - 1);
+           if (p.x >= q - ldata->chars)
+               p.x = (dir == -1 ? q - ldata->chars : term->cols - 1);
        }
        break;
       case SM_WORD:
@@ -4098,26 +4617,30 @@ static pos sel_spread_half(Terminal *term, pos p, int dir)
         * In this mode, the units are maximal runs of characters
         * whose `wordness' has the same value.
         */
-       wvalue = wordtype(term, UCSGET(ldata, p.x));
+       wvalue = wordtype(term, UCSGET(ldata->chars, p.x));
        if (dir == +1) {
            while (1) {
-               int maxcols = (ldata[term->cols] & LATTR_WRAPPED2 ?
+               int maxcols = (ldata->lattr & LATTR_WRAPPED2 ?
                               term->cols-1 : term->cols);
                if (p.x < maxcols-1) {
-                   if (wordtype(term, UCSGET(ldata, p.x + 1)) == wvalue)
+                   if (wordtype(term, UCSGET(ldata->chars, p.x+1)) == wvalue)
                        p.x++;
                    else
                        break;
                } else {
-                   if (ldata[term->cols] & LATTR_WRAPPED) {
-                       unsigned long *ldata2;
+                   if (ldata->lattr & LATTR_WRAPPED) {
+                       termline *ldata2;
                        ldata2 = lineptr(p.y+1);
-                       if (wordtype(term, UCSGET(ldata2, 0)) == wvalue) {
+                       if (wordtype(term, UCSGET(ldata2->chars, 0))
+                           == wvalue) {
                            p.x = 0;
                            p.y++;
+                           unlineptr(ldata);
                            ldata = ldata2;
-                       } else
+                       } else {
+                           unlineptr(ldata2);
                            break;
+                       }
                    } else
                        break;
                }
@@ -4125,26 +4648,29 @@ static pos sel_spread_half(Terminal *term, pos p, int dir)
        } else {
            while (1) {
                if (p.x > 0) {
-                   if (wordtype(term, UCSGET(ldata, p.x - 1)) == wvalue)
+                   if (wordtype(term, UCSGET(ldata->chars, p.x-1)) == wvalue)
                        p.x--;
                    else
                        break;
                } else {
-                   unsigned long *ldata2;
+                   termline *ldata2;
                    int maxcols;
                    if (p.y <= topy)
                        break;
                    ldata2 = lineptr(p.y-1);
-                   maxcols = (ldata2[term->cols] & LATTR_WRAPPED2 ?
+                   maxcols = (ldata2->lattr & LATTR_WRAPPED2 ?
                              term->cols-1 : term->cols);
-                   if (ldata2[term->cols] & LATTR_WRAPPED) {
-                       if (wordtype(term, UCSGET(ldata2, maxcols-1))
+                   if (ldata2->lattr & LATTR_WRAPPED) {
+                       if (wordtype(term, UCSGET(ldata2->chars, maxcols-1))
                            == wvalue) {
                            p.x = maxcols-1;
                            p.y--;
+                           unlineptr(ldata);
                            ldata = ldata2;
-                       } else
+                       } else {
+                           unlineptr(ldata2);
                            break;
+                       }
                    } else
                        break;
                }
@@ -4158,6 +4684,8 @@ static pos sel_spread_half(Terminal *term, pos p, int dir)
        p.x = (dir == -1 ? 0 : term->cols - 1);
        break;
     }
+
+    unlineptr(ldata);
     return p;
 }
 
@@ -4226,7 +4754,7 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
                Mouse_Action a, int x, int y, int shift, int ctrl, int alt)
 {
     pos selpoint;
-    unsigned long *ldata;
+    termline *ldata;
     int raw_mouse = (term->xterm_mouse &&
                     !term->cfg.no_mouse_rep &&
                     !(term->cfg.mouse_override && shift));
@@ -4255,8 +4783,9 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
     selpoint.y = y + term->disptop;
     selpoint.x = x;
     ldata = lineptr(selpoint.y);
-    if ((ldata[term->cols] & LATTR_MODE) != LATTR_NORM)
+    if ((ldata->lattr & LATTR_MODE) != LATTR_NORM)
        selpoint.x /= 2;
+    unlineptr(ldata);
 
     if (raw_mouse) {
        int encstate = 0, r, c;
index e2b06b1..d4eef39 100644 (file)
@@ -29,6 +29,21 @@ struct scrollregion {
 };
 #endif /* OPTIMISE_SCROLL */
 
+typedef struct termchar termchar;
+typedef struct termline termline;
+
+struct termchar {
+    unsigned long chr;
+    unsigned long attr;
+};
+
+struct termline {
+    unsigned short lattr;
+    int cols;
+    int temporary;                    /* TRUE if decompressed from scrollback */
+    struct termchar *chars;
+};
+
 struct terminal_tag {
 
     int compatibility_level;
@@ -40,11 +55,11 @@ struct terminal_tag {
     int tempsblines;                  /* number of lines in temporary
                                          scrollback */
 
-    unsigned long *cpos;              /* cursor position (convenience) */
+    termchar *cpos;                   /* cursor position (convenience) */
 
-    unsigned long *disptext;          /* buffer of text on real screen */
-    unsigned long *dispcurs;          /* location of cursor on real screen */
-    unsigned long curstype;           /* type of cursor on real screen */
+    termline **disptext;              /* buffer of text on real screen */
+    int dispcursx, dispcursy;         /* location of cursor on real screen */
+    int curstype;                     /* type of cursor on real screen */
 
 #define VBELL_TIMEOUT (TICKSPERSEC/10) /* visual bell lasts 1/10 sec */
 
@@ -53,18 +68,18 @@ struct terminal_tag {
     int beep_overloaded;
     long lastbeep;
 
-#define TTYPE unsigned long
+#define TTYPE termchar
 #define TSIZE (sizeof(TTYPE))
 #define fix_cpos do { \
-    term->cpos = lineptr(term->curs.y) + term->curs.x; \
+    term->cpos = lineptr(term->curs.y)->chars + term->curs.x; \
 } while(0)
 
 #ifdef OPTIMISE_SCROLL
     struct scrollregion *scrollhead, *scrolltail;
 #endif /* OPTIMISE_SCROLL */
 
-    unsigned long default_attr, curr_attr, save_attr;
-    unsigned long erase_char;
+    int default_attr, curr_attr, save_attr;
+    termchar basic_erase_char, erase_char;
 
     bufchain inbuf;                   /* terminal input buffer */
     pos curs;                         /* cursor */
@@ -112,7 +127,7 @@ struct terminal_tag {
     int xterm_mouse;                  /* send mouse messages to app */
     int mouse_is_down;                /* used while tracking mouse buttons */
 
-    unsigned long cset_attr[2];
+    int cset_attr[2];
 
 /*
  * Saved settings on the alternate screen.
@@ -173,7 +188,7 @@ struct terminal_tag {
     short wordness[256];
 
     /* Mask of attributes to pay attention to when painting. */
-    unsigned long attr_mask;
+    int attr_mask;
 
     wchar_t *paste_buffer;
     int paste_len, paste_pos, paste_hold;
@@ -212,9 +227,9 @@ struct terminal_tag {
     /*
      * These are buffers used by the bidi and Arabic shaping code.
      */
-    unsigned long *ltemp;
+    termchar *ltemp;
     bidi_char *wcFrom, *wcTo;
-    unsigned long **pre_bidi_cache, **post_bidi_cache;
+    termchar **pre_bidi_cache, **post_bidi_cache;
     int bidi_cache_size;
 };
 
index c3c052a..2412e40 100644 (file)
--- a/unicode.c
+++ b/unicode.c
@@ -465,7 +465,7 @@ void init_ucs(Config *cfg, struct unicode_data *ucsdata)
     if (ucsdata->dbcs_screenfont || ucsdata->font_codepage == 0) {
        get_unitab(ucsdata->font_codepage, ucsdata->unitab_font, 2);
        for (i = 128; i < 256; i++)
-           ucsdata->unitab_font[i] = (WCHAR) (ATTR_ACP + i);
+           ucsdata->unitab_font[i] = (WCHAR) (CSET_ACP + i);
     } else {
        get_unitab(ucsdata->font_codepage, ucsdata->unitab_font, 1);
 
@@ -497,7 +497,7 @@ void init_ucs(Config *cfg, struct unicode_data *ucsdata)
        for (i = 0; i < 32; i++)
            ucsdata->unitab_line[i] = (WCHAR) i;
        for (i = 32; i < 256; i++)
-           ucsdata->unitab_line[i] = (WCHAR) (ATTR_ACP + i);
+           ucsdata->unitab_line[i] = (WCHAR) (CSET_ACP + i);
        ucsdata->unitab_line[127] = (WCHAR) 127;
     } else {
        get_unitab(ucsdata->line_codepage, ucsdata->unitab_line, 0);
@@ -561,15 +561,15 @@ void init_ucs(Config *cfg, struct unicode_data *ucsdata)
 
     /* Generate line->screen direct conversion links. */
     if (cfg->vtmode == VT_OEMANSI || cfg->vtmode == VT_XWINDOWS)
-       link_font(ucsdata->unitab_scoacs, ucsdata->unitab_oemcp, ATTR_OEMCP);
+       link_font(ucsdata->unitab_scoacs, ucsdata->unitab_oemcp, CSET_OEMCP);
 
-    link_font(ucsdata->unitab_line, ucsdata->unitab_font, ATTR_ACP);
-    link_font(ucsdata->unitab_scoacs, ucsdata->unitab_font, ATTR_ACP);
-    link_font(ucsdata->unitab_xterm, ucsdata->unitab_font, ATTR_ACP);
+    link_font(ucsdata->unitab_line, ucsdata->unitab_font, CSET_ACP);
+    link_font(ucsdata->unitab_scoacs, ucsdata->unitab_font, CSET_ACP);
+    link_font(ucsdata->unitab_xterm, ucsdata->unitab_font, CSET_ACP);
 
     if (cfg->vtmode == VT_OEMANSI || cfg->vtmode == VT_XWINDOWS) {
-       link_font(ucsdata->unitab_line, ucsdata->unitab_oemcp, ATTR_OEMCP);
-       link_font(ucsdata->unitab_xterm, ucsdata->unitab_oemcp, ATTR_OEMCP);
+       link_font(ucsdata->unitab_line, ucsdata->unitab_oemcp, CSET_OEMCP);
+       link_font(ucsdata->unitab_xterm, ucsdata->unitab_oemcp, CSET_OEMCP);
     }
 
     if (ucsdata->dbcs_screenfont &&
@@ -577,7 +577,7 @@ void init_ucs(Config *cfg, struct unicode_data *ucsdata)
        /* F***ing Microsoft fonts, Japanese and Korean codepage fonts
         * have a currency symbol at 0x5C but their unicode value is 
         * still given as U+005C not the correct U+00A5. */
-       ucsdata->unitab_line['\\'] = ATTR_OEMCP + '\\';
+       ucsdata->unitab_line['\\'] = CSET_OEMCP + '\\';
     }
 
     /* Last chance, if !unicode then try poorman links. */
@@ -593,17 +593,17 @@ void init_ucs(Config *cfg, struct unicode_data *ucsdata)
                ucsdata->unitab_line[i] >= 160 &&
                ucsdata->unitab_line[i] < 256) {
                ucsdata->unitab_line[i] =
-                   (WCHAR) (ATTR_ACP +
+                   (WCHAR) (CSET_ACP +
                             poorman_latin1[ucsdata->unitab_line[i] - 160]);
            }
        for (i = 96; i < 127; i++)
            if (!DIRECT_FONT(ucsdata->unitab_xterm[i]))
                ucsdata->unitab_xterm[i] =
-           (WCHAR) (ATTR_ACP + poorman_vt100[i - 96]);
+           (WCHAR) (CSET_ACP + poorman_vt100[i - 96]);
        for(i=128;i<256;i++) 
            if (!DIRECT_FONT(ucsdata->unitab_scoacs[i]))
                ucsdata->unitab_scoacs[i] = 
-                   (WCHAR) (ATTR_ACP + poorman_scoacs[i - 128]);
+                   (WCHAR) (CSET_ACP + poorman_scoacs[i - 128]);
     }
 }
 
index f25475a..d374879 100644 (file)
@@ -1798,7 +1798,7 @@ void free_ctx(Context ctx)
  *
  * We are allowed to fiddle with the contents of `text'.
  */
-void do_text_internal(Context ctx, int x, int y, char *text, int len,
+void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
                      unsigned long attr, int lattr)
 {
     struct draw_ctx *dctx = (struct draw_ctx *)ctx;
@@ -1841,7 +1841,7 @@ void do_text_internal(Context ctx, int x, int y, char *text, int len,
            shadow = 1;
     }
 
-    if (lattr != LATTR_NORM) {
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {
        x *= 2;
        if (x >= inst->term->cols)
            return;
@@ -1876,7 +1876,7 @@ void do_text_internal(Context ctx, int x, int y, char *text, int len,
 
        wcs = snewn(len+1, wchar_t);
        for (i = 0; i < len; i++) {
-           wcs[i] = (wchar_t) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
+           wcs[i] = text[i];
        }
 
        if (inst->fonts[fontid] == NULL) {
@@ -1913,6 +1913,11 @@ void do_text_internal(Context ctx, int x, int y, char *text, int len,
                             x*inst->font_width+inst->cfg.window_border,
                             y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
                             gwcs, len*2);
+           if (shadow)
+               gdk_draw_text_wc(inst->pixmap, inst->fonts[fontid], gc,
+                                x*inst->font_width+inst->cfg.window_border+inst->cfg.shadowboldoffset,
+                                y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
+                                gwcs, len*2);
            sfree(gwcs);
        } else {
            gcs = snewn(len+1, gchar);
@@ -1922,18 +1927,16 @@ void do_text_internal(Context ctx, int x, int y, char *text, int len,
                          x*inst->font_width+inst->cfg.window_border,
                          y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
                          gcs, len);
+           if (shadow)
+               gdk_draw_text(inst->pixmap, inst->fonts[fontid], gc,
+                             x*inst->font_width+inst->cfg.window_border+inst->cfg.shadowboldoffset,
+                             y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
+                             gcs, len);
            sfree(gcs);
        }
        sfree(wcs);
     }
 
-    if (shadow) {
-       gdk_draw_text(inst->pixmap, inst->fonts[fontid], gc,
-                     x*inst->font_width+inst->cfg.window_border + inst->cfg.shadowboldoffset,
-                     y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
-                     text, len);
-    }
-
     if (attr & ATTR_UNDER) {
        int uheight = inst->fonts[0]->ascent + 1;
        if (uheight >= inst->font_height)
@@ -1944,7 +1947,7 @@ void do_text_internal(Context ctx, int x, int y, char *text, int len,
                      y*inst->font_height + uheight + inst->cfg.window_border);
     }
 
-    if (lattr != LATTR_NORM) {
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {
        /*
         * I can't find any plausible StretchBlt equivalent in the
         * X server, so I'm going to do this the slow and painful
@@ -1963,10 +1966,10 @@ void do_text_internal(Context ctx, int x, int y, char *text, int len,
                            len * inst->font_width - i, inst->font_height);
        }
        len *= 2;
-       if (lattr != LATTR_WIDE) {
+       if ((lattr & LATTR_MODE) != LATTR_WIDE) {
            int dt, db;
            /* Now stretch vertically, in the same way. */
-           if (lattr == LATTR_BOT)
+           if ((lattr & LATTR_MODE) == LATTR_BOT)
                dt = 0, db = 1;
            else
                dt = 1, db = 0;
@@ -1982,7 +1985,7 @@ void do_text_internal(Context ctx, int x, int y, char *text, int len,
     }
 }
 
-void do_text(Context ctx, int x, int y, char *text, int len,
+void do_text(Context ctx, int x, int y, wchar_t *text, int len,
             unsigned long attr, int lattr)
 {
     struct draw_ctx *dctx = (struct draw_ctx *)ctx;
@@ -1998,7 +2001,7 @@ void do_text(Context ctx, int x, int y, char *text, int len,
        widefactor = 1;
     }
 
-    if (lattr != LATTR_NORM) {
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {
        x *= 2;
        if (x >= inst->term->cols)
            return;
@@ -2015,7 +2018,7 @@ void do_text(Context ctx, int x, int y, char *text, int len,
                    len*widefactor*inst->font_width, inst->font_height);
 }
 
-void do_cursor(Context ctx, int x, int y, char *text, int len,
+void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
               unsigned long attr, int lattr)
 {
     struct draw_ctx *dctx = (struct draw_ctx *)ctx;
@@ -2042,7 +2045,7 @@ void do_cursor(Context ctx, int x, int y, char *text, int len,
        widefactor = 1;
     }
 
-    if (lattr != LATTR_NORM) {
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {
        x *= 2;
        if (x >= inst->term->cols)
            return;
@@ -2070,7 +2073,7 @@ void do_cursor(Context ctx, int x, int y, char *text, int len,
 
        int char_width;
 
-       if ((attr & ATTR_WIDE) || lattr != LATTR_NORM)
+       if ((attr & ATTR_WIDE) || (lattr & LATTR_MODE) != LATTR_NORM)
            char_width = 2*inst->font_width;
        else
            char_width = inst->font_width;
index 39ac38d..5a36973 100644 (file)
--- a/window.c
+++ b/window.c
@@ -2901,7 +2901,7 @@ static void sys_cursor_update(void)
  *
  * We are allowed to fiddle with the contents of `text'.
  */
-void do_text(Context ctx, int x, int y, char *text, int len,
+void do_text(Context ctx, int x, int y, wchar_t *text, int len,
             unsigned long attr, int lattr)
 {
     COLORREF fg, bg, t;
@@ -2909,11 +2909,14 @@ void do_text(Context ctx, int x, int y, char *text, int len,
     HDC hdc = ctx;
     RECT line_box;
     int force_manual_underline = 0;
-    int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
-    int char_width = fnt_width;
+    int fnt_width, char_width;
     int text_adjust = 0;
     static int *IpDx = 0, IpDxLEN = 0;
 
+    lattr &= LATTR_MODE;
+
+    char_width = fnt_width = font_width * (1 + (lattr != LATTR_NORM));
+
     if (attr & ATTR_WIDE)
        char_width *= 2;
 
@@ -2961,43 +2964,39 @@ void do_text(Context ctx, int x, int y, char *text, int len,
        nfont |= FONT_NARROW;
 
     /* Special hack for the VT100 linedraw glyphs. */
-    if ((attr & CSET_MASK) == 0x2300) {
-       if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
-           switch ((unsigned char) (text[0])) {
-             case 0xBA:
-               text_adjust = -2 * font_height / 5;
-               break;
-             case 0xBB:
-               text_adjust = -1 * font_height / 5;
-               break;
-             case 0xBC:
-               text_adjust = font_height / 5;
-               break;
-             case 0xBD:
-               text_adjust = 2 * font_height / 5;
-               break;
-           }
-           if (lattr == LATTR_TOP || lattr == LATTR_BOT)
-               text_adjust *= 2;
-           attr &= ~CSET_MASK;
-           text[0] = (char) (ucsdata.unitab_xterm['q'] & CHAR_MASK);
-           attr |= (ucsdata.unitab_xterm['q'] & CSET_MASK);
-           if (attr & ATTR_UNDER) {
-               attr &= ~ATTR_UNDER;
-               force_manual_underline = 1;
-           }
+    if (text[0] >= 0x23BA && text[0] <= 0x23BD) {
+       switch ((unsigned char) (text[0])) {
+         case 0xBA:
+           text_adjust = -2 * font_height / 5;
+           break;
+         case 0xBB:
+           text_adjust = -1 * font_height / 5;
+           break;
+         case 0xBC:
+           text_adjust = font_height / 5;
+           break;
+         case 0xBD:
+           text_adjust = 2 * font_height / 5;
+           break;
+       }
+       if (lattr == LATTR_TOP || lattr == LATTR_BOT)
+           text_adjust *= 2;
+       text[0] = ucsdata.unitab_xterm['q'];
+       if (attr & ATTR_UNDER) {
+           attr &= ~ATTR_UNDER;
+           force_manual_underline = 1;
        }
     }
 
     /* Anything left as an original character set is unprintable. */
-    if (DIRECT_CHAR(attr)) {
-       attr &= ~CSET_MASK;
-       attr |= 0xFF00;
-       memset(text, 0xFD, len);
+    if (DIRECT_CHAR(text[0])) {
+       int i;
+       for (i = 0; i < len; i++)
+           text[i] = 0xFFFD;
     }
 
     /* OEM CP */
-    if ((attr & CSET_MASK) == ATTR_OEMCP)
+    if ((text[0] & CSET_MASK) == CSET_OEMCP)
        nfont |= FONT_OEM;
 
     nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
@@ -3044,7 +3043,7 @@ void do_text(Context ctx, int x, int y, char *text, int len,
        line_box.right = font_width*term->cols+offset_width;
 
     /* We're using a private area for direct to font. (512 chars.) */
-    if (ucsdata.dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
+    if (ucsdata.dbcs_screenfont && (text[0] & CSET_MASK) == CSET_ACP) {
        /* Ho Hum, dbcs fonts are a PITA! */
        /* To display on W9x I have to convert to UCS */
        static wchar_t *uni_buf = 0;
@@ -3059,15 +3058,20 @@ void do_text(Context ctx, int x, int y, char *text, int len,
        for(nlen = mptr = 0; mptr<len; mptr++) {
            uni_buf[nlen] = 0xFFFD;
            if (IsDBCSLeadByteEx(ucsdata.font_codepage, (BYTE) text[mptr])) {
+               char dbcstext[2];
+               dbcstext[0] = text[mptr] & 0xFF;
+               dbcstext[1] = text[mptr+1] & 0xFF;
                IpDx[nlen] += char_width;
                MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
-                                  text+mptr, 2, uni_buf+nlen, 1);
+                                   dbcstext, 2, uni_buf+nlen, 1);
                mptr++;
            }
            else
            {
+               char dbcstext[1];
+               dbcstext[0] = text[mptr] & 0xFF;
                MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
-                                  text+mptr, 1, uni_buf+nlen, 1);
+                                   dbcstext, 1, uni_buf+nlen, 1);
            }
            nlen++;
        }
@@ -3086,10 +3090,21 @@ void do_text(Context ctx, int x, int y, char *text, int len,
        }
 
        IpDx[0] = -1;
-    } else if (DIRECT_FONT(attr)) {
+    } else if (DIRECT_FONT(text[0])) {
+       static char *directbuf = NULL;
+       static int directlen = 0;
+       int i;
+       if (len > directlen) {
+           directlen = len;
+           directbuf = sresize(directbuf, directlen, char);
+       }
+
+       for (i = 0; i < len; i++)
+           directbuf[i] = text[i] & 0xFF;
+
        ExtTextOut(hdc, x,
                   y - font_height * (lattr == LATTR_BOT) + text_adjust,
-                  ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
+                  ETO_CLIPPED | ETO_OPAQUE, &line_box, directbuf, len, IpDx);
        if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
            SetBkMode(hdc, TRANSPARENT);
 
@@ -3103,7 +3118,7 @@ void do_text(Context ctx, int x, int y, char *text, int len,
            ExtTextOut(hdc, x - 1,
                       y - font_height * (lattr ==
                                          LATTR_BOT) + text_adjust,
-                      ETO_CLIPPED, &line_box, text, len, IpDx);
+                      ETO_CLIPPED, &line_box, directbuf, len, IpDx);
        }
     } else {
        /* And 'normal' unicode characters */
@@ -3116,7 +3131,7 @@ void do_text(Context ctx, int x, int y, char *text, int len,
            wbuf = snewn(wlen, WCHAR);
        }
        for (i = 0; i < len; i++)
-           wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
+           wbuf[i] = text[i];
 
        /* print Glyphs as they are, without Windows' Shaping*/
        exact_textout(hdc, x, y - font_height * (lattr == LATTR_BOT) + text_adjust,
@@ -3151,7 +3166,7 @@ void do_text(Context ctx, int x, int y, char *text, int len,
     }
 }
 
-void do_cursor(Context ctx, int x, int y, char *text, int len,
+void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
               unsigned long attr, int lattr)
 {
 
@@ -3161,7 +3176,7 @@ void do_cursor(Context ctx, int x, int y, char *text, int len,
     int ctype = cfg.cursor_type;
 
     if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) {
-       if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
+       if (*text != UCSWIDE) {
            do_text(ctx, x, y, text, len, attr, lattr);
            return;
        }
@@ -3238,13 +3253,13 @@ int char_width(Context ctx, int uc) {
     if (!font_dualwidth) return 1;
 
     switch (uc & CSET_MASK) {
-      case ATTR_ASCII:
+      case CSET_ASCII:
        uc = ucsdata.unitab_line[uc & 0xFF];
        break;
-      case ATTR_LINEDRW:
+      case CSET_LINEDRW:
        uc = ucsdata.unitab_xterm[uc & 0xFF];
        break;
-      case ATTR_SCOACS:
+      case CSET_SCOACS:
        uc = ucsdata.unitab_scoacs[uc & 0xFF];
        break;
     }
@@ -3252,12 +3267,12 @@ int char_width(Context ctx, int uc) {
        if (ucsdata.dbcs_screenfont) return 1;
 
        /* Speedup, I know of no font where ascii is the wrong width */
-       if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~') 
+       if ((uc&~CSET_MASK) >= ' ' && (uc&~CSET_MASK)<= '~')
            return 1;
 
-       if ( (uc & CSET_MASK) == ATTR_ACP ) {
+       if ( (uc & CSET_MASK) == CSET_ACP ) {
            SelectObject(hdc, fonts[FONT_NORMAL]);
-       } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
+       } else if ( (uc & CSET_MASK) == CSET_OEMCP ) {
            another_font(FONT_OEM);
            if (!fonts[FONT_OEM]) return 0;
 
@@ -3265,8 +3280,8 @@ int char_width(Context ctx, int uc) {
        } else
            return 0;
 
-       if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 && 
-            GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
+       if ( GetCharWidth32(hdc, uc&~CSET_MASK, uc&~CSET_MASK, &ibuf) != 1 &&
+            GetCharWidth(hdc, uc&~CSET_MASK, uc&~CSET_MASK, &ibuf) != 1)
            return 0;
     } else {
        /* Speedup, I know of no font where ascii is the wrong width */