xref: /reactos/dll/win32/riched20/para.c (revision 0f92924a)
1 /*
2  * RichEdit - functions working on paragraphs of text (diParagraph).
3  *
4  * Copyright 2004 by Krzysztof Foltman
5  * Copyright 2006 by Phil Krylov
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 #include "editor.h"
23 
24 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
25 
26 void mark_para_rewrap(ME_TextEditor *editor, ME_DisplayItem *para)
27 {
28     para->member.para.nFlags |= MEPF_REWRAP;
29     add_marked_para(editor, para);
30 }
31 
32 ME_DisplayItem *get_di_from_para(ME_Paragraph *para)
33 {
34     return (ME_DisplayItem *)((ptrdiff_t)para - offsetof(ME_DisplayItem, member));
35 }
36 
37 static ME_DisplayItem *make_para(ME_TextEditor *editor)
38 {
39     ME_DisplayItem *item = ME_MakeDI(diParagraph);
40 
41     ME_SetDefaultParaFormat(editor, &item->member.para.fmt);
42     item->member.para.nFlags = MEPF_REWRAP;
43     item->member.para.next_marked = item->member.para.prev_marked = NULL;
44 
45     return item;
46 }
47 
48 void destroy_para(ME_TextEditor *editor, ME_DisplayItem *item)
49 {
50     assert(item->type == diParagraph);
51 
52     if (item->member.para.nWidth == editor->nTotalWidth)
53     {
54         item->member.para.nWidth = 0;
55         editor->nTotalWidth = get_total_width(editor);
56     }
57     editor->total_rows -= item->member.para.nRows;
58     ME_DestroyString(item->member.para.text);
59     para_num_clear( &item->member.para.para_num );
60     remove_marked_para(editor, item);
61     ME_DestroyDisplayItem(item);
62 }
63 
64 int get_total_width(ME_TextEditor *editor)
65 {
66     ME_Paragraph *para;
67     int total_width = 0;
68 
69     if (editor->pBuffer->pFirst && editor->pBuffer->pLast)
70     {
71         para = &editor->pBuffer->pFirst->next->member.para;
72         while (para != &editor->pBuffer->pLast->member.para && para->next_para)
73         {
74             total_width = max(total_width, para->nWidth);
75             para = &para->next_para->member.para;
76         }
77     }
78 
79     return total_width;
80 }
81 
82 void remove_marked_para(ME_TextEditor *editor, ME_DisplayItem *di)
83 {
84     ME_DisplayItem *head = editor->first_marked_para;
85 
86     assert(di->type == diParagraph);
87     if (!di->member.para.next_marked && !di->member.para.prev_marked)
88     {
89         if (di == head)
90             editor->first_marked_para = NULL;
91     }
92     else if (di->member.para.next_marked && di->member.para.prev_marked)
93     {
94         di->member.para.prev_marked->member.para.next_marked = di->member.para.next_marked;
95         di->member.para.next_marked->member.para.prev_marked = di->member.para.prev_marked;
96         di->member.para.prev_marked = di->member.para.next_marked = NULL;
97     }
98     else if (di->member.para.next_marked)
99     {
100         assert(di == editor->first_marked_para);
101         editor->first_marked_para = di->member.para.next_marked;
102         di->member.para.next_marked->member.para.prev_marked = NULL;
103         di->member.para.next_marked = NULL;
104     }
105     else
106     {
107         di->member.para.prev_marked->member.para.next_marked = NULL;
108         di->member.para.prev_marked = NULL;
109     }
110 }
111 
112 void add_marked_para(ME_TextEditor *editor, ME_DisplayItem *di)
113 {
114     ME_DisplayItem *iter = editor->first_marked_para;
115 
116     if (!iter)
117     {
118         editor->first_marked_para = di;
119         return;
120     }
121     while (iter)
122     {
123         if (iter == di)
124             return;
125         else if (di->member.para.nCharOfs < iter->member.para.nCharOfs)
126         {
127             if (iter == editor->first_marked_para)
128                 editor->first_marked_para = di;
129             di->member.para.next_marked = iter;
130             iter->member.para.prev_marked = di;
131             break;
132         }
133         else if (di->member.para.nCharOfs >= iter->member.para.nCharOfs)
134         {
135             if (!iter->member.para.next_marked || di->member.para.nCharOfs < iter->member.para.next_marked->member.para.nCharOfs)
136             {
137                 if (iter->member.para.next_marked)
138                 {
139                     di->member.para.next_marked = iter->member.para.next_marked;
140                     iter->member.para.next_marked->member.para.prev_marked = di;
141                 }
142                 di->member.para.prev_marked = iter;
143                 iter->member.para.next_marked = di;
144                 break;
145             }
146         }
147         iter = iter->member.para.next_marked;
148     }
149 }
150 
151 void ME_MakeFirstParagraph(ME_TextEditor *editor)
152 {
153   static const WCHAR cr_lf[] = {'\r','\n',0};
154   ME_Context c;
155   CHARFORMAT2W cf;
156   const CHARFORMATW *host_cf;
157   LOGFONTW lf;
158   HFONT hf;
159   ME_TextBuffer *text = editor->pBuffer;
160   ME_DisplayItem *para = make_para(editor);
161   ME_DisplayItem *run;
162   ME_Style *style;
163   int eol_len;
164 
165   ME_InitContext(&c, editor, ITextHost_TxGetDC(editor->texthost));
166 
167   hf = GetStockObject(SYSTEM_FONT);
168   assert(hf);
169   GetObjectW(hf, sizeof(LOGFONTW), &lf);
170   ZeroMemory(&cf, sizeof(cf));
171   cf.cbSize = sizeof(cf);
172   cf.dwMask = CFM_ANIMATION|CFM_BACKCOLOR|CFM_CHARSET|CFM_COLOR|CFM_FACE|CFM_KERNING|CFM_LCID|CFM_OFFSET;
173   cf.dwMask |= CFM_REVAUTHOR|CFM_SIZE|CFM_SPACING|CFM_STYLE|CFM_UNDERLINETYPE|CFM_WEIGHT;
174   cf.dwMask |= CFM_ALLCAPS|CFM_BOLD|CFM_DISABLED|CFM_EMBOSS|CFM_HIDDEN;
175   cf.dwMask |= CFM_IMPRINT|CFM_ITALIC|CFM_LINK|CFM_OUTLINE|CFM_PROTECTED;
176   cf.dwMask |= CFM_REVISED|CFM_SHADOW|CFM_SMALLCAPS|CFM_STRIKEOUT;
177   cf.dwMask |= CFM_SUBSCRIPT|CFM_UNDERLINE;
178 
179   cf.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
180   lstrcpyW(cf.szFaceName, lf.lfFaceName);
181   /* Convert system font height from logical units to twips for cf.yHeight */
182   cf.yHeight = (lf.lfHeight * 72 * 1440) / (c.dpi.cy * c.dpi.cy);
183   if (lf.lfWeight > FW_NORMAL) cf.dwEffects |= CFE_BOLD;
184   cf.wWeight = lf.lfWeight;
185   if (lf.lfItalic) cf.dwEffects |= CFE_ITALIC;
186   if (lf.lfUnderline) cf.dwEffects |= CFE_UNDERLINE;
187   cf.bUnderlineType = CFU_UNDERLINE;
188   if (lf.lfStrikeOut) cf.dwEffects |= CFE_STRIKEOUT;
189   cf.bPitchAndFamily = lf.lfPitchAndFamily;
190   cf.bCharSet = lf.lfCharSet;
191   cf.lcid = GetSystemDefaultLCID();
192 
193   style = ME_MakeStyle(&cf);
194   text->pDefaultStyle = style;
195 
196   if (ITextHost_TxGetCharFormat(editor->texthost, &host_cf) == S_OK)
197   {
198     ZeroMemory(&cf, sizeof(cf));
199     cf.cbSize = sizeof(cf);
200     cfany_to_cf2w(&cf, (CHARFORMAT2W *)host_cf);
201     ME_SetDefaultCharFormat(editor, &cf);
202   }
203 
204   eol_len = editor->bEmulateVersion10 ? 2 : 1;
205   para->member.para.text = ME_MakeStringN( cr_lf, eol_len );
206 
207   run = ME_MakeRun(style, MERF_ENDPARA);
208   run->member.run.nCharOfs = 0;
209   run->member.run.len = eol_len;
210   run->member.run.para = &para->member.para;
211 
212   para->member.para.eop_run = &run->member.run;
213 
214   ME_InsertBefore(text->pLast, para);
215   ME_InsertBefore(text->pLast, run);
216   para->member.para.prev_para = text->pFirst;
217   para->member.para.next_para = text->pLast;
218   text->pFirst->member.para.next_para = para;
219   text->pLast->member.para.prev_para = para;
220 
221   text->pLast->member.para.nCharOfs = editor->bEmulateVersion10 ? 2 : 1;
222 
223   add_marked_para(editor, para);
224   ME_DestroyContext(&c);
225 }
226 
227 static void ME_MarkForWrapping(ME_TextEditor *editor, ME_DisplayItem *first, const ME_DisplayItem *last)
228 {
229   while(first != last)
230   {
231     mark_para_rewrap(editor, first);
232     first = first->member.para.next_para;
233   }
234 }
235 
236 void ME_MarkAllForWrapping(ME_TextEditor *editor)
237 {
238   ME_MarkForWrapping(editor, editor->pBuffer->pFirst->member.para.next_para, editor->pBuffer->pLast);
239 }
240 
241 static void ME_UpdateTableFlags(ME_DisplayItem *para)
242 {
243   para->member.para.fmt.dwMask |= PFM_TABLE|PFM_TABLEROWDELIMITER;
244   if (para->member.para.pCell) {
245     para->member.para.nFlags |= MEPF_CELL;
246   } else {
247     para->member.para.nFlags &= ~MEPF_CELL;
248   }
249   if (para->member.para.nFlags & MEPF_ROWEND) {
250     para->member.para.fmt.wEffects |= PFE_TABLEROWDELIMITER;
251   } else {
252     para->member.para.fmt.wEffects &= ~PFE_TABLEROWDELIMITER;
253   }
254   if (para->member.para.nFlags & (MEPF_ROWSTART|MEPF_CELL|MEPF_ROWEND))
255     para->member.para.fmt.wEffects |= PFE_TABLE;
256   else
257     para->member.para.fmt.wEffects &= ~PFE_TABLE;
258 }
259 
260 static inline BOOL para_num_same_list( const PARAFORMAT2 *item, const PARAFORMAT2 *base )
261 {
262     return item->wNumbering == base->wNumbering &&
263         item->wNumberingStart == base->wNumberingStart &&
264         item->wNumberingStyle == base->wNumberingStyle &&
265         !(item->wNumberingStyle & PFNS_NEWNUMBER);
266 }
267 
268 static int para_num_get_num( ME_Paragraph *para )
269 {
270     ME_DisplayItem *prev;
271     int num = para->fmt.wNumberingStart;
272 
273     for (prev = para->prev_para; prev->type == diParagraph;
274          para = &prev->member.para, prev = prev->member.para.prev_para, num++)
275     {
276         if (!para_num_same_list( &prev->member.para.fmt, &para->fmt )) break;
277     }
278     return num;
279 }
280 
281 static ME_String *para_num_get_str( ME_Paragraph *para, WORD num )
282 {
283     /* max 4 Roman letters (representing '8') / decade + '(' + ')' */
284     ME_String *str = ME_MakeStringEmpty( 20 + 2 );
285     WCHAR *p;
286     static const WCHAR fmtW[] = {'%', 'd', 0};
287     static const WORD letter_base[] = { 1, 26, 26 * 26, 26 * 26 * 26 };
288     /* roman_base should start on a '5' not a '1', otherwise the 'total' code will need adjusting.
289        'N' and 'O' are what MS uses for 5000 and 10000, their version doesn't work well above 30000,
290        but we'll use 'P' as the obvious extension, this gets us up to 2^16, which is all we care about. */
291     static const struct
292     {
293         int base;
294         char letter;
295     }
296     roman_base[] =
297     {
298         {50000, 'P'}, {10000, 'O'}, {5000, 'N'}, {1000, 'M'},
299         {500, 'D'}, {100, 'C'}, {50, 'L'}, {10, 'X'}, {5, 'V'}, {1, 'I'}
300     };
301     int i, len;
302     WORD letter, total, char_offset = 0;
303 
304     if (!str) return NULL;
305 
306     p = str->szData;
307 
308     if ((para->fmt.wNumberingStyle & 0xf00) == PFNS_PARENS)
309         *p++ = '(';
310 
311     switch (para->fmt.wNumbering)
312     {
313     case PFN_ARABIC:
314     default:
315         p += sprintfW( p, fmtW, num );
316         break;
317 
318     case PFN_LCLETTER:
319         char_offset = 'a' - 'A';
320         /* fall through */
321     case PFN_UCLETTER:
322         if (!num) num = 1;
323 
324         /* This is not base-26 (or 27) as zeros don't count unless they are leading zeros.
325            It's simplest to start with the least significant letter, so first calculate how many letters are needed. */
326         for (i = 0, total = 0; i < ARRAY_SIZE( letter_base ); i++)
327         {
328             total += letter_base[i];
329             if (num < total) break;
330         }
331         len = i;
332         for (i = 0; i < len; i++)
333         {
334             num -= letter_base[i];
335             letter = (num / letter_base[i]) % 26;
336             p[len - i - 1] = letter + 'A' + char_offset;
337         }
338         p += len;
339         *p = 0;
340         break;
341 
342     case PFN_LCROMAN:
343         char_offset = 'a' - 'A';
344         /* fall through */
345     case PFN_UCROMAN:
346         if (!num) num = 1;
347 
348         for (i = 0; i < ARRAY_SIZE( roman_base ); i++)
349         {
350             if (i > 0)
351             {
352                 if (i % 2 == 0) /* eg 5000, check for 9000 */
353                     total = roman_base[i].base + 4 * roman_base[i + 1].base;
354                 else  /* eg 1000, check for 4000 */
355                     total = 4 * roman_base[i].base;
356 
357                 if (num / total)
358                 {
359                     *p++ = roman_base[(i & ~1) + 1].letter + char_offset;
360                     *p++ = roman_base[i - 1].letter + char_offset;
361                     num -= total;
362                     continue;
363                 }
364             }
365 
366             len = num / roman_base[i].base;
367             while (len--)
368             {
369                 *p++ = roman_base[i].letter + char_offset;
370                 num -= roman_base[i].base;
371             }
372         }
373         *p = 0;
374         break;
375     }
376 
377     switch (para->fmt.wNumberingStyle & 0xf00)
378     {
379     case PFNS_PARENS:
380     case PFNS_PAREN:
381         *p++ = ')';
382         *p = 0;
383         break;
384 
385     case PFNS_PERIOD:
386         *p++ = '.';
387         *p = 0;
388         break;
389     }
390 
391     str->nLen = p - str->szData;
392     return str;
393 }
394 
395 void para_num_init( ME_Context *c, ME_Paragraph *para )
396 {
397     ME_Style *style;
398     CHARFORMAT2W cf;
399     static const WCHAR bullet_font[] = {'S','y','m','b','o','l',0};
400     static const WCHAR bullet_str[] = {0xb7, 0};
401     static const WCHAR spaceW[] = {' ', 0};
402     HFONT old_font;
403     SIZE sz;
404 
405     if (para->para_num.style && para->para_num.text) return;
406 
407     if (!para->para_num.style)
408     {
409         style = para->eop_run->style;
410 
411         if (para->fmt.wNumbering == PFN_BULLET)
412         {
413             cf.cbSize = sizeof(cf);
414             cf.dwMask = CFM_FACE | CFM_CHARSET;
415             memcpy( cf.szFaceName, bullet_font, sizeof(bullet_font) );
416             cf.bCharSet = SYMBOL_CHARSET;
417             style = ME_ApplyStyle( c->editor, style, &cf );
418         }
419         else
420         {
421             ME_AddRefStyle( style );
422         }
423 
424         para->para_num.style = style;
425     }
426 
427     if (!para->para_num.text)
428     {
429         if (para->fmt.wNumbering != PFN_BULLET)
430             para->para_num.text = para_num_get_str( para, para_num_get_num( para ) );
431         else
432             para->para_num.text = ME_MakeStringConst( bullet_str, 1 );
433     }
434 
435     old_font = ME_SelectStyleFont( c, para->para_num.style );
436     GetTextExtentPointW( c->hDC, para->para_num.text->szData, para->para_num.text->nLen, &sz );
437     para->para_num.width = sz.cx;
438     GetTextExtentPointW( c->hDC, spaceW, 1, &sz );
439     para->para_num.width += sz.cx;
440     ME_UnselectStyleFont( c, para->para_num.style, old_font );
441 }
442 
443 void para_num_clear( struct para_num *pn )
444 {
445     if (pn->style)
446     {
447         ME_ReleaseStyle( pn->style );
448         pn->style = NULL;
449     }
450     ME_DestroyString( pn->text );
451     pn->text = NULL;
452 }
453 
454 static void para_num_clear_list( ME_TextEditor *editor, ME_Paragraph *para, const PARAFORMAT2 *orig_fmt )
455 {
456     do
457     {
458         mark_para_rewrap(editor, get_di_from_para(para));
459         para_num_clear( &para->para_num );
460         if (para->next_para->type != diParagraph) break;
461         para = &para->next_para->member.para;
462     } while (para_num_same_list( &para->fmt, orig_fmt ));
463 }
464 
465 static BOOL ME_SetParaFormat(ME_TextEditor *editor, ME_Paragraph *para, const PARAFORMAT2 *pFmt)
466 {
467   PARAFORMAT2 copy;
468   DWORD dwMask;
469 
470   assert(para->fmt.cbSize == sizeof(PARAFORMAT2));
471   dwMask = pFmt->dwMask;
472   if (pFmt->cbSize < sizeof(PARAFORMAT))
473     return FALSE;
474   else if (pFmt->cbSize < sizeof(PARAFORMAT2))
475     dwMask &= PFM_ALL;
476   else
477     dwMask &= PFM_ALL2;
478 
479   add_undo_set_para_fmt( editor, para );
480 
481   copy = para->fmt;
482 
483 #define COPY_FIELD(m, f) \
484   if (dwMask & (m)) {                           \
485     para->fmt.dwMask |= m;                      \
486     para->fmt.f = pFmt->f;                      \
487   }
488 
489   COPY_FIELD(PFM_NUMBERING, wNumbering);
490   COPY_FIELD(PFM_STARTINDENT, dxStartIndent);
491   if (dwMask & PFM_OFFSETINDENT)
492     para->fmt.dxStartIndent += pFmt->dxStartIndent;
493   COPY_FIELD(PFM_RIGHTINDENT, dxRightIndent);
494   COPY_FIELD(PFM_OFFSET, dxOffset);
495   COPY_FIELD(PFM_ALIGNMENT, wAlignment);
496   if (dwMask & PFM_TABSTOPS)
497   {
498     para->fmt.cTabCount = pFmt->cTabCount;
499     memcpy(para->fmt.rgxTabs, pFmt->rgxTabs, pFmt->cTabCount*sizeof(LONG));
500   }
501 
502 #define EFFECTS_MASK (PFM_RTLPARA|PFM_KEEP|PFM_KEEPNEXT|PFM_PAGEBREAKBEFORE| \
503                       PFM_NOLINENUMBER|PFM_NOWIDOWCONTROL|PFM_DONOTHYPHEN|PFM_SIDEBYSIDE| \
504                       PFM_TABLE)
505   /* we take for granted that PFE_xxx is the hiword of the corresponding PFM_xxx */
506   if (dwMask & EFFECTS_MASK)
507   {
508     para->fmt.dwMask |= dwMask & EFFECTS_MASK;
509     para->fmt.wEffects &= ~HIWORD(dwMask);
510     para->fmt.wEffects |= pFmt->wEffects & HIWORD(dwMask);
511   }
512 #undef EFFECTS_MASK
513 
514   COPY_FIELD(PFM_SPACEBEFORE, dySpaceBefore);
515   COPY_FIELD(PFM_SPACEAFTER, dySpaceAfter);
516   COPY_FIELD(PFM_LINESPACING, dyLineSpacing);
517   COPY_FIELD(PFM_STYLE, sStyle);
518   COPY_FIELD(PFM_LINESPACING, bLineSpacingRule);
519   COPY_FIELD(PFM_SHADING, wShadingWeight);
520   COPY_FIELD(PFM_SHADING, wShadingStyle);
521   COPY_FIELD(PFM_NUMBERINGSTART, wNumberingStart);
522   COPY_FIELD(PFM_NUMBERINGSTYLE, wNumberingStyle);
523   COPY_FIELD(PFM_NUMBERINGTAB, wNumberingTab);
524   COPY_FIELD(PFM_BORDER, wBorderSpace);
525   COPY_FIELD(PFM_BORDER, wBorderWidth);
526   COPY_FIELD(PFM_BORDER, wBorders);
527 
528   para->fmt.dwMask |= dwMask;
529 #undef COPY_FIELD
530 
531   if (memcmp(&copy, &para->fmt, sizeof(PARAFORMAT2)))
532   {
533     mark_para_rewrap(editor, get_di_from_para(para));
534     if (((dwMask & PFM_NUMBERING)      && (copy.wNumbering != para->fmt.wNumbering)) ||
535         ((dwMask & PFM_NUMBERINGSTART) && (copy.wNumberingStart != para->fmt.wNumberingStart)) ||
536         ((dwMask & PFM_NUMBERINGSTYLE) && (copy.wNumberingStyle != para->fmt.wNumberingStyle)))
537     {
538         para_num_clear_list( editor, para, &copy );
539     }
540   }
541 
542   return TRUE;
543 }
544 
545 /* split paragraph at the beginning of the run */
546 ME_DisplayItem *ME_SplitParagraph(ME_TextEditor *editor, ME_DisplayItem *run,
547                                   ME_Style *style, const WCHAR *eol_str, int eol_len,
548                                   int paraFlags)
549 {
550   ME_DisplayItem *next_para = NULL;
551   ME_DisplayItem *run_para = NULL;
552   ME_DisplayItem *new_para = make_para(editor);
553   ME_DisplayItem *end_run;
554   int ofs, i;
555   ME_DisplayItem *pp;
556   int run_flags = MERF_ENDPARA;
557 
558   if (!editor->bEmulateVersion10) { /* v4.1 */
559     /* At most 1 of MEPF_CELL, MEPF_ROWSTART, or MEPF_ROWEND should be set. */
560     assert(!(paraFlags & ~(MEPF_CELL|MEPF_ROWSTART|MEPF_ROWEND)));
561     assert(!(paraFlags & (paraFlags-1)));
562     if (paraFlags == MEPF_CELL)
563       run_flags |= MERF_ENDCELL;
564     else if (paraFlags == MEPF_ROWSTART)
565       run_flags |= MERF_TABLESTART|MERF_HIDDEN;
566   } else { /* v1.0 - v3.0 */
567     assert(!(paraFlags & (MEPF_CELL|MEPF_ROWSTART|MEPF_ROWEND)));
568   }
569   assert(run->type == diRun);
570   run_para = ME_GetParagraph(run);
571   assert(run_para->member.para.fmt.cbSize == sizeof(PARAFORMAT2));
572 
573   /* Clear any cached para numbering following this paragraph */
574   if (run_para->member.para.fmt.wNumbering)
575       para_num_clear_list( editor, &run_para->member.para, &run_para->member.para.fmt );
576 
577   new_para->member.para.text = ME_VSplitString( run_para->member.para.text, run->member.run.nCharOfs );
578 
579   end_run = ME_MakeRun(style, run_flags);
580   ofs = end_run->member.run.nCharOfs = run->member.run.nCharOfs;
581   end_run->member.run.len = eol_len;
582   end_run->member.run.para = run->member.run.para;
583   ME_AppendString( run_para->member.para.text, eol_str, eol_len );
584   next_para = run_para->member.para.next_para;
585   assert(next_para == ME_FindItemFwd(run_para, diParagraphOrEnd));
586 
587   add_undo_join_paras( editor, run_para->member.para.nCharOfs + ofs );
588 
589   /* Update selection cursors to point to the correct paragraph. */
590   for (i = 0; i < editor->nCursors; i++) {
591     if (editor->pCursors[i].pPara == run_para &&
592         run->member.run.nCharOfs <= editor->pCursors[i].pRun->member.run.nCharOfs)
593     {
594       editor->pCursors[i].pPara = new_para;
595     }
596   }
597 
598   /* the new paragraph will have a different starting offset, so let's update its runs */
599   pp = run;
600   while(pp->type == diRun) {
601     pp->member.run.nCharOfs -= ofs;
602     pp->member.run.para = &new_para->member.para;
603     pp = ME_FindItemFwd(pp, diRunOrParagraphOrEnd);
604   }
605   new_para->member.para.nCharOfs = run_para->member.para.nCharOfs + ofs;
606   new_para->member.para.nCharOfs += eol_len;
607   new_para->member.para.nFlags = 0;
608   mark_para_rewrap(editor, new_para);
609 
610   /* FIXME initialize format style and call ME_SetParaFormat blah blah */
611   new_para->member.para.fmt = run_para->member.para.fmt;
612   new_para->member.para.border = run_para->member.para.border;
613 
614   /* insert paragraph into paragraph double linked list */
615   new_para->member.para.prev_para = run_para;
616   new_para->member.para.next_para = next_para;
617   run_para->member.para.next_para = new_para;
618   next_para->member.para.prev_para = new_para;
619 
620   /* insert end run of the old paragraph, and new paragraph, into DI double linked list */
621   ME_InsertBefore(run, new_para);
622   ME_InsertBefore(new_para, end_run);
623 
624   /* Fix up the paras' eop_run ptrs */
625   new_para->member.para.eop_run = run_para->member.para.eop_run;
626   run_para->member.para.eop_run = &end_run->member.run;
627 
628   if (!editor->bEmulateVersion10) { /* v4.1 */
629     if (paraFlags & (MEPF_ROWSTART|MEPF_CELL))
630     {
631       ME_DisplayItem *cell = ME_MakeDI(diCell);
632       ME_InsertBefore(new_para, cell);
633       new_para->member.para.pCell = cell;
634       cell->member.cell.next_cell = NULL;
635       if (paraFlags & MEPF_ROWSTART)
636       {
637         run_para->member.para.nFlags |= MEPF_ROWSTART;
638         cell->member.cell.prev_cell = NULL;
639         cell->member.cell.parent_cell = run_para->member.para.pCell;
640         if (run_para->member.para.pCell)
641           cell->member.cell.nNestingLevel = run_para->member.para.pCell->member.cell.nNestingLevel + 1;
642         else
643           cell->member.cell.nNestingLevel = 1;
644       } else {
645         cell->member.cell.prev_cell = run_para->member.para.pCell;
646         assert(cell->member.cell.prev_cell);
647         cell->member.cell.prev_cell->member.cell.next_cell = cell;
648         assert(run_para->member.para.nFlags & MEPF_CELL);
649         assert(!(run_para->member.para.nFlags & MEPF_ROWSTART));
650         cell->member.cell.nNestingLevel = cell->member.cell.prev_cell->member.cell.nNestingLevel;
651         cell->member.cell.parent_cell = cell->member.cell.prev_cell->member.cell.parent_cell;
652       }
653     } else if (paraFlags & MEPF_ROWEND) {
654       run_para->member.para.nFlags |= MEPF_ROWEND;
655       run_para->member.para.pCell = run_para->member.para.pCell->member.cell.parent_cell;
656       new_para->member.para.pCell = run_para->member.para.pCell;
657       assert(run_para->member.para.prev_para->member.para.nFlags & MEPF_CELL);
658       assert(!(run_para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART));
659       if (new_para->member.para.pCell != new_para->member.para.next_para->member.para.pCell
660           && new_para->member.para.next_para->member.para.pCell
661           && !new_para->member.para.next_para->member.para.pCell->member.cell.prev_cell)
662       {
663         /* Row starts just after the row that was ended. */
664         new_para->member.para.nFlags |= MEPF_ROWSTART;
665       }
666     } else {
667       new_para->member.para.pCell = run_para->member.para.pCell;
668     }
669     ME_UpdateTableFlags(run_para);
670     ME_UpdateTableFlags(new_para);
671   }
672 
673   /* force rewrap of the */
674   if (run_para->member.para.prev_para->type == diParagraph)
675     mark_para_rewrap(editor, run_para->member.para.prev_para);
676 
677   mark_para_rewrap(editor, new_para->member.para.prev_para);
678 
679   /* we've added the end run, so we need to modify nCharOfs in the next paragraphs */
680   ME_PropagateCharOffset(next_para, eol_len);
681   editor->nParagraphs++;
682 
683   return new_para;
684 }
685 
686 /* join tp with tp->member.para.next_para, keeping tp's style; this
687  * is consistent with the original */
688 ME_DisplayItem *ME_JoinParagraphs(ME_TextEditor *editor, ME_DisplayItem *tp,
689                                   BOOL keepFirstParaFormat)
690 {
691   ME_DisplayItem *pNext, *pFirstRunInNext, *pRun, *pTmp, *pCell = NULL;
692   int i, shift;
693   int end_len;
694   CHARFORMAT2W fmt;
695   ME_Cursor startCur, endCur;
696   ME_String *eol_str;
697 
698   assert(tp->type == diParagraph);
699   assert(tp->member.para.next_para);
700   assert(tp->member.para.next_para->type == diParagraph);
701 
702   /* Clear any cached para numbering following this paragraph */
703   if (tp->member.para.fmt.wNumbering)
704       para_num_clear_list( editor, &tp->member.para, &tp->member.para.fmt );
705 
706   pNext = tp->member.para.next_para;
707 
708   /* Need to locate end-of-paragraph run here, in order to know end_len */
709   pRun = ME_FindItemBack(pNext, diRunOrParagraph);
710 
711   assert(pRun);
712   assert(pRun->type == diRun);
713   assert(pRun->member.run.nFlags & MERF_ENDPARA);
714 
715   end_len = pRun->member.run.len;
716   eol_str = ME_VSplitString( tp->member.para.text, pRun->member.run.nCharOfs );
717   ME_AppendString( tp->member.para.text, pNext->member.para.text->szData, pNext->member.para.text->nLen );
718 
719   /* null char format operation to store the original char format for the ENDPARA run */
720   ME_InitCharFormat2W(&fmt);
721   endCur.pPara = pNext;
722   endCur.pRun = ME_FindItemFwd(pNext, diRun);
723   endCur.nOffset = 0;
724   startCur = endCur;
725   ME_PrevRun(&startCur.pPara, &startCur.pRun, TRUE);
726   ME_SetCharFormat(editor, &startCur, &endCur, &fmt);
727 
728   if (!editor->bEmulateVersion10) { /* v4.1 */
729     /* Table cell/row properties are always moved over from the removed para. */
730     tp->member.para.nFlags = pNext->member.para.nFlags;
731     tp->member.para.pCell = pNext->member.para.pCell;
732 
733     /* Remove cell boundary if it is between the end paragraph run and the next
734      * paragraph display item. */
735     for (pTmp = pRun->next; pTmp != pNext; pTmp = pTmp->next)
736     {
737       if (pTmp->type == diCell)
738       {
739         pCell = pTmp;
740         break;
741       }
742     }
743   }
744 
745   add_undo_split_para( editor, &pNext->member.para, eol_str, pCell ? &pCell->member.cell : NULL );
746 
747   if (pCell)
748   {
749     ME_Remove( pCell );
750     if (pCell->member.cell.prev_cell)
751       pCell->member.cell.prev_cell->member.cell.next_cell = pCell->member.cell.next_cell;
752     if (pCell->member.cell.next_cell)
753       pCell->member.cell.next_cell->member.cell.prev_cell = pCell->member.cell.prev_cell;
754     ME_DestroyDisplayItem( pCell );
755   }
756 
757   if (!keepFirstParaFormat)
758   {
759     add_undo_set_para_fmt( editor, &tp->member.para );
760     tp->member.para.fmt = pNext->member.para.fmt;
761     tp->member.para.border = pNext->member.para.border;
762   }
763 
764   shift = pNext->member.para.nCharOfs - tp->member.para.nCharOfs - end_len;
765 
766   pFirstRunInNext = ME_FindItemFwd(pNext, diRunOrParagraph);
767 
768   assert(pFirstRunInNext->type == diRun);
769 
770   /* Update selection cursors so they don't point to the removed end
771    * paragraph run, and point to the correct paragraph. */
772   for (i=0; i < editor->nCursors; i++) {
773     if (editor->pCursors[i].pRun == pRun) {
774       editor->pCursors[i].pRun = pFirstRunInNext;
775       editor->pCursors[i].nOffset = 0;
776     } else if (editor->pCursors[i].pPara == pNext) {
777       editor->pCursors[i].pPara = tp;
778     }
779   }
780 
781   pTmp = pNext;
782   do {
783     pTmp = ME_FindItemFwd(pTmp, diRunOrParagraphOrEnd);
784     if (pTmp->type != diRun)
785       break;
786     TRACE("shifting %s by %d (previous %d)\n", debugstr_run( &pTmp->member.run ), shift, pTmp->member.run.nCharOfs);
787     pTmp->member.run.nCharOfs += shift;
788     pTmp->member.run.para = &tp->member.para;
789   } while(1);
790 
791   /* Fix up the para's eop_run ptr */
792   tp->member.para.eop_run = pNext->member.para.eop_run;
793 
794   ME_Remove(pRun);
795   ME_DestroyDisplayItem(pRun);
796 
797   if (editor->pLastSelStartPara == pNext)
798     editor->pLastSelStartPara = tp;
799   if (editor->pLastSelEndPara == pNext)
800     editor->pLastSelEndPara = tp;
801 
802   tp->member.para.next_para = pNext->member.para.next_para;
803   pNext->member.para.next_para->member.para.prev_para = tp;
804   ME_Remove(pNext);
805   destroy_para(editor, pNext);
806 
807   ME_PropagateCharOffset(tp->member.para.next_para, -end_len);
808 
809   ME_CheckCharOffsets(editor);
810 
811   editor->nParagraphs--;
812   mark_para_rewrap(editor, tp);
813   return tp;
814 }
815 
816 ME_DisplayItem *ME_GetParagraph(ME_DisplayItem *item) {
817   return ME_FindItemBackOrHere(item, diParagraph);
818 }
819 
820 void ME_DumpParaStyleToBuf(const PARAFORMAT2 *pFmt, char buf[2048])
821 {
822   char *p;
823   p = buf;
824 
825 #define DUMP(mask, name, fmt, field) \
826   if (pFmt->dwMask & (mask)) p += sprintf(p, "%-22s" fmt "\n", name, pFmt->field); \
827   else p += sprintf(p, "%-22sN/A\n", name);
828 
829 /* we take for granted that PFE_xxx is the hiword of the corresponding PFM_xxx */
830 #define DUMP_EFFECT(mask, name) \
831   p += sprintf(p, "%-22s%s\n", name, (pFmt->dwMask & (mask)) ? ((pFmt->wEffects & ((mask) >> 16)) ? "yes" : "no") : "N/A");
832 
833   DUMP(PFM_NUMBERING,      "Numbering:",         "%u", wNumbering);
834   DUMP_EFFECT(PFM_DONOTHYPHEN,     "Disable auto-hyphen:");
835   DUMP_EFFECT(PFM_KEEP,            "No page break in para:");
836   DUMP_EFFECT(PFM_KEEPNEXT,        "No page break in para & next:");
837   DUMP_EFFECT(PFM_NOLINENUMBER,    "No line number:");
838   DUMP_EFFECT(PFM_NOWIDOWCONTROL,  "No widow & orphan:");
839   DUMP_EFFECT(PFM_PAGEBREAKBEFORE, "Page break before:");
840   DUMP_EFFECT(PFM_RTLPARA,         "RTL para:");
841   DUMP_EFFECT(PFM_SIDEBYSIDE,      "Side by side:");
842   DUMP_EFFECT(PFM_TABLE,           "Table:");
843   DUMP(PFM_OFFSETINDENT,   "Offset indent:",     "%d", dxStartIndent);
844   DUMP(PFM_STARTINDENT,    "Start indent:",      "%d", dxStartIndent);
845   DUMP(PFM_RIGHTINDENT,    "Right indent:",      "%d", dxRightIndent);
846   DUMP(PFM_OFFSET,         "Offset:",            "%d", dxOffset);
847   if (pFmt->dwMask & PFM_ALIGNMENT) {
848     switch (pFmt->wAlignment) {
849     case PFA_LEFT   : p += sprintf(p, "Alignment:            left\n"); break;
850     case PFA_RIGHT  : p += sprintf(p, "Alignment:            right\n"); break;
851     case PFA_CENTER : p += sprintf(p, "Alignment:            center\n"); break;
852     case PFA_JUSTIFY: p += sprintf(p, "Alignment:            justify\n"); break;
853     default         : p += sprintf(p, "Alignment:            incorrect %d\n", pFmt->wAlignment); break;
854     }
855   }
856   else p += sprintf(p, "Alignment:            N/A\n");
857   DUMP(PFM_TABSTOPS,       "Tab Stops:",         "%d", cTabCount);
858   if (pFmt->dwMask & PFM_TABSTOPS) {
859     int i;
860     p += sprintf(p, "\t");
861     for (i = 0; i < pFmt->cTabCount; i++) p += sprintf(p, "%x ", pFmt->rgxTabs[i]);
862     p += sprintf(p, "\n");
863   }
864   DUMP(PFM_SPACEBEFORE,    "Space Before:",      "%d", dySpaceBefore);
865   DUMP(PFM_SPACEAFTER,     "Space After:",       "%d", dySpaceAfter);
866   DUMP(PFM_LINESPACING,    "Line spacing:",      "%d", dyLineSpacing);
867   DUMP(PFM_STYLE,          "Text style:",        "%d", sStyle);
868   DUMP(PFM_LINESPACING,    "Line spacing rule:", "%u", bLineSpacingRule);
869   /* bOutlineLevel should be 0 */
870   DUMP(PFM_SHADING,        "Shading Weight:",    "%u", wShadingWeight);
871   DUMP(PFM_SHADING,        "Shading Style:",     "%u", wShadingStyle);
872   DUMP(PFM_NUMBERINGSTART, "Numbering Start:",   "%u", wNumberingStart);
873   DUMP(PFM_NUMBERINGSTYLE, "Numbering Style:",   "0x%x", wNumberingStyle);
874   DUMP(PFM_NUMBERINGTAB,   "Numbering Tab:",     "%u", wNumberingStyle);
875   DUMP(PFM_BORDER,         "Border Space:",      "%u", wBorderSpace);
876   DUMP(PFM_BORDER,         "Border Width:",      "%u", wBorderWidth);
877   DUMP(PFM_BORDER,         "Borders:",           "%u", wBorders);
878 
879 #undef DUMP
880 #undef DUMP_EFFECT
881 }
882 
883 void
884 ME_GetSelectionParas(ME_TextEditor *editor, ME_DisplayItem **para, ME_DisplayItem **para_end)
885 {
886   ME_Cursor *pEndCursor = &editor->pCursors[1];
887 
888   *para = editor->pCursors[0].pPara;
889   *para_end = editor->pCursors[1].pPara;
890   if (*para == *para_end)
891     return;
892 
893   if ((*para_end)->member.para.nCharOfs < (*para)->member.para.nCharOfs) {
894     ME_DisplayItem *tmp = *para;
895 
896     *para = *para_end;
897     *para_end = tmp;
898     pEndCursor = &editor->pCursors[0];
899   }
900 
901   /* The paragraph at the end of a non-empty selection isn't included
902    * if the selection ends at the start of the paragraph. */
903   if (!pEndCursor->pRun->member.run.nCharOfs && !pEndCursor->nOffset)
904     *para_end = (*para_end)->member.para.prev_para;
905 }
906 
907 
908 BOOL ME_SetSelectionParaFormat(ME_TextEditor *editor, const PARAFORMAT2 *pFmt)
909 {
910   ME_DisplayItem *para, *para_end;
911 
912   ME_GetSelectionParas(editor, &para, &para_end);
913 
914   do {
915     ME_SetParaFormat(editor, &para->member.para, pFmt);
916     if (para == para_end)
917       break;
918     para = para->member.para.next_para;
919   } while(1);
920 
921   return TRUE;
922 }
923 
924 static void ME_GetParaFormat(ME_TextEditor *editor,
925                              const ME_DisplayItem *para,
926                              PARAFORMAT2 *pFmt)
927 {
928   UINT cbSize = pFmt->cbSize;
929   if (pFmt->cbSize >= sizeof(PARAFORMAT2)) {
930     *pFmt = para->member.para.fmt;
931   } else {
932     CopyMemory(pFmt, &para->member.para.fmt, pFmt->cbSize);
933     pFmt->dwMask &= PFM_ALL;
934   }
935   pFmt->cbSize = cbSize;
936 }
937 
938 void ME_GetSelectionParaFormat(ME_TextEditor *editor, PARAFORMAT2 *pFmt)
939 {
940   ME_DisplayItem *para, *para_end;
941   PARAFORMAT2 *curFmt;
942 
943   if (pFmt->cbSize < sizeof(PARAFORMAT)) {
944     pFmt->dwMask = 0;
945     return;
946   }
947 
948   ME_GetSelectionParas(editor, &para, &para_end);
949 
950   ME_GetParaFormat(editor, para, pFmt);
951 
952   /* Invalidate values that change across the selected paragraphs. */
953   while (para != para_end)
954   {
955     para = para->member.para.next_para;
956     curFmt = &para->member.para.fmt;
957 
958 #define CHECK_FIELD(m, f) \
959     if (pFmt->f != curFmt->f) pFmt->dwMask &= ~(m);
960 
961     CHECK_FIELD(PFM_NUMBERING, wNumbering);
962     CHECK_FIELD(PFM_STARTINDENT, dxStartIndent);
963     CHECK_FIELD(PFM_RIGHTINDENT, dxRightIndent);
964     CHECK_FIELD(PFM_OFFSET, dxOffset);
965     CHECK_FIELD(PFM_ALIGNMENT, wAlignment);
966     if (pFmt->dwMask & PFM_TABSTOPS) {
967       if (pFmt->cTabCount != para->member.para.fmt.cTabCount ||
968           memcmp(pFmt->rgxTabs, curFmt->rgxTabs, curFmt->cTabCount*sizeof(int)))
969         pFmt->dwMask &= ~PFM_TABSTOPS;
970     }
971 
972     if (pFmt->dwMask >= sizeof(PARAFORMAT2))
973     {
974       pFmt->dwMask &= ~((pFmt->wEffects ^ curFmt->wEffects) << 16);
975       CHECK_FIELD(PFM_SPACEBEFORE, dySpaceBefore);
976       CHECK_FIELD(PFM_SPACEAFTER, dySpaceAfter);
977       CHECK_FIELD(PFM_LINESPACING, dyLineSpacing);
978       CHECK_FIELD(PFM_STYLE, sStyle);
979       CHECK_FIELD(PFM_SPACEAFTER, bLineSpacingRule);
980       CHECK_FIELD(PFM_SHADING, wShadingWeight);
981       CHECK_FIELD(PFM_SHADING, wShadingStyle);
982       CHECK_FIELD(PFM_NUMBERINGSTART, wNumberingStart);
983       CHECK_FIELD(PFM_NUMBERINGSTYLE, wNumberingStyle);
984       CHECK_FIELD(PFM_NUMBERINGTAB, wNumberingTab);
985       CHECK_FIELD(PFM_BORDER, wBorderSpace);
986       CHECK_FIELD(PFM_BORDER, wBorderWidth);
987       CHECK_FIELD(PFM_BORDER, wBorders);
988     }
989 #undef CHECK_FIELD
990   }
991 }
992 
993 void ME_SetDefaultParaFormat(ME_TextEditor *editor, PARAFORMAT2 *pFmt)
994 {
995     const PARAFORMAT2 *host_fmt;
996     HRESULT hr;
997 
998     ZeroMemory(pFmt, sizeof(PARAFORMAT2));
999     pFmt->cbSize = sizeof(PARAFORMAT2);
1000     pFmt->dwMask = PFM_ALL2;
1001     pFmt->wAlignment = PFA_LEFT;
1002     pFmt->sStyle = -1;
1003     pFmt->bOutlineLevel = TRUE;
1004 
1005     hr = ITextHost_TxGetParaFormat( editor->texthost, (const PARAFORMAT **)&host_fmt );
1006     if (SUCCEEDED(hr))
1007     {
1008         /* Just use the alignment for now */
1009         if (host_fmt->dwMask & PFM_ALIGNMENT)
1010             pFmt->wAlignment = host_fmt->wAlignment;
1011         ITextHost_OnTxParaFormatChange( editor->texthost, (PARAFORMAT *)pFmt );
1012     }
1013 }
1014