115a557e6SStephan Aßmus/*
215a557e6SStephan Aßmus * Copyright 2001-2013, Haiku, Inc. All rights reserved.
315a557e6SStephan Aßmus * Distributed under the terms of the MIT License.
415a557e6SStephan Aßmus *
515a557e6SStephan Aßmus * Authors:
615a557e6SStephan Aßmus *		Stephan A��mus, superstippi@gmx.de
715a557e6SStephan Aßmus *		Stefano Ceccherini, stefano.ceccherini@gmail.com
815a557e6SStephan Aßmus *		Marc Flerackers, mflerackers@androme.be
915a557e6SStephan Aßmus *		Hiroshi Lockheimer (BTextView is based on his STEEngine)
1015a557e6SStephan Aßmus *		Oliver Tappe, zooey@hirschkaefer.de
1115a557e6SStephan Aßmus */
1215a557e6SStephan Aßmus
1315a557e6SStephan Aßmus#include "ParagraphLayout.h"
1415a557e6SStephan Aßmus
1515a557e6SStephan Aßmus#include <new>
1615a557e6SStephan Aßmus#include <stdio.h>
1715a557e6SStephan Aßmus
1815a557e6SStephan Aßmus#include <AutoDeleter.h>
1915a557e6SStephan Aßmus#include <utf8_functions.h>
2015a557e6SStephan Aßmus#include <View.h>
2115a557e6SStephan Aßmus
2215a557e6SStephan Aßmus
2315a557e6SStephan Aßmusenum {
2415a557e6SStephan Aßmus	CHAR_CLASS_DEFAULT,
2515a557e6SStephan Aßmus	CHAR_CLASS_WHITESPACE,
2615a557e6SStephan Aßmus	CHAR_CLASS_GRAPHICAL,
2715a557e6SStephan Aßmus	CHAR_CLASS_QUOTE,
2815a557e6SStephan Aßmus	CHAR_CLASS_PUNCTUATION,
2915a557e6SStephan Aßmus	CHAR_CLASS_PARENS_OPEN,
3015a557e6SStephan Aßmus	CHAR_CLASS_PARENS_CLOSE,
3115a557e6SStephan Aßmus	CHAR_CLASS_END_OF_TEXT
3215a557e6SStephan Aßmus};
3315a557e6SStephan Aßmus
3415a557e6SStephan Aßmus
3515a557e6SStephan Aßmusinline uint32
3615a557e6SStephan Aßmusget_char_classification(uint32 charCode)
3715a557e6SStephan Aßmus{
3815a557e6SStephan Aßmus	// TODO: Should check against a list of characters containing also
3915a557e6SStephan Aßmus	// word breakers from other languages.
4015a557e6SStephan Aßmus
4115a557e6SStephan Aßmus	switch (charCode) {
4215a557e6SStephan Aßmus		case '\0':
4315a557e6SStephan Aßmus			return CHAR_CLASS_END_OF_TEXT;
4415a557e6SStephan Aßmus
4515a557e6SStephan Aßmus		case ' ':
4615a557e6SStephan Aßmus		case '\t':
4715a557e6SStephan Aßmus		case '\n':
4815a557e6SStephan Aßmus			return CHAR_CLASS_WHITESPACE;
4915a557e6SStephan Aßmus
5015a557e6SStephan Aßmus		case '=':
5115a557e6SStephan Aßmus		case '+':
5215a557e6SStephan Aßmus		case '@':
5315a557e6SStephan Aßmus		case '#':
5415a557e6SStephan Aßmus		case '$':
5515a557e6SStephan Aßmus		case '%':
5615a557e6SStephan Aßmus		case '^':
5715a557e6SStephan Aßmus		case '&':
5815a557e6SStephan Aßmus		case '*':
5915a557e6SStephan Aßmus		case '\\':
6015a557e6SStephan Aßmus		case '|':
6115a557e6SStephan Aßmus		case '<':
6215a557e6SStephan Aßmus		case '>':
6315a557e6SStephan Aßmus		case '/':
6415a557e6SStephan Aßmus		case '~':
6515a557e6SStephan Aßmus			return CHAR_CLASS_GRAPHICAL;
6615a557e6SStephan Aßmus
6715a557e6SStephan Aßmus		case '\'':
6815a557e6SStephan Aßmus		case '"':
6915a557e6SStephan Aßmus			return CHAR_CLASS_QUOTE;
7015a557e6SStephan Aßmus
7115a557e6SStephan Aßmus		case ',':
7215a557e6SStephan Aßmus		case '.':
7315a557e6SStephan Aßmus		case '?':
7415a557e6SStephan Aßmus		case '!':
7515a557e6SStephan Aßmus		case ';':
7615a557e6SStephan Aßmus		case ':':
7715a557e6SStephan Aßmus		case '-':
7815a557e6SStephan Aßmus			return CHAR_CLASS_PUNCTUATION;
7915a557e6SStephan Aßmus
8015a557e6SStephan Aßmus		case '(':
8115a557e6SStephan Aßmus		case '[':
8215a557e6SStephan Aßmus		case '{':
8315a557e6SStephan Aßmus			return CHAR_CLASS_PARENS_OPEN;
8415a557e6SStephan Aßmus
8515a557e6SStephan Aßmus		case ')':
8615a557e6SStephan Aßmus		case ']':
8715a557e6SStephan Aßmus		case '}':
8815a557e6SStephan Aßmus			return CHAR_CLASS_PARENS_CLOSE;
8915a557e6SStephan Aßmus
9015a557e6SStephan Aßmus		default:
9115a557e6SStephan Aßmus			return CHAR_CLASS_DEFAULT;
9215a557e6SStephan Aßmus	}
9315a557e6SStephan Aßmus}
9415a557e6SStephan Aßmus
9515a557e6SStephan Aßmus
9615a557e6SStephan Aßmusinline bool
9715990b01SStephan Aßmuscan_end_line(const GlyphInfoList& glyphInfos, int offset)
9815a557e6SStephan Aßmus{
9915990b01SStephan Aßmus	int count = glyphInfos.CountItems();
10015990b01SStephan Aßmus
10115a557e6SStephan Aßmus	if (offset == count - 1)
10215a557e6SStephan Aßmus		return true;
10315a557e6SStephan Aßmus
10415a557e6SStephan Aßmus	if (offset < 0 || offset > count)
10515a557e6SStephan Aßmus		return false;
10615a557e6SStephan Aßmus
10715990b01SStephan Aßmus	uint32 charCode = glyphInfos.ItemAtFast(offset).charCode;
10815a557e6SStephan Aßmus	uint32 classification = get_char_classification(charCode);
10915a557e6SStephan Aßmus
11015a557e6SStephan Aßmus	// wrapping is always allowed at end of text and at newlines
11115a557e6SStephan Aßmus	if (classification == CHAR_CLASS_END_OF_TEXT || charCode == '\n')
11215a557e6SStephan Aßmus		return true;
11315a557e6SStephan Aßmus
11415990b01SStephan Aßmus	uint32 nextCharCode = glyphInfos.ItemAtFast(offset + 1).charCode;
11515a557e6SStephan Aßmus	uint32 nextClassification = get_char_classification(nextCharCode);
11615a557e6SStephan Aßmus
11715a557e6SStephan Aßmus	// never separate a punctuation char from its preceding word
11815a557e6SStephan Aßmus	if (classification == CHAR_CLASS_DEFAULT
11915a557e6SStephan Aßmus		&& nextClassification == CHAR_CLASS_PUNCTUATION) {
12015a557e6SStephan Aßmus		return false;
12115a557e6SStephan Aßmus	}
12215a557e6SStephan Aßmus
12315a557e6SStephan Aßmus	if ((classification == CHAR_CLASS_WHITESPACE
12415a557e6SStephan Aßmus			&& nextClassification != CHAR_CLASS_WHITESPACE)
12515a557e6SStephan Aßmus		|| (classification != CHAR_CLASS_WHITESPACE
12615a557e6SStephan Aßmus			&& nextClassification == CHAR_CLASS_WHITESPACE)) {
12715a557e6SStephan Aßmus		return true;
12815a557e6SStephan Aßmus	}
12915a557e6SStephan Aßmus
13015a557e6SStephan Aßmus	// allow wrapping after whitespace, unless more whitespace (except for
13115a557e6SStephan Aßmus	// newline) follows
13215a557e6SStephan Aßmus	if (classification == CHAR_CLASS_WHITESPACE
13315a557e6SStephan Aßmus		&& (nextClassification != CHAR_CLASS_WHITESPACE
13415a557e6SStephan Aßmus			|| nextCharCode == '\n')) {
13515a557e6SStephan Aßmus		return true;
13615a557e6SStephan Aßmus	}
13715a557e6SStephan Aßmus
13815a557e6SStephan Aßmus	// allow wrapping after punctuation chars, unless more punctuation, closing
13915a557e6SStephan Aßmus	// parenthesis or quotes follow
14015a557e6SStephan Aßmus	if (classification == CHAR_CLASS_PUNCTUATION
14115a557e6SStephan Aßmus		&& nextClassification != CHAR_CLASS_PUNCTUATION
14215a557e6SStephan Aßmus		&& nextClassification != CHAR_CLASS_PARENS_CLOSE
14315a557e6SStephan Aßmus		&& nextClassification != CHAR_CLASS_QUOTE) {
14415a557e6SStephan Aßmus		return true;
14515a557e6SStephan Aßmus	}
14615a557e6SStephan Aßmus
14715a557e6SStephan Aßmus	// allow wrapping after quotes, graphical chars and closing parenthesis only
14815a557e6SStephan Aßmus	// if whitespace follows (not perfect, but seems to do the right thing most
14915a557e6SStephan Aßmus	// of the time)
15015a557e6SStephan Aßmus	if ((classification == CHAR_CLASS_QUOTE
15115a557e6SStephan Aßmus			|| classification == CHAR_CLASS_GRAPHICAL
15215a557e6SStephan Aßmus			|| classification == CHAR_CLASS_PARENS_CLOSE)
15315a557e6SStephan Aßmus		&& nextClassification == CHAR_CLASS_WHITESPACE) {
15415a557e6SStephan Aßmus		return true;
15515a557e6SStephan Aßmus	}
15615a557e6SStephan Aßmus
15715a557e6SStephan Aßmus	return false;
15815a557e6SStephan Aßmus}
15915a557e6SStephan Aßmus
16015a557e6SStephan Aßmus
16115a557e6SStephan Aßmus// #pragma mark - ParagraphLayout
16215a557e6SStephan Aßmus
16315a557e6SStephan Aßmus
16415a557e6SStephan AßmusParagraphLayout::ParagraphLayout()
16515a557e6SStephan Aßmus	:
16615a557e6SStephan Aßmus	fTextSpans(),
16715990b01SStephan Aßmus	fParagraphStyle(),
16815a557e6SStephan Aßmus
16915990b01SStephan Aßmus	fWidth(0.0f),
17015a557e6SStephan Aßmus	fLayoutValid(false),
17115a557e6SStephan Aßmus
17215990b01SStephan Aßmus	fGlyphInfos(),
17315990b01SStephan Aßmus	fLineInfos()
17415a557e6SStephan Aßmus{
17515a557e6SStephan Aßmus}
17615a557e6SStephan Aßmus
17715a557e6SStephan Aßmus
17815a557e6SStephan AßmusParagraphLayout::ParagraphLayout(const Paragraph& paragraph)
17915a557e6SStephan Aßmus	:
18015a557e6SStephan Aßmus	fTextSpans(paragraph.TextSpans()),
18115990b01SStephan Aßmus	fParagraphStyle(paragraph.Style()),
18215a557e6SStephan Aßmus
18315990b01SStephan Aßmus	fWidth(0.0f),
18415a557e6SStephan Aßmus	fLayoutValid(false),
18515a557e6SStephan Aßmus
18615990b01SStephan Aßmus	fGlyphInfos(),
18715990b01SStephan Aßmus	fLineInfos()
18815a557e6SStephan Aßmus{
18915a557e6SStephan Aßmus	_Init();
19015a557e6SStephan Aßmus}
19115a557e6SStephan Aßmus
19215a557e6SStephan Aßmus
19315a557e6SStephan AßmusParagraphLayout::ParagraphLayout(const ParagraphLayout& other)
19415a557e6SStephan Aßmus	:
19515a557e6SStephan Aßmus	fTextSpans(other.fTextSpans),
19615990b01SStephan Aßmus	fParagraphStyle(other.fParagraphStyle),
1971f67148fSRene Gollent
19815a557e6SStephan Aßmus	fWidth(other.fWidth),
19915a557e6SStephan Aßmus	fLayoutValid(false),
20015a557e6SStephan Aßmus
20124523b86SStephan Aßmus	fGlyphInfos(other.fGlyphInfos),
20215990b01SStephan Aßmus	fLineInfos()
20315a557e6SStephan Aßmus{
20415a557e6SStephan Aßmus}
20515a557e6SStephan Aßmus
20615a557e6SStephan Aßmus
20715a557e6SStephan AßmusParagraphLayout::~ParagraphLayout()
20815a557e6SStephan Aßmus{
20915990b01SStephan Aßmus}
21015990b01SStephan Aßmus
21115990b01SStephan Aßmus
21215990b01SStephan Aßmusvoid
21315990b01SStephan AßmusParagraphLayout::SetParagraph(const Paragraph& paragraph)
21415990b01SStephan Aßmus{
21515990b01SStephan Aßmus	fTextSpans = paragraph.TextSpans();
21615990b01SStephan Aßmus	fParagraphStyle = paragraph.Style();
21715990b01SStephan Aßmus
21815990b01SStephan Aßmus	_Init();
21915990b01SStephan Aßmus
22015990b01SStephan Aßmus	fLayoutValid = false;
22115a557e6SStephan Aßmus}
22215a557e6SStephan Aßmus
22315a557e6SStephan Aßmus
22415a557e6SStephan Aßmusvoid
22515a557e6SStephan AßmusParagraphLayout::SetWidth(float width)
22615a557e6SStephan Aßmus{
22715a557e6SStephan Aßmus	if (fWidth != width) {
22815a557e6SStephan Aßmus		fWidth = width;
22915a557e6SStephan Aßmus		fLayoutValid = false;
23015a557e6SStephan Aßmus	}
23115a557e6SStephan Aßmus}
23215a557e6SStephan Aßmus
23315a557e6SStephan Aßmus
23415a557e6SStephan Aßmusfloat
23515a557e6SStephan AßmusParagraphLayout::Height()
23615a557e6SStephan Aßmus{
23715a557e6SStephan Aßmus	_ValidateLayout();
23815a557e6SStephan Aßmus
23915a557e6SStephan Aßmus	float height = 0.0f;
24015a557e6SStephan Aßmus
24115990b01SStephan Aßmus	if (fLineInfos.CountItems() > 0) {
24215990b01SStephan Aßmus		const LineInfo& lastLine = fLineInfos.LastItem();
24315a557e6SStephan Aßmus		height = lastLine.y + lastLine.height;
24415a557e6SStephan Aßmus	}
24515a557e6SStephan Aßmus
24615a557e6SStephan Aßmus	return height;
24715a557e6SStephan Aßmus}
24815a557e6SStephan Aßmus
24915a557e6SStephan Aßmus
25015a557e6SStephan Aßmusvoid
25115a557e6SStephan AßmusParagraphLayout::Draw(BView* view, const BPoint& offset)
25215a557e6SStephan Aßmus{
25315a557e6SStephan Aßmus	_ValidateLayout();
2541f67148fSRene Gollent
25515990b01SStephan Aßmus	int lineCount = fLineInfos.CountItems();
25615a557e6SStephan Aßmus	for (int i = 0; i < lineCount; i++) {
25715990b01SStephan Aßmus		const LineInfo& line = fLineInfos.ItemAtFast(i);
2588e8d1b55SStephan Aßmus		_DrawLine(view, offset, line);
25915a557e6SStephan Aßmus	}
2601f67148fSRene Gollent
2616576e6c7SStephan Aßmus	const Bullet& bullet = fParagraphStyle.Bullet();
2626576e6c7SStephan Aßmus	if (bullet.Spacing() > 0.0f && bullet.String().Length() > 0) {
2636576e6c7SStephan Aßmus		// Draw bullet at offset
264fa19dd44Slooncraz		view->SetHighUIColor(B_PANEL_TEXT_COLOR);
2656576e6c7SStephan Aßmus		BPoint bulletPos(offset);
2666576e6c7SStephan Aßmus		bulletPos.x += fParagraphStyle.FirstLineInset()
2676576e6c7SStephan Aßmus			+ fParagraphStyle.LineInset();
2686576e6c7SStephan Aßmus		bulletPos.y += fLineInfos.ItemAt(0).maxAscent;
2696576e6c7SStephan Aßmus		view->DrawString(bullet.String(), bulletPos);
2706576e6c7SStephan Aßmus	}
27115a557e6SStephan Aßmus}
27215a557e6SStephan Aßmus
27315a557e6SStephan Aßmus
274cd50559dSStephan Aßmusint32
275cd50559dSStephan AßmusParagraphLayout::CountGlyphs() const
276cd50559dSStephan Aßmus{
277cd50559dSStephan Aßmus	return fGlyphInfos.CountItems();
278cd50559dSStephan Aßmus}
279cd50559dSStephan Aßmus
280cd50559dSStephan Aßmus
281e3dc81ccSStephan Aßmusint32
282e3dc81ccSStephan AßmusParagraphLayout::CountLines()
283e3dc81ccSStephan Aßmus{
284e3dc81ccSStephan Aßmus	_ValidateLayout();
285e3dc81ccSStephan Aßmus	return fLineInfos.CountItems();
286e3dc81ccSStephan Aßmus}
287e3dc81ccSStephan Aßmus
288e3dc81ccSStephan Aßmus
289e3dc81ccSStephan Aßmusint32
290e3dc81ccSStephan AßmusParagraphLayout::LineIndexForOffset(int32 textOffset)
291e3dc81ccSStephan Aßmus{
292e3dc81ccSStephan Aßmus	_ValidateLayout();
293e3dc81ccSStephan Aßmus
294e3dc81ccSStephan Aßmus	if (fGlyphInfos.CountItems() == 0)
295e3dc81ccSStephan Aßmus		return 0;
296e3dc81ccSStephan Aßmus
297e3dc81ccSStephan Aßmus	if (textOffset >= fGlyphInfos.CountItems()) {
298e3dc81ccSStephan Aßmus		const GlyphInfo& glyph = fGlyphInfos.LastItem();
299e3dc81ccSStephan Aßmus		return glyph.lineIndex;
300e3dc81ccSStephan Aßmus	}
301e3dc81ccSStephan Aßmus
302e3dc81ccSStephan Aßmus	if (textOffset < 0)
303e3dc81ccSStephan Aßmus		textOffset = 0;
30487084745SMichael Lotz
305e3dc81ccSStephan Aßmus	const GlyphInfo& glyph = fGlyphInfos.ItemAtFast(textOffset);
306e3dc81ccSStephan Aßmus	return glyph.lineIndex;
307e3dc81ccSStephan Aßmus}
308e3dc81ccSStephan Aßmus
309e3dc81ccSStephan Aßmus
310e97d9097SStephan Aßmusint32
311e97d9097SStephan AßmusParagraphLayout::FirstOffsetOnLine(int32 lineIndex)
312e97d9097SStephan Aßmus{
313e97d9097SStephan Aßmus	_ValidateLayout();
314e97d9097SStephan Aßmus
315e97d9097SStephan Aßmus	if (lineIndex < 0)
316e97d9097SStephan Aßmus		lineIndex = 0;
317e8093258SStephan Aßmus	if (lineIndex >= fLineInfos.CountItems())
318e97d9097SStephan Aßmus		lineIndex = fLineInfos.CountItems() - 1;
319e97d9097SStephan Aßmus
320e97d9097SStephan Aßmus	return fLineInfos.ItemAt(lineIndex).textOffset;
321e97d9097SStephan Aßmus}
322e97d9097SStephan Aßmus
323e97d9097SStephan Aßmus
324e97d9097SStephan Aßmusint32
325e97d9097SStephan AßmusParagraphLayout::LastOffsetOnLine(int32 lineIndex)
326e97d9097SStephan Aßmus{
327e97d9097SStephan Aßmus	_ValidateLayout();
328e97d9097SStephan Aßmus
329e97d9097SStephan Aßmus	if (lineIndex < 0)
330e97d9097SStephan Aßmus		lineIndex = 0;
331937ad5c6SStephan Aßmus
332e97d9097SStephan Aßmus	if (lineIndex >= fLineInfos.CountItems() - 1)
333937ad5c6SStephan Aßmus		return CountGlyphs() - 1;
334e97d9097SStephan Aßmus
335e97d9097SStephan Aßmus	return fLineInfos.ItemAt(lineIndex + 1).textOffset - 1;
336e97d9097SStephan Aßmus}
337e97d9097SStephan Aßmus
338e97d9097SStephan Aßmus
339e97d9097SStephan Aßmusvoid
340e97d9097SStephan AßmusParagraphLayout::GetLineBounds(int32 lineIndex, float& x1, float& y1,
341e97d9097SStephan Aßmus	float& x2, float& y2)
342e97d9097SStephan Aßmus{
343e97d9097SStephan Aßmus	_ValidateLayout();
344e97d9097SStephan Aßmus
345e97d9097SStephan Aßmus	if (fGlyphInfos.CountItems() == 0) {
34641889b59SStephan Aßmus		_GetEmptyLayoutBounds(x1, y1, x2, y2);
347e97d9097SStephan Aßmus		return;
348e97d9097SStephan Aßmus	}
349e97d9097SStephan Aßmus
350e97d9097SStephan Aßmus	if (lineIndex < 0)
351e97d9097SStephan Aßmus		lineIndex = 0;
352e8093258SStephan Aßmus	if (lineIndex >= fLineInfos.CountItems())
353e97d9097SStephan Aßmus		lineIndex = fLineInfos.CountItems() - 1;
354e97d9097SStephan Aßmus
355e97d9097SStephan Aßmus	const LineInfo& lineInfo = fLineInfos.ItemAt(lineIndex);
356e97d9097SStephan Aßmus	int32 firstGlyphIndex = lineInfo.textOffset;
357e97d9097SStephan Aßmus
358e97d9097SStephan Aßmus	int32 lastGlyphIndex;
359e97d9097SStephan Aßmus	if (lineIndex < fLineInfos.CountItems() - 1)
360e97d9097SStephan Aßmus		lastGlyphIndex = fLineInfos.ItemAt(lineIndex + 1).textOffset - 1;
361e97d9097SStephan Aßmus	else
362e97d9097SStephan Aßmus		lastGlyphIndex = fGlyphInfos.CountItems() - 1;
363e97d9097SStephan Aßmus
364e97d9097SStephan Aßmus	const GlyphInfo& firstInfo = fGlyphInfos.ItemAtFast(firstGlyphIndex);
365e97d9097SStephan Aßmus	const GlyphInfo& lastInfo = fGlyphInfos.ItemAtFast(lastGlyphIndex);
366e97d9097SStephan Aßmus
367e97d9097SStephan Aßmus	x1 = firstInfo.x;
368e97d9097SStephan Aßmus	y1 = lineInfo.y;
369e97d9097SStephan Aßmus	x2 = lastInfo.x + lastInfo.width;
370e8093258SStephan Aßmus	y2 = lineInfo.y + lineInfo.height;
371e97d9097SStephan Aßmus}
372e97d9097SStephan Aßmus
373e97d9097SStephan Aßmus
374a681c327SStephan Aßmusvoid
375a681c327SStephan AßmusParagraphLayout::GetTextBounds(int32 textOffset, float& x1, float& y1,
376a681c327SStephan Aßmus	float& x2, float& y2)
377a681c327SStephan Aßmus{
378a681c327SStephan Aßmus	_ValidateLayout();
379a681c327SStephan Aßmus
380a681c327SStephan Aßmus	if (fGlyphInfos.CountItems() == 0) {
38141889b59SStephan Aßmus		_GetEmptyLayoutBounds(x1, y1, x2, y2);
382a681c327SStephan Aßmus		return;
383a681c327SStephan Aßmus	}
384a681c327SStephan Aßmus
385a681c327SStephan Aßmus	if (textOffset >= fGlyphInfos.CountItems()) {
386a681c327SStephan Aßmus		const GlyphInfo& glyph = fGlyphInfos.LastItem();
387a681c327SStephan Aßmus		const LineInfo& line = fLineInfos.ItemAt(glyph.lineIndex);
388a681c327SStephan Aßmus
389a681c327SStephan Aßmus		x1 = glyph.x + glyph.width;
390a681c327SStephan Aßmus		x2 = x1;
391a681c327SStephan Aßmus		y1 = line.y;
392e3dc81ccSStephan Aßmus		y2 = y1 + line.height;
393a681c327SStephan Aßmus
394a681c327SStephan Aßmus		return;
395a681c327SStephan Aßmus	}
396a681c327SStephan Aßmus
397a681c327SStephan Aßmus	if (textOffset < 0)
398a681c327SStephan Aßmus		textOffset = 0;
39987084745SMichael Lotz
400a681c327SStephan Aßmus	const GlyphInfo& glyph = fGlyphInfos.ItemAtFast(textOffset);
401a681c327SStephan Aßmus	const LineInfo& line = fLineInfos.ItemAt(glyph.lineIndex);
402a681c327SStephan Aßmus
403a681c327SStephan Aßmus	x1 = glyph.x;
404a681c327SStephan Aßmus	x2 = x1 + glyph.width;
405a681c327SStephan Aßmus	y1 = line.y;
406e3dc81ccSStephan Aßmus	y2 = y1 + line.height;
407a681c327SStephan Aßmus}
408a681c327SStephan Aßmus
409a681c327SStephan Aßmus
410e1742948SStephan Aßmusint32
411e1742948SStephan AßmusParagraphLayout::TextOffsetAt(float x, float y, bool& rightOfCenter)
412e1742948SStephan Aßmus{
413e1742948SStephan Aßmus	_ValidateLayout();
414e1742948SStephan Aßmus
415e1742948SStephan Aßmus	rightOfCenter = false;
416e1742948SStephan Aßmus
417e1742948SStephan Aßmus	int32 lineCount = fLineInfos.CountItems();
418e1742948SStephan Aßmus	if (fGlyphInfos.CountItems() == 0 || lineCount == 0
419e1742948SStephan Aßmus		|| fLineInfos.ItemAtFast(0).y > y) {
420e1742948SStephan Aßmus		// Above first line or empty text
421e1742948SStephan Aßmus		return 0;
422e1742948SStephan Aßmus	}
423e1742948SStephan Aßmus
424e1742948SStephan Aßmus	int32 lineIndex = 0;
425e1742948SStephan Aßmus	if (floorf(fLineInfos.LastItem().y
426e1742948SStephan Aßmus			+ fLineInfos.LastItem().height + 0.5) > y) {
427e1742948SStephan Aßmus		// TODO: Optimize, can binary search line here:
428e1742948SStephan Aßmus		for (; lineIndex < lineCount; lineIndex++) {
429e1742948SStephan Aßmus			const LineInfo& line = fLineInfos.ItemAtFast(lineIndex);
430e1742948SStephan Aßmus			float lineBottom = floorf(line.y + line.height + 0.5);
431e1742948SStephan Aßmus			if (lineBottom > y)
432e1742948SStephan Aßmus				break;
433e1742948SStephan Aßmus		}
434e1742948SStephan Aßmus	} else {
435e1742948SStephan Aßmus		lineIndex = lineCount - 1;
436e1742948SStephan Aßmus	}
437e1742948SStephan Aßmus
438e1742948SStephan Aßmus	// Found line
439e1742948SStephan Aßmus	const LineInfo& line = fLineInfos.ItemAtFast(lineIndex);
440e1742948SStephan Aßmus	int32 textOffset = line.textOffset;
441e1742948SStephan Aßmus	int32 end;
442e1742948SStephan Aßmus	if (lineIndex < lineCount - 1)
443e1742948SStephan Aßmus		end = fLineInfos.ItemAtFast(lineIndex + 1).textOffset - 1;
444e1742948SStephan Aßmus	else
445e1742948SStephan Aßmus		end = fGlyphInfos.CountItems() - 1;
446e1742948SStephan Aßmus
447e1742948SStephan Aßmus	// TODO: Optimize, can binary search offset here:
448e1742948SStephan Aßmus	for (; textOffset <= end; textOffset++) {
449e1742948SStephan Aßmus		const GlyphInfo& glyph = fGlyphInfos.ItemAtFast(textOffset);
450e1742948SStephan Aßmus		float x1 = glyph.x;
451e1742948SStephan Aßmus		if (x1 > x)
452e1742948SStephan Aßmus			return textOffset;
453e1742948SStephan Aßmus
454e1742948SStephan Aßmus		// x2 is the location at the right bounding box of the glyph
455e1742948SStephan Aßmus		float x2 = x1 + glyph.width;
456e1742948SStephan Aßmus
457e1742948SStephan Aßmus		// x3 is the location of the next glyph, which may be different from
458e1742948SStephan Aßmus		// x2 in case the line is justified.
459e1742948SStephan Aßmus		float x3;
460e1742948SStephan Aßmus		if (textOffset < end - 1)
461e1742948SStephan Aßmus			x3 = fGlyphInfos.ItemAtFast(textOffset + 1).x;
462e1742948SStephan Aßmus		else
463e1742948SStephan Aßmus			x3 = x2;
464e1742948SStephan Aßmus
465e1742948SStephan Aßmus		if (x3 > x) {
466e1742948SStephan Aßmus			rightOfCenter = x > (x1 + x2) / 2.0f;
467e1742948SStephan Aßmus			return textOffset;
468e1742948SStephan Aßmus		}
469e1742948SStephan Aßmus	}
470e1742948SStephan Aßmus
471e1742948SStephan Aßmus	// Account for trailing line break at end of line, the
472e1742948SStephan Aßmus	// returned offset should be before that.
473e1742948SStephan Aßmus	rightOfCenter = fGlyphInfos.ItemAtFast(end).charCode != '\n';
474e1742948SStephan Aßmus
475e1742948SStephan Aßmus	return end;
476e1742948SStephan Aßmus}
477e1742948SStephan Aßmus
478e1742948SStephan Aßmus
47915a557e6SStephan Aßmus// #pragma mark - private
48015a557e6SStephan Aßmus
48115a557e6SStephan Aßmus
482f1a08c08SStephan Aßmusvoid
483f1a08c08SStephan AßmusParagraphLayout::_Init()
484f1a08c08SStephan Aßmus{
485f1a08c08SStephan Aßmus	fGlyphInfos.Clear();
486f1a08c08SStephan Aßmus
487f1a08c08SStephan Aßmus	int spanCount = fTextSpans.CountItems();
488f1a08c08SStephan Aßmus	for (int i = 0; i < spanCount; i++) {
489f1a08c08SStephan Aßmus		const TextSpan& span = fTextSpans.ItemAtFast(i);
490f1a08c08SStephan Aßmus		if (!_AppendGlyphInfos(span)) {
491f1a08c08SStephan Aßmus			fprintf(stderr, "%p->ParagraphLayout::_Init() - Out of memory\n",
492f1a08c08SStephan Aßmus				this);
493f1a08c08SStephan Aßmus			return;
494f1a08c08SStephan Aßmus		}
495f1a08c08SStephan Aßmus	}
496f1a08c08SStephan Aßmus}
497f1a08c08SStephan Aßmus
498f1a08c08SStephan Aßmus
49915a557e6SStephan Aßmusvoid
50015a557e6SStephan AßmusParagraphLayout::_ValidateLayout()
50115a557e6SStephan Aßmus{
5028e8d1b55SStephan Aßmus	if (!fLayoutValid) {
50315a557e6SStephan Aßmus		_Layout();
5048e8d1b55SStephan Aßmus		fLayoutValid = true;
5058e8d1b55SStephan Aßmus	}
50615a557e6SStephan Aßmus}
50715a557e6SStephan Aßmus
50815a557e6SStephan Aßmus
50915a557e6SStephan Aßmusvoid
51015a557e6SStephan AßmusParagraphLayout::_Layout()
51115a557e6SStephan Aßmus{
51215990b01SStephan Aßmus	fLineInfos.Clear();
51315990b01SStephan Aßmus
5146576e6c7SStephan Aßmus	const Bullet& bullet = fParagraphStyle.Bullet();
5156576e6c7SStephan Aßmus
5166576e6c7SStephan Aßmus	float x = fParagraphStyle.LineInset() + fParagraphStyle.FirstLineInset()
5176576e6c7SStephan Aßmus		+ bullet.Spacing();
51815990b01SStephan Aßmus	float y = 0.0f;
51915990b01SStephan Aßmus	int lineIndex = 0;
52015990b01SStephan Aßmus	int lineStart = 0;
52115990b01SStephan Aßmus
52215990b01SStephan Aßmus	int glyphCount = fGlyphInfos.CountItems();
52315990b01SStephan Aßmus	for (int i = 0; i < glyphCount; i++) {
52415990b01SStephan Aßmus		GlyphInfo glyph = fGlyphInfos.ItemAtFast(i);
5251f67148fSRene Gollent
52615990b01SStephan Aßmus		uint32 charClassification = get_char_classification(glyph.charCode);
52715990b01SStephan Aßmus
52815990b01SStephan Aßmus		float advanceX = glyph.width;
52915990b01SStephan Aßmus		float advanceY = 0.0f;
53015990b01SStephan Aßmus
53115990b01SStephan Aßmus		bool nextLine = false;
53215990b01SStephan Aßmus		bool lineBreak = false;
53315990b01SStephan Aßmus
53415990b01SStephan Aßmus//		if (glyph.charCode == '\t') {
53515a557e6SStephan Aßmus//			// Figure out tab width, it's the width between the last two tab
53615a557e6SStephan Aßmus//			// stops.
53715a557e6SStephan Aßmus//			float tabWidth = 0.0f;
53815a557e6SStephan Aßmus//			if (fTabCount > 0)
53915a557e6SStephan Aßmus//				tabWidth = fTabBuffer[fTabCount - 1];
54015a557e6SStephan Aßmus//			if (fTabCount > 1)
54115a557e6SStephan Aßmus//				tabWidth -= fTabBuffer[fTabCount - 2];
54215a557e6SStephan Aßmus//
54315a557e6SStephan Aßmus//			// Try to find a tab stop that is farther than the current x
54415a557e6SStephan Aßmus//			// offset
54515a557e6SStephan Aßmus//			double tabOffset = 0.0;
54615a557e6SStephan Aßmus//			for (unsigned tabIndex = 0; tabIndex < fTabCount; tabIndex++) {
54715a557e6SStephan Aßmus//				tabOffset = fTabBuffer[tabIndex];
54815a557e6SStephan Aßmus//				if (tabOffset > x)
54915a557e6SStephan Aßmus//					break;
55015a557e6SStephan Aßmus//			}
55115a557e6SStephan Aßmus//
55215a557e6SStephan Aßmus//			// If no tab stop has been found, make the tab stop a multiple of
55315a557e6SStephan Aßmus//			// the tab width
55415a557e6SStephan Aßmus//			if (tabOffset <= x && tabWidth > 0.0)
55515a557e6SStephan Aßmus//				tabOffset = ((int) (x / tabWidth) + 1) * tabWidth;
55615a557e6SStephan Aßmus//
55715a557e6SStephan Aßmus//			if (tabOffset - x > 0.0)
55815a557e6SStephan Aßmus//				advanceX = tabOffset - x;
55915a557e6SStephan Aßmus//		}
56015990b01SStephan Aßmus
56115990b01SStephan Aßmus		if (glyph.charCode == '\n') {
56215990b01SStephan Aßmus			nextLine = true;
56315990b01SStephan Aßmus			lineBreak = true;
56415990b01SStephan Aßmus			glyph.x = x;
56515990b01SStephan Aßmus			fGlyphInfos.Replace(i, glyph);
56615990b01SStephan Aßmus		} else if (fWidth > 0.0f && x + advanceX > fWidth) {
56715990b01SStephan Aßmus			fGlyphInfos.Replace(i, glyph);
56815990b01SStephan Aßmus			if (charClassification == CHAR_CLASS_WHITESPACE) {
56915990b01SStephan Aßmus				advanceX = 0.0f;
57015990b01SStephan Aßmus			} else if (i > lineStart) {
57115990b01SStephan Aßmus				nextLine = true;
57215990b01SStephan Aßmus				// The current glyph extends outside the width, we need to wrap
57315990b01SStephan Aßmus				// to the next line. See what previous offset can be the end
57415990b01SStephan Aßmus				// of the line.
57515990b01SStephan Aßmus				int lineEnd = i - 1;
57615990b01SStephan Aßmus				while (lineEnd > lineStart
57715990b01SStephan Aßmus					&& !can_end_line(fGlyphInfos, lineEnd)) {
57815990b01SStephan Aßmus					lineEnd--;
57915990b01SStephan Aßmus				}
58015990b01SStephan Aßmus
58115990b01SStephan Aßmus				if (lineEnd > lineStart) {
58215990b01SStephan Aßmus					// Found a place to perform a line break.
58315990b01SStephan Aßmus					i = lineEnd + 1;
58415990b01SStephan Aßmus
58515990b01SStephan Aßmus					// Adjust the glyph info to point at the changed buffer
58615990b01SStephan Aßmus					// position
58715990b01SStephan Aßmus					glyph = fGlyphInfos.ItemAtFast(i);
58815990b01SStephan Aßmus					advanceX = glyph.width;
58915990b01SStephan Aßmus				} else {
59015990b01SStephan Aßmus					// Just break where we are.
59115990b01SStephan Aßmus				}
59215990b01SStephan Aßmus			}
59315990b01SStephan Aßmus		}
59415990b01SStephan Aßmus
59515990b01SStephan Aßmus		if (nextLine) {
59615990b01SStephan Aßmus			// * Initialize the max ascent/descent of all preceding glyph infos
59715990b01SStephan Aßmus			// on the current/last line
59815990b01SStephan Aßmus			// * Adjust the baseline offset according to the max ascent
59915990b01SStephan Aßmus			// * Fill in the line index.
60015990b01SStephan Aßmus			unsigned lineEnd;
60115990b01SStephan Aßmus			if (lineBreak)
60215990b01SStephan Aßmus				lineEnd = i;
60315990b01SStephan Aßmus			else
60415990b01SStephan Aßmus				lineEnd = i - 1;
60515990b01SStephan Aßmus
60615990b01SStephan Aßmus			float lineHeight = 0.0;
60715990b01SStephan Aßmus			_FinalizeLine(lineStart, lineEnd, lineIndex, y, lineHeight);
60815990b01SStephan Aßmus
60915990b01SStephan Aßmus			// Start position of the next line
6106576e6c7SStephan Aßmus			x = fParagraphStyle.LineInset() + bullet.Spacing();
61115990b01SStephan Aßmus			y += lineHeight + fParagraphStyle.LineSpacing();
61215990b01SStephan Aßmus
61315990b01SStephan Aßmus			if (lineBreak)
61415990b01SStephan Aßmus				lineStart = i + 1;
61515990b01SStephan Aßmus			else
61615990b01SStephan Aßmus				lineStart = i;
61715990b01SStephan Aßmus
61815990b01SStephan Aßmus			lineIndex++;
61915990b01SStephan Aßmus		}
62015990b01SStephan Aßmus
62115990b01SStephan Aßmus		if (!lineBreak && i < glyphCount) {
62215990b01SStephan Aßmus			glyph.x = x;
62315990b01SStephan Aßmus			fGlyphInfos.Replace(i, glyph);
62415990b01SStephan Aßmus		}
62515990b01SStephan Aßmus
62615990b01SStephan Aßmus		x += advanceX;
62715990b01SStephan Aßmus		y += advanceY;
62815990b01SStephan Aßmus	}
62915990b01SStephan Aßmus
63015990b01SStephan Aßmus	// The last line may not have been appended and initialized yet.
63190d1cbe3SStephan Aßmus	if (lineStart <= glyphCount - 1 || glyphCount == 0) {
63215990b01SStephan Aßmus		float lineHeight;
63315990b01SStephan Aßmus		_FinalizeLine(lineStart, glyphCount - 1, lineIndex, y, lineHeight);
63415990b01SStephan Aßmus	}
635f1a08c08SStephan Aßmus
636f1a08c08SStephan Aßmus	_ApplyAlignment();
63715a557e6SStephan Aßmus}
63815a557e6SStephan Aßmus
63915a557e6SStephan Aßmus
64015a557e6SStephan Aßmusvoid
641f1a08c08SStephan AßmusParagraphLayout::_ApplyAlignment()
64215a557e6SStephan Aßmus{
643f1a08c08SStephan Aßmus	Alignment alignment = fParagraphStyle.Alignment();
644f1a08c08SStephan Aßmus	bool justify = fParagraphStyle.Justify();
64515a557e6SStephan Aßmus
646f1a08c08SStephan Aßmus	if (alignment == ALIGN_LEFT && !justify)
647f1a08c08SStephan Aßmus		return;
648f1a08c08SStephan Aßmus
649f1a08c08SStephan Aßmus	int glyphCount = fGlyphInfos.CountItems();
650f1a08c08SStephan Aßmus	if (glyphCount == 0)
651f1a08c08SStephan Aßmus		return;
652f1a08c08SStephan Aßmus
653f1a08c08SStephan Aßmus	int lineIndex = -1;
654f1a08c08SStephan Aßmus	float spaceLeft = 0.0f;
655f1a08c08SStephan Aßmus	float charSpace = 0.0f;
656f1a08c08SStephan Aßmus	float whiteSpace = 0.0f;
657f1a08c08SStephan Aßmus	bool seenChar = false;
658f1a08c08SStephan Aßmus
659f1a08c08SStephan Aßmus	// Iterate all glyphs backwards. On the last character of the next line,
660f1a08c08SStephan Aßmus	// the position of the character determines the available space to be
661f1a08c08SStephan Aßmus	// distributed (spaceLeft).
662f1a08c08SStephan Aßmus	for (int i = glyphCount - 1; i >= 0; i--) {
663f1a08c08SStephan Aßmus		GlyphInfo glyph = fGlyphInfos.ItemAtFast(i);
664f1a08c08SStephan Aßmus
665f1a08c08SStephan Aßmus		if (glyph.lineIndex != lineIndex) {
666f1a08c08SStephan Aßmus			bool lineBreak = glyph.charCode == '\n' || i == glyphCount - 1;
667f1a08c08SStephan Aßmus			lineIndex = glyph.lineIndex;
668f1a08c08SStephan Aßmus
669f1a08c08SStephan Aßmus			// The position of the last character determines the available
670f1a08c08SStephan Aßmus			// space.
671f1a08c08SStephan Aßmus			spaceLeft = fWidth - glyph.x;
672f1a08c08SStephan Aßmus
673f1a08c08SStephan Aßmus			// If the character is visible, the width of the character needs to
674f1a08c08SStephan Aßmus			// be subtracted from the available space, otherwise it would be
675f1a08c08SStephan Aßmus			// pushed outside the line.
676f1a08c08SStephan Aßmus			uint32 charClassification = get_char_classification(glyph.charCode);
677f1a08c08SStephan Aßmus			if (charClassification != CHAR_CLASS_WHITESPACE)
678f1a08c08SStephan Aßmus				spaceLeft -= glyph.width;
679f1a08c08SStephan Aßmus
680f1a08c08SStephan Aßmus			charSpace = 0.0f;
681f1a08c08SStephan Aßmus			whiteSpace = 0.0f;
682f1a08c08SStephan Aßmus			seenChar = false;
683f1a08c08SStephan Aßmus
684f1a08c08SStephan Aßmus			if (lineBreak || !justify) {
685f1a08c08SStephan Aßmus				if (alignment == ALIGN_CENTER)
686f1a08c08SStephan Aßmus					spaceLeft /= 2.0f;
687f1a08c08SStephan Aßmus				else if (alignment == ALIGN_LEFT)
688f1a08c08SStephan Aßmus					spaceLeft = 0.0f;
689f1a08c08SStephan Aßmus			} else {
690f1a08c08SStephan Aßmus				// Figure out how much chars and white space chars are on the
691f1a08c08SStephan Aßmus				// line. Don't count trailing white space.
692f1a08c08SStephan Aßmus				int charCount = 0;
693f1a08c08SStephan Aßmus				int spaceCount = 0;
694f1a08c08SStephan Aßmus				for (int j = i; j >= 0; j--) {
695f1a08c08SStephan Aßmus					const GlyphInfo& previousGlyph = fGlyphInfos.ItemAtFast(j);
696f1a08c08SStephan Aßmus					if (previousGlyph.lineIndex != lineIndex) {
697f1a08c08SStephan Aßmus						j++;
698f1a08c08SStephan Aßmus						break;
699f1a08c08SStephan Aßmus					}
700f1a08c08SStephan Aßmus					uint32 classification = get_char_classification(
701f1a08c08SStephan Aßmus						previousGlyph.charCode);
702f1a08c08SStephan Aßmus					if (classification == CHAR_CLASS_WHITESPACE) {
703f1a08c08SStephan Aßmus						if (charCount > 0)
704f1a08c08SStephan Aßmus							spaceCount++;
705f1a08c08SStephan Aßmus						else if (j < i)
706f1a08c08SStephan Aßmus							spaceLeft += glyph.width;
707f1a08c08SStephan Aßmus					} else {
708f1a08c08SStephan Aßmus						charCount++;
709f1a08c08SStephan Aßmus					}
710f1a08c08SStephan Aßmus				}
711f1a08c08SStephan Aßmus
712f1a08c08SStephan Aßmus				// The first char is not shifted when justifying, so it doesn't
713f1a08c08SStephan Aßmus				// contribute.
714f1a08c08SStephan Aßmus				if (charCount > 0)
715f1a08c08SStephan Aßmus					charCount--;
716f1a08c08SStephan Aßmus
717f1a08c08SStephan Aßmus				// Check if it looks better if both whitespace and chars get
718f1a08c08SStephan Aßmus				// some space distributed, in case there are only 1 or two
719f1a08c08SStephan Aßmus				// space chars on the line.
720f1a08c08SStephan Aßmus				float spaceLeftForSpace = spaceLeft;
721f1a08c08SStephan Aßmus				float spaceLeftForChars = spaceLeft;
722f1a08c08SStephan Aßmus
723f1a08c08SStephan Aßmus				if (spaceCount > 0) {
724f1a08c08SStephan Aßmus					float spaceCharRatio = (float) spaceCount / charCount;
725f1a08c08SStephan Aßmus					if (spaceCount < 3 && spaceCharRatio < 0.4f) {
726f1a08c08SStephan Aßmus						spaceLeftForSpace = spaceLeft * 2.0f * spaceCharRatio;
727f1a08c08SStephan Aßmus						spaceLeftForChars = spaceLeft - spaceLeftForSpace;
728f1a08c08SStephan Aßmus					} else
729f1a08c08SStephan Aßmus						spaceLeftForChars = 0.0f;
730f1a08c08SStephan Aßmus				}
731f1a08c08SStephan Aßmus
732f1a08c08SStephan Aßmus				if (spaceCount > 0)
733f1a08c08SStephan Aßmus					whiteSpace = spaceLeftForSpace / spaceCount;
734f1a08c08SStephan Aßmus				if (charCount > 0)
735f1a08c08SStephan Aßmus					charSpace = spaceLeftForChars / charCount;
7361f67148fSRene Gollent
737f1a08c08SStephan Aßmus				LineInfo line = fLineInfos.ItemAtFast(lineIndex);
738f1a08c08SStephan Aßmus				line.extraGlyphSpacing = charSpace;
739f1a08c08SStephan Aßmus				line.extraWhiteSpacing = whiteSpace;
740f1a08c08SStephan Aßmus
741f1a08c08SStephan Aßmus				fLineInfos.Replace(lineIndex, line);
742f1a08c08SStephan Aßmus			}
743f1a08c08SStephan Aßmus		}
744f1a08c08SStephan Aßmus
745f1a08c08SStephan Aßmus		// Each character is pushed towards the right by the space that is
746f1a08c08SStephan Aßmus		// still available. When justification is performed, the shift is
747f1a08c08SStephan Aßmus		// gradually decreased. This works since the iteration is backwards
748f1a08c08SStephan Aßmus		// and the characters on the right are pushed farthest.
749f1a08c08SStephan Aßmus		glyph.x += spaceLeft;
750f1a08c08SStephan Aßmus
751f1a08c08SStephan Aßmus		unsigned classification = get_char_classification(glyph.charCode);
752f1a08c08SStephan Aßmus
753f1a08c08SStephan Aßmus		if (i < glyphCount - 1) {
754f1a08c08SStephan Aßmus			GlyphInfo nextGlyph = fGlyphInfos.ItemAtFast(i + 1);
755f1a08c08SStephan Aßmus			if (nextGlyph.lineIndex == lineIndex) {
756f1a08c08SStephan Aßmus				uint32 nextClassification
757f1a08c08SStephan Aßmus					= get_char_classification(nextGlyph.charCode);
758f1a08c08SStephan Aßmus				if (nextClassification == CHAR_CLASS_WHITESPACE
759f1a08c08SStephan Aßmus					&& classification != CHAR_CLASS_WHITESPACE) {
760f1a08c08SStephan Aßmus					// When a space character is right of a regular character,
761f1a08c08SStephan Aßmus					// add the additional space to the space instead of the
762f1a08c08SStephan Aßmus					// character
763f1a08c08SStephan Aßmus					float shift = (nextGlyph.x - glyph.x) - glyph.width;
764f1a08c08SStephan Aßmus					nextGlyph.x -= shift;
765f1a08c08SStephan Aßmus					fGlyphInfos.Replace(i + 1, nextGlyph);
766f1a08c08SStephan Aßmus				}
767f1a08c08SStephan Aßmus			}
768f1a08c08SStephan Aßmus		}
769f1a08c08SStephan Aßmus
770f1a08c08SStephan Aßmus		fGlyphInfos.Replace(i, glyph);
771f1a08c08SStephan Aßmus
772f1a08c08SStephan Aßmus		// The shift (spaceLeft) is reduced depending on the character
773f1a08c08SStephan Aßmus		// classification.
774f1a08c08SStephan Aßmus		if (classification == CHAR_CLASS_WHITESPACE) {
775f1a08c08SStephan Aßmus			if (seenChar)
776f1a08c08SStephan Aßmus				spaceLeft -= whiteSpace;
777f1a08c08SStephan Aßmus		} else {
778f1a08c08SStephan Aßmus			seenChar = true;
779f1a08c08SStephan Aßmus			spaceLeft -= charSpace;
78015a557e6SStephan Aßmus		}
78115a557e6SStephan Aßmus	}
78215a557e6SStephan Aßmus}
78315a557e6SStephan Aßmus
78415a557e6SStephan Aßmus
78515a557e6SStephan Aßmusbool
78615a557e6SStephan AßmusParagraphLayout::_AppendGlyphInfos(const TextSpan& span)
78715a557e6SStephan Aßmus{
788300c690eSStephan Aßmus	int charCount = span.CountChars();
78915a557e6SStephan Aßmus	if (charCount == 0)
79015a557e6SStephan Aßmus		return true;
7911f67148fSRene Gollent
79215a557e6SStephan Aßmus	const BString& text = span.Text();
79315990b01SStephan Aßmus	const BFont& font = span.Style().Font();
79415a557e6SStephan Aßmus
79515a557e6SStephan Aßmus	// Allocate arrays
79615a557e6SStephan Aßmus	float* escapementArray = new (std::nothrow) float[charCount];
79715a557e6SStephan Aßmus	if (escapementArray == NULL)
79815a557e6SStephan Aßmus		return false;
79915a557e6SStephan Aßmus	ArrayDeleter<float> escapementDeleter(escapementArray);
80015a557e6SStephan Aßmus
80115a557e6SStephan Aßmus	// Fetch glyph spacing information
80215a557e6SStephan Aßmus	font.GetEscapements(text, charCount, escapementArray);
80315a557e6SStephan Aßmus
80415a557e6SStephan Aßmus	// Append to glyph buffer and convert escapement scale
80515a557e6SStephan Aßmus	float size = font.Size();
80615a557e6SStephan Aßmus	const char* c = text.String();
80715a557e6SStephan Aßmus	for (int i = 0; i < charCount; i++) {
80815990b01SStephan Aßmus		if (!_AppendGlyphInfo(UTF8ToCharCode(&c), escapementArray[i] * size,
80915990b01SStephan Aßmus				span.Style())) {
81015a557e6SStephan Aßmus			return false;
81115990b01SStephan Aßmus		}
81215a557e6SStephan Aßmus	}
81315a557e6SStephan Aßmus
81415a557e6SStephan Aßmus	return true;
81515a557e6SStephan Aßmus}
81615a557e6SStephan Aßmus
81715a557e6SStephan Aßmus
81815990b01SStephan Aßmusbool
81915990b01SStephan AßmusParagraphLayout::_AppendGlyphInfo(uint32 charCode, float width,
82015990b01SStephan Aßmus	const CharacterStyle& style)
82115a557e6SStephan Aßmus{
82215990b01SStephan Aßmus	if (style.Width() >= 0.0f) {
82315990b01SStephan Aßmus		// Use the metrics provided by the CharacterStyle and override
82415990b01SStephan Aßmus		// the font provided metrics passed in "width"
82515990b01SStephan Aßmus		width = style.Width();
82615a557e6SStephan Aßmus	}
82715990b01SStephan Aßmus
82815990b01SStephan Aßmus	width += style.GlyphSpacing();
8291f67148fSRene Gollent
830169de499SStephan Aßmus	return fGlyphInfos.Add(GlyphInfo(charCode, 0.0f, width, 0));
83115a557e6SStephan Aßmus}
83215a557e6SStephan Aßmus
83315a557e6SStephan Aßmus
83424523b86SStephan Aßmusbool
83515990b01SStephan AßmusParagraphLayout::_FinalizeLine(int lineStart, int lineEnd, int lineIndex,
83615990b01SStephan Aßmus	float y, float& lineHeight)
83715a557e6SStephan Aßmus{
83824523b86SStephan Aßmus	LineInfo line(lineStart, y, 0.0f, 0.0f, 0.0f);
83924523b86SStephan Aßmus
84024523b86SStephan Aßmus	int spanIndex = -1;
84124523b86SStephan Aßmus	int spanStart = 0;
84224523b86SStephan Aßmus	int spanEnd = 0;
84315a557e6SStephan Aßmus
84415a557e6SStephan Aßmus	for (int i = lineStart; i <= lineEnd; i++) {
84524523b86SStephan Aßmus		// Mark line index in glyph
84615990b01SStephan Aßmus		GlyphInfo glyph = fGlyphInfos.ItemAtFast(i);
84715990b01SStephan Aßmus		glyph.lineIndex = lineIndex;
84815990b01SStephan Aßmus		fGlyphInfos.Replace(i, glyph);
84915990b01SStephan Aßmus
85024523b86SStephan Aßmus		// See if the next sub-span needs to be added to the LineInfo
85124523b86SStephan Aßmus		bool addSpan = false;
85215990b01SStephan Aßmus
85324523b86SStephan Aßmus		while (i >= spanEnd) {
85424523b86SStephan Aßmus			spanIndex++;
85524523b86SStephan Aßmus			const TextSpan& span = fTextSpans.ItemAt(spanIndex);
85624523b86SStephan Aßmus			spanStart = spanEnd;
857300c690eSStephan Aßmus			spanEnd += span.CountChars();
85824523b86SStephan Aßmus			addSpan = true;
85924523b86SStephan Aßmus		}
86024523b86SStephan Aßmus
86124523b86SStephan Aßmus		if (addSpan) {
86224523b86SStephan Aßmus			const TextSpan& span = fTextSpans.ItemAt(spanIndex);
86324523b86SStephan Aßmus			TextSpan subSpan = span.SubSpan(i - spanStart,
8646576e6c7SStephan Aßmus				(lineEnd - spanStart + 1) - (i - spanStart));
86524523b86SStephan Aßmus			line.layoutedSpans.Add(subSpan);
86624523b86SStephan Aßmus			_IncludeStyleInLine(line, span.Style());
86724523b86SStephan Aßmus		}
86815a557e6SStephan Aßmus	}
86915a557e6SStephan Aßmus
87090d1cbe3SStephan Aßmus	if (fGlyphInfos.CountItems() == 0 && fTextSpans.CountItems() > 0) {
87190d1cbe3SStephan Aßmus		// When the layout contains no glyphs, but there is at least one
87290d1cbe3SStephan Aßmus		// TextSpan in the paragraph, use the font info from that span
87390d1cbe3SStephan Aßmus		// to calculate the height of the first LineInfo.
87490d1cbe3SStephan Aßmus		const TextSpan& span = fTextSpans.ItemAtFast(0);
87590d1cbe3SStephan Aßmus		line.layoutedSpans.Add(span);
87690d1cbe3SStephan Aßmus		_IncludeStyleInLine(line, span.Style());
87790d1cbe3SStephan Aßmus	}
87890d1cbe3SStephan Aßmus
87924523b86SStephan Aßmus	lineHeight = line.height;
88024523b86SStephan Aßmus
88124523b86SStephan Aßmus	return fLineInfos.Add(line);
88224523b86SStephan Aßmus}
88324523b86SStephan Aßmus
88424523b86SStephan Aßmus
88524523b86SStephan Aßmusvoid
88624523b86SStephan AßmusParagraphLayout::_IncludeStyleInLine(LineInfo& line,
88724523b86SStephan Aßmus	const CharacterStyle& style)
88824523b86SStephan Aßmus{
88924523b86SStephan Aßmus	float ascent = style.Ascent();
89024523b86SStephan Aßmus	if (ascent > line.maxAscent)
89124523b86SStephan Aßmus		line.maxAscent = ascent;
89224523b86SStephan Aßmus
89324523b86SStephan Aßmus	float descent = style.Descent();
89424523b86SStephan Aßmus	if (descent > line.maxDescent)
89524523b86SStephan Aßmus		line.maxDescent = descent;
8961f67148fSRene Gollent
89724523b86SStephan Aßmus	float height = ascent + descent;
89824523b86SStephan Aßmus	if (style.Font().Size() > height)
89924523b86SStephan Aßmus		height = style.Font().Size();
90024523b86SStephan Aßmus
90124523b86SStephan Aßmus	if (height > line.height)
90224523b86SStephan Aßmus		line.height = height;
90315a557e6SStephan Aßmus}
90415a557e6SStephan Aßmus
90515a557e6SStephan Aßmus
90615a557e6SStephan Aßmusvoid
9078e8d1b55SStephan AßmusParagraphLayout::_DrawLine(BView* view, const BPoint& offset,
9088e8d1b55SStephan Aßmus	const LineInfo& line) const
90915a557e6SStephan Aßmus{
91015a557e6SStephan Aßmus	int textOffset = line.textOffset;
91115a557e6SStephan Aßmus	int spanCount = line.layoutedSpans.CountItems();
91215990b01SStephan Aßmus	for (int i = 0; i < spanCount; i++) {
91315a557e6SStephan Aßmus		const TextSpan& span = line.layoutedSpans.ItemAtFast(i);
9148e8d1b55SStephan Aßmus		_DrawSpan(view, offset, span, textOffset);
915300c690eSStephan Aßmus		textOffset += span.CountChars();
91615a557e6SStephan Aßmus	}
91715a557e6SStephan Aßmus}
91815a557e6SStephan Aßmus
91915a557e6SStephan Aßmus
92015a557e6SStephan Aßmusvoid
9218e8d1b55SStephan AßmusParagraphLayout::_DrawSpan(BView* view, BPoint offset,
9228e8d1b55SStephan Aßmus	const TextSpan& span, int32 textOffset) const
92315a557e6SStephan Aßmus{
924720f7fdfSStephan Aßmus	const BString& text = span.Text();
925720f7fdfSStephan Aßmus	if (text.Length() == 0)
926720f7fdfSStephan Aßmus		return;
927720f7fdfSStephan Aßmus
92815990b01SStephan Aßmus	const GlyphInfo& glyph = fGlyphInfos.ItemAtFast(textOffset);
92915990b01SStephan Aßmus	const LineInfo& line = fLineInfos.ItemAtFast(glyph.lineIndex);
9301f67148fSRene Gollent
9318e8d1b55SStephan Aßmus	offset.x += glyph.x;
9328e8d1b55SStephan Aßmus	offset.y += line.y + line.maxAscent;
93315a557e6SStephan Aßmus
93415990b01SStephan Aßmus	const CharacterStyle& style = span.Style();
93515a557e6SStephan Aßmus
93615990b01SStephan Aßmus	view->SetFont(&style.Font());
937fa19dd44Slooncraz
938fa19dd44Slooncraz	if (style.WhichForegroundColor() != B_NO_COLOR)
939fa19dd44Slooncraz		view->SetHighUIColor(style.WhichForegroundColor());
940fa19dd44Slooncraz	else
941fa19dd44Slooncraz		view->SetHighColor(style.ForegroundColor());
94215a557e6SStephan Aßmus
94315a557e6SStephan Aßmus	// TODO: Implement other style properties
94415a557e6SStephan Aßmus
945f1a08c08SStephan Aßmus	escapement_delta delta;
946f1a08c08SStephan Aßmus	delta.nonspace = line.extraGlyphSpacing;
947f1a08c08SStephan Aßmus	delta.space = line.extraWhiteSpacing;
948f1a08c08SStephan Aßmus
9498e8d1b55SStephan Aßmus	view->DrawString(span.Text(), offset, &delta);
95015a557e6SStephan Aßmus}
95141889b59SStephan Aßmus
95241889b59SStephan Aßmus
95341889b59SStephan Aßmusvoid
95441889b59SStephan AßmusParagraphLayout::_GetEmptyLayoutBounds(float& x1, float& y1, float& x2,
95541889b59SStephan Aßmus	float& y2) const
95641889b59SStephan Aßmus{
95741889b59SStephan Aßmus	if (fLineInfos.CountItems() == 0) {
95841889b59SStephan Aßmus		x1 = 0.0f;
95941889b59SStephan Aßmus		y1 = 0.0f;
96041889b59SStephan Aßmus		x2 = 0.0f;
96141889b59SStephan Aßmus		y2 = 0.0f;
96241889b59SStephan Aßmus
96341889b59SStephan Aßmus		return;
96441889b59SStephan Aßmus	}
96541889b59SStephan Aßmus
96641889b59SStephan Aßmus	// If the paragraph had at least a single empty TextSpan, the layout
96741889b59SStephan Aßmus	// can compute some meaningful bounds.
96841889b59SStephan Aßmus	const Bullet& bullet = fParagraphStyle.Bullet();
96941889b59SStephan Aßmus	x1 = fParagraphStyle.LineInset() + fParagraphStyle.FirstLineInset()
97041889b59SStephan Aßmus		+ bullet.Spacing();
97141889b59SStephan Aßmus	x2 = x1;
97241889b59SStephan Aßmus	const LineInfo& lineInfo = fLineInfos.ItemAt(0);
97341889b59SStephan Aßmus	y1 = lineInfo.y;
97441889b59SStephan Aßmus	y2 = lineInfo.y + lineInfo.height;
97541889b59SStephan Aßmus}
976