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
24enum {
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
36inline uint32
37get_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
97inline bool
98can_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
165ParagraphLayout::ParagraphLayout()
166	:
167	fTextSpans(),
168	fParagraphStyle(),
169
170	fWidth(0.0f),
171	fLayoutValid(false),
172
173	fGlyphInfos(),
174	fLineInfos()
175{
176}
177
178
179ParagraphLayout::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
195ParagraphLayout::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
209ParagraphLayout::~ParagraphLayout()
210{
211}
212
213
214void
215ParagraphLayout::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
227void
228ParagraphLayout::SetWidth(float width)
229{
230	if (fWidth != width) {
231		fWidth = width;
232		fLayoutValid = false;
233	}
234}
235
236
237float
238ParagraphLayout::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
253void
254ParagraphLayout::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
277int32
278ParagraphLayout::CountGlyphs() const
279{
280	return static_cast<int32>(fGlyphInfos.size());
281}
282
283
284int32
285ParagraphLayout::CountLines()
286{
287	_ValidateLayout();
288	return static_cast<int32>(fLineInfos.size());
289}
290
291
292int32
293ParagraphLayout::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
313int32
314ParagraphLayout::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
328int32
329ParagraphLayout::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
343void
344ParagraphLayout::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
379void
380ParagraphLayout::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
415int32
416ParagraphLayout::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
487void
488ParagraphLayout::_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
504void
505ParagraphLayout::_ValidateLayout()
506{
507	if (!fLayoutValid) {
508		_Layout();
509		fLayoutValid = true;
510	}
511}
512
513
514void
515ParagraphLayout::_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
645void
646ParagraphLayout::_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
790bool
791ParagraphLayout::_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
823bool
824ParagraphLayout::_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
848bool
849ParagraphLayout::_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
907void
908ParagraphLayout::_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
928void
929ParagraphLayout::_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
942void
943ParagraphLayout::_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
975void
976ParagraphLayout::_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
1000void
1001ParagraphLayout::_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