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
get_char_classification(uint32 charCode)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
can_end_line(const std::vector<GlyphInfo> & glyphInfos,int offset)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
ParagraphLayout()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
ParagraphLayout(const Paragraph & paragraph)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
ParagraphLayout(const ParagraphLayout & other)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
~ParagraphLayout()209 ParagraphLayout::~ParagraphLayout()
210 {
211 }
212
213
214 void
SetParagraph(const Paragraph & paragraph)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
SetWidth(float width)228 ParagraphLayout::SetWidth(float width)
229 {
230 if (fWidth != width) {
231 fWidth = width;
232 fLayoutValid = false;
233 }
234 }
235
236
237 float
Height()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
Draw(BView * view,const BPoint & offset)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
CountGlyphs() const278 ParagraphLayout::CountGlyphs() const
279 {
280 return static_cast<int32>(fGlyphInfos.size());
281 }
282
283
284 int32
CountLines()285 ParagraphLayout::CountLines()
286 {
287 _ValidateLayout();
288 return static_cast<int32>(fLineInfos.size());
289 }
290
291
292 int32
LineIndexForOffset(int32 textOffset)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
FirstOffsetOnLine(int32 lineIndex)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
LastOffsetOnLine(int32 lineIndex)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
GetLineBounds(int32 lineIndex,float & x1,float & y1,float & x2,float & y2)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
GetTextBounds(int32 textOffset,float & x1,float & y1,float & x2,float & y2)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
TextOffsetAt(float x,float y,bool & rightOfCenter)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
_Init()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
_ValidateLayout()505 ParagraphLayout::_ValidateLayout()
506 {
507 if (!fLayoutValid) {
508 _Layout();
509 fLayoutValid = true;
510 }
511 }
512
513
514 void
_Layout()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
_ApplyAlignment()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
_AppendGlyphInfos(const TextSpan & span)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
_AppendGlyphInfo(uint32 charCode,float width,const CharacterStyle & style)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
_FinalizeLine(int lineStart,int lineEnd,int lineIndex,float y,float & lineHeight)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
_IncludeStyleInLine(LineInfo & line,const CharacterStyle & style)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
_DrawLine(BView * view,const BPoint & offset,const LineInfo & line) const929 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
_DrawSpan(BView * view,BPoint offset,const TextSpan & span,int32 textOffset) const943 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
_GetEmptyLayoutBounds(float & x1,float & y1,float & x2,float & y2) const976 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
_AppendTextSpans(const Paragraph & paragraph)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