changeset 108750:3339da3cfeb3

Redesign bidi-aware edge positions of glyph rows, fix bug #6036. dispextern.h (struct glyph_row): New members minpos and maxpos. (MATRIX_ROW_START_CHARPOS, MATRIX_ROW_START_BYTEPOS) (MATRIX_ROW_END_CHARPOS, MATRIX_ROW_END_BYTEPOS): Reference minpos and maxpos members instead of start.pos and end.pos, respectively. xdisp.c (display_line): Compare IT_CHARPOS with the position in row->start.pos, rather than with MATRIX_ROW_START_CHARPOS. (cursor_row_p): Use row->end.pos rather than MATRIX_ROW_END_CHARPOS. (try_window_reusing_current_matrix, try_window_id): Use ROW->minpos rather than ROW->start.pos. (init_from_display_pos, init_iterator): Use EMACS_INT for character and byte positions. (find_row_edges): Renamed from find_row_end. Accept additional arguments for minimum and maximum buffer positions seen by display_line for this row. Don't use iterator to find the position following the maximum one; instead, increment the position found by display_line directly. Fix logic; eol_pos should be tested before the rest. Handle the case of characters delivered from display vector (bug#6036). Fix tests related to it->method. Handle the truncated_on_right_p rows. (RECORD_MAX_MIN_POS): New macro. (display_line): Use it to record the minimum and maximum buffer positions for glyphs in the row being assembled. Record the position of the newline that terminates the line. If word wrap is in effect, restore minimum and maximum positions seen up to the wrap point, when iterator returns to it. (try_window_reusing_current_matrix): Give up if in bidi-reordered row and cursor not already at point. Restore original pre-bidi code for unidirectional buffers. dispnew.c (increment_row_positions, check_matrix_invariants): Increment and check row->start.pos and row->end.pos, in addition to MATRIX_ROW_START_CHARPOS and MATRIX_ROW_END_CHARPOS. .gdbinit (prowlims): Display row->minpos and row->maxpos. Display truncated_on_left_p and truncated_on_right_p flags. Formatting fixes. (pmtxrows): Display the ordinal number of each row. Don't display rows beyond the last one. bidi.c (bidi_cache_iterator_state): Don't zero out new_paragraph: it is not copied by bidi_copy_it.
author Eli Zaretskii <eliz@gnu.org>
date Sat, 22 May 2010 22:32:21 +0300
parents 48378bcd6c35 (current diff) ab4686221ccf (diff)
children c40b9bfe9167 0aaff477ec9f
files src/ChangeLog src/bidi.c src/xdisp.c
diffstat 6 files changed, 261 insertions(+), 181 deletions(-) [+]
line wrap: on
line diff
--- a/src/.gdbinit	Sat May 22 22:09:51 2010 +0300
+++ b/src/.gdbinit	Sat May 22 22:32:21 2010 +0300
@@ -616,7 +616,7 @@
 end
 
 define prowlims
-  printf "start=%d,end=%d,reversed=%d,cont=%d,at_zv=%d\n", $arg0->start.pos.charpos, $arg0->end.pos.charpos, $arg0->reversed_p, $arg0->continued_p, $arg0->ends_at_zv_p
+  printf "edges=(%d,%d),r2l=%d,cont=%d,trunc=(%d,%d),at_zv=%d\n", $arg0->minpos.charpos, $arg0->maxpos.charpos, $arg0->reversed_p, $arg0->continued_p, $arg0->truncated_on_left_p, $arg0->truncated_on_right_p, $arg0->ends_at_zv_p
 end
 document prowlims
 Print important attributes of a glyph_row structure.
@@ -626,10 +626,13 @@
 define pmtxrows
   set $mtx = $arg0
   set $gl = $mtx->rows
-  set $glend = $mtx->rows + $mtx->nrows
+  set $glend = $mtx->rows + $mtx->nrows - 1
+  set $i = 0
   while ($gl < $glend)
+    printf "%d: ", $i
     prowlims $gl
     set $gl = $gl + 1
+    set $i = $i + 1
   end
 end
 document pmtxrows
--- a/src/ChangeLog	Sat May 22 22:09:51 2010 +0300
+++ b/src/ChangeLog	Sat May 22 22:32:21 2010 +0300
@@ -1,3 +1,50 @@
+2010-05-19  Eli Zaretskii  <eliz@gnu.org>
+
+	Redesign and reimplement bidi-aware edge positions of glyph rows.
+
+	* dispextern.h (struct glyph_row): New members minpos and maxpos.
+	(MATRIX_ROW_START_CHARPOS, MATRIX_ROW_START_BYTEPOS)
+	(MATRIX_ROW_END_CHARPOS, MATRIX_ROW_END_BYTEPOS): Reference minpos
+	and maxpos members instead of start.pos and end.pos, respectively.
+
+	* xdisp.c (display_line): Compare IT_CHARPOS with the position in
+	row->start.pos, rather than with MATRIX_ROW_START_CHARPOS.
+	(cursor_row_p): Use row->end.pos rather than MATRIX_ROW_END_CHARPOS.
+	(try_window_reusing_current_matrix, try_window_id): Use
+	ROW->minpos rather than ROW->start.pos.
+	(init_from_display_pos, init_iterator): Use EMACS_INT for
+	character and byte positions.
+	(find_row_edges): Renamed from find_row_end.  Accept additional
+	arguments for minimum and maximum buffer positions seen by
+	display_line for this row.  Don't use iterator to find the
+	position following the maximum one; instead, increment the
+	position found by display_line directly.  Fix logic; eol_pos
+	should be tested before the rest.  Handle the case of characters
+	delivered from display vector (bug#6036).  Fix tests related to
+	it->method.  Handle the truncated_on_right_p rows.
+	(RECORD_MAX_MIN_POS): New macro.
+	(display_line): Use it to record the minimum and maximum buffer
+	positions for glyphs in the row being assembled.  Record the
+	position of the newline that terminates the line.  If word wrap is
+	in effect, restore minimum and maximum positions seen up to the
+	wrap point, when iterator returns to it.
+	(try_window_reusing_current_matrix): Give up if in bidi-reordered
+	row and cursor not already at point.  Restore original pre-bidi
+	code for unidirectional buffers.
+
+	* dispnew.c (increment_row_positions, check_matrix_invariants):
+	Increment and check row->start.pos and row->end.pos, in addition
+	to MATRIX_ROW_START_CHARPOS and MATRIX_ROW_END_CHARPOS.
+
+	* .gdbinit (prowlims): Display row->minpos and row->maxpos.
+	Display truncated_on_left_p and truncated_on_right_p flags.
+	Formatting fixes.
+	(pmtxrows): Display the ordinal number of each row.  Don't display
+	rows beyond the last one.
+
+	* bidi.c (bidi_cache_iterator_state): Don't zero out new_paragraph:
+	it is not copied by bidi_copy_it.
+
 2010-05-22  Eli Zaretskii  <eliz@gnu.org>
 
 	* w32.c (sys_write): Break writes into chunks smaller than 32MB.
@@ -51,8 +98,6 @@
 	Move static/dynamic dependency stuff to deps.mk/autodeps.mk.
 	* deps.mk, autodeps.mk: New files, extracted from Makefile.in.
 
-2010-05-19  Eli Zaretskii  <eliz@gnu.org>
-
 	* bidi.c (bidi_cache_shrink, bidi_cache_iterator_state): Fix
 	reallocation of the cache.  (Bug#6210)
 
@@ -175,6 +220,8 @@
 	* xdisp.c (Fcurrent_bidi_paragraph_direction): New function.
 	(syms_of_xdisp): Defsubr it.
 
+	* cmds.c (Fforward_char, Fbackward_char): Doc fix.
+
 	* Makefile.in: Fix MSDOS-related comments.
 
 2010-05-15  Glenn Morris  <rgm@gnu.org>
--- a/src/bidi.c	Sat May 22 22:09:51 2010 +0300
+++ b/src/bidi.c	Sat May 22 22:32:21 2010 +0300
@@ -707,7 +707,6 @@
       bidi_copy_it (&bidi_cache[idx], bidi_it);
       if (!resolved)
 	bidi_cache[idx].resolved_level = -1;
-      bidi_cache[idx].new_paragraph = 0;
     }
   else
     {
--- a/src/dispextern.h	Sat May 22 22:09:51 2010 +0300
+++ b/src/dispextern.h	Sat May 22 22:32:21 2010 +0300
@@ -748,21 +748,29 @@
 
   /* First position in this row.  This is the text position, including
      overlay position information etc, where the display of this row
-     started, and can thus be less the position of the first glyph
-     (e.g. due to invisible text or horizontal scrolling).  BIDI Note:
-     This is the smallest character position in the row, but not
-     necessarily the character that is the leftmost on the display.  */
+     started, and can thus be less than the position of the first
+     glyph (e.g. due to invisible text or horizontal scrolling).
+     BIDI Note: In R2L rows, that have its reversed_p flag set, this
+     position is at or beyond the right edge of the row.  */
   struct display_pos start;
 
   /* Text position at the end of this row.  This is the position after
      the last glyph on this row.  It can be greater than the last
-     glyph position + 1, due to truncation, invisible text etc.  In an
-     up-to-date display, this should always be equal to the start
-     position of the next row.  BIDI Note: this is the character whose
-     buffer position is the largest, but not necessarily the rightmost
-     one on the display.  */
+     glyph position + 1, due to a newline that ends the line,
+     truncation, invisible text etc.  In an up-to-date display, this
+     should always be equal to the start position of the next row.
+     BIDI Note: In R2L rows, this position is at or beyond the left
+     edge of the row.  */
   struct display_pos end;
 
+  /* The smallest and the largest buffer positions that contributed to
+     glyphs in this row.  Note that due to bidi reordering, these are
+     in general different from the text positions stored in `start'
+     and `end' members above, and also different from the buffer
+     positions recorded in the glyphs displayed the leftmost and
+     rightmost on the screen.  */
+  struct text_pos minpos, maxpos;
+
   /* Non-zero means the overlay arrow bitmap is on this line.
      -1 means use default overlay arrow bitmap, else
      it specifies actual fringe bitmap number.  */
@@ -947,16 +955,16 @@
    displayed by ROW, which is not necessarily the smallest horizontal
    position.  */
 
-#define MATRIX_ROW_START_CHARPOS(ROW) ((ROW)->start.pos.charpos)
-#define MATRIX_ROW_START_BYTEPOS(ROW) ((ROW)->start.pos.bytepos)
+#define MATRIX_ROW_START_CHARPOS(ROW) ((ROW)->minpos.charpos)
+#define MATRIX_ROW_START_BYTEPOS(ROW) ((ROW)->minpos.bytepos)
 
 /* Return the character/ byte position at which ROW ends.  BIDI Note:
    this is the largest character/byte position among characters in
    ROW, i.e. the last logical-order character displayed by ROW, which
    is not necessarily the largest horizontal position.  */
 
-#define MATRIX_ROW_END_CHARPOS(ROW) ((ROW)->end.pos.charpos)
-#define MATRIX_ROW_END_BYTEPOS(ROW) ((ROW)->end.pos.bytepos)
+#define MATRIX_ROW_END_CHARPOS(ROW) ((ROW)->maxpos.charpos)
+#define MATRIX_ROW_END_BYTEPOS(ROW) ((ROW)->maxpos.bytepos)
 
 /* Return the vertical position of ROW in MATRIX.  */
 
@@ -1789,7 +1797,7 @@
   EMACS_INT next_en_pos;	/* position of next EN char for ET */
   EMACS_INT ignore_bn_limit;	/* position until which to ignore BNs */
   bidi_dir_t sor;		/* direction of start-of-run in effect */
-  int scan_dir;			/* direction of text scan */
+  int scan_dir;			/* direction of text scan, 1: forw, -1: back */
   int stack_idx;		/* index of current data on the stack */
   /* Note: Everything from here on is not copied/saved when the bidi
      iterator state is saved, pushed, or popped.  So only put here
--- a/src/dispnew.c	Sat May 22 22:09:51 2010 +0300
+++ b/src/dispnew.c	Sat May 22 22:32:21 2010 +0300
@@ -1188,6 +1188,10 @@
   MATRIX_ROW_START_BYTEPOS (row) += delta_bytes;
   MATRIX_ROW_END_CHARPOS (row) += delta;
   MATRIX_ROW_END_BYTEPOS (row) += delta_bytes;
+  CHARPOS (row->start.pos) += delta;
+  BYTEPOS (row->start.pos) += delta_bytes;
+  CHARPOS (row->end.pos) += delta;
+  BYTEPOS (row->end.pos) += delta_bytes;
 
   if (!row->enabled_p)
     return;
@@ -1748,13 +1752,19 @@
       /* Check that character and byte positions are in sync.  */
       xassert (MATRIX_ROW_START_BYTEPOS (row)
 	       == CHAR_TO_BYTE (MATRIX_ROW_START_CHARPOS (row)));
+      xassert (BYTEPOS (row->start.pos)
+	       == CHAR_TO_BYTE (CHARPOS (row->start.pos)));
 
       /* CHAR_TO_BYTE aborts when invoked for a position > Z.  We can
 	 have such a position temporarily in case of a minibuffer
 	 displaying something like `[Sole completion]' at its end.  */
       if (MATRIX_ROW_END_CHARPOS (row) < BUF_ZV (current_buffer))
-	xassert (MATRIX_ROW_END_BYTEPOS (row)
-		 == CHAR_TO_BYTE (MATRIX_ROW_END_CHARPOS (row)));
+	{
+	  xassert (MATRIX_ROW_END_BYTEPOS (row)
+		   == CHAR_TO_BYTE (MATRIX_ROW_END_CHARPOS (row)));
+	  xassert (BYTEPOS (row->end.pos)
+		   == CHAR_TO_BYTE (CHARPOS (row->end.pos)));
+	}
 
       /* Check that end position of `row' is equal to start position
 	 of next row.  */
@@ -1764,6 +1774,8 @@
 		   == MATRIX_ROW_START_CHARPOS (next));
 	  xassert (MATRIX_ROW_END_BYTEPOS (row)
 		   == MATRIX_ROW_START_BYTEPOS (next));
+	  xassert (CHARPOS (row->end.pos) == CHARPOS (next->start.pos));
+	  xassert (BYTEPOS (row->end.pos) == BYTEPOS (next->start.pos));
 	}
       row = next;
     }
--- a/src/xdisp.c	Sat May 22 22:09:51 2010 +0300
+++ b/src/xdisp.c	Sat May 22 22:32:21 2010 +0300
@@ -2598,7 +2598,7 @@
 init_iterator (it, w, charpos, bytepos, row, base_face_id)
      struct it *it;
      struct window *w;
-     int charpos, bytepos;
+     EMACS_INT charpos, bytepos;
      struct glyph_row *row;
      enum face_id base_face_id;
 {
@@ -3012,7 +3012,7 @@
      struct window *w;
      struct display_pos *pos;
 {
-  int charpos = CHARPOS (pos->pos), bytepos = BYTEPOS (pos->pos);
+  EMACS_INT charpos = CHARPOS (pos->pos), bytepos = BYTEPOS (pos->pos);
   int i, overlay_strings_with_newlines = 0;
 
   /* If POS specifies a position in a display vector, this might
@@ -14975,7 +14975,7 @@
   /* The variable new_start now holds the new window start.  The old
      start `start' can be determined from the current matrix.  */
   SET_TEXT_POS_FROM_MARKER (new_start, w->start);
-  start = start_row->start.pos;
+  start = start_row->minpos;
   start_vpos = MATRIX_ROW_VPOS (start_row, w->current_matrix);
 
   /* Clear the desired matrix for the display below.  */
@@ -15014,7 +15014,7 @@
 	    {
 	      /* Advance to the next row as the "start".  */
 	      start_row++;
-	      start = start_row->start.pos;
+	      start = start_row->minpos;
 	      /* If there are no more rows to try, or just one, give up.  */
 	      if (start_row == MATRIX_MODE_LINE_ROW (w->current_matrix) - 1
 		  || w->vscroll || MATRIX_ROW_PARTIALLY_VISIBLE_P (w, start_row)
@@ -15296,39 +15296,26 @@
 	    {
 	      struct glyph *glyph = row->glyphs[TEXT_AREA] + w->cursor.hpos;
 	      struct glyph *end = glyph + row->used[TEXT_AREA];
-	      struct glyph *orig_glyph = glyph;
-	      struct cursor_pos orig_cursor = w->cursor;
-
-	      for (; glyph < end
-		     && (!BUFFERP (glyph->object)
-			 || glyph->charpos != PT);
-		   glyph++)
-		{
-		  w->cursor.hpos++;
-		  w->cursor.x += glyph->pixel_width;
-		}
-	      /* With bidi reordering, charpos changes non-linearly
-		 with hpos, so the right glyph could be to the
-		 left.  */
-	      if (!NILP (XBUFFER (w->buffer)->bidi_display_reordering)
-		  && (!BUFFERP (glyph->object) || glyph->charpos != PT))
-		{
-		  struct glyph *start_glyph = row->glyphs[TEXT_AREA];
-
-		  glyph = orig_glyph - 1;
-		  orig_cursor.hpos--;
-		  orig_cursor.x -= glyph->pixel_width;
-		  for (; glyph >= start_glyph
-			 && (!BUFFERP (glyph->object)
-			     || glyph->charpos != PT);
-		       glyph--)
-		    {
-		      w->cursor.hpos--;
-		      w->cursor.x -= glyph->pixel_width;
-		    }
-		  if (BUFFERP (glyph->object) && glyph->charpos == PT)
-		    w->cursor = orig_cursor;
-		}
+
+	      /* Can't use this optimization with bidi-reordered glyph
+		 rows, unless cursor is already at point. */
+	      if (!NILP (XBUFFER (w->buffer)->bidi_display_reordering))
+		{
+		  if (!(w->cursor.hpos >= 0
+			&& w->cursor.hpos < row->used[TEXT_AREA]
+			&& BUFFERP (glyph->object)
+			&& glyph->charpos == PT))
+		    return 0;
+		}
+	      else
+		for (; glyph < end
+		       && (!BUFFERP (glyph->object)
+			   || glyph->charpos < PT);
+		     glyph++)
+		  {
+		    w->cursor.hpos++;
+		    w->cursor.x += glyph->pixel_width;
+		  }
 	    }
 	}
 
@@ -15908,13 +15895,13 @@
 	 as is, without changing glyph positions since no text has
 	 been added/removed in front of the window end.  */
       r0 = MATRIX_FIRST_TEXT_ROW (current_matrix);
-      if (TEXT_POS_EQUAL_P (start, r0->start.pos)
+      if (TEXT_POS_EQUAL_P (start, r0->minpos)
 	  /* PT must not be in a partially visible line.  */
 	  && !(PT >= MATRIX_ROW_START_CHARPOS (row)
 	       && MATRIX_ROW_BOTTOM_Y (row) > window_text_bottom_y (w)))
 	{
 	  /* We have to compute the window end anew since text
-	     can have been added/removed after it.  */
+	     could have been added/removed after it.  */
 	  w->window_end_pos
 	    = make_number (Z - MATRIX_ROW_END_CHARPOS (row));
 	  w->window_end_bytepos
@@ -15946,7 +15933,7 @@
      start is not in changed text, otherwise positions would not be
      comparable.  */
   row = MATRIX_FIRST_TEXT_ROW (current_matrix);
-  if (!TEXT_POS_EQUAL_P (start, row->start.pos))
+  if (!TEXT_POS_EQUAL_P (start, row->minpos))
     GIVE_UP (16);
 
   /* Give up if the window ends in strings.  Overlay strings
@@ -17338,7 +17325,7 @@
 {
   int cursor_row_p = 1;
 
-  if (PT == MATRIX_ROW_END_CHARPOS (row))
+  if (PT == CHARPOS (row->end.pos))
     {
       /* Suppose the row ends on a string.
 	 Unless the row is continued, that means it ends on a newline
@@ -17375,14 +17362,15 @@
 	{
 	  /* If the row ends in middle of a real character,
 	     and the line is continued, we want the cursor here.
-	     That's because MATRIX_ROW_END_CHARPOS would equal
+	     That's because CHARPOS (ROW->end.pos) would equal
 	     PT if PT is before the character.  */
 	  if (!row->ends_in_ellipsis_p)
 	    cursor_row_p = row->continued_p;
 	  else
 	  /* If the row ends in an ellipsis, then
-	     MATRIX_ROW_END_CHARPOS will equal point after the invisible text.
-	     We want that position to be displayed after the ellipsis.  */
+	     CHARPOS (ROW->end.pos) will equal point after the
+	     invisible text.  We want that position to be displayed
+	     after the ellipsis.  */
 	    cursor_row_p = 0;
 	}
       /* If the row ends at ZV, display the cursor at the end of that
@@ -17518,122 +17506,87 @@
     glyph[-n] = *glyph;
 }
 
-/* Find the positions in a bidi-reordered ROW to serve as ROW->start
-   and ROW->end.  */
-static struct display_pos
-find_row_end (it, row)
+/* Find the positions in a bidi-reordered ROW to serve as ROW->minpos
+   and ROW->maxpos.  */
+static void
+find_row_edges (it, row, min_pos, min_bpos, max_pos, max_bpos)
      struct it *it;
      struct glyph_row *row;
+     EMACS_INT min_pos, min_bpos, max_pos, max_bpos;
 {
   /* FIXME: Revisit this when glyph ``spilling'' in continuation
      lines' rows is implemented for bidi-reordered rows.  */
-  EMACS_INT min_pos = ZV + 1, max_pos = 0;
-  struct glyph *g;
-  struct it save_it;
-  struct text_pos tpos;
-  struct display_pos row_end = it->current;
-
-  for (g = row->glyphs[TEXT_AREA];
-       g < row->glyphs[TEXT_AREA] + row->used[TEXT_AREA];
-       g++)
-    {
-      if (BUFFERP (g->object))
-	{
-	  if (g->charpos > 0 && g->charpos < min_pos)
-	    min_pos = g->charpos;
-	  if (g->charpos > max_pos)
-	    max_pos = g->charpos;
-	}
-    }
-  /* Empty lines have a valid buffer position at their first
-     glyph, but that glyph's OBJECT is zero, as if it didn't come
-     from a buffer.  If we didn't find any valid buffer positions
-     in this row, maybe we have such an empty line.  */
-  if (max_pos == 0 && row->used[TEXT_AREA])
-    {
-      for (g = row->glyphs[TEXT_AREA];
-	   g < row->glyphs[TEXT_AREA] + row->used[TEXT_AREA];
-	   g++)
-	{
-	  if (INTEGERP (g->object))
-	    {
-	      if (g->charpos > 0 && g->charpos < min_pos)
-		min_pos = g->charpos;
-	      if (g->charpos > max_pos)
-		max_pos = g->charpos;
-	    }
-	}
-    }
-
-  /* ROW->start is the value of min_pos, the minimal buffer position
+
+  /* ROW->minpos is the value of min_pos, the minimal buffer position
      we have in ROW.  */
   if (min_pos <= ZV)
-    {
-      /* Avoid calling the costly CHAR_TO_BYTE if possible.  */
-      if (min_pos != row->start.pos.charpos)
-	SET_TEXT_POS (row->start.pos, min_pos, CHAR_TO_BYTE (min_pos));
-      if (max_pos == 0)
-	max_pos = min_pos;
-    }
-
-  /* For ROW->end, we need the position that is _after_ max_pos, in
-     the logical order, unless we are at ZV.  */
+    SET_TEXT_POS (row->minpos, min_pos, min_bpos);
+  else
+    {
+      /* We didn't find _any_ valid buffer positions in any of the
+	 glyphs, so we must trust the iterator's computed
+	 positions.  */
+      row->minpos = row->start.pos;
+      max_pos = CHARPOS (it->current.pos);
+      max_bpos = BYTEPOS (it->current.pos);
+    }
+
+  if (!max_pos)
+    abort ();
+
+  /* Here are the various use-cases for ending the row, and the
+     corresponding values for ROW->maxpos:
+
+     Line ends in a newline from buffer       eol_pos + 1
+     Line is continued from buffer            max_pos + 1
+     Line is truncated on right               it->current.pos
+     Line ends in a newline from string       max_pos
+     Line is continued from string            max_pos
+     Line is continued from display vector    max_pos
+     Line is entirely from a string           min_pos == max_pos
+     Line is entirely from a display vector   min_pos == max_pos
+     Line that ends at ZV                     ZV
+
+     If you discover other use-cases, please add them here as
+     appropriate.  */
   if (row->ends_at_zv_p)
-    {
-      if (!row->used[TEXT_AREA])
-	row->start.pos = row_end.pos;
-    }
-  else if (row->used[TEXT_AREA] && max_pos)
-    {
-      int at_eol_p;
-
-      SET_TEXT_POS (tpos, max_pos, CHAR_TO_BYTE (max_pos));
-      save_it = *it;
-      it->bidi_p = 0;
-      reseat (it, tpos, 0);
-      if (!get_next_display_element (it))
-	abort ();	/* this row cannot be at ZV, see above */
-      at_eol_p = ITERATOR_AT_END_OF_LINE_P (it);
-      set_iterator_to_next (it, 1);
-      row_end = it->current;
-      /* If the character at max_pos is not a newline and the
-	 characters at max_pos+1 is a newline, skip that newline as
-	 well.  Note that this may skip some invisible text.  */
-      if (!at_eol_p
-	  && get_next_display_element (it)
-	  && ITERATOR_AT_END_OF_LINE_P (it))
-	{
-	  set_iterator_to_next (it, 1);
-	  /* Record the position after the newline of a continued row.
-	     We will need that to set ROW->end of the last row
-	     produced for a continued line.  */
-	  if (row->continued_p)
-	    save_it.eol_pos = it->current.pos;
-	  else
-	    {
-	      row_end = it->current;
-	      save_it.eol_pos.charpos = save_it.eol_pos.bytepos = 0;
-	    }
-	}
-      else if (!row->continued_p
-	       && MATRIX_ROW_CONTINUATION_LINE_P (row)
-	       && it->eol_pos.charpos > 0)
-	{
-	  /* Last row of a continued line.  Use the position recorded
-	     in IT->eol_pos, to the effect that the newline belongs to
-	     this row, not to the row which displays the character
-	     with the largest buffer position before the newline.  */
-	  row_end.pos = it->eol_pos;
-	  it->eol_pos.charpos = it->eol_pos.bytepos = 0;
-	}
-      *it = save_it;
-      /* The members of ROW->end that are not taken from buffer
-	 positions are copied from IT->current.  */
-      row_end.string_pos = it->current.string_pos;
-      row_end.overlay_string_index = it->current.overlay_string_index;
-      row_end.dpvec_index = it->current.dpvec_index;
-    }
-  return row_end;
+    row->maxpos = it->current.pos;
+  else if (row->used[TEXT_AREA])
+    {
+      if (row->ends_in_newline_from_string_p)
+	SET_TEXT_POS (row->maxpos, max_pos, max_bpos);
+      else if (CHARPOS (it->eol_pos) > 0)
+	SET_TEXT_POS (row->maxpos,
+		      CHARPOS (it->eol_pos) + 1, BYTEPOS (it->eol_pos) + 1);
+      else if (row->continued_p)
+	{
+	  /* If max_pos is different from IT's current position, it
+	     means IT->method does not belong to the display element
+	     at max_pos.  However, it also means that the display
+	     element at max_pos was displayed in its entirety on this
+	     line, which is equivalent to saying that the next line
+	     starts at the next buffer position.  */
+	  if (IT_CHARPOS (*it) == max_pos && it->method != GET_FROM_BUFFER)
+	    SET_TEXT_POS (row->maxpos, max_pos, max_bpos);
+	  else
+	    {
+	      INC_BOTH (max_pos, max_bpos);
+	      SET_TEXT_POS (row->maxpos, max_pos, max_bpos);
+	    }
+	}
+      else if (row->truncated_on_right_p)
+	/* display_line already called reseat_at_next_visible_line_start,
+	   which puts the iterator at the beginning of the next line, in
+	   the logical order. */
+	row->maxpos = it->current.pos;
+      else if (max_pos == min_pos && it->method != GET_FROM_BUFFER)
+	/* A line that is entirely from a string/image/stretch...  */
+	row->maxpos = row->minpos;
+      else
+	abort ();
+    }
+  else
+    row->maxpos = it->current.pos;
 }
 
 /* Construct the glyph row IT->glyph_row in the desired matrix of
@@ -17653,7 +17606,10 @@
   int wrap_row_used = -1, wrap_row_ascent, wrap_row_height;
   int wrap_row_phys_ascent, wrap_row_phys_height;
   int wrap_row_extra_line_spacing;
+  EMACS_INT wrap_row_min_pos, wrap_row_min_bpos;
+  EMACS_INT wrap_row_max_pos, wrap_row_max_bpos;
   int cvpos;
+  EMACS_INT min_pos = ZV + 1, min_bpos, max_pos = 0, max_bpos;
 
   /* We always start displaying at hpos zero even if hscrolled.  */
   xassert (it->hpos == 0 && it->current_x == 0);
@@ -17710,6 +17666,23 @@
   row->phys_height = it->max_phys_ascent + it->max_phys_descent;
   row->extra_line_spacing = it->max_extra_line_spacing;
 
+/* Utility macro to record max and min buffer positions seen until now.  */
+#define RECORD_MAX_MIN_POS(IT)					\
+  do								\
+    {								\
+      if (IT_CHARPOS (*(IT)) < min_pos)				\
+	{							\
+	  min_pos = IT_CHARPOS (*(IT));				\
+	  min_bpos = IT_BYTEPOS (*(IT));			\
+	}							\
+      if (IT_CHARPOS (*(IT)) > max_pos)				\
+	{							\
+	  max_pos = IT_CHARPOS (*(IT));				\
+	  max_bpos = IT_BYTEPOS (*(IT));			\
+	}							\
+    }								\
+  while (0)
+
   /* Loop generating characters.  The loop is left with IT on the next
      character to display.  */
   while (1)
@@ -17744,7 +17717,8 @@
 	  row->ends_at_zv_p = 1;
 	  /* A row that displays right-to-left text must always have
 	     its last face extended all the way to the end of line,
-	     even if this row ends in ZV.  */
+	     even if this row ends in ZV, because we still write to th
+	     screen left to right.  */
 	  if (row->reversed_p)
 	    extend_face_to_end_of_line (it);
 	  break;
@@ -17778,6 +17752,10 @@
 		  wrap_row_phys_ascent = row->phys_ascent;
 		  wrap_row_phys_height = row->phys_height;
 		  wrap_row_extra_line_spacing = row->extra_line_spacing;
+		  wrap_row_min_pos = min_pos;
+		  wrap_row_min_bpos = min_bpos;
+		  wrap_row_max_pos = max_pos;
+		  wrap_row_max_bpos = max_bpos;
 		  may_wrap = 0;
 		}
 	    }
@@ -17828,6 +17806,10 @@
 					 it->max_extra_line_spacing);
 	  if (it->current_x - it->pixel_width < it->first_visible_x)
 	    row->x = x - it->first_visible_x;
+	  /* Record the maximum and minimum buffer positions seen so
+	     far in glyphs that will be displayed by this row.  */
+	  if (it->bidi_p)
+	    RECORD_MAX_MIN_POS (it);
 	}
       else
 	{
@@ -17861,6 +17843,11 @@
 		      it->current_x = new_x;
 		      it->continuation_lines_width += new_x;
 		      ++it->hpos;
+		      /* Record the maximum and minimum buffer
+			 positions seen so far in glyphs that will be
+			 displayed by this row.  */
+		      if (it->bidi_p)
+			RECORD_MAX_MIN_POS (it);
 		      if (i == nglyphs - 1)
 			{
 			  /* If line-wrap is on, check if a previous
@@ -17935,6 +17922,10 @@
 		      row->phys_ascent = wrap_row_phys_ascent;
 		      row->phys_height = wrap_row_phys_height;
 		      row->extra_line_spacing = wrap_row_extra_line_spacing;
+		      min_pos = wrap_row_min_pos;
+		      min_bpos = wrap_row_min_bpos;
+		      max_pos = wrap_row_max_pos;
+		      max_bpos = wrap_row_max_bpos;
 		      row->continued_p = 1;
 		      row->ends_at_zv_p = 0;
 		      row->exact_window_width_line_p = 0;
@@ -17997,6 +17988,12 @@
 		  /* Increment number of glyphs actually displayed.  */
 		  ++it->hpos;
 
+		  /* Record the maximum and minimum buffer positions
+		     seen so far in glyphs that will be displayed by
+		     this row.  */
+		  if (it->bidi_p)
+		    RECORD_MAX_MIN_POS (it);
+
 		  if (x < it->first_visible_x)
 		    /* Glyph is partially visible, i.e. row starts at
 		       negative X position.  */
@@ -18048,6 +18045,10 @@
 	  if (used_before == 0)
 	    row->glyphs[TEXT_AREA]->charpos = CHARPOS (it->position);
 
+	  /* Record the position of the newline, for use in
+	     find_row_edges.  */
+	  it->eol_pos = it->current.pos;
+
 	  /* Consume the line end.  This skips over invisible lines.  */
 	  set_iterator_to_next (it, 1);
 	  it->continuation_lines_width = 0;
@@ -18127,7 +18128,7 @@
   /* If line is not empty and hscrolled, maybe insert truncation glyphs
      at the left window margin.  */
   if (it->first_visible_x
-      && IT_CHARPOS (*it) != MATRIX_ROW_START_CHARPOS (row))
+      && IT_CHARPOS (*it) != CHARPOS (row->start.pos))
     {
       if (!FRAME_WINDOW_P (it->f))
 	insert_left_trunc_glyphs (it);
@@ -18181,12 +18182,19 @@
 
   /* Remember the position at which this line ends.  */
   row->end = it->current;
-  /* ROW->start and ROW->end must be the smallest and the largest
-     buffer positions in ROW.  But if ROW was bidi-reordered, these
-     two positions can be anywhere in the row, so we must rescan all
-     of the ROW's glyphs to find them.  */
-  if (it->bidi_p)
-    row->end = find_row_end (it, row);
+  if (!it->bidi_p)
+    {
+      row->minpos = row->start.pos;
+      row->maxpos = row->end.pos;
+    }
+  else
+    {
+      /* ROW->minpos and ROW->maxpos must be the smallest and
+	 `1 + the largest' buffer positions in ROW.  But if ROW was
+	 bidi-reordered, these two positions can be anywhere in the
+	 row, so we must determine them now.  */
+      find_row_edges (it, row, min_pos, min_bpos, max_pos, max_bpos);
+    }
 
   /* Record whether this row ends inside an ellipsis.  */
   row->ends_in_ellipsis_p
@@ -18232,6 +18240,7 @@
      row to be used.  */
   it->current_x = it->hpos = 0;
   it->current_y += row->height;
+  SET_TEXT_POS (it->eol_pos, 0, 0);
   ++it->vpos;
   ++it->glyph_row;
   /* The next row should by default use the same value of the
@@ -18242,6 +18251,8 @@
     it->glyph_row->reversed_p = row->reversed_p;
   it->start = row->end;
   return row->displays_text_p;
+
+#undef RECORD_MAX_MIN_POS
 }
 
 DEFUN ("current-bidi-paragraph-direction", Fcurrent_bidi_paragraph_direction,