1 /*
2 * RichEdit - Paragraph wrapping. Don't try to understand it. You've been
3 * warned !
4 *
5 * Copyright 2004 by Krzysztof Foltman
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
23 #include "editor.h"
24
25 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
26
27 /*
28 * Unsolved problems:
29 *
30 * - center and right align in WordPad omits all spaces at the start, we don't
31 * - objects/images are not handled yet
32 * - no tabs
33 */
34
35 typedef struct tagME_WrapContext
36 {
37 ME_Style *style;
38 ME_Context *context;
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 */
43 int nRow;
44 POINT pt;
45 BOOL bOverflown, bWordWrap;
46 ME_DisplayItem *pPara;
47 ME_DisplayItem *pRowStart;
48
49 ME_DisplayItem *pLastSplittableRun;
50 } ME_WrapContext;
51
get_run_glyph_buffers(ME_Run * run)52 static BOOL get_run_glyph_buffers( ME_Run *run )
53 {
54 heap_free( run->glyphs );
55 run->glyphs = heap_alloc( run->max_glyphs * (sizeof(WORD) + sizeof(SCRIPT_VISATTR) + sizeof(int) + sizeof(GOFFSET)) );
56 if (!run->glyphs) return FALSE;
57
58 run->vis_attrs = (SCRIPT_VISATTR*)((char*)run->glyphs + run->max_glyphs * sizeof(WORD));
59 run->advances = (int*)((char*)run->glyphs + run->max_glyphs * (sizeof(WORD) + sizeof(SCRIPT_VISATTR)));
60 run->offsets = (GOFFSET*)((char*)run->glyphs + run->max_glyphs * (sizeof(WORD) + sizeof(SCRIPT_VISATTR) + sizeof(int)));
61
62 return TRUE;
63 }
64
shape_run(ME_Context * c,ME_Run * run)65 static HRESULT shape_run( ME_Context *c, ME_Run *run )
66 {
67 HRESULT hr;
68 int i;
69
70 if (!run->glyphs)
71 {
72 run->max_glyphs = 1.5 * run->len + 16; /* This is suggested in the uniscribe documentation */
73 run->max_glyphs = (run->max_glyphs + 7) & ~7; /* Keep alignment simple */
74 get_run_glyph_buffers( run );
75 }
76
77 if (run->max_clusters < run->len)
78 {
79 heap_free( run->clusters );
80 run->max_clusters = run->len * 2;
81 run->clusters = heap_alloc( run->max_clusters * sizeof(WORD) );
82 }
83
84 select_style( c, run->style );
85 while (1)
86 {
87 hr = ScriptShape( c->hDC, &run->style->script_cache, get_text( run, 0 ), run->len, run->max_glyphs,
88 &run->script_analysis, run->glyphs, run->clusters, run->vis_attrs, &run->num_glyphs );
89 if (hr != E_OUTOFMEMORY) break;
90 if (run->max_glyphs > 10 * run->len) break; /* something has clearly gone wrong */
91 run->max_glyphs *= 2;
92 get_run_glyph_buffers( run );
93 }
94
95 if (SUCCEEDED(hr))
96 hr = ScriptPlace( c->hDC, &run->style->script_cache, run->glyphs, run->num_glyphs, run->vis_attrs,
97 &run->script_analysis, run->advances, run->offsets, NULL );
98
99 if (SUCCEEDED(hr))
100 {
101 for (i = 0, run->nWidth = 0; i < run->num_glyphs; i++)
102 run->nWidth += run->advances[i];
103 }
104
105 return hr;
106 }
107
108 /******************************************************************************
109 * calc_run_extent
110 *
111 * Updates the size of the run (fills width, ascent and descent). The height
112 * is calculated based on whole row's ascent and descent anyway, so no need
113 * to use it here.
114 */
calc_run_extent(ME_Context * c,const ME_Paragraph * para,int startx,ME_Run * run)115 static void calc_run_extent(ME_Context *c, const ME_Paragraph *para, int startx, ME_Run *run)
116 {
117 if (run->nFlags & MERF_HIDDEN) run->nWidth = 0;
118 else
119 {
120 SIZE size = ME_GetRunSizeCommon( c, para, run, run->len, startx, &run->nAscent, &run->nDescent );
121 run->nWidth = size.cx;
122 }
123 }
124
125 /******************************************************************************
126 * split_run_extents
127 *
128 * Splits a run into two in a given place. It also updates the screen position
129 * and size (extent) of the newly generated runs.
130 */
split_run_extents(ME_WrapContext * wc,ME_DisplayItem * item,int nVChar)131 static ME_DisplayItem *split_run_extents(ME_WrapContext *wc, ME_DisplayItem *item, int nVChar)
132 {
133 ME_TextEditor *editor = wc->context->editor;
134 ME_Run *run, *run2;
135 ME_Paragraph *para = &wc->pPara->member.para;
136 ME_Cursor cursor = {wc->pPara, item, nVChar};
137
138 assert(item->member.run.nCharOfs != -1);
139 ME_CheckCharOffsets(editor);
140
141 run = &item->member.run;
142
143 TRACE("Before split: %s(%d, %d)\n", debugstr_run( run ),
144 run->pt.x, run->pt.y);
145
146 ME_SplitRunSimple(editor, &cursor);
147
148 run2 = &cursor.pRun->member.run;
149 run2->script_analysis = run->script_analysis;
150
151 shape_run( wc->context, run );
152 shape_run( wc->context, run2 );
153 calc_run_extent(wc->context, para, wc->nRow ? wc->nLeftMargin : wc->nFirstMargin, run);
154
155 run2->pt.x = run->pt.x+run->nWidth;
156 run2->pt.y = run->pt.y;
157
158 ME_CheckCharOffsets(editor);
159
160 TRACE("After split: %s(%d, %d), %s(%d, %d)\n",
161 debugstr_run( run ), run->pt.x, run->pt.y,
162 debugstr_run( run2 ), run2->pt.x, run2->pt.y);
163
164 return cursor.pRun;
165 }
166
167 /******************************************************************************
168 * find_split_point
169 *
170 * Returns a character position to split inside the run given a run-relative
171 * pixel horizontal position. This version rounds left (ie. if the second
172 * character is at pixel position 8, then for cx=0..7 it returns 0).
173 */
find_split_point(ME_Context * c,int cx,ME_Run * run)174 static int find_split_point( ME_Context *c, int cx, ME_Run *run )
175 {
176 if (!run->len || cx <= 0) return 0;
177 return ME_CharFromPointContext( c, cx, run, FALSE, FALSE );
178 }
179
ME_MakeRow(int height,int baseline,int width)180 static ME_DisplayItem *ME_MakeRow(int height, int baseline, int width)
181 {
182 ME_DisplayItem *item = ME_MakeDI(diStartRow);
183
184 item->member.row.nHeight = height;
185 item->member.row.nBaseline = baseline;
186 item->member.row.nWidth = width;
187 return item;
188 }
189
ME_BeginRow(ME_WrapContext * wc)190 static void ME_BeginRow(ME_WrapContext *wc)
191 {
192 PARAFORMAT2 *pFmt;
193 ME_DisplayItem *para = wc->pPara;
194
195 pFmt = ¶->member.para.fmt;
196 wc->pRowStart = NULL;
197 wc->bOverflown = FALSE;
198 wc->pLastSplittableRun = NULL;
199 wc->bWordWrap = wc->context->editor->bWordWrap;
200 if (para->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND)) {
201 wc->nAvailWidth = 0;
202 wc->bWordWrap = FALSE;
203 if (para->member.para.nFlags & MEPF_ROWEND)
204 {
205 ME_Cell *cell = &ME_FindItemBack(para, diCell)->member.cell;
206 cell->nWidth = 0;
207 }
208 } else if (para->member.para.pCell) {
209 ME_Cell *cell = ¶->member.para.pCell->member.cell;
210 int width;
211
212 width = cell->nRightBoundary;
213 if (cell->prev_cell)
214 width -= cell->prev_cell->member.cell.nRightBoundary;
215 if (!cell->prev_cell)
216 {
217 int rowIndent = ME_GetTableRowEnd(para)->member.para.fmt.dxStartIndent;
218 width -= rowIndent;
219 }
220 cell->nWidth = max(ME_twips2pointsX(wc->context, width), 0);
221
222 wc->nAvailWidth = cell->nWidth
223 - (wc->nRow ? wc->nLeftMargin : wc->nFirstMargin) - wc->nRightMargin;
224 wc->bWordWrap = TRUE;
225 } else {
226 wc->nAvailWidth = wc->context->nAvailWidth
227 - (wc->nRow ? wc->nLeftMargin : wc->nFirstMargin) - wc->nRightMargin;
228 }
229 wc->pt.x = wc->context->pt.x;
230 if (wc->context->editor->bEmulateVersion10 && /* v1.0 - 3.0 */
231 pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
232 /* Shift the text down because of the border. */
233 wc->pt.y++;
234 }
235
layout_row(ME_DisplayItem * start,const ME_DisplayItem * end)236 static void layout_row( ME_DisplayItem *start, const ME_DisplayItem *end )
237 {
238 ME_DisplayItem *p;
239 int i, num_runs = 0;
240 int buf[16 * 5]; /* 5 arrays - 4 of int & 1 of BYTE, alloc space for 5 of ints */
241 int *vis_to_log = buf, *log_to_vis, *widths, *pos;
242 BYTE *levels;
243 BOOL found_black = FALSE;
244
245 for (p = end->prev; p != start->prev; p = p->prev)
246 {
247 if (p->type == diRun)
248 {
249 if (!found_black) found_black = !(p->member.run.nFlags & (MERF_WHITESPACE | MERF_ENDPARA));
250 if (found_black) num_runs++;
251 }
252 }
253
254 TRACE("%d runs\n", num_runs);
255 if (!num_runs) return;
256
257 if (num_runs > ARRAY_SIZE( buf ) / 5)
258 vis_to_log = heap_alloc( num_runs * sizeof(int) * 5 );
259
260 log_to_vis = vis_to_log + num_runs;
261 widths = vis_to_log + 2 * num_runs;
262 pos = vis_to_log + 3 * num_runs;
263 levels = (BYTE*)(vis_to_log + 4 * num_runs);
264
265 for (i = 0, p = start; i < num_runs; p = p->next)
266 {
267 if (p->type == diRun)
268 {
269 levels[i] = p->member.run.script_analysis.s.uBidiLevel;
270 widths[i] = p->member.run.nWidth;
271 TRACE( "%d: level %d width %d\n", i, levels[i], widths[i] );
272 i++;
273 }
274 }
275
276 ScriptLayout( num_runs, levels, vis_to_log, log_to_vis );
277
278 pos[0] = start->member.run.para->pt.x;
279 for (i = 1; i < num_runs; i++)
280 pos[i] = pos[i - 1] + widths[ vis_to_log[ i - 1 ] ];
281
282 for (i = 0, p = start; i < num_runs; p = p->next)
283 {
284 if (p->type == diRun)
285 {
286 p->member.run.pt.x = pos[ log_to_vis[ i ] ];
287 TRACE( "%d: x = %d\n", i, p->member.run.pt.x );
288 i++;
289 }
290 }
291
292 if (vis_to_log != buf) heap_free( vis_to_log );
293 }
294
ME_InsertRowStart(ME_WrapContext * wc,const ME_DisplayItem * pEnd)295 static void ME_InsertRowStart(ME_WrapContext *wc, const ME_DisplayItem *pEnd)
296 {
297 ME_DisplayItem *p, *row;
298 ME_Paragraph *para = &wc->pPara->member.para;
299 BOOL bSkippingSpaces = TRUE;
300 int ascent = 0, descent = 0, width=0, shift = 0, align = 0;
301
302 /* Include height of para numbering label */
303 if (wc->nRow == 0 && para->fmt.wNumbering)
304 {
305 ascent = para->para_num.style->tm.tmAscent;
306 descent = para->para_num.style->tm.tmDescent;
307 }
308
309 for (p = pEnd->prev; p!=wc->pRowStart->prev; p = p->prev)
310 {
311 /* ENDPARA run shouldn't affect row height, except if it's the only run in the paragraph */
312 if (p->type==diRun && ((p==wc->pRowStart) || !(p->member.run.nFlags & MERF_ENDPARA))) { /* FIXME add more run types */
313 if (p->member.run.nAscent>ascent)
314 ascent = p->member.run.nAscent;
315 if (p->member.run.nDescent>descent)
316 descent = p->member.run.nDescent;
317 if (bSkippingSpaces)
318 {
319 /* Exclude space characters from run width.
320 * Other whitespace or delimiters are not treated this way. */
321 int len = p->member.run.len;
322 WCHAR *text = get_text( &p->member.run, len - 1 );
323
324 assert (len);
325 if (~p->member.run.nFlags & MERF_GRAPHICS)
326 while (len && *(text--) == ' ')
327 len--;
328 if (len)
329 {
330 if (len == p->member.run.len)
331 width += p->member.run.nWidth;
332 else
333 width += ME_PointFromCharContext( wc->context, &p->member.run, len, FALSE );
334 }
335 bSkippingSpaces = !len;
336 } else if (!(p->member.run.nFlags & MERF_ENDPARA))
337 width += p->member.run.nWidth;
338 }
339 }
340
341 para->nWidth = max(para->nWidth, width);
342 row = ME_MakeRow(ascent+descent, ascent, width);
343 if (wc->context->editor->bEmulateVersion10 && /* v1.0 - 3.0 */
344 (para->fmt.dwMask & PFM_TABLE) && (para->fmt.wEffects & PFE_TABLE))
345 {
346 /* The text was shifted down in ME_BeginRow so move the wrap context
347 * back to where it should be. */
348 wc->pt.y--;
349 /* The height of the row is increased by the borders. */
350 row->member.row.nHeight += 2;
351 }
352 row->member.row.pt = wc->pt;
353 row->member.row.nLMargin = (!wc->nRow ? wc->nFirstMargin : wc->nLeftMargin);
354 row->member.row.nRMargin = wc->nRightMargin;
355 assert(para->fmt.dwMask & PFM_ALIGNMENT);
356 align = para->fmt.wAlignment;
357 if (align == PFA_CENTER)
358 shift = max((wc->nAvailWidth-width)/2, 0);
359 if (align == PFA_RIGHT)
360 shift = max(wc->nAvailWidth-width, 0);
361
362 if (para->nFlags & MEPF_COMPLEX) layout_row( wc->pRowStart, pEnd );
363
364 row->member.row.pt.x = row->member.row.nLMargin + shift;
365 for (p = wc->pRowStart; p!=pEnd; p = p->next)
366 {
367 if (p->type==diRun) { /* FIXME add more run types */
368 p->member.run.pt.x += row->member.row.nLMargin+shift;
369 }
370 }
371
372 if (wc->nRow == 0 && para->fmt.wNumbering)
373 {
374 para->para_num.pt.x = wc->nParaNumOffset + shift;
375 para->para_num.pt.y = wc->pt.y + row->member.row.nBaseline;
376 }
377
378 ME_InsertBefore(wc->pRowStart, row);
379 wc->nRow++;
380 wc->pt.y += row->member.row.nHeight;
381 ME_BeginRow(wc);
382 }
383
ME_WrapEndParagraph(ME_WrapContext * wc,ME_DisplayItem * p)384 static void ME_WrapEndParagraph(ME_WrapContext *wc, ME_DisplayItem *p)
385 {
386 ME_DisplayItem *para = wc->pPara;
387 PARAFORMAT2 *pFmt = ¶->member.para.fmt;
388 if (wc->pRowStart)
389 ME_InsertRowStart(wc, p);
390 if (wc->context->editor->bEmulateVersion10 && /* v1.0 - 3.0 */
391 pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
392 {
393 /* ME_BeginRow was called an extra time for the paragraph, and it shifts the
394 * text down by one pixel for the border, so fix up the wrap context. */
395 wc->pt.y--;
396 }
397
398 /*
399 p = para->next;
400 while(p) {
401 if (p->type == diParagraph || p->type == diTextEnd)
402 return;
403 if (p->type == diRun)
404 {
405 ME_Run *run = &p->member.run;
406 TRACE("%s - (%d, %d)\n", debugstr_run(run), run->pt.x, run->pt.y);
407 }
408 p = p->next;
409 }
410 */
411 }
412
ME_WrapSizeRun(ME_WrapContext * wc,ME_DisplayItem * p)413 static void ME_WrapSizeRun(ME_WrapContext *wc, ME_DisplayItem *p)
414 {
415 /* FIXME compose style (out of character and paragraph styles) here */
416
417 ME_UpdateRunFlags(wc->context->editor, &p->member.run);
418
419 calc_run_extent(wc->context, &wc->pPara->member.para,
420 wc->nRow ? wc->nLeftMargin : wc->nFirstMargin, &p->member.run);
421 }
422
423
find_non_whitespace(const WCHAR * s,int len,int start)424 static int find_non_whitespace(const WCHAR *s, int len, int start)
425 {
426 int i;
427 for (i = start; i < len && ME_IsWSpace( s[i] ); i++)
428 ;
429
430 return i;
431 }
432
433 /* note: these two really return the first matching offset (starting from EOS)+1
434 * in other words, an offset of the first trailing white/black */
435
436 /* note: returns offset of the first trailing whitespace */
reverse_find_non_whitespace(const WCHAR * s,int start)437 static int reverse_find_non_whitespace(const WCHAR *s, int start)
438 {
439 int i;
440 for (i = start; i > 0 && ME_IsWSpace( s[i - 1] ); i--)
441 ;
442
443 return i;
444 }
445
446 /* note: returns offset of the first trailing nonwhitespace */
reverse_find_whitespace(const WCHAR * s,int start)447 static int reverse_find_whitespace(const WCHAR *s, int start)
448 {
449 int i;
450 for (i = start; i > 0 && !ME_IsWSpace( s[i - 1] ); i--)
451 ;
452
453 return i;
454 }
455
ME_MaximizeSplit(ME_WrapContext * wc,ME_DisplayItem * p,int i)456 static ME_DisplayItem *ME_MaximizeSplit(ME_WrapContext *wc, ME_DisplayItem *p, int i)
457 {
458 ME_DisplayItem *pp, *piter = p;
459 int j;
460 if (!i)
461 return NULL;
462 j = reverse_find_non_whitespace( get_text( &p->member.run, 0 ), i);
463 if (j>0) {
464 pp = split_run_extents(wc, piter, j);
465 wc->pt.x += piter->member.run.nWidth;
466 return pp;
467 }
468 else
469 {
470 pp = piter;
471 /* omit all spaces before split point */
472 while(piter != wc->pRowStart)
473 {
474 piter = ME_FindItemBack(piter, diRun);
475 if (piter->member.run.nFlags & MERF_WHITESPACE)
476 {
477 pp = piter;
478 continue;
479 }
480 if (piter->member.run.nFlags & MERF_ENDWHITE)
481 {
482 i = reverse_find_non_whitespace( get_text( &piter->member.run, 0 ),
483 piter->member.run.len );
484 pp = split_run_extents(wc, piter, i);
485 wc->pt = pp->member.run.pt;
486 return pp;
487 }
488 /* this run is the end of spaces, so the run edge is a good point to split */
489 wc->pt = pp->member.run.pt;
490 wc->bOverflown = TRUE;
491 TRACE("Split point is: %s|%s\n", debugstr_run( &piter->member.run ), debugstr_run( &pp->member.run ));
492 return pp;
493 }
494 wc->pt = piter->member.run.pt;
495 return piter;
496 }
497 }
498
ME_SplitByBacktracking(ME_WrapContext * wc,ME_DisplayItem * p,int loc)499 static ME_DisplayItem *ME_SplitByBacktracking(ME_WrapContext *wc, ME_DisplayItem *p, int loc)
500 {
501 ME_DisplayItem *piter = p, *pp;
502 int i, idesp, len;
503 ME_Run *run = &p->member.run;
504
505 idesp = i = find_split_point( wc->context, loc, run );
506 len = run->len;
507 assert(len>0);
508 assert(i<len);
509 if (i) {
510 /* don't split words */
511 i = reverse_find_whitespace( get_text( run, 0 ), i );
512 pp = ME_MaximizeSplit(wc, p, i);
513 if (pp)
514 return pp;
515 }
516 TRACE("Must backtrack to split at: %s\n", debugstr_run( &p->member.run ));
517 if (wc->pLastSplittableRun)
518 {
519 if (wc->pLastSplittableRun->member.run.nFlags & (MERF_GRAPHICS|MERF_TAB))
520 {
521 wc->pt = wc->pLastSplittableRun->member.run.pt;
522 return wc->pLastSplittableRun;
523 }
524 else if (wc->pLastSplittableRun->member.run.nFlags & MERF_SPLITTABLE)
525 {
526 /* the following two lines are just to check if we forgot to call UpdateRunFlags earlier,
527 they serve no other purpose */
528 ME_UpdateRunFlags(wc->context->editor, run);
529 assert((wc->pLastSplittableRun->member.run.nFlags & MERF_SPLITTABLE));
530
531 piter = wc->pLastSplittableRun;
532 run = &piter->member.run;
533 len = run->len;
534 /* don't split words */
535 i = reverse_find_whitespace( get_text( run, 0 ), len );
536 if (i == len)
537 i = reverse_find_non_whitespace( get_text( run, 0 ), len );
538 if (i) {
539 ME_DisplayItem *piter2 = split_run_extents(wc, piter, i);
540 wc->pt = piter2->member.run.pt;
541 return piter2;
542 }
543 /* splittable = must have whitespaces */
544 assert(0 == "Splittable, but no whitespaces");
545 }
546 else
547 {
548 /* restart from the first run beginning with spaces */
549 wc->pt = wc->pLastSplittableRun->member.run.pt;
550 return wc->pLastSplittableRun;
551 }
552 }
553 TRACE("Backtracking failed, trying desperate: %s\n", debugstr_run( &p->member.run ));
554 /* OK, no better idea, so assume we MAY split words if we can split at all*/
555 if (idesp)
556 return split_run_extents(wc, piter, idesp);
557 else
558 if (wc->pRowStart && piter != wc->pRowStart)
559 {
560 /* don't need to break current run, because it's possible to split
561 before this run */
562 wc->bOverflown = TRUE;
563 return piter;
564 }
565 else
566 {
567 /* split point inside first character - no choice but split after that char */
568 if (len != 1) {
569 /* the run is more than 1 char, so we may split */
570 return split_run_extents(wc, piter, 1);
571 }
572 /* the run is one char, can't split it */
573 return piter;
574 }
575 }
576
ME_WrapHandleRun(ME_WrapContext * wc,ME_DisplayItem * p)577 static ME_DisplayItem *ME_WrapHandleRun(ME_WrapContext *wc, ME_DisplayItem *p)
578 {
579 ME_DisplayItem *pp;
580 ME_Run *run;
581 int len;
582
583 assert(p->type == diRun);
584 if (!wc->pRowStart)
585 wc->pRowStart = p;
586 run = &p->member.run;
587 run->pt.x = wc->pt.x;
588 run->pt.y = wc->pt.y;
589 ME_WrapSizeRun(wc, p);
590 len = run->len;
591
592 if (wc->bOverflown) /* just skipping final whitespaces */
593 {
594 /* End paragraph run can't overflow to the next line by itself. */
595 if (run->nFlags & MERF_ENDPARA)
596 return p->next;
597
598 if (run->nFlags & MERF_WHITESPACE) {
599 wc->pt.x += run->nWidth;
600 /* skip runs consisting of only whitespaces */
601 return p->next;
602 }
603
604 if (run->nFlags & MERF_STARTWHITE) {
605 /* try to split the run at the first non-white char */
606 int black;
607 black = find_non_whitespace( get_text( run, 0 ), run->len, 0 );
608 if (black) {
609 wc->bOverflown = FALSE;
610 pp = split_run_extents(wc, p, black);
611 calc_run_extent(wc->context, &wc->pPara->member.para,
612 wc->nRow ? wc->nLeftMargin : wc->nFirstMargin,
613 &pp->member.run);
614 ME_InsertRowStart(wc, pp);
615 return pp;
616 }
617 }
618 /* black run: the row goes from pRowStart to the previous run */
619 ME_InsertRowStart(wc, p);
620 return p;
621 }
622 /* simply end the current row and move on to next one */
623 if (run->nFlags & MERF_ENDROW)
624 {
625 p = p->next;
626 ME_InsertRowStart(wc, p);
627 return p;
628 }
629
630 /* will current run fit? */
631 if (wc->bWordWrap &&
632 wc->pt.x + run->nWidth - wc->context->pt.x > wc->nAvailWidth)
633 {
634 int loc = wc->context->pt.x + wc->nAvailWidth - wc->pt.x;
635 /* total white run or end para */
636 if (run->nFlags & (MERF_WHITESPACE | MERF_ENDPARA)) {
637 /* let the overflow logic handle it */
638 wc->bOverflown = TRUE;
639 return p;
640 }
641 /* TAB: we can split before */
642 if (run->nFlags & MERF_TAB) {
643 wc->bOverflown = TRUE;
644 if (wc->pRowStart == p)
645 /* Don't split before the start of the run, or we will get an
646 * endless loop. */
647 return p->next;
648 else
649 return p;
650 }
651 /* graphics: we can split before, if run's width is smaller than row's width */
652 if ((run->nFlags & MERF_GRAPHICS) && run->nWidth <= wc->nAvailWidth) {
653 wc->bOverflown = TRUE;
654 return p;
655 }
656 /* can we separate out the last spaces ? (to use overflow logic later) */
657 if (run->nFlags & MERF_ENDWHITE)
658 {
659 /* we aren't sure if it's *really* necessary, it's a good start however */
660 int black = reverse_find_non_whitespace( get_text( run, 0 ), len );
661 split_run_extents(wc, p, black);
662 /* handle both parts again */
663 return p;
664 }
665 /* determine the split point by backtracking */
666 pp = ME_SplitByBacktracking(wc, p, loc);
667 if (pp == wc->pRowStart)
668 {
669 if (run->nFlags & MERF_STARTWHITE)
670 {
671 /* We had only spaces so far, so we must be on the first line of the
672 * paragraph (or the first line after MERF_ENDROW forced the line
673 * break within the paragraph), since no other lines of the paragraph
674 * start with spaces. */
675
676 /* The lines will only contain spaces, and the rest of the run will
677 * overflow onto the next line. */
678 wc->bOverflown = TRUE;
679 return p;
680 }
681 /* Couldn't split the first run, possible because we have a large font
682 * with a single character that caused an overflow.
683 */
684 wc->pt.x += run->nWidth;
685 return p->next;
686 }
687 if (p != pp) /* found a suitable split point */
688 {
689 wc->bOverflown = TRUE;
690 return pp;
691 }
692 /* we detected that it's best to split on start of this run */
693 if (wc->bOverflown)
694 return pp;
695 ERR("failure!\n");
696 /* not found anything - writing over margins is the only option left */
697 }
698 if ((run->nFlags & (MERF_SPLITTABLE | MERF_STARTWHITE))
699 || ((run->nFlags & (MERF_GRAPHICS|MERF_TAB)) && (p != wc->pRowStart)))
700 {
701 wc->pLastSplittableRun = p;
702 }
703 wc->pt.x += run->nWidth;
704 return p->next;
705 }
706
ME_GetParaLineSpace(ME_Context * c,ME_Paragraph * para)707 static int ME_GetParaLineSpace(ME_Context* c, ME_Paragraph* para)
708 {
709 int sp = 0, ls = 0;
710 if (!(para->fmt.dwMask & PFM_LINESPACING)) return 0;
711
712 /* FIXME: how to compute simply the line space in ls ??? */
713 /* FIXME: does line spacing include the line itself ??? */
714 switch (para->fmt.bLineSpacingRule)
715 {
716 case 0: sp = ls; break;
717 case 1: sp = (3 * ls) / 2; break;
718 case 2: sp = 2 * ls; break;
719 case 3: sp = ME_twips2pointsY(c, para->fmt.dyLineSpacing); if (sp < ls) sp = ls; break;
720 case 4: sp = ME_twips2pointsY(c, para->fmt.dyLineSpacing); break;
721 case 5: sp = para->fmt.dyLineSpacing / 20; break;
722 default: FIXME("Unsupported spacing rule value %d\n", para->fmt.bLineSpacingRule);
723 }
724 if (c->editor->nZoomNumerator == 0)
725 return sp;
726 else
727 return sp * c->editor->nZoomNumerator / c->editor->nZoomDenominator;
728 }
729
ME_PrepareParagraphForWrapping(ME_TextEditor * editor,ME_Context * c,ME_DisplayItem * tp)730 static void ME_PrepareParagraphForWrapping(ME_TextEditor *editor, ME_Context *c, ME_DisplayItem *tp) {
731 ME_DisplayItem *p;
732
733 tp->member.para.nWidth = 0;
734 /* remove row start items as they will be reinserted by the
735 * paragraph wrapper anyway */
736 editor->total_rows -= tp->member.para.nRows;
737 tp->member.para.nRows = 0;
738 for (p = tp->next; p != tp->member.para.next_para; p = p->next) {
739 if (p->type == diStartRow) {
740 ME_DisplayItem *pRow = p;
741 p = p->prev;
742 ME_Remove(pRow);
743 ME_DestroyDisplayItem(pRow);
744 }
745 }
746 /* join runs that can be joined */
747 for (p = tp->next; p != tp->member.para.next_para; p = p->next) {
748 assert(p->type != diStartRow); /* should have been deleted above */
749 if (p->type == diRun) {
750 while (p->next->type == diRun && /* FIXME */
751 ME_CanJoinRuns(&p->member.run, &p->next->member.run)) {
752 ME_JoinRuns(c->editor, p);
753 }
754 }
755 }
756 }
757
itemize_para(ME_Context * c,ME_DisplayItem * p)758 static HRESULT itemize_para( ME_Context *c, ME_DisplayItem *p )
759 {
760 ME_Paragraph *para = &p->member.para;
761 ME_Run *run;
762 ME_DisplayItem *di;
763 SCRIPT_ITEM buf[16], *items = buf;
764 int items_passed = ARRAY_SIZE( buf ), num_items, cur_item;
765 SCRIPT_CONTROL control = { LANG_USER_DEFAULT, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
766 FALSE, FALSE, 0 };
767 SCRIPT_STATE state = { 0, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, 0, 0 };
768 HRESULT hr;
769
770 assert( p->type == diParagraph );
771
772 if (para->fmt.dwMask & PFM_RTLPARA && para->fmt.wEffects & PFE_RTLPARA)
773 state.uBidiLevel = 1;
774
775 TRACE( "Base embedding level %d\n", state.uBidiLevel );
776
777 while (1)
778 {
779 hr = ScriptItemize( para->text->szData, para->text->nLen, items_passed, &control,
780 &state, items, &num_items );
781 if (hr != E_OUTOFMEMORY) break; /* may not be enough items if hr == E_OUTOFMEMORY */
782 if (items_passed > para->text->nLen + 1) break; /* something else has gone wrong */
783 items_passed *= 2;
784 if (items == buf)
785 items = heap_alloc( items_passed * sizeof( *items ) );
786 else
787 items = heap_realloc( items, items_passed * sizeof( *items ) );
788 if (!items) break;
789 }
790 if (FAILED( hr )) goto end;
791
792 if (TRACE_ON( richedit ))
793 {
794 TRACE( "got items:\n" );
795 for (cur_item = 0; cur_item < num_items; cur_item++)
796 {
797 TRACE( "\t%d - %d RTL %d bidi level %d\n", items[cur_item].iCharPos, items[cur_item+1].iCharPos - 1,
798 items[cur_item].a.fRTL, items[cur_item].a.s.uBidiLevel );
799 }
800
801 TRACE( "before splitting runs into ranges\n" );
802 for (di = p->next; di != p->member.para.next_para; di = di->next)
803 {
804 if (di->type != diRun) continue;
805 TRACE( "\t%d: %s\n", di->member.run.nCharOfs, debugstr_run( &di->member.run ) );
806 }
807 }
808
809 /* split runs into ranges at item boundaries */
810 for (di = p->next, cur_item = 0; di != p->member.para.next_para; di = di->next)
811 {
812 if (di->type != diRun) continue;
813 run = &di->member.run;
814
815 if (run->nCharOfs == items[cur_item+1].iCharPos) cur_item++;
816
817 items[cur_item].a.fLogicalOrder = TRUE;
818 run->script_analysis = items[cur_item].a;
819
820 if (run->nFlags & MERF_ENDPARA) break; /* don't split eop runs */
821
822 if (run->nCharOfs + run->len > items[cur_item+1].iCharPos)
823 {
824 ME_Cursor cursor = {p, di, items[cur_item+1].iCharPos - run->nCharOfs};
825 ME_SplitRunSimple( c->editor, &cursor );
826 }
827 }
828
829 if (TRACE_ON( richedit ))
830 {
831 TRACE( "after splitting into ranges\n" );
832 for (di = p->next; di != p->member.para.next_para; di = di->next)
833 {
834 if (di->type != diRun) continue;
835 TRACE( "\t%d: %s\n", di->member.run.nCharOfs, debugstr_run( &di->member.run ) );
836 }
837 }
838
839 para->nFlags |= MEPF_COMPLEX;
840
841 end:
842 if (items != buf) heap_free( items );
843 return hr;
844 }
845
846
shape_para(ME_Context * c,ME_DisplayItem * p)847 static HRESULT shape_para( ME_Context *c, ME_DisplayItem *p )
848 {
849 ME_DisplayItem *di;
850 ME_Run *run;
851 HRESULT hr;
852
853 for (di = p->next; di != p->member.para.next_para; di = di->next)
854 {
855 if (di->type != diRun) continue;
856 run = &di->member.run;
857
858 hr = shape_run( c, run );
859 if (FAILED( hr ))
860 {
861 run->para->nFlags &= ~MEPF_COMPLEX;
862 return hr;
863 }
864 }
865 return hr;
866 }
867
ME_WrapTextParagraph(ME_TextEditor * editor,ME_Context * c,ME_DisplayItem * tp)868 static void ME_WrapTextParagraph(ME_TextEditor *editor, ME_Context *c, ME_DisplayItem *tp) {
869 ME_DisplayItem *p;
870 ME_WrapContext wc;
871 int border = 0;
872 int linespace = 0;
873 PARAFORMAT2 *pFmt;
874
875 assert(tp->type == diParagraph);
876 if (!(tp->member.para.nFlags & MEPF_REWRAP)) {
877 return;
878 }
879 ME_PrepareParagraphForWrapping(editor, c, tp);
880
881 /* Calculate paragraph numbering label */
882 para_num_init( c, &tp->member.para );
883
884 /* For now treating all non-password text as complex for better testing */
885 if (!c->editor->cPasswordMask /* &&
886 ScriptIsComplex( tp->member.para.text->szData, tp->member.para.text->nLen, SIC_COMPLEX ) == S_OK */)
887 {
888 if (SUCCEEDED( itemize_para( c, tp ) ))
889 shape_para( c, tp );
890 }
891
892 pFmt = &tp->member.para.fmt;
893
894 wc.context = c;
895 wc.pPara = tp;
896 /* wc.para_style = tp->member.para.style; */
897 wc.style = NULL;
898 wc.nParaNumOffset = 0;
899 if (tp->member.para.nFlags & MEPF_ROWEND) {
900 wc.nFirstMargin = wc.nLeftMargin = wc.nRightMargin = 0;
901 } else {
902 int dxStartIndent = pFmt->dxStartIndent;
903 if (tp->member.para.pCell) {
904 dxStartIndent += ME_GetTableRowEnd(tp)->member.para.fmt.dxOffset;
905 }
906 wc.nLeftMargin = ME_twips2pointsX(c, dxStartIndent + pFmt->dxOffset);
907 wc.nFirstMargin = ME_twips2pointsX(c, dxStartIndent);
908 if (pFmt->wNumbering)
909 {
910 wc.nParaNumOffset = wc.nFirstMargin;
911 dxStartIndent = max( ME_twips2pointsX(c, pFmt->wNumberingTab),
912 tp->member.para.para_num.width );
913 wc.nFirstMargin += dxStartIndent;
914 }
915 wc.nRightMargin = ME_twips2pointsX(c, pFmt->dxRightIndent);
916
917 if (wc.nFirstMargin < 0)
918 wc.nFirstMargin = 0;
919 if (wc.nLeftMargin < 0)
920 wc.nLeftMargin = 0;
921 }
922 if (c->editor->bEmulateVersion10 && /* v1.0 - 3.0 */
923 pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
924 {
925 wc.nFirstMargin += ME_twips2pointsX(c, pFmt->dxOffset * 2);
926 }
927 wc.nRow = 0;
928 wc.pt.y = 0;
929 if (pFmt->dwMask & PFM_SPACEBEFORE)
930 wc.pt.y += ME_twips2pointsY(c, pFmt->dySpaceBefore);
931 if (!(pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE) &&
932 pFmt->dwMask & PFM_BORDER)
933 {
934 border = ME_GetParaBorderWidth(c, tp->member.para.fmt.wBorders);
935 if (pFmt->wBorders & 1) {
936 wc.nFirstMargin += border;
937 wc.nLeftMargin += border;
938 }
939 if (pFmt->wBorders & 2)
940 wc.nRightMargin -= border;
941 if (pFmt->wBorders & 4)
942 wc.pt.y += border;
943 }
944
945 linespace = ME_GetParaLineSpace(c, &tp->member.para);
946
947 ME_BeginRow(&wc);
948 for (p = tp->next; p!=tp->member.para.next_para; ) {
949 assert(p->type != diStartRow);
950 if (p->type == diRun) {
951 p = ME_WrapHandleRun(&wc, p);
952 }
953 else p = p->next;
954 if (wc.nRow && p == wc.pRowStart)
955 wc.pt.y += linespace;
956 }
957 ME_WrapEndParagraph(&wc, p);
958 if (!(pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE) &&
959 (pFmt->dwMask & PFM_BORDER) && (pFmt->wBorders & 8))
960 wc.pt.y += border;
961 if (tp->member.para.fmt.dwMask & PFM_SPACEAFTER)
962 wc.pt.y += ME_twips2pointsY(c, pFmt->dySpaceAfter);
963
964 tp->member.para.nFlags &= ~MEPF_REWRAP;
965 tp->member.para.nHeight = wc.pt.y;
966 tp->member.para.nRows = wc.nRow;
967 editor->total_rows += wc.nRow;
968 }
969
ME_MarkRepaintEnd(ME_DisplayItem * para,ME_DisplayItem ** repaint_start,ME_DisplayItem ** repaint_end)970 static void ME_MarkRepaintEnd(ME_DisplayItem *para,
971 ME_DisplayItem **repaint_start,
972 ME_DisplayItem **repaint_end)
973 {
974 if (!*repaint_start)
975 *repaint_start = para;
976 *repaint_end = para;
977 }
978
adjust_para_y(ME_DisplayItem * item,ME_Context * c,ME_DisplayItem * repaint_start,ME_DisplayItem * repaint_end)979 static void adjust_para_y(ME_DisplayItem *item, ME_Context *c, ME_DisplayItem *repaint_start, ME_DisplayItem *repaint_end)
980 {
981 if (item->member.para.nFlags & MEPF_ROWSTART)
982 {
983 ME_DisplayItem *cell = ME_FindItemFwd(item, diCell);
984 ME_DisplayItem *endRowPara;
985 int borderWidth = 0;
986 cell->member.cell.pt = c->pt;
987 /* Offset the text by the largest top border width. */
988 while (cell->member.cell.next_cell)
989 {
990 borderWidth = max(borderWidth, cell->member.cell.border.top.width);
991 cell = cell->member.cell.next_cell;
992 }
993 endRowPara = ME_FindItemFwd(cell, diParagraph);
994 assert(endRowPara->member.para.nFlags & MEPF_ROWEND);
995 if (borderWidth > 0)
996 {
997 borderWidth = max(ME_twips2pointsY(c, borderWidth), 1);
998 while (cell)
999 {
1000 cell->member.cell.yTextOffset = borderWidth;
1001 cell = cell->member.cell.prev_cell;
1002 }
1003 c->pt.y += borderWidth;
1004 }
1005 if (endRowPara->member.para.fmt.dxStartIndent > 0)
1006 {
1007 int dxStartIndent = endRowPara->member.para.fmt.dxStartIndent;
1008 cell = ME_FindItemFwd(item, diCell);
1009 cell->member.cell.pt.x += ME_twips2pointsX(c, dxStartIndent);
1010 c->pt.x = cell->member.cell.pt.x;
1011 }
1012 }
1013 else if (item->member.para.nFlags & MEPF_ROWEND)
1014 {
1015 /* Set all the cells to the height of the largest cell */
1016 ME_DisplayItem *startRowPara;
1017 int prevHeight, nHeight, bottomBorder = 0;
1018 ME_DisplayItem *cell = ME_FindItemBack(item, diCell);
1019 item->member.para.nWidth = cell->member.cell.pt.x + cell->member.cell.nWidth;
1020 if (!(item->member.para.next_para->member.para.nFlags & MEPF_ROWSTART))
1021 {
1022 /* Last row, the bottom border is added to the height. */
1023 cell = cell->member.cell.prev_cell;
1024 while (cell)
1025 {
1026 bottomBorder = max(bottomBorder, cell->member.cell.border.bottom.width);
1027 cell = cell->member.cell.prev_cell;
1028 }
1029 bottomBorder = ME_twips2pointsY(c, bottomBorder);
1030 cell = ME_FindItemBack(item, diCell);
1031 }
1032 prevHeight = cell->member.cell.nHeight;
1033 nHeight = cell->member.cell.prev_cell->member.cell.nHeight + bottomBorder;
1034 cell->member.cell.nHeight = nHeight;
1035 item->member.para.nHeight = nHeight;
1036 cell = cell->member.cell.prev_cell;
1037 cell->member.cell.nHeight = nHeight;
1038 while (cell->member.cell.prev_cell)
1039 {
1040 cell = cell->member.cell.prev_cell;
1041 cell->member.cell.nHeight = nHeight;
1042 }
1043 /* Also set the height of the start row paragraph */
1044 startRowPara = ME_FindItemBack(cell, diParagraph);
1045 startRowPara->member.para.nHeight = nHeight;
1046 c->pt.x = startRowPara->member.para.pt.x;
1047 c->pt.y = cell->member.cell.pt.y + nHeight;
1048 if (prevHeight < nHeight)
1049 {
1050 /* The height of the cells has grown, so invalidate the bottom of
1051 * the cells. */
1052 ME_MarkRepaintEnd(item, &repaint_start, &repaint_end);
1053 cell = ME_FindItemBack(item, diCell);
1054 while (cell)
1055 {
1056 ME_MarkRepaintEnd(ME_FindItemBack(cell, diParagraph), &repaint_start, &repaint_end);
1057 cell = cell->member.cell.prev_cell;
1058 }
1059 }
1060 }
1061 else if (item->member.para.pCell &&
1062 item->member.para.pCell != item->member.para.next_para->member.para.pCell)
1063 {
1064 /* The next paragraph is in the next cell in the table row. */
1065 ME_Cell *cell = &item->member.para.pCell->member.cell;
1066 cell->nHeight = c->pt.y + item->member.para.nHeight - cell->pt.y;
1067
1068 /* Propagate the largest height to the end so that it can be easily
1069 * sent back to all the cells at the end of the row. */
1070 if (cell->prev_cell)
1071 cell->nHeight = max(cell->nHeight, cell->prev_cell->member.cell.nHeight);
1072
1073 c->pt.x = cell->pt.x + cell->nWidth;
1074 c->pt.y = cell->pt.y;
1075 cell->next_cell->member.cell.pt = c->pt;
1076 if (!(item->member.para.next_para->member.para.nFlags & MEPF_ROWEND))
1077 c->pt.y += cell->yTextOffset;
1078 }
1079 else
1080 {
1081 if (item->member.para.pCell)
1082 {
1083 /* Next paragraph in the same cell. */
1084 c->pt.x = item->member.para.pCell->member.cell.pt.x;
1085 }
1086 else
1087 /* Normal paragraph */
1088 c->pt.x = 0;
1089 c->pt.y += item->member.para.nHeight;
1090 }
1091 }
1092
ME_WrapMarkedParagraphs(ME_TextEditor * editor)1093 BOOL ME_WrapMarkedParagraphs(ME_TextEditor *editor)
1094 {
1095 ME_DisplayItem *item;
1096 ME_Context c;
1097 int totalWidth = editor->nTotalWidth, diff = 0, prev_width;
1098 ME_DisplayItem *repaint_start = NULL, *repaint_end = NULL;
1099 ME_Paragraph *para;
1100
1101 if (!editor->first_marked_para)
1102 return FALSE;
1103
1104 ME_InitContext(&c, editor, ITextHost_TxGetDC(editor->texthost));
1105
1106 item = editor->first_marked_para;
1107 c.pt = item->member.para.pt;
1108 while (item != editor->pBuffer->pLast)
1109 {
1110 assert(item->type == diParagraph);
1111
1112 prev_width = item->member.para.nWidth;
1113 ME_WrapTextParagraph(editor, &c, item);
1114 if (prev_width == totalWidth && item->member.para.nWidth < totalWidth)
1115 totalWidth = get_total_width(editor);
1116 else
1117 totalWidth = max(totalWidth, item->member.para.nWidth);
1118
1119 if (!item->member.para.nCharOfs)
1120 ME_MarkRepaintEnd(item->member.para.prev_para, &repaint_start, &repaint_end);
1121 ME_MarkRepaintEnd(item, &repaint_start, &repaint_end);
1122 adjust_para_y(item, &c, repaint_start, repaint_end);
1123
1124 if (item->member.para.next_para)
1125 {
1126 diff = c.pt.y - item->member.para.next_para->member.para.pt.y;
1127 if (diff)
1128 {
1129 para = &item->member.para;
1130 while (para->next_para && para != &item->member.para.next_marked->member.para &&
1131 para != &editor->pBuffer->pLast->member.para)
1132 {
1133 ME_MarkRepaintEnd(para->next_para, &repaint_start, &repaint_end);
1134 para->next_para->member.para.pt.y = c.pt.y;
1135 adjust_para_y(para->next_para, &c, repaint_start, repaint_end);
1136 para = ¶->next_para->member.para;
1137 }
1138 }
1139 }
1140 if (item->member.para.next_marked)
1141 {
1142 ME_DisplayItem *rem = item;
1143 item = item->member.para.next_marked;
1144 remove_marked_para(editor, rem);
1145 }
1146 else
1147 {
1148 remove_marked_para(editor, item);
1149 item = editor->pBuffer->pLast;
1150 }
1151 c.pt.y = item->member.para.pt.y;
1152 }
1153 editor->sizeWindow.cx = c.rcView.right-c.rcView.left;
1154 editor->sizeWindow.cy = c.rcView.bottom-c.rcView.top;
1155
1156 editor->nTotalLength = c.pt.y;
1157 editor->nTotalWidth = totalWidth;
1158 editor->pBuffer->pLast->member.para.pt.x = 0;
1159 editor->pBuffer->pLast->member.para.pt.y = c.pt.y;
1160
1161 ME_DestroyContext(&c);
1162
1163 if (repaint_start || editor->nTotalLength < editor->nLastTotalLength)
1164 ME_InvalidateParagraphRange(editor, repaint_start, repaint_end);
1165 return !!repaint_start;
1166 }
1167
ME_InvalidateParagraphRange(ME_TextEditor * editor,ME_DisplayItem * start_para,ME_DisplayItem * last_para)1168 void ME_InvalidateParagraphRange(ME_TextEditor *editor,
1169 ME_DisplayItem *start_para,
1170 ME_DisplayItem *last_para)
1171 {
1172 ME_Context c;
1173 RECT rc;
1174 int ofs;
1175
1176 ME_InitContext(&c, editor, ITextHost_TxGetDC(editor->texthost));
1177 rc = c.rcView;
1178 ofs = editor->vert_si.nPos;
1179
1180 if (start_para) {
1181 start_para = ME_GetOuterParagraph(start_para);
1182 last_para = ME_GetOuterParagraph(last_para);
1183 rc.top = c.rcView.top + start_para->member.para.pt.y - ofs;
1184 } else {
1185 rc.top = c.rcView.top + editor->nTotalLength - ofs;
1186 }
1187 if (editor->nTotalLength < editor->nLastTotalLength)
1188 rc.bottom = c.rcView.top + editor->nLastTotalLength - ofs;
1189 else
1190 rc.bottom = c.rcView.top + last_para->member.para.pt.y + last_para->member.para.nHeight - ofs;
1191 ITextHost_TxInvalidateRect(editor->texthost, &rc, TRUE);
1192
1193 ME_DestroyContext(&c);
1194 }
1195
1196
1197 void
ME_SendRequestResize(ME_TextEditor * editor,BOOL force)1198 ME_SendRequestResize(ME_TextEditor *editor, BOOL force)
1199 {
1200 if (editor->nEventMask & ENM_REQUESTRESIZE)
1201 {
1202 RECT rc;
1203
1204 ITextHost_TxGetClientRect(editor->texthost, &rc);
1205
1206 if (force || rc.bottom != editor->nTotalLength)
1207 {
1208 REQRESIZE info;
1209
1210 info.nmhdr.hwndFrom = NULL;
1211 info.nmhdr.idFrom = 0;
1212 info.nmhdr.code = EN_REQUESTRESIZE;
1213 info.rc = rc;
1214 info.rc.right = editor->nTotalWidth;
1215 info.rc.bottom = editor->nTotalLength;
1216
1217 editor->nEventMask &= ~ENM_REQUESTRESIZE;
1218 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
1219 editor->nEventMask |= ENM_REQUESTRESIZE;
1220 }
1221 }
1222 }
1223