1 /* 2 * Copyright 2001-2013, Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus, superstippi@gmx.de 7 * Stefano Ceccherini, stefano.ceccherini@gmail.com 8 * Marc Flerackers, mflerackers@androme.be 9 * Hiroshi Lockheimer (BTextView is based on his STEEngine) 10 * Oliver Tappe, zooey@hirschkaefer.de 11 */ 12 13 #include "ParagraphLayout.h" 14 15 #include <new> 16 #include <stdio.h> 17 18 #include <AutoDeleter.h> 19 #include <utf8_functions.h> 20 #include <View.h> 21 22 23 enum { 24 CHAR_CLASS_DEFAULT, 25 CHAR_CLASS_WHITESPACE, 26 CHAR_CLASS_GRAPHICAL, 27 CHAR_CLASS_QUOTE, 28 CHAR_CLASS_PUNCTUATION, 29 CHAR_CLASS_PARENS_OPEN, 30 CHAR_CLASS_PARENS_CLOSE, 31 CHAR_CLASS_END_OF_TEXT 32 }; 33 34 35 inline uint32 36 get_char_classification(uint32 charCode) 37 { 38 // TODO: Should check against a list of characters containing also 39 // word breakers from other languages. 40 41 switch (charCode) { 42 case '\0': 43 return CHAR_CLASS_END_OF_TEXT; 44 45 case ' ': 46 case '\t': 47 case '\n': 48 return CHAR_CLASS_WHITESPACE; 49 50 case '=': 51 case '+': 52 case '@': 53 case '#': 54 case '$': 55 case '%': 56 case '^': 57 case '&': 58 case '*': 59 case '\\': 60 case '|': 61 case '<': 62 case '>': 63 case '/': 64 case '~': 65 return CHAR_CLASS_GRAPHICAL; 66 67 case '\'': 68 case '"': 69 return CHAR_CLASS_QUOTE; 70 71 case ',': 72 case '.': 73 case '?': 74 case '!': 75 case ';': 76 case ':': 77 case '-': 78 return CHAR_CLASS_PUNCTUATION; 79 80 case '(': 81 case '[': 82 case '{': 83 return CHAR_CLASS_PARENS_OPEN; 84 85 case ')': 86 case ']': 87 case '}': 88 return CHAR_CLASS_PARENS_CLOSE; 89 90 default: 91 return CHAR_CLASS_DEFAULT; 92 } 93 } 94 95 96 inline bool 97 can_end_line(const GlyphInfoList& glyphInfos, int offset) 98 { 99 int count = glyphInfos.CountItems(); 100 101 if (offset == count - 1) 102 return true; 103 104 if (offset < 0 || offset > count) 105 return false; 106 107 uint32 charCode = glyphInfos.ItemAtFast(offset).charCode; 108 uint32 classification = get_char_classification(charCode); 109 110 // wrapping is always allowed at end of text and at newlines 111 if (classification == CHAR_CLASS_END_OF_TEXT || charCode == '\n') 112 return true; 113 114 uint32 nextCharCode = glyphInfos.ItemAtFast(offset + 1).charCode; 115 uint32 nextClassification = get_char_classification(nextCharCode); 116 117 // never separate a punctuation char from its preceding word 118 if (classification == CHAR_CLASS_DEFAULT 119 && nextClassification == CHAR_CLASS_PUNCTUATION) { 120 return false; 121 } 122 123 if ((classification == CHAR_CLASS_WHITESPACE 124 && nextClassification != CHAR_CLASS_WHITESPACE) 125 || (classification != CHAR_CLASS_WHITESPACE 126 && nextClassification == CHAR_CLASS_WHITESPACE)) { 127 return true; 128 } 129 130 // allow wrapping after whitespace, unless more whitespace (except for 131 // newline) follows 132 if (classification == CHAR_CLASS_WHITESPACE 133 && (nextClassification != CHAR_CLASS_WHITESPACE 134 || nextCharCode == '\n')) { 135 return true; 136 } 137 138 // allow wrapping after punctuation chars, unless more punctuation, closing 139 // parenthesis or quotes follow 140 if (classification == CHAR_CLASS_PUNCTUATION 141 && nextClassification != CHAR_CLASS_PUNCTUATION 142 && nextClassification != CHAR_CLASS_PARENS_CLOSE 143 && nextClassification != CHAR_CLASS_QUOTE) { 144 return true; 145 } 146 147 // allow wrapping after quotes, graphical chars and closing parenthesis only 148 // if whitespace follows (not perfect, but seems to do the right thing most 149 // of the time) 150 if ((classification == CHAR_CLASS_QUOTE 151 || classification == CHAR_CLASS_GRAPHICAL 152 || classification == CHAR_CLASS_PARENS_CLOSE) 153 && nextClassification == CHAR_CLASS_WHITESPACE) { 154 return true; 155 } 156 157 return false; 158 } 159 160 161 // #pragma mark - ParagraphLayout 162 163 164 ParagraphLayout::ParagraphLayout() 165 : 166 fTextSpans(), 167 fParagraphStyle(), 168 169 fWidth(0.0f), 170 fLayoutValid(false), 171 172 fGlyphInfos(), 173 fLineInfos() 174 { 175 } 176 177 178 ParagraphLayout::ParagraphLayout(const Paragraph& paragraph) 179 : 180 fTextSpans(paragraph.TextSpans()), 181 fParagraphStyle(paragraph.Style()), 182 183 fWidth(0.0f), 184 fLayoutValid(false), 185 186 fGlyphInfos(), 187 fLineInfos() 188 { 189 _Init(); 190 } 191 192 193 ParagraphLayout::ParagraphLayout(const ParagraphLayout& other) 194 : 195 fTextSpans(other.fTextSpans), 196 fParagraphStyle(other.fParagraphStyle), 197 198 fWidth(other.fWidth), 199 fLayoutValid(false), 200 201 fGlyphInfos(other.fGlyphInfos), 202 fLineInfos() 203 { 204 } 205 206 207 ParagraphLayout::~ParagraphLayout() 208 { 209 } 210 211 212 void 213 ParagraphLayout::SetParagraph(const Paragraph& paragraph) 214 { 215 fTextSpans = paragraph.TextSpans(); 216 fParagraphStyle = paragraph.Style(); 217 218 _Init(); 219 220 fLayoutValid = false; 221 } 222 223 224 void 225 ParagraphLayout::SetWidth(float width) 226 { 227 if (fWidth != width) { 228 fWidth = width; 229 fLayoutValid = false; 230 } 231 } 232 233 234 float 235 ParagraphLayout::Height() 236 { 237 _ValidateLayout(); 238 239 float height = 0.0f; 240 241 if (fLineInfos.CountItems() > 0) { 242 const LineInfo& lastLine = fLineInfos.LastItem(); 243 height = lastLine.y + lastLine.height; 244 } 245 246 return height; 247 } 248 249 250 void 251 ParagraphLayout::Draw(BView* view, const BPoint& offset) 252 { 253 _ValidateLayout(); 254 255 int lineCount = fLineInfos.CountItems(); 256 for (int i = 0; i < lineCount; i++) { 257 const LineInfo& line = fLineInfos.ItemAtFast(i); 258 _DrawLine(view, offset, line); 259 } 260 261 const Bullet& bullet = fParagraphStyle.Bullet(); 262 if (bullet.Spacing() > 0.0f && bullet.String().Length() > 0) { 263 // Draw bullet at offset 264 view->SetHighColor(0, 0, 0, 255); 265 BPoint bulletPos(offset); 266 bulletPos.x += fParagraphStyle.FirstLineInset() 267 + fParagraphStyle.LineInset(); 268 bulletPos.y += fLineInfos.ItemAt(0).maxAscent; 269 view->DrawString(bullet.String(), bulletPos); 270 } 271 } 272 273 274 int32 275 ParagraphLayout::CountGlyphs() const 276 { 277 return fGlyphInfos.CountItems(); 278 } 279 280 281 int32 282 ParagraphLayout::CountLines() 283 { 284 _ValidateLayout(); 285 return fLineInfos.CountItems(); 286 } 287 288 289 int32 290 ParagraphLayout::LineIndexForOffset(int32 textOffset) 291 { 292 _ValidateLayout(); 293 294 if (fGlyphInfos.CountItems() == 0) 295 return 0; 296 297 if (textOffset >= fGlyphInfos.CountItems()) { 298 const GlyphInfo& glyph = fGlyphInfos.LastItem(); 299 return glyph.lineIndex; 300 } 301 302 if (textOffset < 0) 303 textOffset = 0; 304 305 const GlyphInfo& glyph = fGlyphInfos.ItemAtFast(textOffset); 306 return glyph.lineIndex; 307 } 308 309 310 int32 311 ParagraphLayout::FirstOffsetOnLine(int32 lineIndex) 312 { 313 _ValidateLayout(); 314 315 if (lineIndex < 0) 316 lineIndex = 0; 317 if (lineIndex >= fLineInfos.CountItems()) 318 lineIndex = fLineInfos.CountItems() - 1; 319 320 return fLineInfos.ItemAt(lineIndex).textOffset; 321 } 322 323 324 int32 325 ParagraphLayout::LastOffsetOnLine(int32 lineIndex) 326 { 327 _ValidateLayout(); 328 329 if (lineIndex < 0) 330 lineIndex = 0; 331 332 if (lineIndex >= fLineInfos.CountItems() - 1) 333 return CountGlyphs() - 1; 334 335 return fLineInfos.ItemAt(lineIndex + 1).textOffset - 1; 336 } 337 338 339 void 340 ParagraphLayout::GetLineBounds(int32 lineIndex, float& x1, float& y1, 341 float& x2, float& y2) 342 { 343 _ValidateLayout(); 344 345 if (fGlyphInfos.CountItems() == 0) { 346 _GetEmptyLayoutBounds(x1, y1, x2, y2); 347 return; 348 } 349 350 if (lineIndex < 0) 351 lineIndex = 0; 352 if (lineIndex >= fLineInfos.CountItems()) 353 lineIndex = fLineInfos.CountItems() - 1; 354 355 const LineInfo& lineInfo = fLineInfos.ItemAt(lineIndex); 356 int32 firstGlyphIndex = lineInfo.textOffset; 357 358 int32 lastGlyphIndex; 359 if (lineIndex < fLineInfos.CountItems() - 1) 360 lastGlyphIndex = fLineInfos.ItemAt(lineIndex + 1).textOffset - 1; 361 else 362 lastGlyphIndex = fGlyphInfos.CountItems() - 1; 363 364 const GlyphInfo& firstInfo = fGlyphInfos.ItemAtFast(firstGlyphIndex); 365 const GlyphInfo& lastInfo = fGlyphInfos.ItemAtFast(lastGlyphIndex); 366 367 x1 = firstInfo.x; 368 y1 = lineInfo.y; 369 x2 = lastInfo.x + lastInfo.width; 370 y2 = lineInfo.y + lineInfo.height; 371 } 372 373 374 void 375 ParagraphLayout::GetTextBounds(int32 textOffset, float& x1, float& y1, 376 float& x2, float& y2) 377 { 378 _ValidateLayout(); 379 380 if (fGlyphInfos.CountItems() == 0) { 381 _GetEmptyLayoutBounds(x1, y1, x2, y2); 382 return; 383 } 384 385 if (textOffset >= fGlyphInfos.CountItems()) { 386 const GlyphInfo& glyph = fGlyphInfos.LastItem(); 387 const LineInfo& line = fLineInfos.ItemAt(glyph.lineIndex); 388 389 x1 = glyph.x + glyph.width; 390 x2 = x1; 391 y1 = line.y; 392 y2 = y1 + line.height; 393 394 return; 395 } 396 397 if (textOffset < 0) 398 textOffset = 0; 399 400 const GlyphInfo& glyph = fGlyphInfos.ItemAtFast(textOffset); 401 const LineInfo& line = fLineInfos.ItemAt(glyph.lineIndex); 402 403 x1 = glyph.x; 404 x2 = x1 + glyph.width; 405 y1 = line.y; 406 y2 = y1 + line.height; 407 } 408 409 410 int32 411 ParagraphLayout::TextOffsetAt(float x, float y, bool& rightOfCenter) 412 { 413 _ValidateLayout(); 414 415 rightOfCenter = false; 416 417 int32 lineCount = fLineInfos.CountItems(); 418 if (fGlyphInfos.CountItems() == 0 || lineCount == 0 419 || fLineInfos.ItemAtFast(0).y > y) { 420 // Above first line or empty text 421 return 0; 422 } 423 424 int32 lineIndex = 0; 425 if (floorf(fLineInfos.LastItem().y 426 + fLineInfos.LastItem().height + 0.5) > y) { 427 // TODO: Optimize, can binary search line here: 428 for (; lineIndex < lineCount; lineIndex++) { 429 const LineInfo& line = fLineInfos.ItemAtFast(lineIndex); 430 float lineBottom = floorf(line.y + line.height + 0.5); 431 if (lineBottom > y) 432 break; 433 } 434 } else { 435 lineIndex = lineCount - 1; 436 } 437 438 // Found line 439 const LineInfo& line = fLineInfos.ItemAtFast(lineIndex); 440 int32 textOffset = line.textOffset; 441 int32 end; 442 if (lineIndex < lineCount - 1) 443 end = fLineInfos.ItemAtFast(lineIndex + 1).textOffset - 1; 444 else 445 end = fGlyphInfos.CountItems() - 1; 446 447 // TODO: Optimize, can binary search offset here: 448 for (; textOffset <= end; textOffset++) { 449 const GlyphInfo& glyph = fGlyphInfos.ItemAtFast(textOffset); 450 float x1 = glyph.x; 451 if (x1 > x) 452 return textOffset; 453 454 // x2 is the location at the right bounding box of the glyph 455 float x2 = x1 + glyph.width; 456 457 // x3 is the location of the next glyph, which may be different from 458 // x2 in case the line is justified. 459 float x3; 460 if (textOffset < end - 1) 461 x3 = fGlyphInfos.ItemAtFast(textOffset + 1).x; 462 else 463 x3 = x2; 464 465 if (x3 > x) { 466 rightOfCenter = x > (x1 + x2) / 2.0f; 467 return textOffset; 468 } 469 } 470 471 // Account for trailing line break at end of line, the 472 // returned offset should be before that. 473 rightOfCenter = fGlyphInfos.ItemAtFast(end).charCode != '\n'; 474 475 return end; 476 } 477 478 479 // #pragma mark - private 480 481 482 void 483 ParagraphLayout::_Init() 484 { 485 fGlyphInfos.Clear(); 486 487 int spanCount = fTextSpans.CountItems(); 488 for (int i = 0; i < spanCount; i++) { 489 const TextSpan& span = fTextSpans.ItemAtFast(i); 490 if (!_AppendGlyphInfos(span)) { 491 fprintf(stderr, "%p->ParagraphLayout::_Init() - Out of memory\n", 492 this); 493 return; 494 } 495 } 496 } 497 498 499 void 500 ParagraphLayout::_ValidateLayout() 501 { 502 if (!fLayoutValid) { 503 _Layout(); 504 fLayoutValid = true; 505 } 506 } 507 508 509 void 510 ParagraphLayout::_Layout() 511 { 512 fLineInfos.Clear(); 513 514 const Bullet& bullet = fParagraphStyle.Bullet(); 515 516 float x = fParagraphStyle.LineInset() + fParagraphStyle.FirstLineInset() 517 + bullet.Spacing(); 518 float y = 0.0f; 519 int lineIndex = 0; 520 int lineStart = 0; 521 522 int glyphCount = fGlyphInfos.CountItems(); 523 for (int i = 0; i < glyphCount; i++) { 524 GlyphInfo glyph = fGlyphInfos.ItemAtFast(i); 525 526 uint32 charClassification = get_char_classification(glyph.charCode); 527 528 float advanceX = glyph.width; 529 float advanceY = 0.0f; 530 531 bool nextLine = false; 532 bool lineBreak = false; 533 534 // if (glyph.charCode == '\t') { 535 // // Figure out tab width, it's the width between the last two tab 536 // // stops. 537 // float tabWidth = 0.0f; 538 // if (fTabCount > 0) 539 // tabWidth = fTabBuffer[fTabCount - 1]; 540 // if (fTabCount > 1) 541 // tabWidth -= fTabBuffer[fTabCount - 2]; 542 // 543 // // Try to find a tab stop that is farther than the current x 544 // // offset 545 // double tabOffset = 0.0; 546 // for (unsigned tabIndex = 0; tabIndex < fTabCount; tabIndex++) { 547 // tabOffset = fTabBuffer[tabIndex]; 548 // if (tabOffset > x) 549 // break; 550 // } 551 // 552 // // If no tab stop has been found, make the tab stop a multiple of 553 // // the tab width 554 // if (tabOffset <= x && tabWidth > 0.0) 555 // tabOffset = ((int) (x / tabWidth) + 1) * tabWidth; 556 // 557 // if (tabOffset - x > 0.0) 558 // advanceX = tabOffset - x; 559 // } 560 561 if (glyph.charCode == '\n') { 562 nextLine = true; 563 lineBreak = true; 564 glyph.x = x; 565 fGlyphInfos.Replace(i, glyph); 566 } else if (fWidth > 0.0f && x + advanceX > fWidth) { 567 fGlyphInfos.Replace(i, glyph); 568 if (charClassification == CHAR_CLASS_WHITESPACE) { 569 advanceX = 0.0f; 570 } else if (i > lineStart) { 571 nextLine = true; 572 // The current glyph extends outside the width, we need to wrap 573 // to the next line. See what previous offset can be the end 574 // of the line. 575 int lineEnd = i - 1; 576 while (lineEnd > lineStart 577 && !can_end_line(fGlyphInfos, lineEnd)) { 578 lineEnd--; 579 } 580 581 if (lineEnd > lineStart) { 582 // Found a place to perform a line break. 583 i = lineEnd + 1; 584 585 // Adjust the glyph info to point at the changed buffer 586 // position 587 glyph = fGlyphInfos.ItemAtFast(i); 588 advanceX = glyph.width; 589 } else { 590 // Just break where we are. 591 } 592 } 593 } 594 595 if (nextLine) { 596 // * Initialize the max ascent/descent of all preceding glyph infos 597 // on the current/last line 598 // * Adjust the baseline offset according to the max ascent 599 // * Fill in the line index. 600 unsigned lineEnd; 601 if (lineBreak) 602 lineEnd = i; 603 else 604 lineEnd = i - 1; 605 606 float lineHeight = 0.0; 607 _FinalizeLine(lineStart, lineEnd, lineIndex, y, lineHeight); 608 609 // Start position of the next line 610 x = fParagraphStyle.LineInset() + bullet.Spacing(); 611 y += lineHeight + fParagraphStyle.LineSpacing(); 612 613 if (lineBreak) 614 lineStart = i + 1; 615 else 616 lineStart = i; 617 618 lineIndex++; 619 } 620 621 if (!lineBreak && i < glyphCount) { 622 glyph.x = x; 623 fGlyphInfos.Replace(i, glyph); 624 } 625 626 x += advanceX; 627 y += advanceY; 628 } 629 630 // The last line may not have been appended and initialized yet. 631 if (lineStart <= glyphCount - 1 || glyphCount == 0) { 632 float lineHeight; 633 _FinalizeLine(lineStart, glyphCount - 1, lineIndex, y, lineHeight); 634 } 635 636 _ApplyAlignment(); 637 } 638 639 640 void 641 ParagraphLayout::_ApplyAlignment() 642 { 643 Alignment alignment = fParagraphStyle.Alignment(); 644 bool justify = fParagraphStyle.Justify(); 645 646 if (alignment == ALIGN_LEFT && !justify) 647 return; 648 649 int glyphCount = fGlyphInfos.CountItems(); 650 if (glyphCount == 0) 651 return; 652 653 int lineIndex = -1; 654 float spaceLeft = 0.0f; 655 float charSpace = 0.0f; 656 float whiteSpace = 0.0f; 657 bool seenChar = false; 658 659 // Iterate all glyphs backwards. On the last character of the next line, 660 // the position of the character determines the available space to be 661 // distributed (spaceLeft). 662 for (int i = glyphCount - 1; i >= 0; i--) { 663 GlyphInfo glyph = fGlyphInfos.ItemAtFast(i); 664 665 if (glyph.lineIndex != lineIndex) { 666 bool lineBreak = glyph.charCode == '\n' || i == glyphCount - 1; 667 lineIndex = glyph.lineIndex; 668 669 // The position of the last character determines the available 670 // space. 671 spaceLeft = fWidth - glyph.x; 672 673 // If the character is visible, the width of the character needs to 674 // be subtracted from the available space, otherwise it would be 675 // pushed outside the line. 676 uint32 charClassification = get_char_classification(glyph.charCode); 677 if (charClassification != CHAR_CLASS_WHITESPACE) 678 spaceLeft -= glyph.width; 679 680 charSpace = 0.0f; 681 whiteSpace = 0.0f; 682 seenChar = false; 683 684 if (lineBreak || !justify) { 685 if (alignment == ALIGN_CENTER) 686 spaceLeft /= 2.0f; 687 else if (alignment == ALIGN_LEFT) 688 spaceLeft = 0.0f; 689 } else { 690 // Figure out how much chars and white space chars are on the 691 // line. Don't count trailing white space. 692 int charCount = 0; 693 int spaceCount = 0; 694 for (int j = i; j >= 0; j--) { 695 const GlyphInfo& previousGlyph = fGlyphInfos.ItemAtFast(j); 696 if (previousGlyph.lineIndex != lineIndex) { 697 j++; 698 break; 699 } 700 uint32 classification = get_char_classification( 701 previousGlyph.charCode); 702 if (classification == CHAR_CLASS_WHITESPACE) { 703 if (charCount > 0) 704 spaceCount++; 705 else if (j < i) 706 spaceLeft += glyph.width; 707 } else { 708 charCount++; 709 } 710 } 711 712 // The first char is not shifted when justifying, so it doesn't 713 // contribute. 714 if (charCount > 0) 715 charCount--; 716 717 // Check if it looks better if both whitespace and chars get 718 // some space distributed, in case there are only 1 or two 719 // space chars on the line. 720 float spaceLeftForSpace = spaceLeft; 721 float spaceLeftForChars = spaceLeft; 722 723 if (spaceCount > 0) { 724 float spaceCharRatio = (float) spaceCount / charCount; 725 if (spaceCount < 3 && spaceCharRatio < 0.4f) { 726 spaceLeftForSpace = spaceLeft * 2.0f * spaceCharRatio; 727 spaceLeftForChars = spaceLeft - spaceLeftForSpace; 728 } else 729 spaceLeftForChars = 0.0f; 730 } 731 732 if (spaceCount > 0) 733 whiteSpace = spaceLeftForSpace / spaceCount; 734 if (charCount > 0) 735 charSpace = spaceLeftForChars / charCount; 736 737 LineInfo line = fLineInfos.ItemAtFast(lineIndex); 738 line.extraGlyphSpacing = charSpace; 739 line.extraWhiteSpacing = whiteSpace; 740 741 fLineInfos.Replace(lineIndex, line); 742 } 743 } 744 745 // Each character is pushed towards the right by the space that is 746 // still available. When justification is performed, the shift is 747 // gradually decreased. This works since the iteration is backwards 748 // and the characters on the right are pushed farthest. 749 glyph.x += spaceLeft; 750 751 unsigned classification = get_char_classification(glyph.charCode); 752 753 if (i < glyphCount - 1) { 754 GlyphInfo nextGlyph = fGlyphInfos.ItemAtFast(i + 1); 755 if (nextGlyph.lineIndex == lineIndex) { 756 uint32 nextClassification 757 = get_char_classification(nextGlyph.charCode); 758 if (nextClassification == CHAR_CLASS_WHITESPACE 759 && classification != CHAR_CLASS_WHITESPACE) { 760 // When a space character is right of a regular character, 761 // add the additional space to the space instead of the 762 // character 763 float shift = (nextGlyph.x - glyph.x) - glyph.width; 764 nextGlyph.x -= shift; 765 fGlyphInfos.Replace(i + 1, nextGlyph); 766 } 767 } 768 } 769 770 fGlyphInfos.Replace(i, glyph); 771 772 // The shift (spaceLeft) is reduced depending on the character 773 // classification. 774 if (classification == CHAR_CLASS_WHITESPACE) { 775 if (seenChar) 776 spaceLeft -= whiteSpace; 777 } else { 778 seenChar = true; 779 spaceLeft -= charSpace; 780 } 781 } 782 } 783 784 785 bool 786 ParagraphLayout::_AppendGlyphInfos(const TextSpan& span) 787 { 788 int charCount = span.CountChars(); 789 if (charCount == 0) 790 return true; 791 792 const BString& text = span.Text(); 793 const BFont& font = span.Style().Font(); 794 795 // Allocate arrays 796 float* escapementArray = new (std::nothrow) float[charCount]; 797 if (escapementArray == NULL) 798 return false; 799 ArrayDeleter<float> escapementDeleter(escapementArray); 800 801 // Fetch glyph spacing information 802 font.GetEscapements(text, charCount, escapementArray); 803 804 // Append to glyph buffer and convert escapement scale 805 float size = font.Size(); 806 const char* c = text.String(); 807 for (int i = 0; i < charCount; i++) { 808 if (!_AppendGlyphInfo(UTF8ToCharCode(&c), escapementArray[i] * size, 809 span.Style())) { 810 return false; 811 } 812 } 813 814 return true; 815 } 816 817 818 bool 819 ParagraphLayout::_AppendGlyphInfo(uint32 charCode, float width, 820 const CharacterStyle& style) 821 { 822 if (style.Width() >= 0.0f) { 823 // Use the metrics provided by the CharacterStyle and override 824 // the font provided metrics passed in "width" 825 width = style.Width(); 826 } 827 828 width += style.GlyphSpacing(); 829 830 return fGlyphInfos.Add(GlyphInfo(charCode, 0.0f, width, 0)); 831 } 832 833 834 bool 835 ParagraphLayout::_FinalizeLine(int lineStart, int lineEnd, int lineIndex, 836 float y, float& lineHeight) 837 { 838 LineInfo line(lineStart, y, 0.0f, 0.0f, 0.0f); 839 840 int spanIndex = -1; 841 int spanStart = 0; 842 int spanEnd = 0; 843 844 for (int i = lineStart; i <= lineEnd; i++) { 845 // Mark line index in glyph 846 GlyphInfo glyph = fGlyphInfos.ItemAtFast(i); 847 glyph.lineIndex = lineIndex; 848 fGlyphInfos.Replace(i, glyph); 849 850 // See if the next sub-span needs to be added to the LineInfo 851 bool addSpan = false; 852 853 while (i >= spanEnd) { 854 spanIndex++; 855 const TextSpan& span = fTextSpans.ItemAt(spanIndex); 856 spanStart = spanEnd; 857 spanEnd += span.CountChars(); 858 addSpan = true; 859 } 860 861 if (addSpan) { 862 const TextSpan& span = fTextSpans.ItemAt(spanIndex); 863 TextSpan subSpan = span.SubSpan(i - spanStart, 864 (lineEnd - spanStart + 1) - (i - spanStart)); 865 line.layoutedSpans.Add(subSpan); 866 _IncludeStyleInLine(line, span.Style()); 867 } 868 } 869 870 if (fGlyphInfos.CountItems() == 0 && fTextSpans.CountItems() > 0) { 871 // When the layout contains no glyphs, but there is at least one 872 // TextSpan in the paragraph, use the font info from that span 873 // to calculate the height of the first LineInfo. 874 const TextSpan& span = fTextSpans.ItemAtFast(0); 875 line.layoutedSpans.Add(span); 876 _IncludeStyleInLine(line, span.Style()); 877 } 878 879 lineHeight = line.height; 880 881 return fLineInfos.Add(line); 882 } 883 884 885 void 886 ParagraphLayout::_IncludeStyleInLine(LineInfo& line, 887 const CharacterStyle& style) 888 { 889 float ascent = style.Ascent(); 890 if (ascent > line.maxAscent) 891 line.maxAscent = ascent; 892 893 float descent = style.Descent(); 894 if (descent > line.maxDescent) 895 line.maxDescent = descent; 896 897 float height = ascent + descent; 898 if (style.Font().Size() > height) 899 height = style.Font().Size(); 900 901 if (height > line.height) 902 line.height = height; 903 } 904 905 906 void 907 ParagraphLayout::_DrawLine(BView* view, const BPoint& offset, 908 const LineInfo& line) const 909 { 910 int textOffset = line.textOffset; 911 int spanCount = line.layoutedSpans.CountItems(); 912 for (int i = 0; i < spanCount; i++) { 913 const TextSpan& span = line.layoutedSpans.ItemAtFast(i); 914 _DrawSpan(view, offset, span, textOffset); 915 textOffset += span.CountChars(); 916 } 917 } 918 919 920 void 921 ParagraphLayout::_DrawSpan(BView* view, BPoint offset, 922 const TextSpan& span, int32 textOffset) const 923 { 924 const BString& text = span.Text(); 925 if (text.Length() == 0) 926 return; 927 928 const GlyphInfo& glyph = fGlyphInfos.ItemAtFast(textOffset); 929 const LineInfo& line = fLineInfos.ItemAtFast(glyph.lineIndex); 930 931 offset.x += glyph.x; 932 offset.y += line.y + line.maxAscent; 933 934 const CharacterStyle& style = span.Style(); 935 936 view->SetFont(&style.Font()); 937 view->SetHighColor(style.ForegroundColor()); 938 939 // TODO: Implement other style properties 940 941 escapement_delta delta; 942 delta.nonspace = line.extraGlyphSpacing; 943 delta.space = line.extraWhiteSpacing; 944 945 view->DrawString(span.Text(), offset, &delta); 946 } 947 948 949 void 950 ParagraphLayout::_GetEmptyLayoutBounds(float& x1, float& y1, float& x2, 951 float& y2) const 952 { 953 if (fLineInfos.CountItems() == 0) { 954 x1 = 0.0f; 955 y1 = 0.0f; 956 x2 = 0.0f; 957 y2 = 0.0f; 958 959 return; 960 } 961 962 // If the paragraph had at least a single empty TextSpan, the layout 963 // can compute some meaningful bounds. 964 const Bullet& bullet = fParagraphStyle.Bullet(); 965 x1 = fParagraphStyle.LineInset() + fParagraphStyle.FirstLineInset() 966 + bullet.Spacing(); 967 x2 = x1; 968 const LineInfo& lineInfo = fLineInfos.ItemAt(0); 969 y1 = lineInfo.y; 970 y2 = lineInfo.y + lineInfo.height; 971 } 972