xref: /haiku/src/apps/haikudepot/textview/ParagraphLayout.cpp (revision dfbcbde1e1cbe8eb325b72c84598581b7730c30e)
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