richedit: Unsigned cast char to avoid EOF equality.
[wine] / dlls / riched20 / wrap.c
index d48da58..d6a2492 100644 (file)
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */
 
 
@@ -32,7 +32,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(richedit);
  * - no tabs
  */
 
-ME_DisplayItem *ME_MakeRow(int height, int baseline, int width)
+static ME_DisplayItem *ME_MakeRow(int height, int baseline, int width)
 {
   ME_DisplayItem *item = ME_MakeDI(diStartRow);
 
@@ -42,35 +42,107 @@ ME_DisplayItem *ME_MakeRow(int height, int baseline, int width)
   return item;
 }
 
-void ME_BeginRow(ME_WrapContext *wc)
+static void ME_BeginRow(ME_WrapContext *wc, ME_DisplayItem *para)
 {
+  PARAFORMAT2 *pFmt;
+  assert(para && para->type == diParagraph);
+  pFmt = para->member.para.pFmt;
   wc->pRowStart = NULL;
   wc->bOverflown = FALSE;
   wc->pLastSplittableRun = NULL;
-  wc->nAvailWidth = wc->nTotalWidth - (wc->nRow ? wc->nLeftMargin : wc->nFirstMargin) - wc->nRightMargin;
-  wc->pt.x = 0;
+  if (para->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND)) {
+    wc->nAvailWidth = 0;
+    if (para->member.para.nFlags & MEPF_ROWEND)
+    {
+      ME_Cell *cell = &ME_FindItemBack(para, diCell)->member.cell;
+      cell->nWidth = 0;
+    }
+  } else if (para->member.para.pCell) {
+    ME_Cell *cell = &para->member.para.pCell->member.cell;
+    int width;
+
+    width = cell->nRightBoundary;
+    if (cell->prev_cell)
+      width -= cell->prev_cell->member.cell.nRightBoundary;
+    if (!cell->prev_cell)
+    {
+      int rowIndent = ME_GetTableRowEnd(para)->member.para.pFmt->dxStartIndent;
+      width -= rowIndent;
+    }
+    cell->nWidth = max(ME_twips2pointsX(wc->context, width), 0);
+
+    wc->nAvailWidth = cell->nWidth
+        - (wc->nRow ? wc->nLeftMargin : wc->nFirstMargin) - wc->nRightMargin;
+  } else if (wc->context->editor->bWordWrap) {
+    wc->nAvailWidth = wc->context->rcView.right - wc->context->rcView.left
+        - (wc->nRow ? wc->nLeftMargin : wc->nFirstMargin) - wc->nRightMargin;
+  } else {
+    wc->nAvailWidth = ~0u >> 1;
+  }
+  wc->pt.x = wc->context->pt.x;
+  if (wc->context->editor->bEmulateVersion10 && /* v1.0 - 3.0 */
+      pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
+    /* Shift the text down because of the border. */
+    wc->pt.y++;
 }
 
-void ME_InsertRowStart(ME_WrapContext *wc, ME_DisplayItem *pEnd)
+static void ME_InsertRowStart(ME_WrapContext *wc, const ME_DisplayItem *pEnd)
 {
   ME_DisplayItem *p, *row, *para;
+  BOOL bSkippingSpaces = TRUE;
   int ascent = 0, descent = 0, width=0, shift = 0, align = 0;
+  PARAFORMAT2 *pFmt;
   /* wrap text */
   para = ME_GetParagraph(wc->pRowStart);
-  for (p = wc->pRowStart; p!=pEnd; p = p->next)
+  pFmt = para->member.para.pFmt;
+
+  for (p = pEnd->prev; p!=wc->pRowStart->prev; p = p->prev)
   {
-    /* ENDPARA run shouldn't affect row height, except if it's the only run in the paragraph */
-    if (p->type==diRun && ((p==wc->pRowStart) || !(p->member.run.nFlags & MERF_ENDPARA))) { /* FIXME add more run types */
-      if (p->member.run.nAscent>ascent)
-        ascent = p->member.run.nAscent;
-      if (p->member.run.nDescent>descent)
-        descent = p->member.run.nDescent;
-      if (!(p->member.run.nFlags & (MERF_ENDPARA|MERF_SKIPPED)))
-        width += p->member.run.nWidth;
-    }
+      /* ENDPARA run shouldn't affect row height, except if it's the only run in the paragraph */
+      if (p->type==diRun && ((p==wc->pRowStart) || !(p->member.run.nFlags & MERF_ENDPARA))) { /* FIXME add more run types */
+        if (p->member.run.nAscent>ascent)
+          ascent = p->member.run.nAscent;
+        if (p->member.run.nDescent>descent)
+          descent = p->member.run.nDescent;
+        if (bSkippingSpaces)
+        {
+          /* Exclude space characters from run width.
+           * Other whitespace or delimiters are not treated this way. */
+          SIZE sz;
+          int len = p->member.run.strText->nLen;
+          WCHAR *text = p->member.run.strText->szData + len - 1;
+
+          assert (len);
+          while (len && *(text--) == ' ')
+              len--;
+          if (len)
+          {
+              if (len == p->member.run.strText->nLen)
+              {
+                  width += p->member.run.nWidth;
+              } else {
+                  sz = ME_GetRunSize(wc->context, &para->member.para,
+                                     &p->member.run, len, p->member.run.pt.x);
+                  width += sz.cx;
+              }
+          }
+          bSkippingSpaces = !len;
+        } else if (!(p->member.run.nFlags & MERF_ENDPARA))
+          width += p->member.run.nWidth;
+      }
   }
+
   row = ME_MakeRow(ascent+descent, ascent, width);
-  row->member.row.nYPos = wc->pt.y;
+  if (wc->context->editor->bEmulateVersion10 && /* v1.0 - 3.0 */
+      pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
+  {
+    /* The text was shifted down in ME_BeginRow so move the wrap context
+     * back to where it should be. */
+    wc->pt.y--;
+    /* The height of the row is increased by the borders. */
+    row->member.row.nHeight += 2;
+  }
+  row->member.row.pt = wc->pt;
   row->member.row.nLMargin = (!wc->nRow ? wc->nFirstMargin : wc->nLeftMargin);
   row->member.row.nRMargin = wc->nRightMargin;
   assert(para->member.para.pFmt->dwMask & PFM_ALIGNMENT);
@@ -87,14 +159,23 @@ void ME_InsertRowStart(ME_WrapContext *wc, ME_DisplayItem *pEnd)
   }
   ME_InsertBefore(wc->pRowStart, row);
   wc->nRow++;
-  wc->pt.y += ascent+descent;
-  ME_BeginRow(wc);
+  wc->pt.y += row->member.row.nHeight;
+  ME_BeginRow(wc, para);
 }
 
-void ME_WrapEndParagraph(ME_WrapContext *wc, ME_DisplayItem *p)
+static void ME_WrapEndParagraph(ME_WrapContext *wc, ME_DisplayItem *p)
 {
+  ME_DisplayItem *para = p->member.para.prev_para;
+  PARAFORMAT2 *pFmt = para->member.para.pFmt;
   if (wc->pRowStart)
-    ME_InsertRowStart(wc, p->next);
+    ME_InsertRowStart(wc, p);
+  if (wc->context->editor->bEmulateVersion10 && /* v1.0 - 3.0 */
+      pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
+  {
+    /* ME_BeginRow was called an extra time for the paragraph, and it shifts the
+     * text down by one pixel for the border, so fix up the wrap context. */
+    wc->pt.y--;
+  }
 
   /*
   p = p->member.para.prev_para->next;
@@ -111,16 +192,17 @@ void ME_WrapEndParagraph(ME_WrapContext *wc, ME_DisplayItem *p)
   */
 }
 
-void ME_WrapSizeRun(ME_WrapContext *wc, ME_DisplayItem *p)
+static void ME_WrapSizeRun(ME_WrapContext *wc, ME_DisplayItem *p)
 {
   /* FIXME compose style (out of character and paragraph styles) here */
 
   ME_UpdateRunFlags(wc->context->editor, &p->member.run);
 
-  ME_CalcRunExtent(wc->context, &ME_GetParagraph(p)->member.para, &p->member.run);
+  ME_CalcRunExtent(wc->context, &ME_GetParagraph(p)->member.para,
+                   wc->nRow ? wc->nLeftMargin : wc->nFirstMargin, &p->member.run);
 }
 
-ME_DisplayItem *ME_MaximizeSplit(ME_WrapContext *wc, ME_DisplayItem *p, int i)
+static ME_DisplayItem *ME_MaximizeSplit(ME_WrapContext *wc, ME_DisplayItem *p, int i)
 {
   ME_DisplayItem *pp, *piter = p;
   int j;
@@ -128,7 +210,7 @@ ME_DisplayItem *ME_MaximizeSplit(ME_WrapContext *wc, ME_DisplayItem *p, int i)
     return NULL;
   j = ME_ReverseFindNonWhitespaceV(p->member.run.strText, i);
   if (j>0) {
-    pp = ME_SplitRun(wc->context, piter, j);
+    pp = ME_SplitRun(wc, piter, j);
     wc->pt.x += piter->member.run.nWidth;
     return pp;
   }
@@ -147,7 +229,7 @@ ME_DisplayItem *ME_MaximizeSplit(ME_WrapContext *wc, ME_DisplayItem *p, int i)
       if (piter->member.run.nFlags & MERF_ENDWHITE)
       {
         j = ME_ReverseFindNonWhitespaceV(piter->member.run.strText, i);
-        pp = ME_SplitRun(wc->context, piter, i);
+        pp = ME_SplitRun(wc, piter, i);
         wc->pt = pp->member.run.pt;
         return pp;
       }
@@ -162,13 +244,13 @@ ME_DisplayItem *ME_MaximizeSplit(ME_WrapContext *wc, ME_DisplayItem *p, int i)
   }
 }
 
-ME_DisplayItem *ME_SplitByBacktracking(ME_WrapContext *wc, ME_DisplayItem *p, int loc)
+static ME_DisplayItem *ME_SplitByBacktracking(ME_WrapContext *wc, ME_DisplayItem *p, int loc)
 {
   ME_DisplayItem *piter = p, *pp;
   int i, idesp, len;
   ME_Run *run = &p->member.run;
 
-  idesp = i = ME_CharFromPoint(wc->context->editor, loc, &ME_GetParagraph(p)->member.para, run);
+  idesp = i = ME_CharFromPoint(wc->context, loc, run);
   len = ME_StrVLen(run->strText);
   assert(len>0);
   assert(i<len);
@@ -202,7 +284,7 @@ ME_DisplayItem *ME_SplitByBacktracking(ME_WrapContext *wc, ME_DisplayItem *p, in
       if (i == len)
         i = ME_ReverseFindNonWhitespaceV(run->strText, len);
       if (i) {
-        ME_DisplayItem *piter2 = ME_SplitRun(wc->context, piter, i);
+        ME_DisplayItem *piter2 = ME_SplitRun(wc, piter, i);
         wc->pt = piter2->member.run.pt;
         return piter2;
       }
@@ -219,7 +301,7 @@ ME_DisplayItem *ME_SplitByBacktracking(ME_WrapContext *wc, ME_DisplayItem *p, in
   TRACE("Backtracking failed, trying desperate: %s\n", debugstr_w(p->member.run.strText->szData));
   /* OK, no better idea, so assume we MAY split words if we can split at all*/
   if (idesp)
-    return ME_SplitRun(wc->context, piter, idesp);
+    return ME_SplitRun(wc, piter, idesp);
   else
   if (wc->pRowStart && piter != wc->pRowStart)
   {
@@ -235,14 +317,14 @@ ME_DisplayItem *ME_SplitByBacktracking(ME_WrapContext *wc, ME_DisplayItem *p, in
     int pos2 = ME_StrRelPos(run->strText, 0, &chars);
     if (pos2 != len) {
       /* the run is more than 1 char, so we may split */
-      return ME_SplitRun(wc->context, piter, pos2);
+      return ME_SplitRun(wc, piter, pos2);
     }
     /* the run is one char, can't split it */
     return piter;
   }
 }
 
-ME_DisplayItem *ME_WrapHandleRun(ME_WrapContext *wc, ME_DisplayItem *p)
+static ME_DisplayItem *ME_WrapHandleRun(ME_WrapContext *wc, ME_DisplayItem *p)
 {
   ME_DisplayItem *pp;
   ME_Run *run;
@@ -259,9 +341,13 @@ ME_DisplayItem *ME_WrapHandleRun(ME_WrapContext *wc, ME_DisplayItem *p)
 
   if (wc->bOverflown) /* just skipping final whitespaces */
   {
-    if (run->nFlags & (MERF_WHITESPACE|MERF_TAB)) {
+    /* End paragraph run can't overflow to the next line by itself. */
+    if (run->nFlags & MERF_ENDPARA)
+      return p->next;
+
+    if (run->nFlags & MERF_WHITESPACE) {
       p->member.run.nFlags |= MERF_SKIPPED;
-      /* wc->pt.x += run->nWidth; */
+      wc->pt.x += run->nWidth;
       /* skip runs consisting of only whitespaces */
       return p->next;
     }
@@ -272,7 +358,7 @@ ME_DisplayItem *ME_WrapHandleRun(ME_WrapContext *wc, ME_DisplayItem *p)
       black = ME_FindNonWhitespaceV(run->strText, 0);
       if (black) {
         wc->bOverflown = FALSE;
-        pp = ME_SplitRun(wc->context, p, black);
+        pp = ME_SplitRun(wc, p, black);
         p->member.run.nFlags |= MERF_SKIPPED;
         ME_InsertRowStart(wc, pp);
         return pp;
@@ -282,19 +368,36 @@ ME_DisplayItem *ME_WrapHandleRun(ME_WrapContext *wc, ME_DisplayItem *p)
     ME_InsertRowStart(wc, p);
     return p;
   }
-  /* we're not at the end of the row */
+  /* simply end the current row and move on to next one */
+  if (run->nFlags & MERF_ENDROW)
+  {
+    p = p->next;
+    ME_InsertRowStart(wc, p);
+    return p;
+  }
+
   /* will current run fit? */
-  if (wc->pt.x + run->nWidth > wc->nAvailWidth)
+  if (wc->pt.x + run->nWidth - wc->context->pt.x > wc->nAvailWidth)
   {
-    int loc = wc->nAvailWidth - wc->pt.x;
+    int loc = wc->context->pt.x + wc->nAvailWidth - wc->pt.x;
     /* total white run ? */
     if (run->nFlags & MERF_WHITESPACE) {
       /* let the overflow logic handle it */
       wc->bOverflown = TRUE;
       return p;
     }
-    /* graphics or TAB - we can split before */
-    if (run->nFlags & (MERF_GRAPHICS|MERF_TAB)) {
+    /* TAB: we can split before */
+    if (run->nFlags & MERF_TAB) {
+      wc->bOverflown = TRUE;
+      if (wc->pRowStart == p)
+        /* Don't split before the start of the run, or we will get an
+         * endless loop. */
+        return p->next;
+      else
+        return p;
+    }
+    /* graphics: we can split before, if run's width is smaller than row's width */
+    if ((run->nFlags & MERF_GRAPHICS) && run->nWidth <= wc->nAvailWidth) {
       wc->bOverflown = TRUE;
       return p;
     }
@@ -303,7 +406,7 @@ ME_DisplayItem *ME_WrapHandleRun(ME_WrapContext *wc, ME_DisplayItem *p)
     {
       /* we aren't sure if it's *really* necessary, it's a good start however */
       int black = ME_ReverseFindNonWhitespaceV(run->strText, len);
-      ME_SplitRun(wc->context, p, black);
+      ME_SplitRun(wc, p, black);
       /* handle both parts again */
       return p;
     }
@@ -311,8 +414,22 @@ ME_DisplayItem *ME_WrapHandleRun(ME_WrapContext *wc, ME_DisplayItem *p)
     pp = ME_SplitByBacktracking(wc, p, loc);
     if (pp == wc->pRowStart)
     {
-      /* we had only spaces so far, entire content can be omitted */
-      wc->pt.x = 0;
+      if (run->nFlags & MERF_STARTWHITE)
+      {
+          /* We had only spaces so far, so we must be on the first line of the
+           * paragraph (or the first line after MERF_ENDROW forced the line
+           * break within the paragraph), since no other lines of the paragraph
+           * start with spaces. */
+
+          /* The lines will only contain spaces, and the rest of the run will
+           * overflow onto the next line. */
+          wc->bOverflown = TRUE;
+          return p;
+      }
+      /* Couldn't split the first run, possible because we have a large font
+       * with a single character that caused an overflow.
+       */
+      wc->pt.x += run->nWidth;
       return p->next;
     }
     if (p != pp) /* found a suitable split point */
@@ -336,56 +453,96 @@ ME_DisplayItem *ME_WrapHandleRun(ME_WrapContext *wc, ME_DisplayItem *p)
   return p->next;
 }
 
-void ME_WrapTextParagraph(ME_Context *c, ME_DisplayItem *tp) {
+static void ME_PrepareParagraphForWrapping(ME_Context *c, ME_DisplayItem *tp);
+
+static void ME_WrapTextParagraph(ME_Context *c, ME_DisplayItem *tp, DWORD beginofs) {
   ME_DisplayItem *p;
   ME_WrapContext wc;
-  int dpi = GetDeviceCaps(c->hDC, LOGPIXELSX);
+  int border = 0;
+  int linespace = 0;
+  PARAFORMAT2 *pFmt;
 
   assert(tp->type == diParagraph);
   if (!(tp->member.para.nFlags & MEPF_REWRAP)) {
     return;
   }
   ME_PrepareParagraphForWrapping(c, tp);
+  pFmt = tp->member.para.pFmt;
 
   wc.context = c;
 /*   wc.para_style = tp->member.para.style; */
   wc.style = NULL;
-  tp->member.para.nRightMargin = tp->member.para.pFmt->dxRightIndent*dpi/1440;
-  tp->member.para.nFirstMargin = tp->member.para.pFmt->dxStartIndent*dpi/1440;
-  tp->member.para.nLeftMargin = (tp->member.para.pFmt->dxStartIndent+tp->member.para.pFmt->dxOffset)*dpi/1440;
-  wc.nFirstMargin = tp->member.para.nFirstMargin;
-  wc.nLeftMargin = tp->member.para.nLeftMargin;
-  wc.nRightMargin = tp->member.para.nRightMargin;
+  if (tp->member.para.nFlags & MEPF_ROWEND) {
+    wc.nFirstMargin = wc.nLeftMargin = wc.nRightMargin = 0;
+  } else {
+    int dxStartIndent = pFmt->dxStartIndent;
+    if (tp->member.para.pCell) {
+      dxStartIndent += ME_GetTableRowEnd(tp)->member.para.pFmt->dxOffset;
+    }
+    wc.nFirstMargin = ME_twips2pointsX(c, dxStartIndent);
+    wc.nLeftMargin = wc.nFirstMargin + ME_twips2pointsX(c, pFmt->dxOffset);
+    wc.nRightMargin = ME_twips2pointsX(c, pFmt->dxRightIndent);
+  }
+  if (c->editor->bEmulateVersion10 && /* v1.0 - 3.0 */
+      pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
+  {
+    wc.nFirstMargin += ME_twips2pointsX(c, pFmt->dxOffset * 2);
+  }
   wc.nRow = 0;
-  wc.pt.x = 0;
   wc.pt.y = 0;
-  wc.nTotalWidth = c->rcView.right - c->rcView.left;
-  wc.nAvailWidth = wc.nTotalWidth - wc.nFirstMargin - wc.nRightMargin;
-  wc.pRowStart = NULL;
+  if (pFmt->dwMask & PFM_SPACEBEFORE)
+    wc.pt.y += ME_twips2pointsY(c, pFmt->dySpaceBefore);
+  if (!(pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE) &&
+      pFmt->dwMask & PFM_BORDER)
+  {
+    border = ME_GetParaBorderWidth(c->editor, tp->member.para.pFmt->wBorders);
+    if (pFmt->wBorders & 1) {
+      wc.nFirstMargin += border;
+      wc.nLeftMargin += border;
+    }
+    if (pFmt->wBorders & 2)
+      wc.nRightMargin -= border;
+    if (pFmt->wBorders & 4)
+      wc.pt.y += border;
+  }
+
+  linespace = ME_GetParaLineSpace(c, &tp->member.para);
 
-  ME_BeginRow(&wc);
+  ME_BeginRow(&wc, tp);
   for (p = tp->next; p!=tp->member.para.next_para; ) {
     assert(p->type != diStartRow);
     if (p->type == diRun) {
       p = ME_WrapHandleRun(&wc, p);
-      continue;
     }
-    p = p->next;
+    else p = p->next;
+    if (wc.nRow && p == wc.pRowStart)
+      wc.pt.y += linespace;
   }
   ME_WrapEndParagraph(&wc, p);
+  if (!(pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE) &&
+      (pFmt->dwMask & PFM_BORDER) && (pFmt->wBorders & 8))
+    wc.pt.y += border;
+  if (tp->member.para.pFmt->dwMask & PFM_SPACEAFTER)
+    wc.pt.y += ME_twips2pointsY(c, pFmt->dySpaceAfter);
+
   tp->member.para.nFlags &= ~MEPF_REWRAP;
   tp->member.para.nHeight = wc.pt.y;
+  tp->member.para.nRows = wc.nRow;
 }
 
 
-void ME_PrepareParagraphForWrapping(ME_Context *c, ME_DisplayItem *tp) {
-  ME_DisplayItem *p;
+static void ME_PrepareParagraphForWrapping(ME_Context *c, ME_DisplayItem *tp) {
+  ME_DisplayItem *p, *pRow;
+
   /* remove all items that will be reinserted by paragraph wrapper anyway */
+  tp->member.para.nRows = 0;
   for (p = tp->next; p!=tp->member.para.next_para; p = p->next) {
     switch(p->type) {
       case diStartRow:
+        pRow = p;
         p = p->prev;
-        ME_Remove(p->next);
+        ME_Remove(pRow);
+        ME_DestroyDisplayItem(pRow);
         break;
       default:
         break;
@@ -414,40 +571,219 @@ void ME_PrepareParagraphForWrapping(ME_Context *c, ME_DisplayItem *tp) {
 }
 
 BOOL ME_WrapMarkedParagraphs(ME_TextEditor *editor) {
-  HWND hWnd = editor->hWnd;
-  HDC hDC = GetDC(hWnd);
   ME_DisplayItem *item;
   ME_Context c;
   BOOL bModified = FALSE;
+  int yStart = -1;
+  int yLastPos = 0;
 
-  ME_InitContext(&c, editor, hDC);
-  c.pt.x = 0;
-  c.pt.y = 0;
+  ME_InitContext(&c, editor, GetDC(editor->hWnd));
+  c.pt.x = editor->selofs;
+  editor->nHeight = 0;
   item = editor->pBuffer->pFirst->next;
   while(item != editor->pBuffer->pLast) {
     BOOL bRedraw = FALSE;
 
     assert(item->type == diParagraph);
+    editor->nHeight = max(editor->nHeight, item->member.para.pt.y);
     if ((item->member.para.nFlags & MEPF_REWRAP)
-     || (item->member.para.nYPos != c.pt.y))
+     || (item->member.para.pt.y != c.pt.y))
       bRedraw = TRUE;
-    item->member.para.nYPos = c.pt.y;
+    item->member.para.pt = c.pt;
 
-    ME_WrapTextParagraph(&c, item);
+    ME_WrapTextParagraph(&c, item, editor->selofs);
 
     if (bRedraw)
+    {
       item->member.para.nFlags |= MEPF_REPAINT;
+      if (yStart == -1)
+        yStart = c.pt.y;
+    }
 
     bModified = bModified | bRedraw;
 
-    c.pt.y += item->member.para.nHeight;
+    yLastPos = max(yLastPos, c.pt.y);
+
+    if (item->member.para.nFlags & MEPF_ROWSTART)
+    {
+      ME_DisplayItem *cell = ME_FindItemFwd(item, diCell);
+      ME_DisplayItem *endRowPara;
+      int borderWidth = 0;
+      cell->member.cell.pt = c.pt;
+      /* Offset the text by the largest top border width. */
+      while (cell->member.cell.next_cell) {
+        borderWidth = max(borderWidth, cell->member.cell.border.top.width);
+        cell = cell->member.cell.next_cell;
+      }
+      endRowPara = ME_FindItemFwd(cell, diParagraph);
+      assert(endRowPara->member.para.nFlags & MEPF_ROWEND);
+      if (borderWidth > 0)
+      {
+        borderWidth = max(ME_twips2pointsY(&c, borderWidth), 1);
+        while (cell) {
+          cell->member.cell.yTextOffset = borderWidth;
+          cell = cell->member.cell.prev_cell;
+        }
+        c.pt.y += borderWidth;
+      }
+      if (endRowPara->member.para.pFmt->dxStartIndent > 0)
+      {
+        int dxStartIndent = endRowPara->member.para.pFmt->dxStartIndent;
+        cell = ME_FindItemFwd(item, diCell);
+        cell->member.cell.pt.x += ME_twips2pointsX(&c, dxStartIndent);
+        c.pt.x = cell->member.cell.pt.x;
+      }
+    }
+    else if (item->member.para.nFlags & MEPF_ROWEND)
+    {
+      /* Set all the cells to the height of the largest cell */
+      ME_DisplayItem *startRowPara;
+      int prevHeight, nHeight, bottomBorder = 0;
+      ME_DisplayItem *cell = ME_FindItemBack(item, diCell);
+      if (!(item->member.para.next_para->member.para.nFlags & MEPF_ROWSTART))
+      {
+        /* Last row, the bottom border is added to the height. */
+        cell = cell->member.cell.prev_cell;
+        while (cell)
+        {
+          bottomBorder = max(bottomBorder, cell->member.cell.border.bottom.width);
+          cell = cell->member.cell.prev_cell;
+        }
+        bottomBorder = ME_twips2pointsY(&c, bottomBorder);
+        cell = ME_FindItemBack(item, diCell);
+      }
+      prevHeight = cell->member.cell.nHeight;
+      nHeight = cell->member.cell.prev_cell->member.cell.nHeight + bottomBorder;
+      cell->member.cell.nHeight = nHeight;
+      item->member.para.nHeight = nHeight;
+      cell = cell->member.cell.prev_cell;
+      cell->member.cell.nHeight = nHeight;
+      while (cell->member.cell.prev_cell)
+      {
+        cell = cell->member.cell.prev_cell;
+        cell->member.cell.nHeight = nHeight;
+      }
+      /* Also set the height of the start row paragraph */
+      startRowPara = ME_FindItemBack(cell, diParagraph);
+      startRowPara->member.para.nHeight = nHeight;
+      c.pt.x = startRowPara->member.para.pt.x;
+      c.pt.y = cell->member.cell.pt.y + nHeight;
+      if (prevHeight < nHeight)
+      {
+        /* The height of the cells has grown, so invalidate the bottom of
+         * the cells. */
+        item->member.para.nFlags |= MEPF_REPAINT;
+        cell = ME_FindItemBack(item, diCell);
+        while (cell) {
+          ME_FindItemBack(cell, diParagraph)->member.para.nFlags |= MEPF_REPAINT;
+          cell = cell->member.cell.prev_cell;
+        }
+      }
+    }
+    else if (item->member.para.pCell &&
+             item->member.para.pCell != item->member.para.next_para->member.para.pCell)
+    {
+      /* The next paragraph is in the next cell in the table row. */
+      ME_Cell *cell = &item->member.para.pCell->member.cell;
+      cell->nHeight = c.pt.y + item->member.para.nHeight - cell->pt.y;
+
+      /* Propagate the largest height to the end so that it can be easily
+       * sent back to all the cells at the end of the row. */
+      if (cell->prev_cell)
+        cell->nHeight = max(cell->nHeight, cell->prev_cell->member.cell.nHeight);
+
+      c.pt.x = cell->pt.x + cell->nWidth;
+      c.pt.y = cell->pt.y;
+      cell->next_cell->member.cell.pt = c.pt;
+      c.pt.y += cell->yTextOffset;
+    }
+    else
+    {
+      if (item->member.para.pCell) {
+        /* Next paragraph in the same cell. */
+        c.pt.x = item->member.para.pCell->member.cell.pt.x;
+      } else {
+        /* Normal paragraph */
+        c.pt.x = editor->selofs;
+      }
+      c.pt.y += item->member.para.nHeight;
+    }
     item = item->member.para.next_para;
   }
   editor->sizeWindow.cx = c.rcView.right-c.rcView.left;
   editor->sizeWindow.cy = c.rcView.bottom-c.rcView.top;
+  
   editor->nTotalLength = c.pt.y;
+  editor->pBuffer->pLast->member.para.pt.x = 0;
+  editor->pBuffer->pLast->member.para.pt.y = yLastPos;
 
-  ME_DestroyContext(&c);
-  ReleaseDC(hWnd, hDC);
+  ME_DestroyContext(&c, editor->hWnd);
+
+  /* Each paragraph may contain multiple rows, which should be scrollable, even
+     if the containing paragraph has pt.y == 0 */
+  item = editor->pBuffer->pFirst;
+  while ((item = ME_FindItemFwd(item, diStartRow)) != NULL) {
+    assert(item->type == diStartRow);
+    editor->nHeight = max(editor->nHeight, item->member.row.pt.y);
+  }
+
+  if (bModified || editor->nTotalLength < editor->nLastTotalLength)
+    ME_InvalidateMarkedParagraphs(editor);
   return bModified;
 }
+
+void ME_InvalidateMarkedParagraphs(ME_TextEditor *editor) {
+  ME_Context c;
+
+  ME_InitContext(&c, editor, GetDC(editor->hWnd));
+  if (editor->bRedraw)
+  {
+    RECT rc = c.rcView;
+    int ofs = ME_GetYScrollPos(editor); 
+     
+    ME_DisplayItem *item = editor->pBuffer->pFirst;
+    while(item != editor->pBuffer->pLast) {
+      if (item->member.para.nFlags & MEPF_REPAINT) { 
+        rc.top = item->member.para.pt.y - ofs;
+        rc.bottom = item->member.para.pt.y + item->member.para.nHeight - ofs;
+        InvalidateRect(editor->hWnd, &rc, TRUE);
+      }
+      item = item->member.para.next_para;
+    }
+    if (editor->nTotalLength < editor->nLastTotalLength)
+    {
+      rc.top = editor->nTotalLength - ofs;
+      rc.bottom = editor->nLastTotalLength - ofs;
+      InvalidateRect(editor->hWnd, &rc, TRUE);
+    }
+  }
+  ME_DestroyContext(&c, editor->hWnd);
+}
+
+
+void
+ME_SendRequestResize(ME_TextEditor *editor, BOOL force)
+{
+  if (editor->nEventMask & ENM_REQUESTRESIZE)
+  {
+    RECT rc;
+
+    GetClientRect(editor->hWnd, &rc);
+
+    if (force || rc.bottom != editor->nTotalLength)
+    {
+      REQRESIZE info;
+
+      info.nmhdr.hwndFrom = editor->hWnd;
+      info.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
+      info.nmhdr.code = EN_REQUESTRESIZE;
+      info.rc = rc;
+      info.rc.bottom = editor->nTotalLength;
+
+      editor->nEventMask &= ~ENM_REQUESTRESIZE;
+      SendMessageW(GetParent(editor->hWnd), WM_NOTIFY,
+                   info.nmhdr.idFrom, (LPARAM)&info);
+      editor->nEventMask |= ENM_REQUESTRESIZE;
+    }
+  }
+}