Update libvterm to upstream revision 619.

Change-Id: I9a734b40a172d9c13be4fdf38db60c6916d7d8d0
diff --git a/Android.mk b/Android.mk
index 4eeca87..dac5231 100644
--- a/Android.mk
+++ b/Android.mk
@@ -18,6 +18,12 @@
     src/screen.c \
     src/state.c
 
-LOCAL_CFLAGS := -std=c99
+LOCAL_CFLAGS += \
+    -std=c99 \
+    -Wno-missing-field-initializers \
+    -Wno-sign-compare \
+    -Wno-unused-parameter \
+
+LOCAL_CLANG := true
 
 include $(BUILD_STATIC_LIBRARY)
diff --git a/include/vterm.h b/include/vterm.h
index 2e2b32b..d43add7 100644
--- a/include/vterm.h
+++ b/include/vterm.h
@@ -103,9 +103,16 @@
   const uint32_t *chars;
   int             width;
   unsigned int    protected_cell:1;  /* DECSCA-protected against DECSEL/DECSED */
+  unsigned int    dwl:1;             /* DECDWL or DECDHL double-width line */
+  unsigned int    dhl:2;             /* DECDHL double-height line (1=top 2=bottom) */
 } VTermGlyphInfo;
 
 typedef struct {
+  unsigned int    doublewidth:1;     /* DECDWL or DECDHL line */
+  unsigned int    doubleheight:2;    /* DECDHL line (1=top 2=bottom) */
+} VTermLineInfo;
+
+typedef struct {
   /* libvterm relies on this memory to be zeroed out before it is returned
    * by the allocator. */
   void *(*malloc)(size_t size, void *allocdata);
@@ -187,6 +194,7 @@
   int (*setmousefunc)(VTermMouseFunc func, void *data, void *user);
   int (*bell)(void *user);
   int (*resize)(int rows, int cols, VTermPos *delta, void *user);
+  int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user);
 } VTermStateCallbacks;
 
 VTermState *vterm_obtain_state(VTerm *vt);
@@ -197,9 +205,11 @@
 void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg);
 void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col);
 void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg);
+void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col);
 void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright);
 int  vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val);
 int  vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val);
+const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row);
 
 // ------------
 // Screen layer
@@ -217,6 +227,8 @@
     unsigned int reverse   : 1;
     unsigned int strike    : 1;
     unsigned int font      : 4; /* 0 to 9 */
+    unsigned int dwl       : 1; /* On a DECDWL or DECDHL line */
+    unsigned int dhl       : 2; /* On a DECDHL line (1=top 2=bottom) */
   } attrs;
   VTermColor fg, bg;
 } VTermScreenCell;
@@ -249,6 +261,8 @@
 void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size);
 
 void   vterm_screen_reset(VTermScreen *screen, int hard);
+
+/* Neither of these functions NUL-terminate the buffer */
 size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect);
 size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect);
 
diff --git a/src/encoding.c b/src/encoding.c
index a7629f9..1495855 100644
--- a/src/encoding.c
+++ b/src/encoding.c
@@ -2,7 +2,7 @@
 
 #define UNICODE_INVALID 0xFFFD
 
-#ifdef DEBUG
+#if defined(DEBUG) && DEBUG > 1
 # define DEBUG_PRINT_UTF8
 #endif
 
@@ -212,6 +212,7 @@
   { 0 },
 };
 
+/* This ought to be INTERNAL but isn't because it's used by unit testing */
 VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation)
 {
   for(int i = 0; encodings[i].designation; i++)
diff --git a/src/input.c b/src/input.c
index 36bec56..0eaf0e9 100644
--- a/src/input.c
+++ b/src/input.c
@@ -11,11 +11,6 @@
    */
   if(c != ' ')
     mod &= ~VTERM_MOD_SHIFT;
-  /* However, since Shift-Space is too easy to mistype accidentally, remove
-   * shift if it's the only modifier
-   */
-  else if(mod == VTERM_MOD_SHIFT)
-    mod = 0;
 
   if(mod == 0) {
     // Normal text - ignore just shift
@@ -128,12 +123,6 @@
 
 void vterm_input_push_key(VTerm *vt, VTermModifier mod, VTermKey key)
 {
-  /* Since Shift-Enter and Shift-Backspace are too easy to mistype
-   * accidentally, remove shift if it's the only modifier
-   */
-  if((key == VTERM_KEY_ENTER || key == VTERM_KEY_BACKSPACE) && mod == VTERM_MOD_SHIFT)
-    mod = 0;
-
   if(key == VTERM_KEY_NONE)
     return;
 
diff --git a/src/pen.c b/src/pen.c
index 044e9aa..fb8c8e3 100644
--- a/src/pen.c
+++ b/src/pen.c
@@ -33,18 +33,18 @@
   0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF,
 };
 
-static void lookup_colour_ansi(long index, VTermColor *col)
+static void lookup_colour_ansi(const VTermState *state, long index, VTermColor *col)
 {
   if(index >= 0 && index < 16) {
-    *col = ansi_colors[index];
+    *col = state->colors[index];
   }
 }
 
-static void lookup_colour_palette(long index, VTermColor *col)
+static void lookup_colour_palette(const VTermState *state, long index, VTermColor *col)
 {
   if(index >= 0 && index < 16) {
     // Normal 8 colours or high intensity - parse as palette 0
-    lookup_colour_ansi(index, col);
+    lookup_colour_ansi(state, index, col);
   }
   else if(index >= 16 && index < 232) {
     // 216-colour cube
@@ -64,7 +64,7 @@
   }
 }
 
-static int lookup_colour(int palette, const long args[], int argcount, VTermColor *col, int *index)
+static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col, int *index)
 {
   switch(palette) {
   case 2: // RGB mode - 3 args contain colour values directly
@@ -81,7 +81,7 @@
     if(index)
       *index = CSI_ARG_OR(args[0], -1);
 
-    lookup_colour_palette(argcount ? CSI_ARG_OR(args[0], -1) : -1, col);
+    lookup_colour_palette(state, argcount ? CSI_ARG_OR(args[0], -1) : -1, col);
 
     return argcount ? 1 : 0;
 
@@ -128,12 +128,22 @@
 {
   VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg;
 
-  lookup_colour_ansi(col, colp);
+  lookup_colour_ansi(state, col, colp);
 
   setpenattr_col(state, attr, *colp);
 }
 
-void vterm_state_resetpen(VTermState *state)
+INTERNAL void vterm_state_newpen(VTermState *state)
+{
+  // 90% grey so that pure white is brighter
+  state->default_fg.red = state->default_fg.green = state->default_fg.blue = 240;
+  state->default_bg.red = state->default_bg.green = state->default_bg.blue = 0;
+
+  for(int col = 0; col < 16; col++)
+    state->colors[col] = ansi_colors[col];
+}
+
+INTERNAL void vterm_state_resetpen(VTermState *state)
 {
   state->pen.bold = 0;      setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
   state->pen.underline = 0; setpenattr_int( state, VTERM_ATTR_UNDERLINE, 0);
@@ -149,7 +159,7 @@
   state->pen.bg = state->default_bg;  setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg);
 }
 
-void vterm_state_savepen(VTermState *state, int save)
+INTERNAL void vterm_state_savepen(VTermState *state, int save)
 {
   if(save) {
     state->saved.pen = state->pen;
@@ -177,7 +187,7 @@
 
 void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col)
 {
-  lookup_colour_palette(index, col);
+  lookup_colour_palette(state, index, col);
 }
 
 void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg)
@@ -186,12 +196,18 @@
   state->default_bg = *default_bg;
 }
 
+void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col)
+{
+  if(index >= 0 && index < 16)
+    state->colors[index] = *col;
+}
+
 void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright)
 {
   state->bold_is_highbright = bold_is_highbright;
 }
 
-void vterm_state_setpen(VTermState *state, const long args[], int argcount)
+INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount)
 {
   // SGR - ECMA-48 8.3.117
 
@@ -296,7 +312,7 @@
       state->fg_index = -1;
       if(argcount - argi < 1)
         return;
-      argi += 1 + lookup_colour(CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg, &state->fg_index);
+      argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg, &state->fg_index);
       setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
       break;
 
@@ -317,7 +333,7 @@
       state->bg_index = -1;
       if(argcount - argi < 1)
         return;
-      argi += 1 + lookup_colour(CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg, &state->bg_index);
+      argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg, &state->bg_index);
       setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
       break;
 
@@ -353,7 +369,7 @@
   }
 }
 
-int vterm_state_getpen(VTermState *state, long args[], int argcount)
+INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount)
 {
   int argi = 0;
 
diff --git a/src/screen.c b/src/screen.c
index db11c6d..c4de59e 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -25,6 +25,8 @@
 
   /* Extra state storage that isn't strictly pen-related */
   unsigned int protected_cell : 1;
+  unsigned int dwl            : 1; /* on a DECDWL or DECDHL line */
+  unsigned int dhl            : 2; /* on a DECDHL line (1=top 2=bottom) */
 } ScreenPen;
 
 /* Internal representation of a screen cell */
@@ -194,6 +196,8 @@
   };
 
   cell->pen.protected_cell = info->protected_cell;
+  cell->pen.dwl            = info->dwl;
+  cell->pen.dhl            = info->dhl;
 
   damagerect(screen, rect);
 
@@ -262,7 +266,9 @@
 {
   VTermScreen *screen = user;
 
-  for(int row = rect.start_row; row < rect.end_row; row++)
+  for(int row = rect.start_row; row < rect.end_row; row++) {
+    const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row);
+
     for(int col = rect.start_col; col < rect.end_col; col++) {
       ScreenCell *cell = getcell(screen, row, col);
 
@@ -271,7 +277,10 @@
 
       cell->chars[0] = 0;
       cell->pen = screen->pen;
+      cell->pen.dwl = info->doublewidth;
+      cell->pen.dhl = info->doubleheight;
     }
+  }
 
   return 1;
 }
@@ -568,6 +577,37 @@
   return 1;
 }
 
+static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user)
+{
+  VTermScreen *screen = user;
+
+  if(newinfo->doublewidth != oldinfo->doublewidth ||
+     newinfo->doubleheight != oldinfo->doubleheight) {
+    for(int col = 0; col < screen->cols; col++) {
+      ScreenCell *cell = getcell(screen, row, col);
+      cell->pen.dwl = newinfo->doublewidth;
+      cell->pen.dhl = newinfo->doubleheight;
+    }
+
+    VTermRect rect = {
+      .start_row = row,
+      .end_row   = row + 1,
+      .start_col = 0,
+      .end_col   = newinfo->doublewidth ? screen->cols / 2 : screen->cols,
+    };
+    damagerect(screen, rect);
+
+    if(newinfo->doublewidth) {
+      rect.start_col = screen->cols / 2;
+      rect.end_col   = screen->cols;
+
+      erase_internal(rect, 0, user);
+    }
+  }
+
+  return 1;
+}
+
 static VTermStateCallbacks state_cbs = {
   .putglyph     = &putglyph,
   .movecursor   = &movecursor,
@@ -578,6 +618,7 @@
   .setmousefunc = &setmousefunc,
   .bell         = &bell,
   .resize       = &resize,
+  .setlineinfo  = &setlineinfo,
 };
 
 static VTermScreen *screen_new(VTerm *vt)
@@ -612,7 +653,7 @@
   return screen;
 }
 
-void vterm_screen_free(VTermScreen *screen)
+INTERNAL void vterm_screen_free(VTermScreen *screen)
 {
   vterm_allocator_free(screen->vt, screen->buffers[0]);
   if(screen->buffers[1])
@@ -712,6 +753,9 @@
   cell->attrs.strike    = intcell->pen.strike;
   cell->attrs.font      = intcell->pen.font;
 
+  cell->attrs.dwl = intcell->pen.dwl;
+  cell->attrs.dhl = intcell->pen.dhl;
+
   cell->fg = intcell->pen.fg;
   cell->bg = intcell->pen.bg;
 
diff --git a/src/state.c b/src/state.c
index f5348c9..e42be53 100644
--- a/src/state.c
+++ b/src/state.c
@@ -7,7 +7,7 @@
 
 #include "utf8.h"
 
-#ifdef DEBUG
+#if defined(DEBUG) && DEBUG > 1
 # define DEBUG_GLYPH_COMBINE
 #endif
 
@@ -23,6 +23,8 @@
     .chars = chars,
     .width = width,
     .protected_cell = state->protected_cell,
+    .dwl = state->lineinfo[pos.row].doublewidth,
+    .dhl = state->lineinfo[pos.row].doubleheight,
   };
 
   if(state->callbacks && state->callbacks->putglyph)
@@ -61,18 +63,17 @@
   state->rows = vt->rows;
   state->cols = vt->cols;
 
-  // 90% grey so that pure white is brighter
-  state->default_fg.red = state->default_fg.green = state->default_fg.blue = 240;
-  state->default_bg.red = state->default_bg.green = state->default_bg.blue = 0;
+  vterm_state_newpen(state);
 
   state->bold_is_highbright = 0;
 
   return state;
 }
 
-void vterm_state_free(VTermState *state)
+INTERNAL void vterm_state_free(VTermState *state)
 {
   vterm_allocator_free(state->vt, state->tabstops);
+  vterm_allocator_free(state->vt, state->lineinfo);
   vterm_allocator_free(state->vt, state->combine_chars);
   vterm_allocator_free(state->vt, state);
 }
@@ -82,6 +83,20 @@
   if(!downward && !rightward)
     return;
 
+  // Update lineinfo if full line
+  if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) {
+    int height = rect.end_row - rect.start_row - abs(downward);
+
+    if(downward > 0)
+      memmove(state->lineinfo + rect.start_row,
+              state->lineinfo + rect.start_row + downward,
+              height * sizeof(state->lineinfo[0]));
+    else
+      memmove(state->lineinfo + rect.start_row - downward,
+              state->lineinfo + rect.start_row,
+              height * sizeof(state->lineinfo[0]));
+  }
+
   if(state->callbacks && state->callbacks->scrollrect)
     if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata))
       return;
@@ -115,7 +130,9 @@
   memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0]));
 
   vterm_allocator_free(state->vt, state->combine_chars);
+
   state->combine_chars = new_chars;
+  state->combine_chars_size = new_size;
 }
 
 static void set_col_tabstop(VTermState *state, int col)
@@ -139,7 +156,7 @@
 static void tab(VTermState *state, int count, int direction)
 {
   while(count--)
-    while(state->pos.col >= 0 && state->pos.col < state->cols-1) {
+    while(state->pos.col >= 0 && state->pos.col < THISROWWIDTH(state)-1) {
       state->pos.col += direction;
 
       if(is_col_tabstop(state, state->pos.col))
@@ -147,6 +164,40 @@
     }
 }
 
+#define NO_FORCE 0
+#define FORCE    1
+
+#define DWL_OFF 0
+#define DWL_ON  1
+
+#define DHL_OFF    0
+#define DHL_TOP    1
+#define DHL_BOTTOM 2
+
+static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl)
+{
+  VTermLineInfo info = state->lineinfo[row];
+
+  if(dwl == DWL_OFF)
+    info.doublewidth = DWL_OFF;
+  else if(dwl == DWL_ON)
+    info.doublewidth = DWL_ON;
+  // else -1 to ignore
+
+  if(dhl == DHL_OFF)
+    info.doubleheight = DHL_OFF;
+  else if(dhl == DHL_TOP)
+    info.doubleheight = DHL_TOP;
+  else if(dhl == DHL_BOTTOM)
+    info.doubleheight = DHL_BOTTOM;
+
+  if((state->callbacks &&
+      state->callbacks->setlineinfo &&
+      (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata))
+      || force)
+    state->lineinfo[row] = info;
+}
+
 static int on_text(const char bytes[], size_t len, void *user)
 {
   VTermState *state = user;
@@ -243,7 +294,7 @@
     printf("}, onscreen width %d\n", width);
 #endif
 
-    if(state->at_phantom || state->pos.col + width > state->cols) {
+    if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) {
       linefeed(state);
       state->pos.col = 0;
       state->at_phantom = 0;
@@ -258,10 +309,11 @@
         .start_row = state->pos.row,
         .end_row   = state->pos.row + 1,
         .start_col = state->pos.col,
-        .end_col   = state->cols,
+        .end_col   = THISROWWIDTH(state),
       };
       scroll(state, rect, 0, -1);
     }
+
     putglyph(state, chars, width, state->pos);
 
     if(i == npoints - 1) {
@@ -280,7 +332,7 @@
       state->combine_pos = state->pos;
     }
 
-    if(state->pos.col + width >= state->cols) {
+    if(state->pos.col + width >= THISROWWIDTH(state)) {
       if(state->mode.autowrap)
         state->at_phantom = 1;
     }
@@ -408,6 +460,7 @@
       len += fill_utf8((code | modifiers) + 0x20, utf8 + len);
       len += fill_utf8(col + 0x21, utf8 + len);
       len += fill_utf8(row + 0x21, utf8 + len);
+      utf8[len] = 0;
 
       vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8);
     }
@@ -547,12 +600,36 @@
       return 0;
 
     switch(bytes[1]) {
+      case '3': // DECDHL top
+        if(state->mode.leftrightmargin)
+          break;
+        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP);
+        break;
+
+      case '4': // DECDHL bottom
+        if(state->mode.leftrightmargin)
+          break;
+        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM);
+        break;
+
+      case '5': // DECSWL
+        if(state->mode.leftrightmargin)
+          break;
+        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF);
+        break;
+
+      case '6': // DECDWL
+        if(state->mode.leftrightmargin)
+          break;
+        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF);
+        break;
+
       case '8': // DECALN
       {
         VTermPos pos;
         uint32_t E[] = { 'E', 0 };
         for(pos.row = 0; pos.row < state->rows; pos.row++)
-          for(pos.col = 0; pos.col < state->cols; pos.col++)
+          for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++)
             putglyph(state, E, 1, pos);
         break;
       }
@@ -684,7 +761,14 @@
     break;
 
   case 69: // DECVSSM - vertical split screen mode
+           // DECLRMM - left/right margin mode
     state->mode.leftrightmargin = val;
+    if(val) {
+      // Setting DECVSSM must clear doublewidth/doubleheight state of every line
+      for(int row = 0; row < state->rows; row++)
+        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
+    }
+
     break;
 
   case 1000:
@@ -869,7 +953,10 @@
     rect.start_row = state->pos.row;
     rect.end_row   = state->pos.row + 1;
     rect.start_col = state->pos.col;
-    rect.end_col   = state->cols;
+    if(state->mode.leftrightmargin)
+      rect.end_col = SCROLLREGION_RIGHT(state);
+    else
+      rect.end_col = THISROWWIDTH(state);
 
     scroll(state, rect, 0, -count);
 
@@ -950,6 +1037,8 @@
 
       rect.start_row = state->pos.row + 1; rect.end_row = state->rows;
       rect.start_col = 0;
+      for(int row = rect.start_row; row < rect.end_row; row++)
+        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
       if(rect.end_row > rect.start_row)
         erase(state, rect, selective);
       break;
@@ -957,6 +1046,8 @@
     case 1:
       rect.start_row = 0; rect.end_row = state->pos.row;
       rect.start_col = 0; rect.end_col = state->cols;
+      for(int row = rect.start_row; row < rect.end_row; row++)
+        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
       if(rect.end_col > rect.start_col)
         erase(state, rect, selective);
 
@@ -969,6 +1060,8 @@
     case 2:
       rect.start_row = 0; rect.end_row = state->rows;
       rect.start_col = 0; rect.end_col = state->cols;
+      for(int row = rect.start_row; row < rect.end_row; row++)
+        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
       erase(state, rect, selective);
       break;
     }
@@ -983,11 +1076,11 @@
     switch(CSI_ARG(args[0])) {
     case CSI_ARG_MISSING:
     case 0:
-      rect.start_col = state->pos.col; rect.end_col = state->cols; break;
+      rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break;
     case 1:
       rect.start_col = 0; rect.end_col = state->pos.col + 1; break;
     case 2:
-      rect.start_col = 0; rect.end_col = state->cols; break;
+      rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break;
     default:
       return 0;
     }
@@ -1027,7 +1120,10 @@
     rect.start_row = state->pos.row;
     rect.end_row   = state->pos.row + 1;
     rect.start_col = state->pos.col;
-    rect.end_col   = state->cols;
+    if(state->mode.leftrightmargin)
+      rect.end_col = SCROLLREGION_RIGHT(state);
+    else
+      rect.end_col = THISROWWIDTH(state);
 
     scroll(state, rect, 0, count);
 
@@ -1064,7 +1160,7 @@
     rect.end_row   = state->pos.row + 1;
     rect.start_col = state->pos.col;
     rect.end_col   = state->pos.col + count;
-    UBOUND(rect.end_col, state->cols);
+    UBOUND(rect.end_col, THISROWWIDTH(state));
 
     erase(state, rect, 0);
     break;
@@ -1261,16 +1357,28 @@
   case 0x72: // DECSTBM - DEC custom
     state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1;
     state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
+    LBOUND(state->scrollregion_top, -1);
+    UBOUND(state->scrollregion_top, state->rows);
+    LBOUND(state->scrollregion_bottom, -1);
     if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows)
       state->scrollregion_bottom = -1;
+    else
+      UBOUND(state->scrollregion_bottom, state->rows);
+
     break;
 
   case 0x73: // DECSLRM - DEC custom
     // Always allow setting these margins, just they won't take effect without DECVSSM
     state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1;
     state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
+    LBOUND(state->scrollregion_left, -1);
+    UBOUND(state->scrollregion_left, state->cols);
+    LBOUND(state->scrollregion_right, -1);
     if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols)
       state->scrollregion_right = -1;
+    else
+      UBOUND(state->scrollregion_right, state->cols);
+
     break;
 
   case INTERMED('\'', 0x7D): // DECIC
@@ -1311,7 +1419,7 @@
     LBOUND(state->pos.row, 0);
     UBOUND(state->pos.row, state->rows-1);
     LBOUND(state->pos.col, 0);
-    UBOUND(state->pos.col, state->cols-1);
+    UBOUND(state->pos.col, THISROWWIDTH(state)-1);
   }
 
   updatecursor(state, &oldpos, 1);
@@ -1434,6 +1542,24 @@
     state->tabstops = newtabstops;
   }
 
+  if(rows != state->rows) {
+    VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));
+
+    int row;
+    for(row = 0; row < state->rows && row < rows; row++) {
+      newlineinfo[row] = state->lineinfo[row];
+    }
+
+    for( ; row < rows; row++) {
+      newlineinfo[row] = (VTermLineInfo){
+        .doublewidth = 0,
+      };
+    }
+
+    vterm_allocator_free(state->vt, state->lineinfo);
+    state->lineinfo = newlineinfo;
+  }
+
   state->rows = rows;
   state->cols = cols;
 
@@ -1483,6 +1609,8 @@
 
   state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8);
 
+  state->lineinfo = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
+
   state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
   if(*state->encoding_utf8.enc->init)
     (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);
@@ -1516,6 +1644,9 @@
     else
       clear_col_tabstop(state, col);
 
+  for(int row = 0; row < state->rows; row++)
+    set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
+
   if(state->callbacks && state->callbacks->initpen)
     (*state->callbacks->initpen)(state->cbdata);
 
@@ -1613,3 +1744,8 @@
 
   return 0;
 }
+
+const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row)
+{
+  return state->lineinfo + row;
+}
diff --git a/src/unicode.c b/src/unicode.c
index ac1748c..69b7682 100644
--- a/src/unicode.c
+++ b/src/unicode.c
@@ -321,12 +321,12 @@
 // ################################
 // ### The rest added by Paul Evans
 
-int vterm_unicode_width(int codepoint)
+INTERNAL int vterm_unicode_width(int codepoint)
 {
   return mk_wcwidth(codepoint);
 }
 
-int vterm_unicode_is_combining(int codepoint)
+INTERNAL int vterm_unicode_is_combining(int codepoint)
 {
   return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1);
 }
diff --git a/src/utf8.h b/src/utf8.h
index 1c2a2d9..9a336d3 100644
--- a/src/utf8.h
+++ b/src/utf8.h
@@ -12,12 +12,11 @@
   return 6;
 }
 
+/* Does NOT NUL-terminate the buffer */
 static int fill_utf8(long codepoint, char *str)
 {
   int nbytes = utf8_seqlen(codepoint);
 
-  str[nbytes] = 0;
-
   // This is easier done backwards
   int b = nbytes;
   while(b > 1) {
diff --git a/src/vterm.c b/src/vterm.c
index 940d1fb..04651d3 100644
--- a/src/vterm.c
+++ b/src/vterm.c
@@ -70,12 +70,12 @@
   vterm_allocator_free(vt, vt);
 }
 
-void *vterm_allocator_malloc(VTerm *vt, size_t size)
+INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size)
 {
   return (*vt->allocator->malloc)(size, vt->allocdata);
 }
 
-void vterm_allocator_free(VTerm *vt, void *ptr)
+INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr)
 {
   (*vt->allocator->free)(ptr, vt->allocdata);
 }
@@ -108,7 +108,7 @@
   vt->mode.utf8 = is_utf8;
 }
 
-void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len)
+INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len)
 {
   if(len > vt->outbuffer_len - vt->outbuffer_cur) {
     fprintf(stderr, "vterm_push_output(): buffer overflow; truncating output\n");
@@ -119,7 +119,7 @@
   vt->outbuffer_cur += len;
 }
 
-void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args)
+INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args)
 {
   int written = vsnprintf(vt->outbuffer + vt->outbuffer_cur,
       vt->outbuffer_len - vt->outbuffer_cur,
@@ -127,7 +127,7 @@
   vt->outbuffer_cur += written;
 }
 
-void vterm_push_output_sprintf(VTerm *vt, const char *format, ...)
+INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...)
 {
   va_list args;
   va_start(args, format);
@@ -135,7 +135,7 @@
   va_end(args);
 }
 
-void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...)
+INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...)
 {
   if(ctrl >= 0x80 && !vt->mode.ctrl8bit)
     vterm_push_output_sprintf(vt, "\e%c", ctrl - 0x40);
@@ -148,7 +148,7 @@
   va_end(args);
 }
 
-void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...)
+INTERNAL void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...)
 {
   if(!vt->mode.ctrl8bit)
     vterm_push_output_sprintf(vt, "\e%c", C1_DCS - 0x40);
diff --git a/src/vterm_internal.h b/src/vterm_internal.h
index 9a0183e..4bc8e6b 100644
--- a/src/vterm_internal.h
+++ b/src/vterm_internal.h
@@ -5,6 +5,12 @@
 
 #include <stdarg.h>
 
+#if defined(__GNUC__)
+# define INTERNAL __attribute__((visibility("internal")))
+#else
+# define INTERNAL
+#endif
+
 typedef struct VTermEncoding VTermEncoding;
 
 typedef struct {
@@ -58,6 +64,10 @@
   /* Bitvector of tab stops */
   unsigned char *tabstops;
 
+  VTermLineInfo *lineinfo;
+#define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols)
+#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row)
+
   /* Mouse state */
   int mouse_col, mouse_row;
   int mouse_buttons;
@@ -92,6 +102,8 @@
 
   VTermColor default_fg;
   VTermColor default_bg;
+  VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only
+
   int fg_index;
   int bg_index;
   int bold_is_highbright;
@@ -172,6 +184,7 @@
 
 void vterm_state_free(VTermState *state);
 
+void vterm_state_newpen(VTermState *state);
 void vterm_state_resetpen(VTermState *state);
 void vterm_state_setpen(VTermState *state, const long args[], int argcount);
 int  vterm_state_getpen(VTermState *state, long args[], int argcount);