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