Prevent crash when no URL is specified.
[wine] / dlls / riched20 / run.c
1 /*
2  * RichEdit - operations on runs (diRun, rectangular pieces of paragraphs).
3  * Splitting/joining runs. Adjusting offsets after deleting/adding content.
4  * Character/pixel conversions.
5  *
6  * Copyright 2004 by Krzysztof Foltman
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 #include "editor.h"
24
25 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
26
27 int ME_CanJoinRuns(ME_Run *run1, ME_Run *run2)
28 {
29   if ((run1->nFlags | run2->nFlags) & (MERF_ENDPARA | MERF_GRAPHICS))
30     return 0;
31   if (run1->style != run2->style)
32     return 0;
33   if ((run1->nFlags & MERF_STYLEFLAGS) != (run2->nFlags & MERF_STYLEFLAGS))
34     return 0;
35   return 1;
36 }
37
38 void ME_SkipAndPropagateCharOffset(ME_DisplayItem *p, int shift)
39 {
40   p = ME_FindItemFwd(p, diRunOrParagraphOrEnd);
41   assert(p);
42   ME_PropagateCharOffset(p, shift);
43 }
44
45 void ME_PropagateCharOffset(ME_DisplayItem *p, int shift)
46 {
47   if (p->type == diRun) /* propagate in all runs in this para */
48   {
49     TRACE("PropagateCharOffset(%s, %d)\n", debugstr_w(p->member.run.strText->szData), shift);
50     do {
51       p->member.run.nCharOfs += shift;
52       assert(p->member.run.nCharOfs >= 0);
53       p = ME_FindItemFwd(p, diRunOrParagraphOrEnd);
54     } while(p->type == diRun);
55   }
56   if (p->type == diParagraph) /* propagate in all next paras */
57   {
58     do {
59       p->member.para.nCharOfs += shift;
60       assert(p->member.para.nCharOfs >= 0);
61       p = p->member.para.next_para;
62     } while(p->type == diParagraph);
63   }
64   if (p->type == diTextEnd)
65   {
66     p->member.para.nCharOfs += shift;
67     assert(p->member.para.nCharOfs >= 0);
68   }
69 }
70
71 void ME_CheckCharOffsets(ME_TextEditor *editor)
72 {
73   ME_DisplayItem *p = editor->pBuffer->pFirst;
74   int ofs = 0, ofsp = 0;
75   if(TRACE_ON(richedit))
76   {
77     TRACE("---\n");
78     ME_DumpDocument(editor->pBuffer);
79   }
80   do {
81     p = ME_FindItemFwd(p, diRunOrParagraphOrEnd);
82     switch(p->type) {
83       case diTextEnd:
84         TRACE("tend, real ofsp = %d, counted = %d\n", p->member.para.nCharOfs, ofsp+ofs);
85         assert(ofsp+ofs == p->member.para.nCharOfs);
86         return;
87       case diParagraph:
88         TRACE("para, real ofsp = %d, counted = %d\n", p->member.para.nCharOfs, ofsp+ofs);
89         assert(ofsp+ofs == p->member.para.nCharOfs);
90         ofsp = p->member.para.nCharOfs;
91         ofs = 0;
92         break;
93       case diRun:
94         TRACE("run, real ofs = %d (+ofsp = %d), counted = %d, len = %d, txt = \"%s\", flags=%08x, fx&mask = %08lx\n", 
95           p->member.run.nCharOfs, p->member.run.nCharOfs+ofsp, ofsp+ofs, 
96           p->member.run.strText->nLen, debugstr_w(p->member.run.strText->szData),
97           p->member.run.nFlags,
98           p->member.run.style->fmt.dwMask & p->member.run.style->fmt.dwEffects);
99         assert(ofs == p->member.run.nCharOfs);
100         ofs += ME_StrLen(p->member.run.strText);
101         break;
102       default:
103         assert(0);
104     }
105   } while(1);
106 }
107
108 int ME_CharOfsFromRunOfs(ME_TextEditor *editor, ME_DisplayItem *pRun, int nOfs)
109 {
110   ME_DisplayItem *pPara;
111   
112   assert(pRun->type == diRun);
113   assert(pRun->member.run.nCharOfs != -1);
114
115   pPara = ME_FindItemBack(pRun, diParagraph);
116   assert(pPara);
117   assert(pPara->type==diParagraph);
118   return pPara->member.para.nCharOfs + pRun->member.run.nCharOfs 
119     + ME_VPosToPos(pRun->member.run.strText, nOfs);
120 }
121
122 void ME_CursorFromCharOfs(ME_TextEditor *editor, int nCharOfs, ME_Cursor *pCursor)
123 {
124   ME_RunOfsFromCharOfs(editor, nCharOfs, &pCursor->pRun, &pCursor->nOffset);
125 }
126
127 void ME_RunOfsFromCharOfs(ME_TextEditor *editor, int nCharOfs, ME_DisplayItem **ppRun, int *pOfs)
128 {
129   ME_DisplayItem *pPara;
130   int nParaOfs;
131   
132   pPara = editor->pBuffer->pFirst->member.para.next_para;
133   assert(pPara);
134   assert(ppRun);
135   assert(pOfs);
136   while (pPara->type == diParagraph)
137   {
138     nParaOfs = pPara->member.para.nCharOfs;
139     assert(nCharOfs >= nParaOfs);
140     
141     if (nCharOfs < pPara->member.para.next_para->member.para.nCharOfs)
142     {
143       *ppRun = ME_FindItemFwd(pPara, diRun);
144       assert(*ppRun);      
145       while (!((*ppRun)->member.run.nFlags & MERF_ENDPARA))
146       {
147         ME_DisplayItem *pNext = ME_FindItemFwd(*ppRun, diRun);
148         assert(pNext);
149         assert(pNext->type == diRun);
150         if (nCharOfs < nParaOfs + pNext->member.run.nCharOfs) {
151           *pOfs = ME_PosToVPos((*ppRun)->member.run.strText, 
152             nCharOfs - nParaOfs - (*ppRun)->member.run.nCharOfs);
153           return;
154         }
155         *ppRun = pNext;
156       }
157       if (nCharOfs == nParaOfs + (*ppRun)->member.run.nCharOfs) {
158         *pOfs = 0;
159         return;
160       }        
161     }
162     pPara = pPara->member.para.next_para;
163   }
164   *ppRun = ME_FindItemBack(editor->pBuffer->pLast, diRun);
165   *pOfs = 0;  
166   assert((*ppRun)->member.run.nFlags & MERF_ENDPARA);
167 }
168
169 void ME_JoinRuns(ME_TextEditor *editor, ME_DisplayItem *p)
170 {
171   ME_DisplayItem *pNext = p->next;
172   int i;
173   assert(p->type == diRun && pNext->type == diRun);
174   assert(p->member.run.nCharOfs != -1);
175   ME_GetParagraph(p)->member.para.nFlags |= MEPF_REWRAP;
176
177   if (editor->bCaretAtEnd && editor->pCursors[0].pRun == pNext)
178     editor->bCaretAtEnd = FALSE;
179   for (i=0; i<editor->nCursors; i++) {
180     if (editor->pCursors[i].pRun == pNext) {
181       editor->pCursors[i].pRun = p;
182       editor->pCursors[i].nOffset += ME_StrVLen(p->member.run.strText);
183     }
184   }
185   
186   ME_AppendString(p->member.run.strText, pNext->member.run.strText);
187   ME_Remove(pNext);
188   ME_DestroyDisplayItem(pNext);
189   ME_UpdateRunFlags(editor, &p->member.run);
190   if(TRACE_ON(richedit))
191   {
192     TRACE("Before check after join\n");
193     ME_CheckCharOffsets(editor);
194     TRACE("After check after join\n");
195   }
196 }
197
198 ME_DisplayItem *ME_SplitRun(ME_Context *c, ME_DisplayItem *item, int nVChar)
199 {
200   ME_TextEditor *editor = c->editor;
201   ME_DisplayItem *item2 = NULL;
202   ME_Run *run, *run2;
203   
204   assert(item->member.run.nCharOfs != -1);
205   if(TRACE_ON(richedit))
206   {
207     TRACE("Before check before split\n");
208     ME_CheckCharOffsets(editor);
209     TRACE("After check before split\n");
210   }
211
212   run = &item->member.run;
213
214   TRACE("Before split: %s(%ld, %ld)\n", debugstr_w(run->strText->szData),
215         run->pt.x, run->pt.y);
216
217   item2 = ME_SplitRunSimple(editor, item, nVChar);
218   
219   run2 = &item2->member.run;
220   
221   ME_CalcRunExtent(c, run);
222   ME_CalcRunExtent(c, run2);
223     
224   run2->pt.x = run->pt.x+run->nWidth;
225   run2->pt.y = run->pt.y;
226   
227   if(TRACE_ON(richedit))
228   {
229     TRACE("Before check after split\n");
230     ME_CheckCharOffsets(editor);
231     TRACE("After check after split\n");
232     TRACE("After split: %s(%ld, %ld), %s(%ld, %ld)\n", 
233       debugstr_w(run->strText->szData), run->pt.x, run->pt.y,
234       debugstr_w(run2->strText->szData), run2->pt.x, run2->pt.y);
235   }
236
237   return item2;
238 }
239   
240 /* split a run starting from voffset */
241 ME_DisplayItem *ME_SplitRunSimple(ME_TextEditor *editor, ME_DisplayItem *item, int nVChar)
242 {
243   ME_Run *run = &item->member.run;
244   ME_DisplayItem *item2;
245   ME_Run *run2;
246   int i;
247   assert(nVChar > 0 && nVChar < ME_StrVLen(run->strText));
248   assert(item->type == diRun);
249   assert(!(item->member.run.nFlags & MERF_GRAPHICS));
250   assert(item->member.run.nCharOfs != -1);
251
252   item2 = ME_MakeRun(run->style, 
253       ME_VSplitString(run->strText, nVChar), run->nFlags&MERF_SPLITMASK);
254   
255   item2->member.run.nCharOfs = item->member.run.nCharOfs+
256     ME_VPosToPos(item->member.run.strText, nVChar);
257
258   run2 = &item2->member.run;
259   ME_InsertBefore(item->next, item2);
260   
261   ME_UpdateRunFlags(editor, run);
262   ME_UpdateRunFlags(editor, run2);
263   for (i=0; i<editor->nCursors; i++) {
264     if (editor->pCursors[i].pRun == item && 
265         editor->pCursors[i].nOffset >= nVChar) {
266       assert(item2->type == diRun);
267       editor->pCursors[i].pRun = item2;
268       editor->pCursors[i].nOffset -= nVChar;
269     }
270   }
271   ME_GetParagraph(item)->member.para.nFlags |= MEPF_REWRAP;
272   return item2;
273 }
274
275 /* split the start and final whitespace into separate runs */
276 /* returns the last run added */
277 /*
278 ME_DisplayItem *ME_SplitFurther(ME_TextEditor *editor, ME_DisplayItem *item)
279 {
280   int i, nVLen, nChanged;
281   assert(item->type == diRun);
282   assert(!(item->member.run.nFlags & MERF_GRAPHICS));
283   return item;
284 }
285 */
286
287 ME_DisplayItem *ME_MakeRun(ME_Style *s, ME_String *strData, int nFlags)
288 {
289   ME_DisplayItem *item = ME_MakeDI(diRun);
290   item->member.run.style = s;
291   item->member.run.strText = strData;
292   item->member.run.nFlags = nFlags;
293   item->member.run.nCharOfs = -1;
294   ME_AddRefStyle(s);
295   return item;
296 }
297
298 ME_DisplayItem *ME_InsertRun(ME_TextEditor *editor, int nCharOfs, ME_DisplayItem *pItem)
299 {
300   ME_Cursor tmp;
301   ME_DisplayItem *pDI;
302   ME_UndoItem *pUI;
303   
304   assert(pItem->type == diRun || pItem->type == diUndoInsertRun);
305   
306   pUI = ME_AddUndoItem(editor, diUndoDeleteRun, NULL);
307   if (pUI) {
308     pUI->nStart = nCharOfs;
309     pUI->nLen = pItem->member.run.strText->nLen;
310   }
311   ME_CursorFromCharOfs(editor, nCharOfs, &tmp);
312   if (tmp.nOffset) {
313     tmp.pRun = ME_SplitRunSimple(editor, tmp.pRun, tmp.nOffset);
314     tmp.nOffset = 0;
315   }
316   pDI = ME_MakeRun(pItem->member.run.style, ME_StrDup(pItem->member.run.strText), pItem->member.run.nFlags);
317   pDI->member.run.nCharOfs = tmp.pRun->member.run.nCharOfs;
318   ME_InsertBefore(tmp.pRun, pDI);
319   TRACE("Shift length:%d\n", pDI->member.run.strText->nLen);
320   ME_PropagateCharOffset(tmp.pRun, pDI->member.run.strText->nLen);
321   ME_GetParagraph(tmp.pRun)->member.para.nFlags |= MEPF_REWRAP;
322   
323   return pDI;
324 }
325
326 void ME_UpdateRunFlags(ME_TextEditor *editor, ME_Run *run)
327 {
328   assert(run->nCharOfs != -1);
329   if (ME_IsSplitable(run->strText))
330     run->nFlags |= MERF_SPLITTABLE;
331   else
332     run->nFlags &= ~MERF_SPLITTABLE;
333     
334   if (!(run->nFlags & MERF_GRAPHICS)) {
335     if (ME_IsWhitespaces(run->strText))
336       run->nFlags |= MERF_WHITESPACE | MERF_STARTWHITE | MERF_ENDWHITE;
337     else
338     {
339       run->nFlags &= ~MERF_WHITESPACE;
340     
341       if (ME_IsWSpace(ME_GetCharFwd(run->strText,0)))
342         run->nFlags |= MERF_STARTWHITE;
343       else
344         run->nFlags &= ~MERF_STARTWHITE;
345     
346       if (ME_IsWSpace(ME_GetCharBack(run->strText,0)))
347         run->nFlags |= MERF_ENDWHITE;
348       else
349         run->nFlags &= ~MERF_ENDWHITE;
350     }
351   }
352   else
353     run->nFlags &= ~(MERF_WHITESPACE | MERF_STARTWHITE | MERF_ENDWHITE);
354 }
355
356 void ME_GetGraphicsSize(ME_TextEditor *editor, ME_Run *run, SIZE *pSize)
357 {
358   assert(run->nFlags & MERF_GRAPHICS);
359   pSize->cx = 64;
360   pSize->cy = 64;
361 }
362
363 int ME_CharFromPoint(ME_TextEditor *editor, int cx, ME_Run *run)
364 {
365   int fit = 0;
366   HGDIOBJ hOldFont;
367   HDC hDC;
368   SIZE sz;
369   if (!run->strText->nLen)
370     return 0;
371
372   if (run->nFlags & MERF_GRAPHICS)
373   {
374     SIZE sz;
375     ME_GetGraphicsSize(editor, run, &sz);
376     if (cx < sz.cx)
377       return 0;
378     return 1;
379   }
380   hDC = GetDC(editor->hWnd);
381   hOldFont = ME_SelectStyleFont(editor, hDC, run->style);
382   GetTextExtentExPointW(hDC, run->strText->szData, run->strText->nLen, 
383     cx, &fit, NULL, &sz);
384   ME_UnselectStyleFont(editor, hDC, run->style, hOldFont);  
385   ReleaseDC(editor->hWnd, hDC);
386   return fit;
387 }
388
389 int ME_CharFromPointCursor(ME_TextEditor *editor, int cx, ME_Run *run)
390 {
391   int fit = 0, fit1 = 0;
392   HGDIOBJ hOldFont;
393   HDC hDC;
394   SIZE sz, sz2, sz3;
395   if (!run->strText->nLen)
396     return 0;
397
398   if (run->nFlags & MERF_GRAPHICS)
399   {
400     SIZE sz;
401     ME_GetGraphicsSize(editor, run, &sz);
402     if (cx < sz.cx/2)
403       return 0;
404     return 1;
405   }
406
407   hDC = GetDC(editor->hWnd);
408   hOldFont = ME_SelectStyleFont(editor, hDC, run->style);
409   GetTextExtentExPointW(hDC, run->strText->szData, run->strText->nLen, 
410     cx, &fit, NULL, &sz);
411   if (fit != run->strText->nLen)
412   {
413     int chars = 1;
414     
415     GetTextExtentPoint32W(hDC, run->strText->szData, fit, &sz2);
416     fit1 = ME_StrRelPos(run->strText, fit, &chars);
417     GetTextExtentPoint32W(hDC, run->strText->szData, fit1, &sz3);
418     if (cx >= (sz2.cx+sz3.cx)/2)
419       fit = fit1;
420   }
421   ME_UnselectStyleFont(editor, hDC, run->style, hOldFont);  
422   ReleaseDC(editor->hWnd, hDC);
423   return fit;
424 }
425
426 int ME_PointFromChar(ME_TextEditor *editor, ME_Run *pRun, int nOffset)
427 {
428   SIZE size;
429   HDC hDC = GetDC(editor->hWnd);
430   HGDIOBJ hOldFont;
431   
432   if (pRun->nFlags & MERF_GRAPHICS)
433   {
434     if (!nOffset) return 0;
435     ME_GetGraphicsSize(editor, pRun, &size);
436     return 1;
437   }
438   hOldFont = ME_SelectStyleFont(editor, hDC, pRun->style);
439   GetTextExtentPoint32W(hDC, pRun->strText->szData, nOffset, &size);
440   ME_UnselectStyleFont(editor, hDC, pRun->style, hOldFont);  
441   ReleaseDC(editor->hWnd, hDC);
442   return size.cx;
443 }
444
445 void ME_GetTextExtent(ME_Context *c, LPCWSTR szText, int nChars, ME_Style *s, 
446   SIZE *size)
447 {
448   HDC hDC = c->hDC;
449   HGDIOBJ hOldFont;
450   hOldFont = ME_SelectStyleFont(c->editor, hDC, s);
451   GetTextExtentPoint32W(hDC, szText, nChars, size);
452   ME_UnselectStyleFont(c->editor, hDC, s, hOldFont);  
453 }
454
455 SIZE ME_GetRunSize(ME_Context *c, ME_Run *run, int nLen)
456 {
457   SIZE size;
458   int nMaxLen = ME_StrVLen(run->strText);
459
460   if (nLen>nMaxLen)
461     nLen = nMaxLen;
462     
463   if (run->nFlags & MERF_GRAPHICS)
464   {
465     ME_GetGraphicsSize(c->editor, run, &size);
466     return size;
467   }
468
469   ME_GetTextExtent(c, run->strText->szData, nLen, run->style, &size);
470
471   return size;
472 }
473
474 void ME_CalcRunExtent(ME_Context *c, ME_Run *run)
475 {
476   SIZE size;
477   int nEnd = ME_StrVLen(run->strText);
478   
479   if (run->nFlags & MERF_GRAPHICS) {
480     ME_GetGraphicsSize(c->editor, run, &size);
481     run->nWidth = size.cx;
482     run->nAscent = size.cy;
483     run->nDescent = 0;
484     return;
485   }
486   ME_GetTextExtent(c, run->strText->szData, nEnd, run->style, &size);
487   run->nWidth = size.cx;
488   run->nAscent = run->style->tm.tmAscent;
489   run->nDescent = run->style->tm.tmDescent;
490 }
491
492 void ME_MustBeWrapped(ME_Context *c, ME_DisplayItem *para)
493 {
494   assert(para->type == diParagraph);
495   /* FIXME */
496 }
497
498 void ME_SetSelectionCharFormat(ME_TextEditor *editor, CHARFORMAT2W *pFmt)
499 {
500   int nFrom, nTo;
501   ME_GetSelection(editor, &nFrom, &nTo);
502   if (nFrom == nTo)
503   {
504     ME_Style *s;
505     if (!editor->pBuffer->pCharStyle)
506       editor->pBuffer->pCharStyle = ME_GetInsertStyle(editor, 0);
507     s = ME_ApplyStyle(editor->pBuffer->pCharStyle, pFmt);
508     ME_ReleaseStyle(editor->pBuffer->pCharStyle);
509     editor->pBuffer->pCharStyle = s;
510   }
511   else
512     ME_SetCharFormat(editor, nFrom, nTo-nFrom, pFmt);
513 }
514
515 void ME_SetCharFormat(ME_TextEditor *editor, int nOfs, int nChars, CHARFORMAT2W *pFmt)
516 {
517   ME_Cursor tmp, tmp2;
518   ME_DisplayItem *para;
519   
520   ME_CursorFromCharOfs(editor, nOfs, &tmp);
521   if (tmp.nOffset)
522     tmp.pRun = ME_SplitRunSimple(editor, tmp.pRun, tmp.nOffset);
523
524   ME_CursorFromCharOfs(editor, nOfs+nChars, &tmp2);
525   if (tmp2.nOffset)
526     tmp2.pRun = ME_SplitRunSimple(editor, tmp2.pRun, tmp2.nOffset);
527
528   para = ME_GetParagraph(tmp.pRun);
529   para->member.para.nFlags |= MEPF_REWRAP;
530     
531   while(tmp.pRun != tmp2.pRun)
532   {
533     ME_UndoItem *undo = NULL;
534     ME_Style *new_style = ME_ApplyStyle(tmp.pRun->member.run.style, pFmt);
535     /* ME_DumpStyle(new_style); */
536     undo = ME_AddUndoItem(editor, diUndoSetCharFormat, NULL);
537     if (undo) {
538       undo->nStart = tmp.pRun->member.run.nCharOfs+para->member.para.nCharOfs;
539       undo->nLen = tmp.pRun->member.run.strText->nLen;
540       undo->di.member.ustyle = tmp.pRun->member.run.style;
541       /* we'd have to addref undo..ustyle and release tmp...style
542          but they'd cancel each other out so we can do nothing instead */
543     }
544     else
545       ME_ReleaseStyle(tmp.pRun->member.run.style);
546     tmp.pRun->member.run.style = new_style;
547     tmp.pRun = ME_FindItemFwd(tmp.pRun, diRunOrParagraph);
548     if (tmp.pRun->type == diParagraph)
549     {
550       para = tmp.pRun;
551       tmp.pRun = ME_FindItemFwd(tmp.pRun, diRun);
552       if (tmp.pRun != tmp2.pRun)
553         para->member.para.nFlags |= MEPF_REWRAP;
554     }
555     assert(tmp.pRun);
556   }
557 }
558
559 void ME_SetDefaultCharFormat(ME_TextEditor *editor, CHARFORMAT2W *mod)
560 {
561   ME_Style *style; 
562   ME_UndoItem *undo;
563   
564   assert(mod->cbSize == sizeof(CHARFORMAT2W));
565   undo = ME_AddUndoItem(editor, diUndoSetDefaultCharFormat, NULL);
566   if (undo) {
567     undo->nStart = -1;
568     undo->nLen = -1;
569     undo->di.member.ustyle = editor->pBuffer->pDefaultStyle;
570     ME_AddRefStyle(undo->di.member.ustyle);
571   }
572   style = ME_ApplyStyle(editor->pBuffer->pDefaultStyle, mod);
573   editor->pBuffer->pDefaultStyle->fmt = style->fmt;
574   editor->pBuffer->pDefaultStyle->tm = style->tm;
575   ME_ReleaseStyle(style);
576   ME_MarkAllForWrapping(editor);
577   /*  pcf = editor->pBuffer->pDefaultStyle->fmt; */
578 }
579
580 void ME_GetRunCharFormat(ME_TextEditor *editor, ME_DisplayItem *run, CHARFORMAT2W *pFmt)
581 {
582   ME_CopyCharFormat(pFmt, &run->member.run.style->fmt);
583 }
584
585 void ME_GetDefaultCharFormat(ME_TextEditor *editor, CHARFORMAT2W *pFmt)
586 {
587   int nFrom, nTo;
588   ME_GetSelection(editor, &nFrom, &nTo);
589   ME_CopyCharFormat(pFmt, &editor->pBuffer->pDefaultStyle->fmt);
590 }
591
592 void ME_GetSelectionCharFormat(ME_TextEditor *editor, CHARFORMAT2W *pFmt)
593 {
594   int nFrom, nTo;
595   ME_GetSelection(editor, &nFrom, &nTo);
596   if (nFrom == nTo && editor->pBuffer->pCharStyle)
597   {
598     ME_CopyCharFormat(pFmt, &editor->pBuffer->pCharStyle->fmt);
599     return;
600   }
601   ME_GetCharFormat(editor, nFrom, nTo, pFmt);
602 }
603
604 void ME_GetCharFormat(ME_TextEditor *editor, int nFrom, int nTo, CHARFORMAT2W *pFmt)
605 {
606   ME_DisplayItem *run, *run_end;
607   int nOffset, nOffset2;
608   CHARFORMAT2W tmp;
609   
610   if (nTo>nFrom) /* selection consists of chars from nFrom up to nTo-1 */
611     nTo--;
612   
613   ME_RunOfsFromCharOfs(editor, nFrom, &run, &nOffset);
614   if (nFrom == nTo) /* special case - if selection is empty, take previous char's formatting */
615   {
616     if (!nOffset)
617     {
618       ME_DisplayItem *tmp_run = ME_FindItemBack(run, diRunOrParagraph);
619       if (tmp_run->type == diRun) {
620         ME_GetRunCharFormat(editor, tmp_run, pFmt);
621         return;
622       }
623     }
624     ME_GetRunCharFormat(editor, run, pFmt);
625     return;
626   }
627   ME_RunOfsFromCharOfs(editor, nTo, &run_end, &nOffset2);
628   
629   ME_GetRunCharFormat(editor, run, pFmt);
630   
631   if (run == run_end) return;
632   
633   do {
634     /* FIXME add more style feature comparisons */
635     int nAttribs = CFM_SIZE | CFM_FACE | CFM_COLOR;
636     int nEffects = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE;
637
638     run = ME_FindItemFwd(run, diRun);
639     
640     ZeroMemory(&tmp, sizeof(tmp));
641     tmp.cbSize = sizeof(tmp);
642     ME_GetRunCharFormat(editor, run, &tmp);
643     
644     assert((tmp.dwMask & nAttribs) == nAttribs);
645     assert((tmp.dwMask & nEffects) == nEffects);
646     /* reset flags that differ */
647
648     if (pFmt->yHeight != tmp.yHeight)
649       pFmt->dwMask &= ~CFM_SIZE;
650     if (pFmt->dwMask & CFM_FACE)
651     {
652       if (!(tmp.dwMask & CFM_FACE))
653         pFmt->dwMask &= ~CFM_FACE;
654       else if (lstrcmpW(pFmt->szFaceName, tmp.szFaceName))
655         pFmt->dwMask &= ~CFM_FACE;
656     }
657     if (pFmt->yHeight != tmp.yHeight)
658       pFmt->dwMask &= ~CFM_SIZE;
659     if (pFmt->dwMask & CFM_COLOR)
660     {
661       if (!((pFmt->dwEffects&CFE_AUTOCOLOR) & (tmp.dwEffects&CFE_AUTOCOLOR)))
662       {
663         if (pFmt->crTextColor != tmp.crTextColor)
664           pFmt->dwMask &= ~CFM_COLOR;
665       }
666     }
667       
668     pFmt->dwMask &= ~((pFmt->dwEffects ^ tmp.dwEffects) & nEffects);
669     
670   } while(run != run_end);
671 }