2 * RichEdit - operations on runs (diRun, rectangular pieces of paragraphs).
3 * Splitting/joining runs. Adjusting offsets after deleting/adding content.
4 * Character/pixel conversions.
6 * Copyright 2004 by Krzysztof Foltman
7 * Copyright 2006 by Phil Krylov
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 WINE_DEFAULT_DEBUG_CHANNEL(richedit
);
27 WINE_DECLARE_DEBUG_CHANNEL(richedit_check
);
28 WINE_DECLARE_DEBUG_CHANNEL(richedit_lists
);
30 ME_Run
*run_next( ME_Run
*run
)
32 ME_DisplayItem
*item
= run_get_di( run
);
34 if (ME_NextRun( NULL
, &item
, FALSE
))
35 return &item
->member
.run
;
40 ME_Run
*run_prev( ME_Run
*run
)
42 ME_DisplayItem
*item
= run_get_di( run
);
44 if (ME_PrevRun( NULL
, &item
, FALSE
))
45 return &item
->member
.run
;
50 ME_Run
*run_next_all_paras( ME_Run
*run
)
52 ME_DisplayItem
*item
= run_get_di( run
), *dummy
= para_get_di( run
->para
);
54 if (ME_NextRun( &dummy
, &item
, TRUE
))
55 return &item
->member
.run
;
60 ME_Run
*run_prev_all_paras( ME_Run
*run
)
62 ME_DisplayItem
*item
= run_get_di( run
), *dummy
= para_get_di( run
->para
);
64 if (ME_PrevRun( &dummy
, &item
, TRUE
))
65 return &item
->member
.run
;
70 /******************************************************************************
73 * Returns TRUE if two runs can be safely merged into one, FALSE otherwise.
75 BOOL
ME_CanJoinRuns(const ME_Run
*run1
, const ME_Run
*run2
)
77 if ((run1
->nFlags
| run2
->nFlags
) & MERF_NOJOIN
)
79 if (run1
->style
!= run2
->style
)
81 if ((run1
->nFlags
& MERF_STYLEFLAGS
) != (run2
->nFlags
& MERF_STYLEFLAGS
))
86 void ME_SkipAndPropagateCharOffset(ME_DisplayItem
*p
, int shift
)
88 p
= ME_FindItemFwd(p
, diRunOrParagraphOrEnd
);
90 ME_PropagateCharOffset(p
, shift
);
93 /******************************************************************************
94 * ME_PropagateCharOffsets
96 * Shifts (increases or decreases) character offset (relative to beginning of
97 * the document) of the part of the text starting from given place.
99 void ME_PropagateCharOffset(ME_DisplayItem
*p
, int shift
)
101 /* Runs in one paragraph contain character offset relative to their owning
102 * paragraph. If we start the shifting from the run, we need to shift
103 * all the relative offsets until the end of the paragraph
105 if (p
->type
== diRun
) /* propagate in all runs in this para */
107 TRACE("PropagateCharOffset(%s, %d)\n", debugstr_run( &p
->member
.run
), shift
);
109 p
->member
.run
.nCharOfs
+= shift
;
110 assert(p
->member
.run
.nCharOfs
>= 0);
111 p
= ME_FindItemFwd(p
, diRunOrParagraphOrEnd
);
112 } while(p
->type
== diRun
);
114 /* Runs in next paragraphs don't need their offsets updated, because they,
115 * again, those offsets are relative to their respective paragraphs.
116 * Instead of that, we're updating paragraphs' character offsets.
118 if (p
->type
== diParagraph
) /* propagate in all next paras */
121 p
->member
.para
.nCharOfs
+= shift
;
122 assert(p
->member
.para
.nCharOfs
>= 0);
123 p
= p
->member
.para
.next_para
;
124 } while(p
->type
== diParagraph
);
126 /* diTextEnd also has character offset in it, which makes finding text length
127 * easier. But it needs to be up to date first.
129 if (p
->type
== diTextEnd
)
131 p
->member
.para
.nCharOfs
+= shift
;
132 assert(p
->member
.para
.nCharOfs
>= 0);
136 /******************************************************************************
137 * ME_CheckCharOffsets
139 * Checks if editor lists' validity and optionally dumps the document structure
141 void ME_CheckCharOffsets(ME_TextEditor
*editor
)
143 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
;
144 int ofs
= 0, ofsp
= 0;
146 if (!TRACE_ON(richedit_check
))
149 TRACE_(richedit_check
)("Checking begin\n");
150 if(TRACE_ON(richedit_lists
))
152 TRACE_(richedit_lists
)("---\n");
153 ME_DumpDocument(editor
->pBuffer
);
156 p
= ME_FindItemFwd(p
, diRunOrParagraphOrEnd
);
159 TRACE_(richedit_check
)("tend, real ofsp = %d, counted = %d\n", p
->member
.para
.nCharOfs
, ofsp
+ofs
);
160 assert(ofsp
+ofs
== p
->member
.para
.nCharOfs
);
161 TRACE_(richedit_check
)("Checking finished\n");
164 TRACE_(richedit_check
)("para, real ofsp = %d, counted = %d\n", p
->member
.para
.nCharOfs
, ofsp
+ofs
);
165 assert(ofsp
+ofs
== p
->member
.para
.nCharOfs
);
166 ofsp
= p
->member
.para
.nCharOfs
;
170 TRACE_(richedit_check
)("run, real ofs = %d (+ofsp = %d), counted = %d, len = %d, txt = %s, flags=%08x, fx&mask = %08x\n",
171 p
->member
.run
.nCharOfs
, p
->member
.run
.nCharOfs
+ofsp
, ofsp
+ofs
,
172 p
->member
.run
.len
, debugstr_run( &p
->member
.run
),
173 p
->member
.run
.nFlags
,
174 p
->member
.run
.style
->fmt
.dwMask
& p
->member
.run
.style
->fmt
.dwEffects
);
175 assert(ofs
== p
->member
.run
.nCharOfs
);
176 assert(p
->member
.run
.len
);
177 ofs
+= p
->member
.run
.len
;
180 TRACE_(richedit_check
)("cell\n");
186 TRACE_(richedit_check
)("Checking finished\n");
189 /******************************************************************************
192 * Converts a character position relative to the start of the run to a
193 * character position relative to the start of the document.
196 int run_char_ofs( ME_Run
*run
, int ofs
)
198 return run
->para
->nCharOfs
+ run
->nCharOfs
+ ofs
;
201 /******************************************************************************
202 * cursor_from_char_ofs
204 * Converts a character offset (relative to the start of the document) to
205 * a cursor structure (which contains a run and a position relative to that
208 void cursor_from_char_ofs( ME_TextEditor
*editor
, int char_ofs
, ME_Cursor
*cursor
)
213 char_ofs
= min( max( char_ofs
, 0 ), ME_GetTextLength( editor
) );
215 /* Find the paragraph at the offset. */
216 for (para
= editor_first_para( editor
);
217 para_next( para
)->nCharOfs
<= char_ofs
;
218 para
= para_next( para
))
221 char_ofs
-= para
->nCharOfs
;
223 /* Find the run at the offset. */
224 for (run
= para_first_run( para
);
225 run_next( run
) && run_next( run
)->nCharOfs
<= char_ofs
;
226 run
= run_next( run
))
229 char_ofs
-= run
->nCharOfs
;
231 cursor
->pPara
= para_get_di( para
);
232 cursor
->pRun
= run_get_di( run
);
233 cursor
->nOffset
= char_ofs
;
236 /******************************************************************************
239 * Merges two adjacent runs, the one given as a parameter and the next one.
241 void run_join( ME_TextEditor
*editor
, ME_Run
*run
)
243 ME_Run
*next
= run_next( run
);
247 assert( run
->nCharOfs
!= -1 );
248 para_mark_rewrap( editor
, run
->para
);
250 /* Update all cursors so that they don't contain the soon deleted run */
251 for (i
= 0; i
< editor
->nCursors
; i
++)
253 if (&editor
->pCursors
[i
].pRun
->member
.run
== next
)
255 editor
->pCursors
[i
].pRun
= run_get_di( run
);
256 editor
->pCursors
[i
].nOffset
+= run
->len
;
260 run
->len
+= next
->len
;
261 ME_Remove( run_get_di( next
) );
262 ME_DestroyDisplayItem( run_get_di( next
) );
263 ME_UpdateRunFlags( editor
, run
);
264 ME_CheckCharOffsets( editor
);
267 /******************************************************************************
270 * Does the most basic job of splitting a run into two - it does not
271 * update the positions and extents.
273 ME_Run
*run_split( ME_TextEditor
*editor
, ME_Cursor
*cursor
)
275 ME_Run
*run
= &cursor
->pRun
->member
.run
, *new_run
;
277 int nOffset
= cursor
->nOffset
;
279 assert( !(run
->nFlags
& MERF_NONTEXT
) );
281 new_run
= run_create( run
->style
, run
->nFlags
& MERF_SPLITMASK
);
282 new_run
->nCharOfs
= run
->nCharOfs
+ nOffset
;
283 new_run
->len
= run
->len
- nOffset
;
284 new_run
->para
= run
->para
;
286 cursor
->pRun
= run_get_di( new_run
);
289 ME_InsertBefore( run_get_di( run
)->next
, run_get_di( new_run
) );
291 ME_UpdateRunFlags( editor
, run
);
292 ME_UpdateRunFlags( editor
, new_run
);
293 for (i
= 0; i
< editor
->nCursors
; i
++)
295 if (editor
->pCursors
[i
].pRun
== run_get_di( run
) &&
296 editor
->pCursors
[i
].nOffset
>= nOffset
)
298 editor
->pCursors
[i
].pRun
= run_get_di( new_run
);
299 editor
->pCursors
[i
].nOffset
-= nOffset
;
302 para_mark_rewrap( editor
, run
->para
);
306 /******************************************************************************
309 * A helper function to create run structures quickly.
311 ME_Run
*run_create( ME_Style
*s
, int flags
)
313 ME_DisplayItem
*item
= ME_MakeDI( diRun
);
314 ME_Run
*run
= &item
->member
.run
;
316 if (!item
) return NULL
;
328 run
->vis_attrs
= NULL
;
329 run
->advances
= NULL
;
331 run
->max_clusters
= 0;
332 run
->clusters
= NULL
;
336 /******************************************************************************
339 * Inserts a new run with given style, flags and content at a given position,
340 * which is passed as a cursor structure (which consists of a run and
341 * a run-relative character offset).
343 ME_Run
*run_insert( ME_TextEditor
*editor
, ME_Cursor
*cursor
, ME_Style
*style
,
344 const WCHAR
*str
, int len
, int flags
)
346 ME_Run
*insert_before
= &cursor
->pRun
->member
.run
, *run
, *prev
;
350 if (cursor
->nOffset
== insert_before
->len
)
352 insert_before
= run_next_all_paras( insert_before
);
353 if (!insert_before
) insert_before
= &cursor
->pRun
->member
.run
; /* Always insert before the final eop run */
357 run_split( editor
, cursor
);
358 insert_before
= &cursor
->pRun
->member
.run
;
362 add_undo_delete_run( editor
, insert_before
->para
->nCharOfs
+ insert_before
->nCharOfs
, len
);
364 run
= run_create( style
, flags
);
365 run
->nCharOfs
= insert_before
->nCharOfs
;
367 run
->para
= insert_before
->para
;
368 ME_InsertString( run
->para
->text
, run
->nCharOfs
, str
, len
);
369 ME_InsertBefore( run_get_di( insert_before
), run_get_di( run
) );
370 TRACE("Shift length:%d\n", len
);
371 ME_PropagateCharOffset( run_get_di( insert_before
), len
);
372 para_mark_rewrap( editor
, insert_before
->para
);
374 /* Move any cursors that were at the end of the previous run to the end of the inserted run */
375 prev
= run_prev_all_paras( run
);
380 for (i
= 0; i
< editor
->nCursors
; i
++)
382 if (editor
->pCursors
[i
].pRun
== run_get_di( prev
) &&
383 editor
->pCursors
[i
].nOffset
== prev
->len
)
385 editor
->pCursors
[i
].pRun
= run_get_di( run
);
386 editor
->pCursors
[i
].nOffset
= len
;
394 static BOOL
run_is_splittable( const ME_Run
*run
)
396 WCHAR
*str
= get_text( run
, 0 ), *p
;
398 BOOL found_ink
= FALSE
;
400 for (i
= 0, p
= str
; i
< run
->len
; i
++, p
++)
402 if (ME_IsWSpace( *p
))
404 if (found_ink
) return TRUE
;
412 static BOOL
run_is_entirely_ws( const ME_Run
*run
)
414 WCHAR
*str
= get_text( run
, 0 ), *p
;
417 for (i
= 0, p
= str
; i
< run
->len
; i
++, p
++)
418 if (!ME_IsWSpace( *p
)) return FALSE
;
423 /******************************************************************************
426 * Determine some of run attributes given its content (style, text content).
427 * Some flags cannot be determined by this function (MERF_GRAPHICS,
430 void ME_UpdateRunFlags(ME_TextEditor
*editor
, ME_Run
*run
)
432 assert(run
->nCharOfs
>= 0);
434 if (RUN_IS_HIDDEN(run
) || run
->nFlags
& MERF_TABLESTART
)
435 run
->nFlags
|= MERF_HIDDEN
;
437 run
->nFlags
&= ~MERF_HIDDEN
;
439 if (run_is_splittable( run
))
440 run
->nFlags
|= MERF_SPLITTABLE
;
442 run
->nFlags
&= ~MERF_SPLITTABLE
;
444 if (!(run
->nFlags
& MERF_NOTEXT
))
446 if (run_is_entirely_ws( run
))
447 run
->nFlags
|= MERF_WHITESPACE
| MERF_STARTWHITE
| MERF_ENDWHITE
;
450 run
->nFlags
&= ~MERF_WHITESPACE
;
452 if (ME_IsWSpace( *get_text( run
, 0 ) ))
453 run
->nFlags
|= MERF_STARTWHITE
;
455 run
->nFlags
&= ~MERF_STARTWHITE
;
457 if (ME_IsWSpace( *get_text( run
, run
->len
- 1 ) ))
458 run
->nFlags
|= MERF_ENDWHITE
;
460 run
->nFlags
&= ~MERF_ENDWHITE
;
464 run
->nFlags
&= ~(MERF_WHITESPACE
| MERF_STARTWHITE
| MERF_ENDWHITE
);
467 /******************************************************************************
468 * ME_CharFromPointContext
470 * Returns a character position inside the run given a run-relative
471 * pixel horizontal position.
473 * If closest is FALSE return the actual character
474 * If closest is TRUE will round to the closest leading edge.
475 * ie. if the second character is at pixel position 8 and third at 16 then for:
476 * closest = FALSE cx = 0..7 return 0, cx = 8..15 return 1
477 * closest = TRUE cx = 0..3 return 0, cx = 4..11 return 1.
479 int ME_CharFromPointContext(ME_Context
*c
, int cx
, ME_Run
*run
, BOOL closest
, BOOL visual_order
)
481 ME_String
*mask_text
= NULL
;
485 if (!run
->len
|| cx
<= 0)
488 if (run
->nFlags
& (MERF_TAB
| MERF_ENDCELL
))
490 if (!closest
|| cx
< run
->nWidth
/ 2) return 0;
494 if (run
->nFlags
& MERF_GRAPHICS
)
497 ME_GetOLEObjectSize(c
, run
, &sz
);
498 if (!closest
|| cx
< sz
.cx
/ 2) return 0;
502 if (run
->para
->nFlags
& MEPF_COMPLEX
)
505 if (visual_order
&& run
->script_analysis
.fRTL
) cx
= run
->nWidth
- cx
- 1;
507 ScriptXtoCP( cx
, run
->len
, run
->num_glyphs
, run
->clusters
, run
->vis_attrs
, run
->advances
, &run
->script_analysis
,
509 TRACE("x %d cp %d trailing %d (run width %d) rtl %d log order %d\n", cx
, cp
, trailing
, run
->nWidth
,
510 run
->script_analysis
.fRTL
, run
->script_analysis
.fLogicalOrder
);
511 return closest
? cp
+ trailing
: cp
;
514 if (c
->editor
->cPasswordMask
)
516 mask_text
= ME_MakeStringR( c
->editor
->cPasswordMask
, run
->len
);
517 str
= mask_text
->szData
;
520 str
= get_text( run
, 0 );
522 select_style(c
, run
->style
);
523 GetTextExtentExPointW(c
->hDC
, str
, run
->len
,
524 cx
, &fit
, NULL
, &sz
);
525 if (closest
&& fit
!= run
->len
)
527 GetTextExtentPoint32W(c
->hDC
, str
, fit
, &sz2
);
528 GetTextExtentPoint32W(c
->hDC
, str
, fit
+ 1, &sz3
);
529 if (cx
>= (sz2
.cx
+sz3
.cx
)/2)
533 ME_DestroyString( mask_text
);
538 int ME_CharFromPoint(ME_TextEditor
*editor
, int cx
, ME_Run
*run
, BOOL closest
, BOOL visual_order
)
543 ME_InitContext( &c
, editor
, ITextHost_TxGetDC( editor
->texthost
) );
544 ret
= ME_CharFromPointContext( &c
, cx
, run
, closest
, visual_order
);
545 ME_DestroyContext(&c
);
549 /******************************************************************************
552 * Finds a width and a height of the text using a specified style
554 static void ME_GetTextExtent(ME_Context
*c
, LPCWSTR szText
, int nChars
, ME_Style
*s
, SIZE
*size
)
558 select_style( c
, s
);
559 GetTextExtentPoint32W( c
->hDC
, szText
, nChars
, size
);
568 /******************************************************************************
569 * ME_PointFromCharContext
571 * Returns a run-relative pixel position given a run-relative character
572 * position (character offset)
574 int ME_PointFromCharContext(ME_Context
*c
, ME_Run
*pRun
, int nOffset
, BOOL visual_order
)
577 ME_String
*mask_text
= NULL
;
580 if (pRun
->nFlags
& MERF_GRAPHICS
)
583 ME_GetOLEObjectSize(c
, pRun
, &size
);
585 } else if (pRun
->nFlags
& MERF_ENDPARA
) {
589 if (pRun
->para
->nFlags
& MEPF_COMPLEX
)
592 ScriptCPtoX( nOffset
, FALSE
, pRun
->len
, pRun
->num_glyphs
, pRun
->clusters
,
593 pRun
->vis_attrs
, pRun
->advances
, &pRun
->script_analysis
, &x
);
594 if (visual_order
&& pRun
->script_analysis
.fRTL
) x
= pRun
->nWidth
- x
- 1;
597 if (c
->editor
->cPasswordMask
)
599 mask_text
= ME_MakeStringR(c
->editor
->cPasswordMask
, pRun
->len
);
600 str
= mask_text
->szData
;
603 str
= get_text( pRun
, 0 );
605 ME_GetTextExtent(c
, str
, nOffset
, pRun
->style
, &size
);
606 ME_DestroyString( mask_text
);
610 /******************************************************************************
613 * Calls ME_PointFromCharContext after first creating a context.
615 int ME_PointFromChar(ME_TextEditor
*editor
, ME_Run
*pRun
, int nOffset
, BOOL visual_order
)
620 ME_InitContext(&c
, editor
, ITextHost_TxGetDC(editor
->texthost
));
621 ret
= ME_PointFromCharContext( &c
, pRun
, nOffset
, visual_order
);
622 ME_DestroyContext(&c
);
627 /******************************************************************************
628 * ME_GetRunSizeCommon
630 * Finds width, height, ascent and descent of a run, up to given character
633 SIZE
ME_GetRunSizeCommon(ME_Context
*c
, const ME_Paragraph
*para
, ME_Run
*run
, int nLen
,
634 int startx
, int *pAscent
, int *pDescent
)
636 static const WCHAR spaceW
[] = {' ',0};
639 nLen
= min( nLen
, run
->len
);
641 if (run
->nFlags
& MERF_ENDPARA
)
643 nLen
= min( nLen
, 1 );
644 ME_GetTextExtent(c
, spaceW
, nLen
, run
->style
, &size
);
646 else if (para
->nFlags
& MEPF_COMPLEX
)
648 size
.cx
= run
->nWidth
;
650 else if (c
->editor
->cPasswordMask
)
652 ME_String
*szMasked
= ME_MakeStringR(c
->editor
->cPasswordMask
,nLen
);
653 ME_GetTextExtent(c
, szMasked
->szData
, nLen
,run
->style
, &size
);
654 ME_DestroyString(szMasked
);
658 ME_GetTextExtent(c
, get_text( run
, 0 ), nLen
, run
->style
, &size
);
660 *pAscent
= run
->style
->tm
.tmAscent
;
661 *pDescent
= run
->style
->tm
.tmDescent
;
662 size
.cy
= *pAscent
+ *pDescent
;
664 if (run
->nFlags
& MERF_TAB
)
666 int pos
= 0, i
= 0, ppos
, shift
= 0;
667 const PARAFORMAT2
*pFmt
= ¶
->fmt
;
669 if (c
->editor
->bEmulateVersion10
&& /* v1.0 - 3.0 */
670 pFmt
->dwMask
& PFM_TABLE
&& pFmt
->wEffects
& PFE_TABLE
)
671 /* The horizontal gap shifts the tab positions to leave the gap. */
672 shift
= pFmt
->dxOffset
* 2;
674 if (i
< pFmt
->cTabCount
)
676 /* Only one side of the horizontal gap is needed at the end of
678 if (i
== pFmt
->cTabCount
-1)
680 pos
= shift
+ (pFmt
->rgxTabs
[i
]&0x00FFFFFF);
685 pos
+= lDefaultTab
- (pos
% lDefaultTab
);
687 ppos
= ME_twips2pointsX(c
, pos
);
688 if (ppos
> startx
+ run
->pt
.x
) {
689 size
.cx
= ppos
- startx
- run
->pt
.x
;
693 size
.cy
= *pAscent
+ *pDescent
;
696 if (run
->nFlags
& MERF_GRAPHICS
)
698 ME_GetOLEObjectSize(c
, run
, &size
);
699 if (size
.cy
> *pAscent
)
701 /* descent is unchanged */
707 /******************************************************************************
708 * ME_SetSelectionCharFormat
710 * Applies a style change, either to a current selection, or to insert cursor
711 * (ie. the style next typed characters will use).
713 void ME_SetSelectionCharFormat(ME_TextEditor
*editor
, CHARFORMAT2W
*pFmt
)
715 if (!ME_IsSelection(editor
))
718 if (!editor
->pBuffer
->pCharStyle
)
719 editor
->pBuffer
->pCharStyle
= style_get_insert_style( editor
, editor
->pCursors
);
720 s
= ME_ApplyStyle(editor
, editor
->pBuffer
->pCharStyle
, pFmt
);
721 ME_ReleaseStyle(editor
->pBuffer
->pCharStyle
);
722 editor
->pBuffer
->pCharStyle
= s
;
724 ME_Cursor
*from
, *to
;
725 ME_GetSelection(editor
, &from
, &to
);
726 ME_SetCharFormat(editor
, from
, to
, pFmt
);
730 /******************************************************************************
733 * Applies a style change to the specified part of the text
735 * The start and end cursors specify the part of the text. These cursors will
736 * be updated to stay valid, but this function may invalidate other
737 * non-selection cursors. The end cursor may be NULL to specify all the text
738 * following the start cursor.
740 * If no text is selected, then nothing is done.
742 void ME_SetCharFormat( ME_TextEditor
*editor
, ME_Cursor
*start
, ME_Cursor
*end
, CHARFORMAT2W
*fmt
)
744 ME_Run
*run
, *start_run
= &start
->pRun
->member
.run
, *end_run
= NULL
;
746 if (end
&& start
->pRun
== end
->pRun
&& start
->nOffset
== end
->nOffset
)
749 if (start
->nOffset
== start
->pRun
->member
.run
.len
)
750 start_run
= run_next_all_paras( &start
->pRun
->member
.run
);
751 else if (start
->nOffset
)
753 /* run_split() may or may not update the cursors, depending on whether they
754 * are selection cursors, but we need to make sure they are valid. */
755 int split_offset
= start
->nOffset
;
756 ME_Run
*split_run
= run_split( editor
, start
);
757 start_run
= &start
->pRun
->member
.run
;
758 if (end
&& &end
->pRun
->member
.run
== split_run
)
760 end
->pRun
= start
->pRun
;
761 end
->nOffset
-= split_offset
;
767 if (end
->nOffset
== end
->pRun
->member
.run
.len
)
768 end_run
= run_next_all_paras( &end
->pRun
->member
.run
);
771 if (end
->nOffset
) run_split( editor
, end
);
772 end_run
= &end
->pRun
->member
.run
;
776 for (run
= start_run
; run
!= end_run
; run
= run_next_all_paras( run
))
778 ME_Style
*new_style
= ME_ApplyStyle( editor
, run
->style
, fmt
);
779 ME_Paragraph
*para
= run
->para
;
781 add_undo_set_char_fmt( editor
, para
->nCharOfs
+ run
->nCharOfs
,
782 run
->len
, &run
->style
->fmt
);
783 ME_ReleaseStyle( run
->style
);
784 run
->style
= new_style
;
786 /* The para numbering style depends on the eop style */
787 if ((run
->nFlags
& MERF_ENDPARA
) && para
->para_num
.style
)
789 ME_ReleaseStyle(para
->para_num
.style
);
790 para
->para_num
.style
= NULL
;
792 para_mark_rewrap( editor
, para
);
796 static void run_copy_char_fmt( ME_Run
*run
, CHARFORMAT2W
*fmt
)
798 ME_CopyCharFormat( fmt
, &run
->style
->fmt
);
801 /******************************************************************************
802 * ME_GetDefaultCharFormat
804 * Retrieves the current default character style (the one applied where no
805 * other style was applied) .
807 void ME_GetDefaultCharFormat(ME_TextEditor
*editor
, CHARFORMAT2W
*pFmt
)
809 ME_CopyCharFormat(pFmt
, &editor
->pBuffer
->pDefaultStyle
->fmt
);
812 /******************************************************************************
813 * ME_GetSelectionCharFormat
815 * If selection exists, it returns all style elements that are set consistently
816 * in the whole selection. If not, it just returns the current style.
818 void ME_GetSelectionCharFormat(ME_TextEditor
*editor
, CHARFORMAT2W
*pFmt
)
820 ME_Cursor
*from
, *to
;
821 if (!ME_IsSelection(editor
) && editor
->pBuffer
->pCharStyle
)
823 ME_CopyCharFormat(pFmt
, &editor
->pBuffer
->pCharStyle
->fmt
);
826 ME_GetSelection(editor
, &from
, &to
);
827 ME_GetCharFormat(editor
, from
, to
, pFmt
);
830 /******************************************************************************
833 * Returns the style consisting of those attributes which are consistently set
834 * in the whole character range.
836 void ME_GetCharFormat( ME_TextEditor
*editor
, const ME_Cursor
*from
,
837 const ME_Cursor
*to
, CHARFORMAT2W
*fmt
)
839 ME_Run
*run
, *run_end
, *prev_run
;
842 run
= &from
->pRun
->member
.run
;
843 /* special case - if selection is empty, take previous char's formatting */
844 if (from
->pRun
== to
->pRun
&& from
->nOffset
== to
->nOffset
)
846 if (!from
->nOffset
&& (prev_run
= run_prev( run
))) run
= prev_run
;
847 run_copy_char_fmt( run
, fmt
);
851 run_end
= &to
->pRun
->member
.run
;
852 if (!to
->nOffset
) run_end
= run_prev_all_paras( run_end
);
854 run_copy_char_fmt( run
, fmt
);
856 if (run
== run_end
) return;
859 /* FIXME add more style feature comparisons */
860 DWORD dwAttribs
= CFM_SIZE
| CFM_FACE
| CFM_COLOR
| CFM_UNDERLINETYPE
;
861 DWORD dwEffects
= CFM_BOLD
| CFM_ITALIC
| CFM_UNDERLINE
| CFM_STRIKEOUT
| CFM_PROTECTED
| CFM_LINK
| CFM_SUPERSCRIPT
;
863 run
= run_next_all_paras( run
);
865 memset( &tmp
, 0, sizeof(tmp
) );
866 tmp
.cbSize
= sizeof(tmp
);
867 run_copy_char_fmt( run
, &tmp
);
869 assert((tmp
.dwMask
& dwAttribs
) == dwAttribs
);
870 /* reset flags that differ */
872 if (fmt
->yHeight
!= tmp
.yHeight
) fmt
->dwMask
&= ~CFM_SIZE
;
873 if (fmt
->dwMask
& CFM_FACE
)
875 if (!(tmp
.dwMask
& CFM_FACE
))
876 fmt
->dwMask
&= ~CFM_FACE
;
877 else if (wcscmp( fmt
->szFaceName
, tmp
.szFaceName
) ||
878 fmt
->bPitchAndFamily
!= tmp
.bPitchAndFamily
)
879 fmt
->dwMask
&= ~CFM_FACE
;
881 if (fmt
->yHeight
!= tmp
.yHeight
) fmt
->dwMask
&= ~CFM_SIZE
;
882 if (fmt
->bUnderlineType
!= tmp
.bUnderlineType
) fmt
->dwMask
&= ~CFM_UNDERLINETYPE
;
883 if (fmt
->dwMask
& CFM_COLOR
)
885 if (!((fmt
->dwEffects
&CFE_AUTOCOLOR
) & (tmp
.dwEffects
&CFE_AUTOCOLOR
)))
887 if (fmt
->crTextColor
!= tmp
.crTextColor
)
888 fmt
->dwMask
&= ~CFM_COLOR
;
892 fmt
->dwMask
&= ~((fmt
->dwEffects
^ tmp
.dwEffects
) & dwEffects
);
893 fmt
->dwEffects
= tmp
.dwEffects
;
895 } while(run
!= run_end
);