2 * RichEdit - Paragraph wrapping. Don't try to understand it. You've been
5 * Copyright 2004 by Krzysztof Foltman
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.
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.
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
25 WINE_DEFAULT_DEBUG_CHANNEL(richedit
);
30 * - center and right align in WordPad omits all spaces at the start, we don't
31 * - objects/images are not handled yet
35 typedef struct tagME_WrapContext
39 int nLeftMargin
, nRightMargin
;
40 int nFirstMargin
; /* Offset to first line's text, always to the text itself even if a para number is present */
41 int nParaNumOffset
; /* Offset to the para number */
42 int nAvailWidth
; /* Width avail for text to wrap into. Does not include any para number text */
45 BOOL bOverflown
, bWordWrap
;
48 ME_Run
*pLastSplittableRun
;
51 static BOOL
get_run_glyph_buffers( ME_Run
*run
)
53 heap_free( run
->glyphs
);
54 run
->glyphs
= heap_alloc( run
->max_glyphs
* (sizeof(WORD
) + sizeof(SCRIPT_VISATTR
) + sizeof(int) + sizeof(GOFFSET
)) );
55 if (!run
->glyphs
) return FALSE
;
57 run
->vis_attrs
= (SCRIPT_VISATTR
*)((char*)run
->glyphs
+ run
->max_glyphs
* sizeof(WORD
));
58 run
->advances
= (int*)((char*)run
->glyphs
+ run
->max_glyphs
* (sizeof(WORD
) + sizeof(SCRIPT_VISATTR
)));
59 run
->offsets
= (GOFFSET
*)((char*)run
->glyphs
+ run
->max_glyphs
* (sizeof(WORD
) + sizeof(SCRIPT_VISATTR
) + sizeof(int)));
64 static HRESULT
shape_run( ME_Context
*c
, ME_Run
*run
)
71 run
->max_glyphs
= 1.5 * run
->len
+ 16; /* This is suggested in the uniscribe documentation */
72 run
->max_glyphs
= (run
->max_glyphs
+ 7) & ~7; /* Keep alignment simple */
73 get_run_glyph_buffers( run
);
76 if (run
->max_clusters
< run
->len
)
78 heap_free( run
->clusters
);
79 run
->max_clusters
= run
->len
* 2;
80 run
->clusters
= heap_alloc( run
->max_clusters
* sizeof(WORD
) );
83 select_style( c
, run
->style
);
86 hr
= ScriptShape( c
->hDC
, &run
->style
->script_cache
, get_text( run
, 0 ), run
->len
, run
->max_glyphs
,
87 &run
->script_analysis
, run
->glyphs
, run
->clusters
, run
->vis_attrs
, &run
->num_glyphs
);
88 if (hr
!= E_OUTOFMEMORY
) break;
89 if (run
->max_glyphs
> 10 * run
->len
) break; /* something has clearly gone wrong */
91 get_run_glyph_buffers( run
);
95 hr
= ScriptPlace( c
->hDC
, &run
->style
->script_cache
, run
->glyphs
, run
->num_glyphs
, run
->vis_attrs
,
96 &run
->script_analysis
, run
->advances
, run
->offsets
, NULL
);
100 for (i
= 0, run
->nWidth
= 0; i
< run
->num_glyphs
; i
++)
101 run
->nWidth
+= run
->advances
[i
];
107 /******************************************************************************
110 * Updates the size of the run (fills width, ascent and descent). The height
111 * is calculated based on whole row's ascent and descent anyway, so no need
114 static void calc_run_extent(ME_Context
*c
, const ME_Paragraph
*para
, int startx
, ME_Run
*run
)
116 if (run
->nFlags
& MERF_HIDDEN
) run
->nWidth
= 0;
119 SIZE size
= ME_GetRunSizeCommon( c
, para
, run
, run
->len
, startx
, &run
->nAscent
, &run
->nDescent
);
120 run
->nWidth
= size
.cx
;
124 /******************************************************************************
127 * Splits a run into two in a given place. It also updates the screen position
128 * and size (extent) of the newly generated runs.
130 static ME_Run
*split_run_extents( ME_WrapContext
*wc
, ME_Run
*run
, int nVChar
)
132 ME_TextEditor
*editor
= wc
->context
->editor
;
134 ME_Cursor cursor
= { wc
->para
, run
, nVChar
};
136 assert( run
->nCharOfs
!= -1 );
137 ME_CheckCharOffsets(editor
);
139 TRACE("Before split: %s(%d, %d)\n", debugstr_run( run
),
140 run
->pt
.x
, run
->pt
.y
);
142 run_split( editor
, &cursor
);
145 run2
->script_analysis
= run
->script_analysis
;
147 shape_run( wc
->context
, run
);
148 shape_run( wc
->context
, run2
);
149 calc_run_extent(wc
->context
, wc
->para
, wc
->nRow
? wc
->nLeftMargin
: wc
->nFirstMargin
, run
);
151 run2
->pt
.x
= run
->pt
.x
+ run
->nWidth
;
152 run2
->pt
.y
= run
->pt
.y
;
154 ME_CheckCharOffsets(editor
);
156 TRACE("After split: %s(%d, %d), %s(%d, %d)\n",
157 debugstr_run( run
), run
->pt
.x
, run
->pt
.y
,
158 debugstr_run( run2
), run2
->pt
.x
, run2
->pt
.y
);
163 /******************************************************************************
166 * Returns a character position to split inside the run given a run-relative
167 * pixel horizontal position. This version rounds left (ie. if the second
168 * character is at pixel position 8, then for cx=0..7 it returns 0).
170 static int find_split_point( ME_Context
*c
, int cx
, ME_Run
*run
)
172 if (!run
->len
|| cx
<= 0) return 0;
173 return ME_CharFromPointContext( c
, cx
, run
, FALSE
, FALSE
);
176 static ME_Row
*row_create( int height
, int baseline
, int width
)
178 ME_DisplayItem
*item
= ME_MakeDI(diStartRow
);
180 item
->member
.row
.nHeight
= height
;
181 item
->member
.row
.nBaseline
= baseline
;
182 item
->member
.row
.nWidth
= width
;
183 return &item
->member
.row
;
186 static void ME_BeginRow(ME_WrapContext
*wc
)
190 wc
->pRowStart
= NULL
;
191 wc
->bOverflown
= FALSE
;
192 wc
->pLastSplittableRun
= NULL
;
193 wc
->bWordWrap
= wc
->context
->editor
->bWordWrap
;
194 if (wc
->para
->nFlags
& (MEPF_ROWSTART
| MEPF_ROWEND
))
197 wc
->bWordWrap
= FALSE
;
198 if (wc
->para
->nFlags
& MEPF_ROWEND
)
200 cell
= table_row_end_cell( wc
->para
);
204 else if (para_cell( wc
->para
))
208 cell
= para_cell( wc
->para
);
209 width
= cell
->nRightBoundary
;
210 if (cell_prev( cell
)) width
-= cell_prev( cell
)->nRightBoundary
;
213 int rowIndent
= table_row_end( wc
->para
)->fmt
.dxStartIndent
;
216 cell
->nWidth
= max(ME_twips2pointsX(wc
->context
, width
), 0);
218 wc
->nAvailWidth
= cell
->nWidth
219 - (wc
->nRow
? wc
->nLeftMargin
: wc
->nFirstMargin
) - wc
->nRightMargin
;
220 wc
->bWordWrap
= TRUE
;
223 wc
->nAvailWidth
= wc
->context
->nAvailWidth
224 - (wc
->nRow
? wc
->nLeftMargin
: wc
->nFirstMargin
) - wc
->nRightMargin
;
226 wc
->pt
.x
= wc
->context
->pt
.x
;
227 if (wc
->context
->editor
->bEmulateVersion10
&& /* v1.0 - 3.0 */
228 para_in_table( wc
->para
))
229 /* Shift the text down because of the border. */
233 static void layout_row( ME_Run
*start
, ME_Run
*last
)
237 int buf
[16 * 5]; /* 5 arrays - 4 of int & 1 of BYTE, alloc space for 5 of ints */
238 int *vis_to_log
= buf
, *log_to_vis
, *widths
, *pos
;
240 BOOL found_black
= FALSE
;
242 for (run
= last
; run
; run
= run_prev( run
))
244 if (!found_black
) found_black
= !(run
->nFlags
& (MERF_WHITESPACE
| MERF_ENDPARA
));
245 if (found_black
) num_runs
++;
246 if (run
== start
) break;
249 TRACE("%d runs\n", num_runs
);
250 if (!num_runs
) return;
252 if (num_runs
> ARRAY_SIZE( buf
) / 5)
253 vis_to_log
= heap_alloc( num_runs
* sizeof(int) * 5 );
255 log_to_vis
= vis_to_log
+ num_runs
;
256 widths
= vis_to_log
+ 2 * num_runs
;
257 pos
= vis_to_log
+ 3 * num_runs
;
258 levels
= (BYTE
*)(vis_to_log
+ 4 * num_runs
);
260 for (i
= 0, run
= start
; i
< num_runs
; run
= run_next( run
))
262 levels
[i
] = run
->script_analysis
.s
.uBidiLevel
;
263 widths
[i
] = run
->nWidth
;
264 TRACE( "%d: level %d width %d\n", i
, levels
[i
], widths
[i
] );
268 ScriptLayout( num_runs
, levels
, vis_to_log
, log_to_vis
);
270 pos
[0] = run
->para
->pt
.x
;
271 for (i
= 1; i
< num_runs
; i
++)
272 pos
[i
] = pos
[i
- 1] + widths
[ vis_to_log
[ i
- 1 ] ];
274 for (i
= 0, run
= start
; i
< num_runs
; run
= run_next( run
))
276 run
->pt
.x
= pos
[ log_to_vis
[ i
] ];
277 TRACE( "%d: x = %d\n", i
, run
->pt
.x
);
281 if (vis_to_log
!= buf
) heap_free( vis_to_log
);
284 static void ME_InsertRowStart( ME_WrapContext
*wc
, ME_Run
*last
)
288 BOOL bSkippingSpaces
= TRUE
;
289 int ascent
= 0, descent
= 0, width
= 0, shift
= 0, align
= 0;
291 /* Include height of para numbering label */
292 if (wc
->nRow
== 0 && wc
->para
->fmt
.wNumbering
)
294 ascent
= wc
->para
->para_num
.style
->tm
.tmAscent
;
295 descent
= wc
->para
->para_num
.style
->tm
.tmDescent
;
298 for (run
= last
; run
; run
= run_prev( run
))
300 /* ENDPARA run shouldn't affect row height, except if it's the only run in the paragraph */
301 if (run
== wc
->pRowStart
|| !(run
->nFlags
& MERF_ENDPARA
))
303 if (run
->nAscent
> ascent
) ascent
= run
->nAscent
;
304 if (run
->nDescent
> descent
) descent
= run
->nDescent
;
307 /* Exclude space characters from run width.
308 * Other whitespace or delimiters are not treated this way. */
310 WCHAR
*text
= get_text( run
, len
- 1 );
313 if (~run
->nFlags
& MERF_GRAPHICS
)
314 while (len
&& *(text
--) == ' ') len
--;
318 width
+= run
->nWidth
;
320 width
+= ME_PointFromCharContext( wc
->context
, run
, len
, FALSE
);
322 bSkippingSpaces
= !len
;
324 else if (!(run
->nFlags
& MERF_ENDPARA
))
325 width
+= run
->nWidth
;
327 if (run
== wc
->pRowStart
) break;
330 wc
->para
->nWidth
= max( wc
->para
->nWidth
, width
);
331 row
= row_create( ascent
+ descent
, ascent
, width
);
332 if (wc
->context
->editor
->bEmulateVersion10
&& /* v1.0 - 3.0 */
333 (wc
->para
->fmt
.dwMask
& PFM_TABLE
) && (wc
->para
->fmt
.wEffects
& PFE_TABLE
))
335 /* The text was shifted down in ME_BeginRow so move the wrap context
336 * back to where it should be. */
338 /* The height of the row is increased by the borders. */
342 row
->nLMargin
= (!wc
->nRow
? wc
->nFirstMargin
: wc
->nLeftMargin
);
343 row
->nRMargin
= wc
->nRightMargin
;
344 assert(wc
->para
->fmt
.dwMask
& PFM_ALIGNMENT
);
345 align
= wc
->para
->fmt
.wAlignment
;
346 if (align
== PFA_CENTER
) shift
= max((wc
->nAvailWidth
-width
)/2, 0);
347 if (align
== PFA_RIGHT
) shift
= max(wc
->nAvailWidth
-width
, 0);
349 if (wc
->para
->nFlags
& MEPF_COMPLEX
) layout_row( wc
->pRowStart
, last
);
351 row
->pt
.x
= row
->nLMargin
+ shift
;
353 for (run
= wc
->pRowStart
; run
; run
= run_next( run
))
355 run
->pt
.x
+= row
->nLMargin
+ shift
;
356 if (run
== last
) break;
359 if (wc
->nRow
== 0 && wc
->para
->fmt
.wNumbering
)
361 wc
->para
->para_num
.pt
.x
= wc
->nParaNumOffset
+ shift
;
362 wc
->para
->para_num
.pt
.y
= wc
->pt
.y
+ row
->nBaseline
;
365 ME_InsertBefore( run_get_di( wc
->pRowStart
), row_get_di( row
) );
367 wc
->pt
.y
+= row
->nHeight
;
371 static void ME_WrapEndParagraph( ME_WrapContext
*wc
)
373 if (wc
->pRowStart
) ME_InsertRowStart( wc
, wc
->para
->eop_run
);
375 if (wc
->context
->editor
->bEmulateVersion10
&& /* v1.0 - 3.0 */
376 wc
->para
->fmt
.dwMask
& PFM_TABLE
&& wc
->para
->fmt
.wEffects
& PFE_TABLE
)
378 /* ME_BeginRow was called an extra time for the paragraph, and it shifts the
379 * text down by one pixel for the border, so fix up the wrap context. */
384 static void ME_WrapSizeRun( ME_WrapContext
*wc
, ME_Run
*run
)
386 /* FIXME compose style (out of character and paragraph styles) here */
388 ME_UpdateRunFlags( wc
->context
->editor
, run
);
390 calc_run_extent( wc
->context
, wc
->para
,
391 wc
->nRow
? wc
->nLeftMargin
: wc
->nFirstMargin
, run
);
395 static int find_non_whitespace(const WCHAR
*s
, int len
, int start
)
398 for (i
= start
; i
< len
&& ME_IsWSpace( s
[i
] ); i
++)
404 /* note: these two really return the first matching offset (starting from EOS)+1
405 * in other words, an offset of the first trailing white/black */
407 /* note: returns offset of the first trailing whitespace */
408 static int reverse_find_non_whitespace(const WCHAR
*s
, int start
)
411 for (i
= start
; i
> 0 && ME_IsWSpace( s
[i
- 1] ); i
--)
417 /* note: returns offset of the first trailing nonwhitespace */
418 static int reverse_find_whitespace(const WCHAR
*s
, int start
)
421 for (i
= start
; i
> 0 && !ME_IsWSpace( s
[i
- 1] ); i
--)
427 static ME_Run
*ME_MaximizeSplit( ME_WrapContext
*wc
, ME_Run
*run
, int i
)
429 ME_Run
*new_run
, *iter
= run
;
433 j
= reverse_find_non_whitespace( get_text( run
, 0 ), i
);
436 new_run
= split_run_extents( wc
, iter
, j
);
437 wc
->pt
.x
+= iter
->nWidth
;
443 /* omit all spaces before split point */
444 while (iter
!= wc
->pRowStart
)
446 iter
= run_prev( iter
);
447 if (iter
->nFlags
& MERF_WHITESPACE
)
452 if (iter
->nFlags
& MERF_ENDWHITE
)
454 i
= reverse_find_non_whitespace( get_text( iter
, 0 ), iter
->len
);
455 new_run
= split_run_extents( wc
, iter
, i
);
456 wc
->pt
= new_run
->pt
;
459 /* this run is the end of spaces, so the run edge is a good point to split */
460 wc
->pt
= new_run
->pt
;
461 wc
->bOverflown
= TRUE
;
462 TRACE( "Split point is: %s|%s\n", debugstr_run( iter
), debugstr_run( new_run
) );
470 static ME_Run
*ME_SplitByBacktracking( ME_WrapContext
*wc
, ME_Run
*run
, int loc
)
475 idesp
= i
= find_split_point( wc
->context
, loc
, run
);
481 /* don't split words */
482 i
= reverse_find_whitespace( get_text( run
, 0 ), i
);
483 new_run
= ME_MaximizeSplit(wc
, run
, i
);
484 if (new_run
) return new_run
;
486 TRACE("Must backtrack to split at: %s\n", debugstr_run( run
));
487 if (wc
->pLastSplittableRun
)
489 if (wc
->pLastSplittableRun
->nFlags
& (MERF_GRAPHICS
|MERF_TAB
))
491 wc
->pt
= wc
->pLastSplittableRun
->pt
;
492 return wc
->pLastSplittableRun
;
494 else if (wc
->pLastSplittableRun
->nFlags
& MERF_SPLITTABLE
)
496 /* the following two lines are just to check if we forgot to call UpdateRunFlags earlier,
497 they serve no other purpose */
498 ME_UpdateRunFlags(wc
->context
->editor
, run
);
499 assert((wc
->pLastSplittableRun
->nFlags
& MERF_SPLITTABLE
));
501 run
= wc
->pLastSplittableRun
;
503 /* don't split words */
504 i
= reverse_find_whitespace( get_text( run
, 0 ), len
);
506 i
= reverse_find_non_whitespace( get_text( run
, 0 ), len
);
507 new_run
= split_run_extents(wc
, run
, i
);
508 wc
->pt
= new_run
->pt
;
513 /* restart from the first run beginning with spaces */
514 wc
->pt
= wc
->pLastSplittableRun
->pt
;
515 return wc
->pLastSplittableRun
;
518 TRACE("Backtracking failed, trying desperate: %s\n", debugstr_run( run
));
519 /* OK, no better idea, so assume we MAY split words if we can split at all*/
521 return split_run_extents(wc
, run
, idesp
);
523 if (wc
->pRowStart
&& run
!= wc
->pRowStart
)
525 /* don't need to break current run, because it's possible to split
527 wc
->bOverflown
= TRUE
;
532 /* split point inside first character - no choice but split after that char */
534 /* the run is more than 1 char, so we may split */
535 return split_run_extents( wc
, run
, 1 );
537 /* the run is one char, can't split it */
542 static ME_Run
*ME_WrapHandleRun( ME_WrapContext
*wc
, ME_Run
*run
)
547 if (!wc
->pRowStart
) wc
->pRowStart
= run
;
548 run
->pt
.x
= wc
->pt
.x
;
549 run
->pt
.y
= wc
->pt
.y
;
550 ME_WrapSizeRun( wc
, run
);
553 if (wc
->bOverflown
) /* just skipping final whitespaces */
555 /* End paragraph run can't overflow to the next line by itself. */
556 if (run
->nFlags
& MERF_ENDPARA
) return run_next( run
);
558 if (run
->nFlags
& MERF_WHITESPACE
)
560 wc
->pt
.x
+= run
->nWidth
;
561 /* skip runs consisting of only whitespaces */
562 return run_next( run
);
565 if (run
->nFlags
& MERF_STARTWHITE
)
567 /* try to split the run at the first non-white char */
569 black
= find_non_whitespace( get_text( run
, 0 ), run
->len
, 0 );
573 wc
->bOverflown
= FALSE
;
574 new_run
= split_run_extents( wc
, run
, black
);
575 calc_run_extent( wc
->context
, wc
->para
,
576 wc
->nRow
? wc
->nLeftMargin
: wc
->nFirstMargin
, run
);
577 ME_InsertRowStart( wc
, run
);
581 /* black run: the row goes from pRowStart to the previous run */
582 ME_InsertRowStart( wc
, run_prev( run
) );
585 /* simply end the current row and move on to next one */
586 if (run
->nFlags
& MERF_ENDROW
)
588 ME_InsertRowStart( wc
, run
);
589 return run_next( run
);
592 /* will current run fit? */
594 wc
->pt
.x
+ run
->nWidth
- wc
->context
->pt
.x
> wc
->nAvailWidth
)
596 int loc
= wc
->context
->pt
.x
+ wc
->nAvailWidth
- wc
->pt
.x
;
597 /* total white run or end para */
598 if (run
->nFlags
& (MERF_WHITESPACE
| MERF_ENDPARA
)) {
599 /* let the overflow logic handle it */
600 wc
->bOverflown
= TRUE
;
603 /* TAB: we can split before */
604 if (run
->nFlags
& MERF_TAB
) {
605 wc
->bOverflown
= TRUE
;
606 if (wc
->pRowStart
== run
)
607 /* Don't split before the start of the run, or we will get an
609 return run_next( run
);
613 /* graphics: we can split before, if run's width is smaller than row's width */
614 if ((run
->nFlags
& MERF_GRAPHICS
) && run
->nWidth
<= wc
->nAvailWidth
) {
615 wc
->bOverflown
= TRUE
;
618 /* can we separate out the last spaces ? (to use overflow logic later) */
619 if (run
->nFlags
& MERF_ENDWHITE
)
621 /* we aren't sure if it's *really* necessary, it's a good start however */
622 int black
= reverse_find_non_whitespace( get_text( run
, 0 ), len
);
623 split_run_extents( wc
, run
, black
);
624 /* handle both parts again */
627 /* determine the split point by backtracking */
628 new_run
= ME_SplitByBacktracking( wc
, run
, loc
);
629 if (new_run
== wc
->pRowStart
)
631 if (run
->nFlags
& MERF_STARTWHITE
)
633 /* We had only spaces so far, so we must be on the first line of the
634 * paragraph (or the first line after MERF_ENDROW forced the line
635 * break within the paragraph), since no other lines of the paragraph
636 * start with spaces. */
638 /* The lines will only contain spaces, and the rest of the run will
639 * overflow onto the next line. */
640 wc
->bOverflown
= TRUE
;
643 /* Couldn't split the first run, possible because we have a large font
644 * with a single character that caused an overflow.
646 wc
->pt
.x
+= run
->nWidth
;
647 return run_next( run
);
649 if (run
!= new_run
) /* found a suitable split point */
651 wc
->bOverflown
= TRUE
;
654 /* we detected that it's best to split on start of this run */
658 /* not found anything - writing over margins is the only option left */
660 if ((run
->nFlags
& (MERF_SPLITTABLE
| MERF_STARTWHITE
))
661 || ((run
->nFlags
& (MERF_GRAPHICS
|MERF_TAB
)) && (run
!= wc
->pRowStart
)))
663 wc
->pLastSplittableRun
= run
;
665 wc
->pt
.x
+= run
->nWidth
;
666 return run_next( run
);
669 static int ME_GetParaLineSpace(ME_Context
* c
, ME_Paragraph
* para
)
672 if (!(para
->fmt
.dwMask
& PFM_LINESPACING
)) return 0;
674 /* FIXME: how to compute simply the line space in ls ??? */
675 /* FIXME: does line spacing include the line itself ??? */
676 switch (para
->fmt
.bLineSpacingRule
)
678 case 0: sp
= ls
; break;
679 case 1: sp
= (3 * ls
) / 2; break;
680 case 2: sp
= 2 * ls
; break;
681 case 3: sp
= ME_twips2pointsY(c
, para
->fmt
.dyLineSpacing
); if (sp
< ls
) sp
= ls
; break;
682 case 4: sp
= ME_twips2pointsY(c
, para
->fmt
.dyLineSpacing
); break;
683 case 5: sp
= para
->fmt
.dyLineSpacing
/ 20; break;
684 default: FIXME("Unsupported spacing rule value %d\n", para
->fmt
.bLineSpacingRule
);
686 if (c
->editor
->nZoomNumerator
== 0)
689 return sp
* c
->editor
->nZoomNumerator
/ c
->editor
->nZoomDenominator
;
692 static void ME_PrepareParagraphForWrapping( ME_TextEditor
*editor
, ME_Context
*c
, ME_Paragraph
*para
)
697 /* remove row start items as they will be reinserted by the
698 * paragraph wrapper anyway */
699 editor
->total_rows
-= para
->nRows
;
701 for (p
= para_get_di( para
); p
!= para
->next_para
; p
= p
->next
)
703 if (p
->type
== diStartRow
)
705 ME_DisplayItem
*pRow
= p
;
708 ME_DestroyDisplayItem( pRow
);
712 /* join runs that can be joined */
713 for (p
= para_get_di( para
)->next
; p
!= para
->next_para
; p
= p
->next
)
715 assert(p
->type
!= diStartRow
); /* should have been deleted above */
716 if (p
->type
== diRun
)
718 while (p
->next
->type
== diRun
&& ME_CanJoinRuns( &p
->member
.run
, &p
->next
->member
.run
))
719 run_join( c
->editor
, &p
->member
.run
);
724 static HRESULT
itemize_para( ME_Context
*c
, ME_Paragraph
*para
)
727 SCRIPT_ITEM buf
[16], *items
= buf
;
728 int items_passed
= ARRAY_SIZE( buf
), num_items
, cur_item
;
729 SCRIPT_CONTROL control
= { LANG_USER_DEFAULT
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
,
731 SCRIPT_STATE state
= { 0, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, 0, 0 };
734 if (para
->fmt
.dwMask
& PFM_RTLPARA
&& para
->fmt
.wEffects
& PFE_RTLPARA
)
735 state
.uBidiLevel
= 1;
737 TRACE( "Base embedding level %d\n", state
.uBidiLevel
);
741 hr
= ScriptItemize( para
->text
->szData
, para
->text
->nLen
, items_passed
, &control
,
742 &state
, items
, &num_items
);
743 if (hr
!= E_OUTOFMEMORY
) break; /* may not be enough items if hr == E_OUTOFMEMORY */
744 if (items_passed
> para
->text
->nLen
+ 1) break; /* something else has gone wrong */
747 items
= heap_alloc( items_passed
* sizeof( *items
) );
749 items
= heap_realloc( items
, items_passed
* sizeof( *items
) );
752 if (FAILED( hr
)) goto end
;
754 if (TRACE_ON( richedit
))
756 TRACE( "got items:\n" );
757 for (cur_item
= 0; cur_item
< num_items
; cur_item
++)
759 TRACE( "\t%d - %d RTL %d bidi level %d\n", items
[cur_item
].iCharPos
, items
[cur_item
+1].iCharPos
- 1,
760 items
[cur_item
].a
.fRTL
, items
[cur_item
].a
.s
.uBidiLevel
);
763 TRACE( "before splitting runs into ranges\n" );
764 for (run
= para_first_run( para
); run
; run
= run_next( run
))
765 TRACE( "\t%d: %s\n", run
->nCharOfs
, debugstr_run( run
) );
768 /* split runs into ranges at item boundaries */
769 for (run
= para_first_run( para
), cur_item
= 0; run
; run
= run_next( run
))
771 if (run
->nCharOfs
== items
[cur_item
+1].iCharPos
) cur_item
++;
773 items
[cur_item
].a
.fLogicalOrder
= TRUE
;
774 run
->script_analysis
= items
[cur_item
].a
;
776 if (run
->nFlags
& MERF_ENDPARA
) break; /* don't split eop runs */
778 if (run
->nCharOfs
+ run
->len
> items
[cur_item
+1].iCharPos
)
780 ME_Cursor cursor
= { para
, run
, items
[cur_item
+ 1].iCharPos
- run
->nCharOfs
};
781 run_split( c
->editor
, &cursor
);
785 if (TRACE_ON( richedit
))
787 TRACE( "after splitting into ranges\n" );
788 for (run
= para_first_run( para
); run
; run
= run_next( run
))
789 TRACE( "\t%d: %s\n", run
->nCharOfs
, debugstr_run( run
) );
792 para
->nFlags
|= MEPF_COMPLEX
;
795 if (items
!= buf
) heap_free( items
);
800 static HRESULT
shape_para( ME_Context
*c
, ME_Paragraph
*para
)
805 for (run
= para_first_run( para
); run
; run
= run_next( run
))
807 hr
= shape_run( c
, run
);
810 para
->nFlags
&= ~MEPF_COMPLEX
;
817 static void ME_WrapTextParagraph( ME_TextEditor
*editor
, ME_Context
*c
, ME_Paragraph
*para
)
824 if (!(para
->nFlags
& MEPF_REWRAP
)) return;
826 ME_PrepareParagraphForWrapping( editor
, c
, para
);
828 /* Calculate paragraph numbering label */
829 para_num_init( c
, para
);
831 /* For now treating all non-password text as complex for better testing */
832 if (!c
->editor
->password_char
/* &&
833 ScriptIsComplex( tp->member.para.text->szData, tp->member.para.text->nLen, SIC_COMPLEX ) == S_OK */)
835 if (SUCCEEDED( itemize_para( c
, para
) ))
836 shape_para( c
, para
);
842 wc
.nParaNumOffset
= 0;
843 if (para
->nFlags
& MEPF_ROWEND
)
844 wc
.nFirstMargin
= wc
.nLeftMargin
= wc
.nRightMargin
= 0;
847 int dxStartIndent
= para
->fmt
.dxStartIndent
;
848 if (para_cell( wc
.para
)) dxStartIndent
+= table_row_end( para
)->fmt
.dxOffset
;
850 wc
.nLeftMargin
= ME_twips2pointsX( c
, dxStartIndent
+ para
->fmt
.dxOffset
);
851 wc
.nFirstMargin
= ME_twips2pointsX( c
, dxStartIndent
);
852 if (para
->fmt
.wNumbering
)
854 wc
.nParaNumOffset
= wc
.nFirstMargin
;
855 dxStartIndent
= max( ME_twips2pointsX(c
, para
->fmt
.wNumberingTab
),
856 para
->para_num
.width
);
857 wc
.nFirstMargin
+= dxStartIndent
;
859 wc
.nRightMargin
= ME_twips2pointsX( c
, para
->fmt
.dxRightIndent
);
861 if (wc
.nFirstMargin
< 0) wc
.nFirstMargin
= 0;
862 if (wc
.nLeftMargin
< 0) wc
.nLeftMargin
= 0;
864 if (c
->editor
->bEmulateVersion10
&& /* v1.0 - 3.0 */
865 para
->fmt
.dwMask
& PFM_TABLE
&& para
->fmt
.wEffects
& PFE_TABLE
)
867 wc
.nFirstMargin
+= ME_twips2pointsX( c
, para
->fmt
.dxOffset
* 2 );
871 if (para
->fmt
.dwMask
& PFM_SPACEBEFORE
)
872 wc
.pt
.y
+= ME_twips2pointsY( c
, para
->fmt
.dySpaceBefore
);
873 if (!(para
->fmt
.dwMask
& PFM_TABLE
&& para
->fmt
.wEffects
& PFE_TABLE
) &&
874 para
->fmt
.dwMask
& PFM_BORDER
)
876 border
= ME_GetParaBorderWidth( c
, para
->fmt
.wBorders
);
877 if (para
->fmt
.wBorders
& 1)
879 wc
.nFirstMargin
+= border
;
880 wc
.nLeftMargin
+= border
;
882 if (para
->fmt
.wBorders
& 2) wc
.nRightMargin
-= border
;
883 if (para
->fmt
.wBorders
& 4) wc
.pt
.y
+= border
;
886 linespace
= ME_GetParaLineSpace( c
, para
);
889 run
= para_first_run( para
);
892 run
= ME_WrapHandleRun( &wc
, run
);
893 if (wc
.nRow
&& run
== wc
.pRowStart
) wc
.pt
.y
+= linespace
;
895 ME_WrapEndParagraph( &wc
);
896 if (!(para
->fmt
.dwMask
& PFM_TABLE
&& para
->fmt
.wEffects
& PFE_TABLE
) &&
897 (para
->fmt
.dwMask
& PFM_BORDER
) && (para
->fmt
.wBorders
& 8))
899 if (para
->fmt
.dwMask
& PFM_SPACEAFTER
)
900 wc
.pt
.y
+= ME_twips2pointsY( c
, para
->fmt
.dySpaceAfter
);
902 para
->nFlags
&= ~MEPF_REWRAP
;
903 para
->nHeight
= wc
.pt
.y
;
904 para
->nRows
= wc
.nRow
;
905 editor
->total_rows
+= wc
.nRow
;
910 ME_Paragraph
*start
, *end
;
913 static void update_repaint( ME_Paragraph
*para
, struct repaint_range
*repaint
)
915 if (!repaint
->start
) repaint
->start
= para
;
919 static void adjust_para_y( ME_Paragraph
*para
, ME_Context
*c
, struct repaint_range
*repaint
)
923 if (para
->nFlags
& MEPF_ROWSTART
)
925 ME_Paragraph
*end_row_para
= table_row_end( para
);
928 cell
= table_row_first_cell( para
);
930 /* Offset the text by the largest top border width. */
931 while (cell_next( cell
))
933 borderWidth
= max( borderWidth
, cell
->border
.top
.width
);
934 cell
= cell_next( cell
);
938 borderWidth
= max(ME_twips2pointsY(c
, borderWidth
), 1);
941 cell
->yTextOffset
= borderWidth
;
942 cell
= cell_prev( cell
);
944 c
->pt
.y
+= borderWidth
;
946 if (end_row_para
->fmt
.dxStartIndent
> 0)
948 cell
= table_row_first_cell( para
);
949 cell
->pt
.x
+= ME_twips2pointsX( c
, end_row_para
->fmt
.dxStartIndent
);
950 c
->pt
.x
= cell
->pt
.x
;
953 else if (para
->nFlags
& MEPF_ROWEND
)
955 /* Set all the cells to the height of the largest cell */
956 ME_Paragraph
*start_row_para
= table_row_start( para
);
957 int prevHeight
, nHeight
, bottomBorder
= 0;
959 cell
= table_row_end_cell( para
);
960 para
->nWidth
= cell
->pt
.x
+ cell
->nWidth
;
961 if (!(para_next( para
)->nFlags
& MEPF_ROWSTART
))
963 /* Last row, the bottom border is added to the height. */
964 while ((cell
= cell_prev( cell
)))
965 bottomBorder
= max( bottomBorder
, cell
->border
.bottom
.width
);
967 bottomBorder
= ME_twips2pointsY(c
, bottomBorder
);
968 cell
= table_row_end_cell( para
);
970 prevHeight
= cell
->nHeight
;
971 nHeight
= cell_prev( cell
)->nHeight
+ bottomBorder
;
972 cell
->nHeight
= nHeight
;
973 para
->nHeight
= nHeight
;
974 while (cell_prev( cell
))
976 cell
= cell_prev( cell
);
977 cell
->nHeight
= nHeight
;
980 /* Also set the height of the start row paragraph */
981 start_row_para
->nHeight
= nHeight
;
982 c
->pt
.x
= start_row_para
->pt
.x
;
983 c
->pt
.y
= cell
->pt
.y
+ nHeight
;
984 if (prevHeight
< nHeight
)
986 /* The height of the cells has grown, so invalidate the bottom of
988 update_repaint( para
, repaint
);
989 cell
= cell_prev( table_row_end_cell( para
) );
992 update_repaint( cell_end_para( cell
), repaint
);
993 cell
= cell_prev( cell
);
997 else if ((cell
= para_cell( para
)) && para
== cell_end_para( cell
))
999 /* The next paragraph is in the next cell in the table row. */
1000 cell
->nHeight
= c
->pt
.y
+ para
->nHeight
- cell
->pt
.y
;
1002 /* Propagate the largest height to the end so that it can be easily
1003 * sent back to all the cells at the end of the row. */
1004 if (cell_prev( cell
))
1005 cell
->nHeight
= max( cell
->nHeight
, cell_prev( cell
)->nHeight
);
1007 c
->pt
.x
= cell
->pt
.x
+ cell
->nWidth
;
1008 c
->pt
.y
= cell
->pt
.y
;
1009 cell_next( cell
)->pt
= c
->pt
;
1010 if (!(para_next( para
)->nFlags
& MEPF_ROWEND
))
1011 c
->pt
.y
+= cell
->yTextOffset
;
1015 if ((cell
= para_cell( para
))) /* Next paragraph in the same cell. */
1016 c
->pt
.x
= cell
->pt
.x
;
1017 else /* Normal paragraph */
1019 c
->pt
.y
+= para
->nHeight
;
1023 BOOL
wrap_marked_paras_dc( ME_TextEditor
*editor
, HDC hdc
, BOOL invalidate
)
1025 ME_Paragraph
*para
, *next
;
1026 struct wine_rb_entry
*entry
, *next_entry
= NULL
;
1028 int totalWidth
= editor
->nTotalWidth
, prev_width
;
1029 struct repaint_range repaint
= { NULL
, NULL
};
1031 if (!editor
->marked_paras
.root
) return FALSE
;
1033 ME_InitContext( &c
, editor
, hdc
);
1035 entry
= wine_rb_head( editor
->marked_paras
.root
);
1038 para
= WINE_RB_ENTRY_VALUE( entry
, ME_Paragraph
, marked_entry
);
1040 /* If the first entry lies inside a table, go back to the start
1041 of the table to ensure cell heights are kept in sync. */
1042 if (!next_entry
&& para_in_table( para
) && para
!= table_outer_para( para
))
1044 para
= table_outer_para( para
);
1048 next_entry
= wine_rb_next( entry
);
1051 prev_width
= para
->nWidth
;
1052 ME_WrapTextParagraph( editor
, &c
, para
);
1053 if (prev_width
== totalWidth
&& para
->nWidth
< totalWidth
)
1054 totalWidth
= get_total_width(editor
);
1056 totalWidth
= max(totalWidth
, para
->nWidth
);
1058 update_repaint( para
, &repaint
);
1059 adjust_para_y( para
, &c
, &repaint
);
1061 if (para_next( para
))
1063 if (c
.pt
.x
!= para_next( para
)->pt
.x
|| c
.pt
.y
!= para_next( para
)->pt
.y
||
1064 para_in_table( para
))
1067 while (para_next( next
) && &next
->marked_entry
!= next_entry
&&
1068 next
!= &editor
->pBuffer
->pLast
->member
.para
)
1070 update_repaint( para_next( next
), &repaint
);
1071 para_next( next
)->pt
= c
.pt
;
1072 adjust_para_y( para_next( next
), &c
, &repaint
);
1073 next
= para_next( next
);
1079 wine_rb_clear( &editor
->marked_paras
, NULL
, NULL
);
1081 editor
->sizeWindow
.cx
= c
.rcView
.right
-c
.rcView
.left
;
1082 editor
->sizeWindow
.cy
= c
.rcView
.bottom
-c
.rcView
.top
;
1084 editor
->nTotalLength
= editor
->pBuffer
->pLast
->member
.para
.pt
.y
;
1085 editor
->nTotalWidth
= totalWidth
;
1087 ME_DestroyContext(&c
);
1089 if (invalidate
&& (repaint
.start
|| editor
->nTotalLength
< editor
->nLastTotalLength
))
1090 para_range_invalidate( editor
, repaint
.start
, repaint
.end
);
1091 return !!repaint
.start
;
1094 BOOL
ME_WrapMarkedParagraphs( ME_TextEditor
*editor
)
1096 HDC hdc
= ITextHost_TxGetDC( editor
->texthost
);
1097 BOOL ret
= wrap_marked_paras_dc( editor
, hdc
, TRUE
);
1098 ITextHost_TxReleaseDC( editor
->texthost
, hdc
);
1102 void para_range_invalidate( ME_TextEditor
*editor
, ME_Paragraph
*start_para
,
1103 ME_Paragraph
*last_para
)
1108 rc
= editor
->rcFormat
;
1109 ofs
= editor
->vert_si
.nPos
;
1113 start_para
= table_outer_para( start_para
);
1114 last_para
= table_outer_para( last_para
);
1115 rc
.top
+= start_para
->pt
.y
- ofs
;
1117 rc
.top
+= editor
->nTotalLength
- ofs
;
1119 if (editor
->nTotalLength
< editor
->nLastTotalLength
)
1120 rc
.bottom
= editor
->rcFormat
.top
+ editor
->nLastTotalLength
- ofs
;
1122 rc
.bottom
= editor
->rcFormat
.top
+ last_para
->pt
.y
+ last_para
->nHeight
- ofs
;
1123 ITextHost_TxInvalidateRect(editor
->texthost
, &rc
, TRUE
);
1128 ME_SendRequestResize(ME_TextEditor
*editor
, BOOL force
)
1130 if (editor
->nEventMask
& ENM_REQUESTRESIZE
)
1134 ITextHost_TxGetClientRect(editor
->texthost
, &rc
);
1136 if (force
|| rc
.bottom
!= editor
->nTotalLength
)
1140 info
.nmhdr
.hwndFrom
= NULL
;
1141 info
.nmhdr
.idFrom
= 0;
1142 info
.nmhdr
.code
= EN_REQUESTRESIZE
;
1144 info
.rc
.right
= editor
->nTotalWidth
;
1145 info
.rc
.bottom
= editor
->nTotalLength
;
1147 editor
->nEventMask
&= ~ENM_REQUESTRESIZE
;
1148 ITextHost_TxNotify(editor
->texthost
, info
.nmhdr
.code
, &info
);
1149 editor
->nEventMask
|= ENM_REQUESTRESIZE
;