Reactos
at master 1161 lines 37 kB view raw
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 25WINE_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 35typedef 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_Paragraph *para; 47 ME_Run *pRowStart; 48 ME_Run *pLastSplittableRun; 49} ME_WrapContext; 50 51static BOOL get_run_glyph_buffers( ME_Run *run ) 52{ 53 free( run->glyphs ); 54 run->glyphs = malloc( run->max_glyphs * (sizeof(WORD) + sizeof(SCRIPT_VISATTR) + sizeof(int) + sizeof(GOFFSET)) ); 55 if (!run->glyphs) return FALSE; 56 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))); 60 61 return TRUE; 62} 63 64static HRESULT shape_run( ME_Context *c, ME_Run *run ) 65{ 66 HRESULT hr; 67 int i; 68 69 if (!run->glyphs) 70 { 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 ); 74 } 75 76 if (run->max_clusters < run->len) 77 { 78 free( run->clusters ); 79 run->max_clusters = run->len * 2; 80 run->clusters = malloc( run->max_clusters * sizeof(WORD) ); 81 } 82 83 select_style( c, run->style ); 84 while (1) 85 { 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 */ 90 run->max_glyphs *= 2; 91 get_run_glyph_buffers( run ); 92 } 93 94 if (SUCCEEDED(hr)) 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 ); 97 98 if (SUCCEEDED(hr)) 99 { 100 for (i = 0, run->nWidth = 0; i < run->num_glyphs; i++) 101 run->nWidth += run->advances[i]; 102 } 103 104 return hr; 105} 106 107/****************************************************************************** 108 * calc_run_extent 109 * 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 112 * to use it here. 113 */ 114static void calc_run_extent(ME_Context *c, const ME_Paragraph *para, int startx, ME_Run *run) 115{ 116 if (run->nFlags & MERF_HIDDEN) run->nWidth = 0; 117 else 118 { 119 SIZE size = ME_GetRunSizeCommon( c, para, run, run->len, startx, &run->nAscent, &run->nDescent ); 120 run->nWidth = size.cx; 121 } 122} 123 124/****************************************************************************** 125 * split_run_extents 126 * 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. 129 */ 130static ME_Run *split_run_extents( ME_WrapContext *wc, ME_Run *run, int nVChar ) 131{ 132 ME_TextEditor *editor = wc->context->editor; 133 ME_Run *run2; 134 ME_Cursor cursor = { wc->para, run, nVChar }; 135 136 assert( run->nCharOfs != -1 ); 137 ME_CheckCharOffsets(editor); 138 139 TRACE("Before split: %s(%ld, %ld)\n", debugstr_run( run ), 140 run->pt.x, run->pt.y); 141 142 run_split( editor, &cursor ); 143 144 run2 = cursor.run; 145 run2->script_analysis = run->script_analysis; 146 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); 150 151 run2->pt.x = run->pt.x + run->nWidth; 152 run2->pt.y = run->pt.y; 153 154 ME_CheckCharOffsets(editor); 155 156 TRACE("After split: %s(%ld, %ld), %s(%ld, %ld)\n", 157 debugstr_run( run ), run->pt.x, run->pt.y, 158 debugstr_run( run2 ), run2->pt.x, run2->pt.y); 159 160 return cursor.run; 161} 162 163/****************************************************************************** 164 * find_split_point 165 * 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). 169 */ 170static int find_split_point( ME_Context *c, int cx, ME_Run *run ) 171{ 172 if (!run->len || cx <= 0) return 0; 173 return ME_CharFromPointContext( c, cx, run, FALSE, FALSE ); 174} 175 176static ME_Row *row_create( int height, int baseline, int width ) 177{ 178 ME_DisplayItem *item = ME_MakeDI(diStartRow); 179 180 item->member.row.nHeight = height; 181 item->member.row.nBaseline = baseline; 182 item->member.row.nWidth = width; 183 return &item->member.row; 184} 185 186static void ME_BeginRow(ME_WrapContext *wc) 187{ 188 ME_Cell *cell; 189 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)) 195 { 196 wc->nAvailWidth = 0; 197 wc->bWordWrap = FALSE; 198 if (wc->para->nFlags & MEPF_ROWEND) 199 { 200 cell = table_row_end_cell( wc->para ); 201 cell->nWidth = 0; 202 } 203 } 204 else if (para_cell( wc->para )) 205 { 206 int width; 207 208 cell = para_cell( wc->para ); 209 width = cell->nRightBoundary; 210 if (cell_prev( cell )) width -= cell_prev( cell )->nRightBoundary; 211 else 212 { 213 int rowIndent = table_row_end( wc->para )->fmt.dxStartIndent; 214 width -= rowIndent; 215 } 216 cell->nWidth = max(ME_twips2pointsX(wc->context, width), 0); 217 218 wc->nAvailWidth = cell->nWidth 219 - (wc->nRow ? wc->nLeftMargin : wc->nFirstMargin) - wc->nRightMargin; 220 wc->bWordWrap = TRUE; 221 } 222 else 223 wc->nAvailWidth = wc->context->nAvailWidth 224 - (wc->nRow ? wc->nLeftMargin : wc->nFirstMargin) - wc->nRightMargin; 225 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. */ 230 wc->pt.y++; 231} 232 233static void layout_row( ME_Run *start, ME_Run *last ) 234{ 235 ME_Run *run; 236 int i, num_runs = 0; 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; 239 BYTE *levels; 240 BOOL found_black = FALSE; 241 242 for (run = last; run; run = run_prev( run )) 243 { 244 if (!found_black) found_black = !(run->nFlags & (MERF_WHITESPACE | MERF_ENDPARA)); 245 if (found_black) num_runs++; 246 if (run == start) break; 247 } 248 249 TRACE("%d runs\n", num_runs); 250 if (!num_runs) return; 251 252 if (num_runs > ARRAY_SIZE( buf ) / 5) 253 vis_to_log = malloc( num_runs * sizeof(int) * 5 ); 254 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); 259 260 for (i = 0, run = start; i < num_runs; run = run_next( run )) 261 { 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] ); 265 i++; 266 } 267 268 ScriptLayout( num_runs, levels, vis_to_log, log_to_vis ); 269 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 ] ]; 273 274 for (i = 0, run = start; i < num_runs; run = run_next( run )) 275 { 276 run->pt.x = pos[ log_to_vis[ i ] ]; 277 TRACE( "%d: x = %ld\n", i, run->pt.x ); 278 i++; 279 } 280 281 if (vis_to_log != buf) free( vis_to_log ); 282} 283 284static void ME_InsertRowStart( ME_WrapContext *wc, ME_Run *last ) 285{ 286 ME_Run *run; 287 ME_Row *row; 288 BOOL bSkippingSpaces = TRUE; 289 int ascent = 0, descent = 0, width = 0, shift = 0, align = 0; 290 291 /* Include height of para numbering label */ 292 if (wc->nRow == 0 && wc->para->fmt.wNumbering) 293 { 294 ascent = wc->para->para_num.style->tm.tmAscent; 295 descent = wc->para->para_num.style->tm.tmDescent; 296 } 297 298 for (run = last; run; run = run_prev( run )) 299 { 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)) 302 { 303 if (run->nAscent > ascent) ascent = run->nAscent; 304 if (run->nDescent > descent) descent = run->nDescent; 305 if (bSkippingSpaces) 306 { 307 /* Exclude space characters from run width. 308 * Other whitespace or delimiters are not treated this way. */ 309 int len = run->len; 310 WCHAR *text = get_text( run, len - 1 ); 311 312 assert(len); 313 if (~run->nFlags & MERF_GRAPHICS) 314 while (len && *(text--) == ' ') len--; 315 if (len) 316 { 317 if (len == run->len) 318 width += run->nWidth; 319 else 320 width += ME_PointFromCharContext( wc->context, run, len, FALSE ); 321 } 322 bSkippingSpaces = !len; 323 } 324 else if (!(run->nFlags & MERF_ENDPARA)) 325 width += run->nWidth; 326 } 327 if (run == wc->pRowStart) break; 328 } 329 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)) 334 { 335 /* The text was shifted down in ME_BeginRow so move the wrap context 336 * back to where it should be. */ 337 wc->pt.y--; 338 /* The height of the row is increased by the borders. */ 339 row->nHeight += 2; 340 } 341 row->pt = wc->pt; 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); 348 349 if (wc->para->nFlags & MEPF_COMPLEX) layout_row( wc->pRowStart, last ); 350 351 row->pt.x = row->nLMargin + shift; 352 353 for (run = wc->pRowStart; run; run = run_next( run )) 354 { 355 run->pt.x += row->nLMargin + shift; 356 if (run == last) break; 357 } 358 359 if (wc->nRow == 0 && wc->para->fmt.wNumbering) 360 { 361 wc->para->para_num.pt.x = wc->nParaNumOffset + shift; 362 wc->para->para_num.pt.y = wc->pt.y + row->nBaseline; 363 } 364 365 ME_InsertBefore( run_get_di( wc->pRowStart ), row_get_di( row ) ); 366 wc->nRow++; 367 wc->pt.y += row->nHeight; 368 ME_BeginRow( wc ); 369} 370 371static void ME_WrapEndParagraph( ME_WrapContext *wc ) 372{ 373 if (wc->pRowStart) ME_InsertRowStart( wc, wc->para->eop_run ); 374 375 if (wc->context->editor->bEmulateVersion10 && /* v1.0 - 3.0 */ 376 wc->para->fmt.dwMask & PFM_TABLE && wc->para->fmt.wEffects & PFE_TABLE) 377 { 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. */ 380 wc->pt.y--; 381 } 382} 383 384static void ME_WrapSizeRun( ME_WrapContext *wc, ME_Run *run ) 385{ 386 /* FIXME compose style (out of character and paragraph styles) here */ 387 388 ME_UpdateRunFlags( wc->context->editor, run ); 389 390 calc_run_extent( wc->context, wc->para, 391 wc->nRow ? wc->nLeftMargin : wc->nFirstMargin, run ); 392} 393 394 395static int find_non_whitespace(const WCHAR *s, int len, int start) 396{ 397 int i; 398 for (i = start; i < len && ME_IsWSpace( s[i] ); i++) 399 ; 400 401 return i; 402} 403 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 */ 406 407/* note: returns offset of the first trailing whitespace */ 408static int reverse_find_non_whitespace(const WCHAR *s, int start) 409{ 410 int i; 411 for (i = start; i > 0 && ME_IsWSpace( s[i - 1] ); i--) 412 ; 413 414 return i; 415} 416 417/* note: returns offset of the first trailing nonwhitespace */ 418static int reverse_find_whitespace(const WCHAR *s, int start) 419{ 420 int i; 421 for (i = start; i > 0 && !ME_IsWSpace( s[i - 1] ); i--) 422 ; 423 424 return i; 425} 426 427static ME_Run *ME_MaximizeSplit( ME_WrapContext *wc, ME_Run *run, int i ) 428{ 429 ME_Run *new_run, *iter = run; 430 int j; 431 if (!i) 432 return NULL; 433 j = reverse_find_non_whitespace( get_text( run, 0 ), i ); 434 if (j > 0) 435 { 436 new_run = split_run_extents( wc, iter, j ); 437 wc->pt.x += iter->nWidth; 438 return new_run; 439 } 440 else 441 { 442 new_run = iter; 443 /* omit all spaces before split point */ 444 while (iter != wc->pRowStart) 445 { 446 iter = run_prev( iter ); 447 if (iter->nFlags & MERF_WHITESPACE) 448 { 449 new_run = iter; 450 continue; 451 } 452 if (iter->nFlags & MERF_ENDWHITE) 453 { 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; 457 return new_run; 458 } 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 ) ); 463 return new_run; 464 } 465 wc->pt = iter->pt; 466 return iter; 467 } 468} 469 470static ME_Run *ME_SplitByBacktracking( ME_WrapContext *wc, ME_Run *run, int loc ) 471{ 472 ME_Run *new_run; 473 int i, idesp, len; 474 475 idesp = i = find_split_point( wc->context, loc, run ); 476 len = run->len; 477 assert( len > 0 ); 478 assert( i < len ); 479 if (i) 480 { 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; 485 } 486 TRACE("Must backtrack to split at: %s\n", debugstr_run( run )); 487 if (wc->pLastSplittableRun) 488 { 489 if (wc->pLastSplittableRun->nFlags & (MERF_GRAPHICS|MERF_TAB)) 490 { 491 wc->pt = wc->pLastSplittableRun->pt; 492 return wc->pLastSplittableRun; 493 } 494 else if (wc->pLastSplittableRun->nFlags & MERF_SPLITTABLE) 495 { 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)); 500 501 run = wc->pLastSplittableRun; 502 len = run->len; 503 /* don't split words */ 504 i = reverse_find_whitespace( get_text( run, 0 ), len ); 505 if (i == 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; 509 return new_run; 510 } 511 else 512 { 513 /* restart from the first run beginning with spaces */ 514 wc->pt = wc->pLastSplittableRun->pt; 515 return wc->pLastSplittableRun; 516 } 517 } 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*/ 520 if (idesp) 521 return split_run_extents(wc, run, idesp); 522 else 523 if (wc->pRowStart && run != wc->pRowStart) 524 { 525 /* don't need to break current run, because it's possible to split 526 before this run */ 527 wc->bOverflown = TRUE; 528 return run; 529 } 530 else 531 { 532 /* split point inside first character - no choice but split after that char */ 533 if (len != 1) 534 /* the run is more than 1 char, so we may split */ 535 return split_run_extents( wc, run, 1 ); 536 537 /* the run is one char, can't split it */ 538 return run; 539 } 540} 541 542static ME_Run *ME_WrapHandleRun( ME_WrapContext *wc, ME_Run *run ) 543{ 544 ME_Run *new_run; 545 int len; 546 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 ); 551 len = run->len; 552 553 if (wc->bOverflown) /* just skipping final whitespaces */ 554 { 555 /* End paragraph run can't overflow to the next line by itself. */ 556 if (run->nFlags & MERF_ENDPARA) return run_next( run ); 557 558 if (run->nFlags & MERF_WHITESPACE) 559 { 560 wc->pt.x += run->nWidth; 561 /* skip runs consisting of only whitespaces */ 562 return run_next( run ); 563 } 564 565 if (run->nFlags & MERF_STARTWHITE) 566 { 567 /* try to split the run at the first non-white char */ 568 int black; 569 black = find_non_whitespace( get_text( run, 0 ), run->len, 0 ); 570 if (black) 571 { 572 ME_Run *new_run; 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 ); 578 return new_run; 579 } 580 } 581 /* black run: the row goes from pRowStart to the previous run */ 582 ME_InsertRowStart( wc, run_prev( run ) ); 583 return run; 584 } 585 /* simply end the current row and move on to next one */ 586 if (run->nFlags & MERF_ENDROW) 587 { 588 ME_InsertRowStart( wc, run ); 589 return run_next( run ); 590 } 591 592 /* will current run fit? */ 593 if (wc->bWordWrap && 594 wc->pt.x + run->nWidth - wc->context->pt.x > wc->nAvailWidth) 595 { 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; 601 return run; 602 } 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 608 * endless loop. */ 609 return run_next( run ); 610 else 611 return run; 612 } 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; 616 return run; 617 } 618 /* can we separate out the last spaces ? (to use overflow logic later) */ 619 if (run->nFlags & MERF_ENDWHITE) 620 { 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 */ 625 return run; 626 } 627 /* determine the split point by backtracking */ 628 new_run = ME_SplitByBacktracking( wc, run, loc ); 629 if (new_run == wc->pRowStart) 630 { 631 if (run->nFlags & MERF_STARTWHITE) 632 { 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. */ 637 638 /* The lines will only contain spaces, and the rest of the run will 639 * overflow onto the next line. */ 640 wc->bOverflown = TRUE; 641 return run; 642 } 643 /* Couldn't split the first run, possible because we have a large font 644 * with a single character that caused an overflow. 645 */ 646 wc->pt.x += run->nWidth; 647 return run_next( run ); 648 } 649 if (run != new_run) /* found a suitable split point */ 650 { 651 wc->bOverflown = TRUE; 652 return new_run; 653 } 654 /* we detected that it's best to split on start of this run */ 655 if (wc->bOverflown) 656 return new_run; 657 ERR("failure!\n"); 658 /* not found anything - writing over margins is the only option left */ 659 } 660 if ((run->nFlags & (MERF_SPLITTABLE | MERF_STARTWHITE)) 661 || ((run->nFlags & (MERF_GRAPHICS|MERF_TAB)) && (run != wc->pRowStart))) 662 { 663 wc->pLastSplittableRun = run; 664 } 665 wc->pt.x += run->nWidth; 666 return run_next( run ); 667} 668 669static int ME_GetParaLineSpace(ME_Context* c, ME_Paragraph* para) 670{ 671 int sp = 0, ls = 0; 672 if (!(para->fmt.dwMask & PFM_LINESPACING)) return 0; 673 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) 677 { 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); 685 } 686 if (c->editor->nZoomNumerator == 0) 687 return sp; 688 else 689 return sp * c->editor->nZoomNumerator / c->editor->nZoomDenominator; 690} 691 692static void ME_PrepareParagraphForWrapping( ME_TextEditor *editor, ME_Context *c, ME_Paragraph *para ) 693{ 694 ME_DisplayItem *p; 695 696 para->nWidth = 0; 697 /* remove row start items as they will be reinserted by the 698 * paragraph wrapper anyway */ 699 editor->total_rows -= para->nRows; 700 para->nRows = 0; 701 for (p = para_get_di( para ); p != para->next_para; p = p->next) 702 { 703 if (p->type == diStartRow) 704 { 705 ME_DisplayItem *pRow = p; 706 p = p->prev; 707 ME_Remove( pRow ); 708 ME_DestroyDisplayItem( pRow ); 709 } 710 } 711 712 /* join runs that can be joined */ 713 for (p = para_get_di( para )->next; p != para->next_para; p = p->next) 714 { 715 assert(p->type != diStartRow); /* should have been deleted above */ 716 if (p->type == diRun) 717 { 718 while (p->next->type == diRun && ME_CanJoinRuns( &p->member.run, &p->next->member.run )) 719 run_join( c->editor, &p->member.run ); 720 } 721 } 722} 723 724static HRESULT itemize_para( ME_Context *c, ME_Paragraph *para ) 725{ 726 ME_Run *run; 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, 730 FALSE, FALSE, 0 }; 731 SCRIPT_STATE state = { 0, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, 0, 0 }; 732 HRESULT hr; 733 734 if (para->fmt.dwMask & PFM_RTLPARA && para->fmt.wEffects & PFE_RTLPARA) 735 state.uBidiLevel = 1; 736 737 TRACE( "Base embedding level %d\n", state.uBidiLevel ); 738 739 while (1) 740 { 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 */ 745 items_passed *= 2; 746 if (items == buf) 747 items = malloc( items_passed * sizeof( *items ) ); 748 else 749 items = realloc( items, items_passed * sizeof( *items ) ); 750 if (!items) break; 751 } 752 if (FAILED( hr )) goto end; 753 754 if (TRACE_ON( richedit )) 755 { 756 TRACE( "got items:\n" ); 757 for (cur_item = 0; cur_item < num_items; cur_item++) 758 { 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 ); 761 } 762 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 ) ); 766 } 767 768 /* split runs into ranges at item boundaries */ 769 for (run = para_first_run( para ), cur_item = 0; run; run = run_next( run )) 770 { 771 if (run->nCharOfs == items[cur_item+1].iCharPos) cur_item++; 772 773 items[cur_item].a.fLogicalOrder = TRUE; 774 run->script_analysis = items[cur_item].a; 775 776 if (run->nFlags & MERF_ENDPARA) break; /* don't split eop runs */ 777 778 if (run->nCharOfs + run->len > items[cur_item+1].iCharPos) 779 { 780 ME_Cursor cursor = { para, run, items[cur_item + 1].iCharPos - run->nCharOfs }; 781 run_split( c->editor, &cursor ); 782 } 783 } 784 785 if (TRACE_ON( richedit )) 786 { 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 ) ); 790 } 791 792 para->nFlags |= MEPF_COMPLEX; 793 794end: 795 if (items != buf) free( items ); 796 return hr; 797} 798 799 800static HRESULT shape_para( ME_Context *c, ME_Paragraph *para ) 801{ 802 ME_Run *run; 803 HRESULT hr; 804 805 for (run = para_first_run( para ); run; run = run_next( run )) 806 { 807 hr = shape_run( c, run ); 808 if (FAILED( hr )) 809 { 810 para->nFlags &= ~MEPF_COMPLEX; 811 return hr; 812 } 813 } 814 return hr; 815} 816 817static void ME_WrapTextParagraph( ME_TextEditor *editor, ME_Context *c, ME_Paragraph *para ) 818{ 819 ME_Run *run; 820 ME_WrapContext wc; 821 int border = 0; 822 int linespace = 0; 823 824 if (!(para->nFlags & MEPF_REWRAP)) return; 825 826 ME_PrepareParagraphForWrapping( editor, c, para ); 827 828 /* Calculate paragraph numbering label */ 829 para_num_init( c, para ); 830 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 */) 834 { 835 if (SUCCEEDED( itemize_para( c, para ) )) 836 shape_para( c, para ); 837 } 838 else 839 { 840 /* If the user has just converted a normal rich editor with already 841 * existing text into a password input, the text may contain paragraphs 842 * with MEPF_COMPLEX set. Since we don't really shape any paragraphs 843 * here, we need to ensure that the MEPF_COMPLEX flag is unset. 844 */ 845 para->nFlags &= ~MEPF_COMPLEX; 846 } 847 848 wc.context = c; 849 wc.para = para; 850 wc.style = NULL; 851 wc.nParaNumOffset = 0; 852 if (para->nFlags & MEPF_ROWEND) 853 wc.nFirstMargin = wc.nLeftMargin = wc.nRightMargin = 0; 854 else 855 { 856 int dxStartIndent = para->fmt.dxStartIndent; 857 if (para_cell( wc.para )) dxStartIndent += table_row_end( para )->fmt.dxOffset; 858 859 wc.nLeftMargin = ME_twips2pointsX( c, dxStartIndent + para->fmt.dxOffset ); 860 wc.nFirstMargin = ME_twips2pointsX( c, dxStartIndent ); 861 if (para->fmt.wNumbering) 862 { 863 wc.nParaNumOffset = wc.nFirstMargin; 864 dxStartIndent = max( ME_twips2pointsX(c, para->fmt.wNumberingTab), 865 para->para_num.width ); 866 wc.nFirstMargin += dxStartIndent; 867 } 868 wc.nRightMargin = ME_twips2pointsX( c, para->fmt.dxRightIndent ); 869 870 if (wc.nFirstMargin < 0) wc.nFirstMargin = 0; 871 if (wc.nLeftMargin < 0) wc.nLeftMargin = 0; 872 } 873 if (c->editor->bEmulateVersion10 && /* v1.0 - 3.0 */ 874 para->fmt.dwMask & PFM_TABLE && para->fmt.wEffects & PFE_TABLE) 875 { 876 wc.nFirstMargin += ME_twips2pointsX( c, para->fmt.dxOffset * 2 ); 877 } 878 wc.nRow = 0; 879 wc.pt.y = 0; 880 if (para->fmt.dwMask & PFM_SPACEBEFORE) 881 wc.pt.y += ME_twips2pointsY( c, para->fmt.dySpaceBefore ); 882 if (!(para->fmt.dwMask & PFM_TABLE && para->fmt.wEffects & PFE_TABLE) && 883 para->fmt.dwMask & PFM_BORDER) 884 { 885 border = ME_GetParaBorderWidth( c, para->fmt.wBorders ); 886 if (para->fmt.wBorders & 1) 887 { 888 wc.nFirstMargin += border; 889 wc.nLeftMargin += border; 890 } 891 if (para->fmt.wBorders & 2) wc.nRightMargin -= border; 892 if (para->fmt.wBorders & 4) wc.pt.y += border; 893 } 894 895 linespace = ME_GetParaLineSpace( c, para ); 896 897 ME_BeginRow( &wc ); 898 run = para_first_run( para ); 899 while (run) 900 { 901 run = ME_WrapHandleRun( &wc, run ); 902 if (wc.nRow && run == wc.pRowStart) wc.pt.y += linespace; 903 } 904 ME_WrapEndParagraph( &wc ); 905 if (!(para->fmt.dwMask & PFM_TABLE && para->fmt.wEffects & PFE_TABLE) && 906 (para->fmt.dwMask & PFM_BORDER) && (para->fmt.wBorders & 8)) 907 wc.pt.y += border; 908 if (para->fmt.dwMask & PFM_SPACEAFTER) 909 wc.pt.y += ME_twips2pointsY( c, para->fmt.dySpaceAfter ); 910 911 para->nFlags &= ~MEPF_REWRAP; 912 para->nHeight = wc.pt.y; 913 para->nRows = wc.nRow; 914 editor->total_rows += wc.nRow; 915} 916 917struct repaint_range 918{ 919 ME_Paragraph *start, *end; 920}; 921 922static void update_repaint( ME_Paragraph *para, struct repaint_range *repaint ) 923{ 924 if (!repaint->start) repaint->start = para; 925 repaint->end = para; 926} 927 928static void adjust_para_y( ME_Paragraph *para, ME_Context *c, struct repaint_range *repaint ) 929{ 930 ME_Cell *cell; 931 932 if (para->nFlags & MEPF_ROWSTART) 933 { 934 ME_Paragraph *end_row_para = table_row_end( para ); 935 int borderWidth = 0; 936 937 cell = table_row_first_cell( para ); 938 cell->pt = c->pt; 939 /* Offset the text by the largest top border width. */ 940 while (cell_next( cell )) 941 { 942 borderWidth = max( borderWidth, cell->border.top.width ); 943 cell = cell_next( cell ); 944 } 945 if (borderWidth > 0) 946 { 947 borderWidth = max(ME_twips2pointsY(c, borderWidth), 1); 948 while (cell) 949 { 950 cell->yTextOffset = borderWidth; 951 cell = cell_prev( cell ); 952 } 953 c->pt.y += borderWidth; 954 } 955 if (end_row_para->fmt.dxStartIndent > 0) 956 { 957 cell = table_row_first_cell( para ); 958 cell->pt.x += ME_twips2pointsX( c, end_row_para->fmt.dxStartIndent ); 959 c->pt.x = cell->pt.x; 960 } 961 } 962 else if (para->nFlags & MEPF_ROWEND) 963 { 964 /* Set all the cells to the height of the largest cell */ 965 ME_Paragraph *start_row_para = table_row_start( para ); 966 int prevHeight, nHeight, bottomBorder = 0; 967 968 cell = table_row_end_cell( para ); 969 para->nWidth = cell->pt.x + cell->nWidth; 970 if (!(para_next( para )->nFlags & MEPF_ROWSTART)) 971 { 972 /* Last row, the bottom border is added to the height. */ 973 while ((cell = cell_prev( cell ))) 974 bottomBorder = max( bottomBorder, cell->border.bottom.width ); 975 976 bottomBorder = ME_twips2pointsY(c, bottomBorder); 977 cell = table_row_end_cell( para ); 978 } 979 prevHeight = cell->nHeight; 980 nHeight = cell_prev( cell )->nHeight + bottomBorder; 981 cell->nHeight = nHeight; 982 para->nHeight = nHeight; 983 while (cell_prev( cell )) 984 { 985 cell = cell_prev( cell ); 986 cell->nHeight = nHeight; 987 } 988 989 /* Also set the height of the start row paragraph */ 990 start_row_para->nHeight = nHeight; 991 c->pt.x = start_row_para->pt.x; 992 c->pt.y = cell->pt.y + nHeight; 993 if (prevHeight < nHeight) 994 { 995 /* The height of the cells has grown, so invalidate the bottom of 996 * the cells. */ 997 update_repaint( para, repaint ); 998 cell = cell_prev( table_row_end_cell( para ) ); 999 while (cell) 1000 { 1001 update_repaint( cell_end_para( cell ), repaint ); 1002 cell = cell_prev( cell ); 1003 } 1004 } 1005 } 1006 else if ((cell = para_cell( para )) && para == cell_end_para( cell )) 1007 { 1008 /* The next paragraph is in the next cell in the table row. */ 1009 cell->nHeight = c->pt.y + para->nHeight - cell->pt.y; 1010 1011 /* Propagate the largest height to the end so that it can be easily 1012 * sent back to all the cells at the end of the row. */ 1013 if (cell_prev( cell )) 1014 cell->nHeight = max( cell->nHeight, cell_prev( cell )->nHeight ); 1015 1016 c->pt.x = cell->pt.x + cell->nWidth; 1017 c->pt.y = cell->pt.y; 1018 cell_next( cell )->pt = c->pt; 1019 if (!(para_next( para )->nFlags & MEPF_ROWEND)) 1020 c->pt.y += cell->yTextOffset; 1021 } 1022 else 1023 { 1024 if ((cell = para_cell( para ))) /* Next paragraph in the same cell. */ 1025 c->pt.x = cell->pt.x; 1026 else /* Normal paragraph */ 1027 c->pt.x = 0; 1028 c->pt.y += para->nHeight; 1029 } 1030} 1031 1032BOOL wrap_marked_paras_dc( ME_TextEditor *editor, HDC hdc, BOOL invalidate ) 1033{ 1034 ME_Paragraph *para, *next; 1035 struct wine_rb_entry *entry, *next_entry = NULL; 1036 ME_Context c; 1037 int totalWidth = editor->nTotalWidth, prev_width; 1038 struct repaint_range repaint = { NULL, NULL }; 1039 1040 if (!editor->marked_paras.root) return FALSE; 1041 1042 ME_InitContext( &c, editor, hdc ); 1043 1044 entry = rb_head( editor->marked_paras.root ); 1045 while (entry) 1046 { 1047 para = WINE_RB_ENTRY_VALUE( entry, ME_Paragraph, marked_entry ); 1048 1049 /* If the first entry lies inside a table, go back to the start 1050 of the table to ensure cell heights are kept in sync. */ 1051 if (!next_entry && para_in_table( para ) && para != table_outer_para( para )) 1052 { 1053 para = table_outer_para( para ); 1054 next_entry = entry; 1055 } 1056 else 1057 next_entry = rb_next( entry ); 1058 1059 c.pt = para->pt; 1060 prev_width = para->nWidth; 1061 ME_WrapTextParagraph( editor, &c, para ); 1062 if (prev_width == totalWidth && para->nWidth < totalWidth) 1063 totalWidth = get_total_width(editor); 1064 else 1065 totalWidth = max(totalWidth, para->nWidth); 1066 1067 update_repaint( para, &repaint ); 1068 adjust_para_y( para, &c, &repaint ); 1069 1070 if (para_next( para )) 1071 { 1072 if (c.pt.x != para_next( para )->pt.x || c.pt.y != para_next( para )->pt.y || 1073 para_in_table( para )) 1074 { 1075 next = para; 1076 while (para_next( next ) && &next->marked_entry != next_entry && 1077 next != &editor->pBuffer->pLast->member.para) 1078 { 1079 update_repaint( para_next( next ), &repaint ); 1080 para_next( next )->pt = c.pt; 1081 adjust_para_y( para_next( next ), &c, &repaint ); 1082 next = para_next( next ); 1083 } 1084 } 1085 } 1086 entry = next_entry; 1087 } 1088 wine_rb_destroy( &editor->marked_paras, NULL, NULL ); 1089 1090 editor->sizeWindow.cx = c.rcView.right-c.rcView.left; 1091 editor->sizeWindow.cy = c.rcView.bottom-c.rcView.top; 1092 1093 editor->nTotalLength = editor->pBuffer->pLast->member.para.pt.y; 1094 editor->nTotalWidth = totalWidth; 1095 1096 ME_DestroyContext(&c); 1097 1098 if (invalidate && (repaint.start || editor->nTotalLength < editor->nLastTotalLength)) 1099 para_range_invalidate( editor, repaint.start, repaint.end); 1100 return !!repaint.start; 1101} 1102 1103BOOL ME_WrapMarkedParagraphs( ME_TextEditor *editor ) 1104{ 1105 HDC hdc = ITextHost_TxGetDC( editor->texthost ); 1106 BOOL ret = wrap_marked_paras_dc( editor, hdc, TRUE ); 1107 ITextHost_TxReleaseDC( editor->texthost, hdc ); 1108 return ret; 1109} 1110 1111void para_range_invalidate( ME_TextEditor *editor, ME_Paragraph *start_para, 1112 ME_Paragraph *last_para ) 1113{ 1114 RECT rc; 1115 int ofs; 1116 1117 rc = editor->rcFormat; 1118 ofs = editor->vert_si.nPos; 1119 1120 if (start_para) 1121 { 1122 start_para = table_outer_para( start_para ); 1123 last_para = table_outer_para( last_para ); 1124 rc.top += start_para->pt.y - ofs; 1125 } else { 1126 rc.top += editor->nTotalLength - ofs; 1127 } 1128 if (editor->nTotalLength < editor->nLastTotalLength) 1129 rc.bottom = editor->rcFormat.top + editor->nLastTotalLength - ofs; 1130 else 1131 rc.bottom = editor->rcFormat.top + last_para->pt.y + last_para->nHeight - ofs; 1132 ITextHost_TxInvalidateRect(editor->texthost, &rc, TRUE); 1133} 1134 1135 1136void 1137ME_SendRequestResize(ME_TextEditor *editor, BOOL force) 1138{ 1139 if (editor->nEventMask & ENM_REQUESTRESIZE) 1140 { 1141 RECT rc; 1142 1143 ITextHost_TxGetClientRect(editor->texthost, &rc); 1144 1145 if (force || rc.bottom != editor->nTotalLength) 1146 { 1147 REQRESIZE info; 1148 1149 info.nmhdr.hwndFrom = NULL; 1150 info.nmhdr.idFrom = 0; 1151 info.nmhdr.code = EN_REQUESTRESIZE; 1152 info.rc = rc; 1153 info.rc.right = editor->nTotalWidth; 1154 info.rc.bottom = editor->nTotalLength; 1155 1156 editor->nEventMask &= ~ENM_REQUESTRESIZE; 1157 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info); 1158 editor->nEventMask |= ENM_REQUESTRESIZE; 1159 } 1160 } 1161}