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