rpcrt4: Pass the interface identifier to the lower-level context handle routines...
[wine] / dlls / riched20 / para.c
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 static const WCHAR wszParagraphSign[] = {0xB6, 0};
27
28 void ME_MakeFirstParagraph(ME_TextEditor *editor)
29 {
30   ME_Context c;
31   HDC hDC;
32   PARAFORMAT2 fmt;
33   CHARFORMAT2W cf;
34   LOGFONTW lf;
35   HFONT hf;
36   ME_TextBuffer *text = editor->pBuffer;
37   ME_DisplayItem *para = ME_MakeDI(diParagraph);
38   ME_DisplayItem *run;
39   ME_Style *style;
40
41   hDC = GetDC(editor->hWnd);
42
43   ME_InitContext(&c, editor, hDC);
44   hf = (HFONT)GetStockObject(SYSTEM_FONT);
45   assert(hf);
46   GetObjectW(hf, sizeof(LOGFONTW), &lf);
47   ZeroMemory(&cf, sizeof(cf));
48   cf.cbSize = sizeof(cf);
49   cf.dwMask = CFM_BACKCOLOR|CFM_COLOR|CFM_FACE|CFM_SIZE|CFM_CHARSET;
50   cf.dwMask |= CFM_ALLCAPS|CFM_BOLD|CFM_DISABLED|CFM_EMBOSS|CFM_HIDDEN;
51   cf.dwMask |= CFM_IMPRINT|CFM_ITALIC|CFM_LINK|CFM_OUTLINE|CFM_PROTECTED;
52   cf.dwMask |= CFM_REVISED|CFM_SHADOW|CFM_SMALLCAPS|CFM_STRIKEOUT;
53   cf.dwMask |= CFM_SUBSCRIPT|CFM_UNDERLINETYPE|CFM_WEIGHT;
54   
55   cf.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
56   lstrcpyW(cf.szFaceName, lf.lfFaceName);
57   cf.yHeight = ME_twips2pointsY(&c, lf.lfHeight);
58   if (lf.lfWeight >= 700) cf.dwEffects |= CFE_BOLD;
59   cf.wWeight = lf.lfWeight;
60   if (lf.lfItalic) cf.dwEffects |= CFE_ITALIC;
61   cf.bUnderlineType = (lf.lfUnderline) ? CFU_CF1UNDERLINE : CFU_UNDERLINENONE;
62   if (lf.lfStrikeOut) cf.dwEffects |= CFE_STRIKEOUT;
63   cf.bPitchAndFamily = lf.lfPitchAndFamily;
64   cf.bCharSet = lf.lfCharSet;
65
66   ZeroMemory(&fmt, sizeof(fmt));
67   fmt.cbSize = sizeof(fmt);
68   fmt.dwMask = PFM_ALIGNMENT | PFM_OFFSET | PFM_STARTINDENT | PFM_RIGHTINDENT | PFM_TABSTOPS;
69   fmt.wAlignment = PFA_LEFT;
70
71   CopyMemory(para->member.para.pFmt, &fmt, sizeof(PARAFORMAT2));
72   
73   style = ME_MakeStyle(&cf);
74   text->pDefaultStyle = style;
75   
76   run = ME_MakeRun(style, ME_MakeString(wszParagraphSign), MERF_ENDPARA);
77   run->member.run.nCharOfs = 0;
78
79   ME_InsertBefore(text->pLast, para);
80   ME_InsertBefore(text->pLast, run);
81   para->member.para.prev_para = text->pFirst;
82   para->member.para.next_para = text->pLast;
83   text->pFirst->member.para.next_para = para;
84   text->pLast->member.para.prev_para = para;
85
86   text->pLast->member.para.nCharOfs = 1;
87
88   ME_DestroyContext(&c);
89   ReleaseDC(editor->hWnd, hDC);
90 }
91  
92 void ME_MarkAllForWrapping(ME_TextEditor *editor)
93 {
94   ME_MarkForWrapping(editor, editor->pBuffer->pFirst->member.para.next_para, editor->pBuffer->pLast);
95 }
96
97 void ME_MarkForWrapping(ME_TextEditor *editor, ME_DisplayItem *first, const ME_DisplayItem *last)
98 {
99   while(first != last)
100   {
101     first->member.para.nFlags |= MEPF_REWRAP;
102     first = first->member.para.next_para;
103   }
104 }
105
106 void ME_MarkForPainting(ME_TextEditor *editor, ME_DisplayItem *first, const ME_DisplayItem *last)
107 {
108   while(first != last)
109   {
110     first->member.para.nFlags |= MEPF_REPAINT;
111     first = first->member.para.next_para;
112   }
113 }
114
115 /* split paragraph at the beginning of the run */
116 ME_DisplayItem *ME_SplitParagraph(ME_TextEditor *editor, ME_DisplayItem *run, ME_Style *style)
117 {
118   ME_DisplayItem *next_para = NULL;
119   ME_DisplayItem *run_para = NULL;
120   ME_DisplayItem *new_para = ME_MakeDI(diParagraph);
121   ME_DisplayItem *end_run = ME_MakeRun(style,ME_MakeString(wszParagraphSign), MERF_ENDPARA);
122   ME_UndoItem *undo = NULL;
123   int ofs;
124   ME_DisplayItem *pp;
125   int end_len = (editor->bEmulateVersion10 ? 2 : 1);
126   
127   assert(run->type == diRun);  
128
129   run_para = ME_GetParagraph(run);
130   assert(run_para->member.para.pFmt->cbSize == sizeof(PARAFORMAT2));
131
132   ofs = end_run->member.run.nCharOfs = run->member.run.nCharOfs;
133   next_para = run_para->member.para.next_para;
134   assert(next_para == ME_FindItemFwd(run_para, diParagraphOrEnd));
135   
136   undo = ME_AddUndoItem(editor, diUndoJoinParagraphs, NULL);
137   if (undo)
138     undo->nStart = run_para->member.para.nCharOfs + ofs;
139   
140   /* the new paragraph will have a different starting offset, so let's update its runs */
141   pp = run;
142   while(pp->type == diRun) {
143     pp->member.run.nCharOfs -= ofs;
144     pp = ME_FindItemFwd(pp, diRunOrParagraphOrEnd);
145   }
146   new_para->member.para.nCharOfs = ME_GetParagraph(run)->member.para.nCharOfs+ofs;
147   new_para->member.para.nCharOfs += end_len;
148   
149   new_para->member.para.nFlags = MEPF_REWRAP; /* FIXME copy flags (if applicable) */
150   /* FIXME initialize format style and call ME_SetParaFormat blah blah */
151   CopyMemory(new_para->member.para.pFmt, run_para->member.para.pFmt, sizeof(PARAFORMAT2));
152
153   new_para->member.para.bTable = run_para->member.para.bTable;
154   
155   /* Inherit previous cell definitions if any */
156   new_para->member.para.pCells = NULL;
157   if (run_para->member.para.pCells)
158   {
159     ME_TableCell *pCell, *pNewCell;
160
161     for (pCell = run_para->member.para.pCells; pCell; pCell = pCell->next)
162     {
163       pNewCell = ALLOC_OBJ(ME_TableCell);
164       pNewCell->nRightBoundary = pCell->nRightBoundary;
165       pNewCell->next = NULL;
166       if (new_para->member.para.pCells)
167         new_para->member.para.pLastCell->next = pNewCell;
168       else
169         new_para->member.para.pCells = pNewCell;
170       new_para->member.para.pLastCell = pNewCell;
171     }
172   }
173     
174   /* fix paragraph properties. FIXME only needed when called from RTF reader */
175   if (run_para->member.para.pCells && !run_para->member.para.bTable)
176   {
177     /* Paragraph does not have an \intbl keyword, so any table definition
178      * stored is invalid */
179     ME_DestroyTableCellList(run_para);
180   }
181   
182   /* insert paragraph into paragraph double linked list */
183   new_para->member.para.prev_para = run_para;
184   new_para->member.para.next_para = next_para;
185   run_para->member.para.next_para = new_para;
186   next_para->member.para.prev_para = new_para;
187
188   /* insert end run of the old paragraph, and new paragraph, into DI double linked list */
189   ME_InsertBefore(run, new_para);
190   ME_InsertBefore(new_para, end_run);
191
192   /* force rewrap of the */
193   run_para->member.para.prev_para->member.para.nFlags |= MEPF_REWRAP;
194   new_para->member.para.prev_para->member.para.nFlags |= MEPF_REWRAP;
195   
196   /* we've added the end run, so we need to modify nCharOfs in the next paragraphs */
197   ME_PropagateCharOffset(next_para, end_len);
198   editor->nParagraphs++;
199   
200   return new_para;
201 }
202
203 /* join tp with tp->member.para.next_para, keeping tp's style; this
204  * is consistent with the original */
205 ME_DisplayItem *ME_JoinParagraphs(ME_TextEditor *editor, ME_DisplayItem *tp)
206 {
207   ME_DisplayItem *pNext, *pFirstRunInNext, *pRun, *pTmp;
208   int i, shift;
209   ME_UndoItem *undo = NULL;
210   int end_len = (editor->bEmulateVersion10 ? 2 : 1);
211
212   assert(tp->type == diParagraph);
213   assert(tp->member.para.next_para);
214   assert(tp->member.para.next_para->type == diParagraph);
215   
216   pNext = tp->member.para.next_para;
217   
218   {
219     /* null char format operation to store the original char format for the ENDPARA run */
220     CHARFORMAT2W fmt;
221     ME_InitCharFormat2W(&fmt);
222     ME_SetCharFormat(editor, pNext->member.para.nCharOfs - end_len, end_len, &fmt);
223   }
224   undo = ME_AddUndoItem(editor, diUndoSplitParagraph, NULL);
225   if (undo)
226   {
227     undo->nStart = pNext->member.para.nCharOfs - end_len;
228     assert(pNext->member.para.pFmt->cbSize == sizeof(PARAFORMAT2));
229     CopyMemory(undo->di.member.para.pFmt, pNext->member.para.pFmt, sizeof(PARAFORMAT2));
230   }
231   
232   shift = pNext->member.para.nCharOfs - tp->member.para.nCharOfs - end_len;
233   
234   pRun = ME_FindItemBack(pNext, diRunOrParagraph);
235   pFirstRunInNext = ME_FindItemFwd(pNext, diRunOrParagraph);
236   
237   assert(pRun);
238   assert(pRun->type == diRun);
239   assert(pRun->member.run.nFlags & MERF_ENDPARA);
240   assert(pFirstRunInNext->type == diRun);
241   
242   /* if some cursor points at end of paragraph, make it point to the first
243      run of the next joined paragraph */
244   for (i=0; i<editor->nCursors; i++) {
245     if (editor->pCursors[i].pRun == pRun) {
246       editor->pCursors[i].pRun = pFirstRunInNext;
247       editor->pCursors[i].nOffset = 0;
248     }
249   }
250
251   pTmp = pNext;
252   do {
253     pTmp = ME_FindItemFwd(pTmp, diRunOrParagraphOrEnd);
254     if (pTmp->type != diRun)
255       break;
256     TRACE("shifting \"%s\" by %d (previous %d)\n", debugstr_w(pTmp->member.run.strText->szData), shift, pTmp->member.run.nCharOfs);
257     pTmp->member.run.nCharOfs += shift;
258   } while(1);
259   
260   ME_Remove(pRun);
261   ME_DestroyDisplayItem(pRun);
262
263   if (editor->pLastSelStartPara == pNext)
264     editor->pLastSelStartPara = tp;
265   if (editor->pLastSelEndPara == pNext)
266     editor->pLastSelEndPara = tp;
267     
268   tp->member.para.next_para = pNext->member.para.next_para;
269   pNext->member.para.next_para->member.para.prev_para = tp;
270   ME_Remove(pNext);
271   ME_DestroyDisplayItem(pNext);
272
273   ME_PropagateCharOffset(tp->member.para.next_para, -end_len);
274   
275   ME_CheckCharOffsets(editor);
276   
277   editor->nParagraphs--;
278   tp->member.para.nFlags |= MEPF_REWRAP;
279   return tp;
280 }
281
282 ME_DisplayItem *ME_GetParagraph(ME_DisplayItem *item) {
283   return ME_FindItemBackOrHere(item, diParagraph);
284 }
285
286 void ME_DumpParaStyleToBuf(const PARAFORMAT2 *pFmt, char buf[2048])
287 {
288   char *p;
289   p = buf;
290
291 #define DUMP(mask, name, fmt, field) \
292   if (pFmt->dwMask & (mask)) p += sprintf(p, "%-22s" fmt "\n", name, pFmt->field); \
293   else p += sprintf(p, "%-22sN/A\n", name);
294
295 /* we take for granted that PFE_xxx is the hiword of the corresponding PFM_xxx */
296 #define DUMP_EFFECT(mask, name) \
297   p += sprintf(p, "%-22s%s\n", name, (pFmt->dwMask & (mask)) ? ((pFmt->wEffects & ((mask) >> 8)) ? "yes" : "no") : "N/A");
298
299   DUMP(PFM_NUMBERING,      "Numbering:",         "%u", wNumbering);
300   DUMP_EFFECT(PFM_DONOTHYPHEN,     "Disable auto-hyphen:");
301   DUMP_EFFECT(PFM_KEEP,            "No page break in para:");
302   DUMP_EFFECT(PFM_KEEPNEXT,        "No page break in para & next:");
303   DUMP_EFFECT(PFM_NOLINENUMBER,    "No line number:");
304   DUMP_EFFECT(PFM_NOWIDOWCONTROL,  "No widow & orphan:");
305   DUMP_EFFECT(PFM_PAGEBREAKBEFORE, "Page break before:");
306   DUMP_EFFECT(PFM_RTLPARA,         "RTL para:");
307   DUMP_EFFECT(PFM_SIDEBYSIDE,      "Side by side:");
308   DUMP_EFFECT(PFM_TABLE,           "Table:");
309   DUMP(PFM_OFFSETINDENT,   "Offset indent:",     "%d", dxStartIndent);
310   DUMP(PFM_STARTINDENT,    "Start indent:",      "%d", dxStartIndent);
311   DUMP(PFM_RIGHTINDENT,    "Right indent:",      "%d", dxRightIndent);
312   DUMP(PFM_OFFSET,         "Offset:",            "%d", dxOffset);
313   if (pFmt->dwMask & PFM_ALIGNMENT) {
314     switch (pFmt->wAlignment) {
315     case PFA_LEFT   : p += sprintf(p, "Alignment:            left\n"); break;
316     case PFA_RIGHT  : p += sprintf(p, "Alignment:            right\n"); break;
317     case PFA_CENTER : p += sprintf(p, "Alignment:            center\n"); break;
318     case PFA_JUSTIFY: p += sprintf(p, "Alignment:            justify\n"); break;
319     default         : p += sprintf(p, "Alignment:            incorrect %d\n", pFmt->wAlignment); break;
320     }
321   }
322   else p += sprintf(p, "Alignment:            N/A\n");
323   DUMP(PFM_TABSTOPS,       "Tab Stops:",         "%d", cTabCount);
324   if (pFmt->dwMask & PFM_TABSTOPS) {
325     int i;
326     p += sprintf(p, "\t");
327     for (i = 0; i < pFmt->cTabCount; i++) p += sprintf(p, "%x ", pFmt->rgxTabs[i]);
328     p += sprintf(p, "\n");
329   }
330   DUMP(PFM_SPACEBEFORE,    "Space Before:",      "%d", dySpaceBefore);
331   DUMP(PFM_SPACEAFTER,     "Space After:",       "%d", dySpaceAfter);
332   DUMP(PFM_LINESPACING,    "Line spacing:",      "%d", dyLineSpacing);
333   DUMP(PFM_STYLE,          "Text style:",        "%d", sStyle);
334   DUMP(PFM_LINESPACING,    "Line spacing rule:", "%u", bLineSpacingRule);
335   /* bOutlineLevel should be 0 */
336   DUMP(PFM_SHADING,        "Shading Weigth:",    "%u", wShadingWeight);
337   DUMP(PFM_SHADING,        "Shading Style:",     "%u", wShadingStyle);
338   DUMP(PFM_NUMBERINGSTART, "Numbering Start:",   "%u", wNumberingStart);
339   DUMP(PFM_NUMBERINGSTYLE, "Numbering Style:",   "0x%x", wNumberingStyle);
340   DUMP(PFM_NUMBERINGTAB,   "Numbering Tab:",     "%u", wNumberingStyle);
341   DUMP(PFM_BORDER,         "Border Space:",      "%u", wBorderSpace);
342   DUMP(PFM_BORDER,         "Border Width:",      "%u", wBorderWidth);
343   DUMP(PFM_BORDER,         "Borders:",           "%u", wBorders);
344
345 #undef DUMP
346 #undef DUMP_EFFECT
347 }
348
349 void ME_SetParaFormat(ME_TextEditor *editor, ME_DisplayItem *para, const PARAFORMAT2 *pFmt)
350 {
351   PARAFORMAT2 copy;
352   assert(sizeof(*para->member.para.pFmt) == sizeof(PARAFORMAT2));
353   ME_AddUndoItem(editor, diUndoSetParagraphFormat, para);
354   
355   CopyMemory(&copy, para->member.para.pFmt, sizeof(PARAFORMAT2));
356
357 #define COPY_FIELD(m, f) \
358   if (pFmt->dwMask & (m)) {                     \
359     para->member.para.pFmt->dwMask |= m;        \
360     para->member.para.pFmt->f = pFmt->f;        \
361   }
362
363   COPY_FIELD(PFM_NUMBERING, wNumbering);
364 #define EFFECTS_MASK (PFM_RTLPARA|PFM_KEEP|PFM_KEEPNEXT|PFM_PAGEBREAKBEFORE| \
365                       PFM_NOLINENUMBER|PFM_NOWIDOWCONTROL|PFM_DONOTHYPHEN|PFM_SIDEBYSIDE| \
366                       PFM_TABLE)
367   /* we take for granted that PFE_xxx is the hiword of the corresponding PFM_xxx */
368   if (pFmt->dwMask & EFFECTS_MASK) {
369     para->member.para.pFmt->dwMask &= ~(pFmt->dwMask & EFFECTS_MASK);
370     para->member.para.pFmt->wEffects |= pFmt->wEffects & HIWORD(pFmt->dwMask);
371   }
372 #undef EFFECTS_MASK
373
374   COPY_FIELD(PFM_STARTINDENT, dxStartIndent);
375   if (pFmt->dwMask & PFM_OFFSETINDENT)
376     para->member.para.pFmt->dxStartIndent += pFmt->dxStartIndent;
377   COPY_FIELD(PFM_RIGHTINDENT, dxRightIndent);
378   COPY_FIELD(PFM_OFFSET, dxOffset);
379   COPY_FIELD(PFM_ALIGNMENT, wAlignment);
380
381   if (pFmt->dwMask & PFM_TABSTOPS)
382   {
383     para->member.para.pFmt->cTabCount = pFmt->cTabCount;
384     memcpy(para->member.para.pFmt->rgxTabs, pFmt->rgxTabs, pFmt->cTabCount*sizeof(LONG));
385   }
386   COPY_FIELD(PFM_SPACEBEFORE, dySpaceBefore);
387   COPY_FIELD(PFM_SPACEAFTER, dySpaceAfter);
388   COPY_FIELD(PFM_LINESPACING, dyLineSpacing);
389   COPY_FIELD(PFM_STYLE, sStyle);
390   COPY_FIELD(PFM_LINESPACING, bLineSpacingRule);
391   COPY_FIELD(PFM_SHADING, wShadingWeight);
392   COPY_FIELD(PFM_SHADING, wShadingStyle);
393   COPY_FIELD(PFM_NUMBERINGSTART, wNumberingStart);
394   COPY_FIELD(PFM_NUMBERINGSTYLE, wNumberingStyle);
395   COPY_FIELD(PFM_NUMBERINGTAB, wNumberingTab);
396   COPY_FIELD(PFM_BORDER, wBorderSpace);
397   COPY_FIELD(PFM_BORDER, wBorderWidth);
398   COPY_FIELD(PFM_BORDER, wBorders);
399
400   para->member.para.pFmt->dwMask |= pFmt->dwMask;
401 #undef COPY_FIELD
402
403   if (memcmp(&copy, para->member.para.pFmt, sizeof(PARAFORMAT2)))
404     para->member.para.nFlags |= MEPF_REWRAP;
405 }
406
407
408 void
409 ME_GetSelectionParas(ME_TextEditor *editor, ME_DisplayItem **para, ME_DisplayItem **para_end)
410 {
411   ME_Cursor *pEndCursor = &editor->pCursors[1];
412   
413   *para = ME_GetParagraph(editor->pCursors[0].pRun);
414   *para_end = ME_GetParagraph(editor->pCursors[1].pRun);
415   if ((*para_end)->member.para.nCharOfs < (*para)->member.para.nCharOfs) {
416     ME_DisplayItem *tmp = *para;
417
418     *para = *para_end;
419     *para_end = tmp;
420     pEndCursor = &editor->pCursors[0];
421   }
422   
423   /* selection consists of chars from nFrom up to nTo-1 */
424   if ((*para_end)->member.para.nCharOfs > (*para)->member.para.nCharOfs) {
425     if (!pEndCursor->nOffset) {
426       *para_end = ME_GetParagraph(ME_FindItemBack(pEndCursor->pRun, diRun));
427     }
428   }
429 }
430
431
432 void ME_SetSelectionParaFormat(ME_TextEditor *editor, const PARAFORMAT2 *pFmt)
433 {
434   ME_DisplayItem *para, *para_end;
435   
436   ME_GetSelectionParas(editor, &para, &para_end);
437  
438   do {
439     ME_SetParaFormat(editor, para, pFmt);
440     if (para == para_end)
441       break;
442     para = para->member.para.next_para;
443   } while(1);
444 }
445
446 void ME_GetParaFormat(ME_TextEditor *editor, const ME_DisplayItem *para, PARAFORMAT2 *pFmt)
447 {
448   if (pFmt->cbSize >= sizeof(PARAFORMAT2))
449   {
450     CopyMemory(pFmt, para->member.para.pFmt, sizeof(PARAFORMAT2));
451     return;
452   }
453   CopyMemory(pFmt, para->member.para.pFmt, pFmt->cbSize);  
454 }
455
456 void ME_GetSelectionParaFormat(ME_TextEditor *editor, PARAFORMAT2 *pFmt)
457 {
458   ME_DisplayItem *para, *para_end;
459   PARAFORMAT2 tmp;
460   
461   ME_GetSelectionParas(editor, &para, &para_end);
462   
463   ME_GetParaFormat(editor, para, pFmt);
464   if (para == para_end) return;
465   
466   do {
467     ZeroMemory(&tmp, sizeof(tmp));
468     tmp.cbSize = sizeof(tmp);
469     ME_GetParaFormat(editor, para, &tmp);
470
471 #define CHECK_FIELD(m, f) \
472     if (pFmt->f != tmp.f) pFmt->dwMask &= ~(m);
473
474     CHECK_FIELD(PFM_NUMBERING, wNumbering);
475     /* para->member.para.pFmt->wEffects = pFmt->wEffects; */
476     assert(tmp.dwMask & PFM_ALIGNMENT);
477     CHECK_FIELD(PFM_NUMBERING, wNumbering);
478     assert(tmp.dwMask & PFM_STARTINDENT);
479     CHECK_FIELD(PFM_STARTINDENT, dxStartIndent);
480     assert(tmp.dwMask & PFM_RIGHTINDENT);
481     CHECK_FIELD(PFM_RIGHTINDENT, dxRightIndent);
482     assert(tmp.dwMask & PFM_OFFSET);
483     CHECK_FIELD(PFM_OFFSET, dxOffset);
484     CHECK_FIELD(PFM_ALIGNMENT, wAlignment);
485
486     assert(tmp.dwMask & PFM_TABSTOPS);
487     if (pFmt->dwMask & PFM_TABSTOPS) {
488       if (pFmt->cTabCount != tmp.cTabCount ||
489           memcmp(pFmt->rgxTabs, tmp.rgxTabs, tmp.cTabCount*sizeof(int)))
490         pFmt->dwMask &= ~PFM_TABSTOPS;
491     }
492
493     CHECK_FIELD(PFM_SPACEBEFORE, dySpaceBefore);
494     CHECK_FIELD(PFM_SPACEAFTER, dySpaceAfter);
495     CHECK_FIELD(PFM_LINESPACING, dyLineSpacing);
496     CHECK_FIELD(PFM_STYLE, sStyle);
497     CHECK_FIELD(PFM_SPACEAFTER, bLineSpacingRule);
498     CHECK_FIELD(PFM_SHADING, wShadingWeight);
499     CHECK_FIELD(PFM_SHADING, wShadingStyle);
500     CHECK_FIELD(PFM_NUMBERINGSTART, wNumberingStart);
501     CHECK_FIELD(PFM_NUMBERINGSTYLE, wNumberingStyle);
502     CHECK_FIELD(PFM_NUMBERINGTAB, wNumberingTab);
503     CHECK_FIELD(PFM_BORDER, wBorderSpace);
504     CHECK_FIELD(PFM_BORDER, wBorderWidth);
505     CHECK_FIELD(PFM_BORDER, wBorders);
506
507 #undef CHECK_FIELD
508
509     if (para == para_end)
510       return;
511     para = para->member.para.next_para;
512   } while(1);
513 }