xref: /haiku/src/apps/mediaplayer/interface/SubtitleBitmap.cpp (revision 01e0d3278a2849546efa72c14eacd70854e0bc2e)
125fb0e67SStephan Aßmus /*
225fb0e67SStephan Aßmus  * Copyright 2010, Stephan Aßmus <superstippi@gmx.de>.
325fb0e67SStephan Aßmus  * Distributed under the terms of the MIT License.
425fb0e67SStephan Aßmus  */
525fb0e67SStephan Aßmus 
625fb0e67SStephan Aßmus 
725fb0e67SStephan Aßmus #include "SubtitleBitmap.h"
825fb0e67SStephan Aßmus 
925fb0e67SStephan Aßmus #include <stdio.h>
1025fb0e67SStephan Aßmus 
1125fb0e67SStephan Aßmus #include <Bitmap.h>
1225fb0e67SStephan Aßmus #include <TextView.h>
1325fb0e67SStephan Aßmus 
14c8ccdf52SStephan Aßmus #include "StackBlurFilter.h"
15c8ccdf52SStephan Aßmus 
1625fb0e67SStephan Aßmus 
1725fb0e67SStephan Aßmus SubtitleBitmap::SubtitleBitmap()
1825fb0e67SStephan Aßmus 	:
1925fb0e67SStephan Aßmus 	fBitmap(NULL),
2025fb0e67SStephan Aßmus 	fTextView(new BTextView("offscreen text")),
21*01e0d327SStephan Aßmus 	fShadowTextView(new BTextView("offscreen text shadow")),
22*01e0d327SStephan Aßmus 	fUseSoftShadow(true),
23*01e0d327SStephan Aßmus 	fOverlayMode(false)
2425fb0e67SStephan Aßmus {
2525fb0e67SStephan Aßmus 	fTextView->SetStylable(true);
2625fb0e67SStephan Aßmus 	fTextView->MakeEditable(false);
2725fb0e67SStephan Aßmus 	fTextView->SetWordWrap(false);
2825fb0e67SStephan Aßmus 	fTextView->SetAlignment(B_ALIGN_CENTER);
2925fb0e67SStephan Aßmus 
3025fb0e67SStephan Aßmus 	fShadowTextView->SetStylable(true);
3125fb0e67SStephan Aßmus 	fShadowTextView->MakeEditable(false);
3225fb0e67SStephan Aßmus 	fShadowTextView->SetWordWrap(false);
3325fb0e67SStephan Aßmus 	fShadowTextView->SetAlignment(B_ALIGN_CENTER);
3425fb0e67SStephan Aßmus }
3525fb0e67SStephan Aßmus 
3625fb0e67SStephan Aßmus 
3725fb0e67SStephan Aßmus SubtitleBitmap::~SubtitleBitmap()
3825fb0e67SStephan Aßmus {
3925fb0e67SStephan Aßmus 	delete fBitmap;
4025fb0e67SStephan Aßmus 	delete fTextView;
4125fb0e67SStephan Aßmus 	delete fShadowTextView;
4225fb0e67SStephan Aßmus }
4325fb0e67SStephan Aßmus 
4425fb0e67SStephan Aßmus 
4525fb0e67SStephan Aßmus void
4625fb0e67SStephan Aßmus SubtitleBitmap::SetText(const char* text)
4725fb0e67SStephan Aßmus {
4825fb0e67SStephan Aßmus 	if (text == fText)
4925fb0e67SStephan Aßmus 		return;
5025fb0e67SStephan Aßmus 
5125fb0e67SStephan Aßmus 	fText = text;
5225fb0e67SStephan Aßmus 
5325fb0e67SStephan Aßmus 	_GenerateBitmap();
5425fb0e67SStephan Aßmus }
5525fb0e67SStephan Aßmus 
5625fb0e67SStephan Aßmus 
5725fb0e67SStephan Aßmus void
5825fb0e67SStephan Aßmus SubtitleBitmap::SetVideoBounds(BRect bounds)
5925fb0e67SStephan Aßmus {
6025fb0e67SStephan Aßmus 	if (bounds == fVideoBounds)
6125fb0e67SStephan Aßmus 		return;
6225fb0e67SStephan Aßmus 
6325fb0e67SStephan Aßmus 	fVideoBounds = bounds;
6425fb0e67SStephan Aßmus 
65*01e0d327SStephan Aßmus 	fUseSoftShadow = true;
66*01e0d327SStephan Aßmus 	_GenerateBitmap();
67*01e0d327SStephan Aßmus }
68*01e0d327SStephan Aßmus 
69*01e0d327SStephan Aßmus 
70*01e0d327SStephan Aßmus void
71*01e0d327SStephan Aßmus SubtitleBitmap::SetOverlayMode(bool overlayMode)
72*01e0d327SStephan Aßmus {
73*01e0d327SStephan Aßmus 	if (overlayMode == fOverlayMode)
74*01e0d327SStephan Aßmus 		return;
75*01e0d327SStephan Aßmus 
76*01e0d327SStephan Aßmus 	fOverlayMode = overlayMode;
77*01e0d327SStephan Aßmus 
7825fb0e67SStephan Aßmus 	_GenerateBitmap();
7925fb0e67SStephan Aßmus }
8025fb0e67SStephan Aßmus 
8125fb0e67SStephan Aßmus 
8225fb0e67SStephan Aßmus const BBitmap*
8325fb0e67SStephan Aßmus SubtitleBitmap::Bitmap() const
8425fb0e67SStephan Aßmus {
8525fb0e67SStephan Aßmus 	return fBitmap;
8625fb0e67SStephan Aßmus }
8725fb0e67SStephan Aßmus 
8825fb0e67SStephan Aßmus 
8925fb0e67SStephan Aßmus void
9025fb0e67SStephan Aßmus SubtitleBitmap::_GenerateBitmap()
9125fb0e67SStephan Aßmus {
9225fb0e67SStephan Aßmus 	if (!fVideoBounds.IsValid())
9325fb0e67SStephan Aßmus 		return;
9425fb0e67SStephan Aßmus 
9525fb0e67SStephan Aßmus 	delete fBitmap;
9625fb0e67SStephan Aßmus 
97c8ccdf52SStephan Aßmus 	BRect bounds;
98c8ccdf52SStephan Aßmus 	float outlineRadius;
99*01e0d327SStephan Aßmus 	_InsertText(bounds, outlineRadius, fOverlayMode);
100*01e0d327SStephan Aßmus 
101*01e0d327SStephan Aßmus 	bigtime_t startTime = 0;
102*01e0d327SStephan Aßmus 	if (!fOverlayMode && fUseSoftShadow)
103*01e0d327SStephan Aßmus 		startTime = system_time();
10425fb0e67SStephan Aßmus 
10525fb0e67SStephan Aßmus 	fBitmap = new BBitmap(bounds, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32);
10625fb0e67SStephan Aßmus 	memset(fBitmap->Bits(), 0, fBitmap->BitsLength());
10725fb0e67SStephan Aßmus 
10825fb0e67SStephan Aßmus 	if (fBitmap->Lock()) {
10925fb0e67SStephan Aßmus 		fBitmap->AddChild(fShadowTextView);
11025fb0e67SStephan Aßmus 		fShadowTextView->ResizeTo(bounds.Width(), bounds.Height());
11125fb0e67SStephan Aßmus 
11225fb0e67SStephan Aßmus 		fShadowTextView->SetViewColor(0, 0, 0, 0);
11325fb0e67SStephan Aßmus 		fShadowTextView->SetDrawingMode(B_OP_ALPHA);
11425fb0e67SStephan Aßmus 		fShadowTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
11525fb0e67SStephan Aßmus 
11625fb0e67SStephan Aßmus 		fShadowTextView->PushState();
11725fb0e67SStephan Aßmus 		fShadowTextView->Draw(bounds);
11825fb0e67SStephan Aßmus 		fShadowTextView->PopState();
11925fb0e67SStephan Aßmus 
120*01e0d327SStephan Aßmus 		if (!fOverlayMode && fUseSoftShadow) {
12125fb0e67SStephan Aßmus 			fShadowTextView->Sync();
122*01e0d327SStephan Aßmus 			StackBlurFilter filter;
123c8ccdf52SStephan Aßmus 			filter.Filter(fBitmap, outlineRadius * 2);
124*01e0d327SStephan Aßmus 		}
125*01e0d327SStephan Aßmus 
126*01e0d327SStephan Aßmus 		fShadowTextView->RemoveSelf();
127c8ccdf52SStephan Aßmus 
12825fb0e67SStephan Aßmus 		fBitmap->AddChild(fTextView);
12925fb0e67SStephan Aßmus 		fTextView->ResizeTo(bounds.Width(), bounds.Height());
130*01e0d327SStephan Aßmus 		if (!fOverlayMode && fUseSoftShadow)
131c8ccdf52SStephan Aßmus 			fTextView->MoveTo(-outlineRadius / 2, -outlineRadius / 2);
132*01e0d327SStephan Aßmus 		else
133*01e0d327SStephan Aßmus 			fTextView->MoveTo(0, 0);
13425fb0e67SStephan Aßmus 
13525fb0e67SStephan Aßmus 		fTextView->SetViewColor(0, 0, 0, 0);
13625fb0e67SStephan Aßmus 		fTextView->SetDrawingMode(B_OP_ALPHA);
13725fb0e67SStephan Aßmus 		fTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
13825fb0e67SStephan Aßmus 
13925fb0e67SStephan Aßmus 		fTextView->PushState();
14025fb0e67SStephan Aßmus 		fTextView->Draw(bounds);
14125fb0e67SStephan Aßmus 		fTextView->PopState();
14225fb0e67SStephan Aßmus 
14325fb0e67SStephan Aßmus 		fTextView->Sync();
14425fb0e67SStephan Aßmus 		fTextView->RemoveSelf();
14525fb0e67SStephan Aßmus 
14625fb0e67SStephan Aßmus 		fBitmap->Unlock();
14725fb0e67SStephan Aßmus 	}
148*01e0d327SStephan Aßmus 
149*01e0d327SStephan Aßmus 	if (!fOverlayMode && fUseSoftShadow && system_time() - startTime > 10000)
150*01e0d327SStephan Aßmus 		fUseSoftShadow = false;
15125fb0e67SStephan Aßmus }
15225fb0e67SStephan Aßmus 
15325fb0e67SStephan Aßmus 
15425fb0e67SStephan Aßmus struct ParseState {
1554ad39ed7SStephan Aßmus 	ParseState(rgb_color color)
15625fb0e67SStephan Aßmus 		:
1574ad39ed7SStephan Aßmus 		color(color),
15825fb0e67SStephan Aßmus 		bold(false),
15925fb0e67SStephan Aßmus 		italic(false),
16025fb0e67SStephan Aßmus 		underlined(false),
16125fb0e67SStephan Aßmus 
16225fb0e67SStephan Aßmus 		previous(NULL)
16325fb0e67SStephan Aßmus 	{
16425fb0e67SStephan Aßmus 	}
16525fb0e67SStephan Aßmus 
16625fb0e67SStephan Aßmus 	ParseState(ParseState* previous)
16725fb0e67SStephan Aßmus 		:
16825fb0e67SStephan Aßmus 		color(previous->color),
16925fb0e67SStephan Aßmus 		bold(previous->bold),
17025fb0e67SStephan Aßmus 		italic(previous->italic),
17125fb0e67SStephan Aßmus 		underlined(previous->underlined),
17225fb0e67SStephan Aßmus 
17325fb0e67SStephan Aßmus 		previous(previous)
17425fb0e67SStephan Aßmus 	{
17525fb0e67SStephan Aßmus 	}
1764ad39ed7SStephan Aßmus 
17725fb0e67SStephan Aßmus 	rgb_color	color;
17825fb0e67SStephan Aßmus 	bool		bold;
17925fb0e67SStephan Aßmus 	bool		italic;
18025fb0e67SStephan Aßmus 	bool		underlined;
18125fb0e67SStephan Aßmus 
18225fb0e67SStephan Aßmus 	ParseState*	previous;
18325fb0e67SStephan Aßmus };
18425fb0e67SStephan Aßmus 
18525fb0e67SStephan Aßmus 
18625fb0e67SStephan Aßmus static bool
18725fb0e67SStephan Aßmus find_next_tag(const BString& string, int32& tagPos, int32& tagLength,
18825fb0e67SStephan Aßmus 	ParseState*& state)
18925fb0e67SStephan Aßmus {
19025fb0e67SStephan Aßmus 	static const char* kTags[] = {
19125fb0e67SStephan Aßmus 		"<b>", "</b>",
19225fb0e67SStephan Aßmus 		"<i>", "</i>",
19325fb0e67SStephan Aßmus 		"<u>", "</u>",
19425fb0e67SStephan Aßmus 		"<font color=\"#", "</font>"
19525fb0e67SStephan Aßmus 	};
19625fb0e67SStephan Aßmus 	static const int32 kTagCount = sizeof(kTags) / sizeof(const char*);
19725fb0e67SStephan Aßmus 
1984ad39ed7SStephan Aßmus 	int32 startPos = tagPos;
19925fb0e67SStephan Aßmus 	tagPos = string.Length();
20025fb0e67SStephan Aßmus 	tagLength = 0;
20125fb0e67SStephan Aßmus 
202ed74106bSStephan Aßmus 	// Find the next tag closest from the current position
203ed74106bSStephan Aßmus 	// This way of doing it allows broken input with overlapping tags even.
20425fb0e67SStephan Aßmus 	BString tag;
20525fb0e67SStephan Aßmus 	for (int32 i = 0; i < kTagCount; i++) {
2064ad39ed7SStephan Aßmus 		int32 nextTag = string.IFindFirst(kTags[i], startPos);
2074ad39ed7SStephan Aßmus 		if (nextTag >= startPos && nextTag < tagPos) {
20825fb0e67SStephan Aßmus 			tagPos = nextTag;
20925fb0e67SStephan Aßmus 			tag = kTags[i];
21025fb0e67SStephan Aßmus 		}
21125fb0e67SStephan Aßmus 	}
21225fb0e67SStephan Aßmus 
2134ad39ed7SStephan Aßmus 	if (tag.Length() == 0)
2144ad39ed7SStephan Aßmus 		return false;
2154ad39ed7SStephan Aßmus 
216ed74106bSStephan Aßmus 	// Tag found, ParseState will change.
21725fb0e67SStephan Aßmus 	tagLength = tag.Length();
21825fb0e67SStephan Aßmus 	if (tag == "<b>") {
21925fb0e67SStephan Aßmus 		state = new ParseState(state);
22025fb0e67SStephan Aßmus 		state->bold = true;
22125fb0e67SStephan Aßmus 	} else if (tag == "<i>") {
22225fb0e67SStephan Aßmus 		state = new ParseState(state);
22325fb0e67SStephan Aßmus 		state->italic = true;
22425fb0e67SStephan Aßmus 	} else if (tag == "<u>") {
22525fb0e67SStephan Aßmus 		state = new ParseState(state);
22625fb0e67SStephan Aßmus 		state->underlined = true;
22725fb0e67SStephan Aßmus 	} else if (tag == "<font color=\"#") {
22825fb0e67SStephan Aßmus 		state = new ParseState(state);
22925fb0e67SStephan Aßmus 		char number[16];
23025fb0e67SStephan Aßmus 		snprintf(number, sizeof(number), "0x%.6s",
23125fb0e67SStephan Aßmus 			string.String() + tagPos + tag.Length());
23225fb0e67SStephan Aßmus 		int colorInt;
23325fb0e67SStephan Aßmus 		if (sscanf(number, "%x", &colorInt) == 1) {
23425fb0e67SStephan Aßmus 			state->color.red = (colorInt & 0xff0000) >> 16;
23525fb0e67SStephan Aßmus 			state->color.green = (colorInt & 0x00ff00) >> 8;
23625fb0e67SStephan Aßmus 			state->color.blue = (colorInt & 0x0000ff);
2374ad39ed7SStephan Aßmus 			// skip 'RRGGBB">' part, too
23825fb0e67SStephan Aßmus 			tagLength += 8;
23925fb0e67SStephan Aßmus 		}
24025fb0e67SStephan Aßmus 	} else if (tag == "</b>" || tag == "</i>" || tag == "</u>"
24125fb0e67SStephan Aßmus 		|| tag == "</font>") {
242ed74106bSStephan Aßmus 		// Closing tag, pop state
24325fb0e67SStephan Aßmus 		if (state->previous != NULL) {
24425fb0e67SStephan Aßmus 			ParseState* oldState = state;
24525fb0e67SStephan Aßmus 			state = state->previous;
24625fb0e67SStephan Aßmus 			delete oldState;
24725fb0e67SStephan Aßmus 		}
24825fb0e67SStephan Aßmus 	}
24925fb0e67SStephan Aßmus 	return true;
25025fb0e67SStephan Aßmus }
25125fb0e67SStephan Aßmus 
25225fb0e67SStephan Aßmus 
25325fb0e67SStephan Aßmus static void
25425fb0e67SStephan Aßmus apply_state(BTextView* textView, const ParseState* state, BFont font,
25525fb0e67SStephan Aßmus 	bool changeColor)
25625fb0e67SStephan Aßmus {
25725fb0e67SStephan Aßmus 	uint16 face = 0;
25825fb0e67SStephan Aßmus 	if (state->bold || state->italic || state->underlined) {
25925fb0e67SStephan Aßmus 		if (state->bold)
26025fb0e67SStephan Aßmus 			face |= B_BOLD_FACE;
26125fb0e67SStephan Aßmus 		if (state->italic)
26225fb0e67SStephan Aßmus 			face |= B_ITALIC_FACE;
2634ad39ed7SStephan Aßmus 		// NOTE: This is probably not supported by the app_server (perhaps
2644ad39ed7SStephan Aßmus 		// it is if the font contains a specific underline face).
26525fb0e67SStephan Aßmus 		if (state->underlined)
26625fb0e67SStephan Aßmus 			face |= B_UNDERSCORE_FACE;
26725fb0e67SStephan Aßmus 	} else
26825fb0e67SStephan Aßmus 		face = B_REGULAR_FACE;
26925fb0e67SStephan Aßmus 	font.SetFace(face);
27025fb0e67SStephan Aßmus 	if (changeColor)
27125fb0e67SStephan Aßmus 		textView->SetFontAndColor(&font, B_FONT_ALL, &state->color);
27225fb0e67SStephan Aßmus 	else
27325fb0e67SStephan Aßmus 		textView->SetFontAndColor(&font, B_FONT_ALL, NULL);
27425fb0e67SStephan Aßmus }
27525fb0e67SStephan Aßmus 
27625fb0e67SStephan Aßmus 
27725fb0e67SStephan Aßmus static void
27825fb0e67SStephan Aßmus parse_text(const BString& string, BTextView* textView, const BFont& font,
2794ad39ed7SStephan Aßmus 	const rgb_color& color, bool changeColor)
28025fb0e67SStephan Aßmus {
2814ad39ed7SStephan Aßmus 	ParseState rootState(color);
2824ad39ed7SStephan Aßmus 		// Colors may change, but alpha channel will be preserved
2834ad39ed7SStephan Aßmus 
28425fb0e67SStephan Aßmus 	ParseState* state = &rootState;
28525fb0e67SStephan Aßmus 
28625fb0e67SStephan Aßmus 	int32 pos = 0;
28725fb0e67SStephan Aßmus 	while (pos < string.Length()) {
28825fb0e67SStephan Aßmus 		int32 nextPos = pos;
28925fb0e67SStephan Aßmus 		int32 tagLength;
29025fb0e67SStephan Aßmus 		bool stateChanged = find_next_tag(string, nextPos, tagLength, state);
29125fb0e67SStephan Aßmus 		if (nextPos > pos) {
292ed74106bSStephan Aßmus 			// Insert text between last and next tags
29325fb0e67SStephan Aßmus 			BString subString;
29425fb0e67SStephan Aßmus 			string.CopyInto(subString, pos, nextPos - pos);
29525fb0e67SStephan Aßmus 			textView->Insert(subString.String());
29625fb0e67SStephan Aßmus 		}
29725fb0e67SStephan Aßmus 		pos = nextPos + tagLength;
29825fb0e67SStephan Aßmus 		if (stateChanged)
29925fb0e67SStephan Aßmus 			apply_state(textView, state, font, changeColor);
30025fb0e67SStephan Aßmus 	}
301ed74106bSStephan Aßmus 
302ed74106bSStephan Aßmus 	// Cleanup states in case the input text had non-matching tags.
303ed74106bSStephan Aßmus 	while (state->previous != NULL) {
304ed74106bSStephan Aßmus 		ParseState* oldState = state->previous;
305ed74106bSStephan Aßmus 		state = state->previous;
306ed74106bSStephan Aßmus 		delete oldState;
307ed74106bSStephan Aßmus 	}
30825fb0e67SStephan Aßmus }
30925fb0e67SStephan Aßmus 
31025fb0e67SStephan Aßmus 
311c8ccdf52SStephan Aßmus void
312*01e0d327SStephan Aßmus SubtitleBitmap::_InsertText(BRect& textRect, float& outlineRadius,
313*01e0d327SStephan Aßmus 	bool overlayMode)
31425fb0e67SStephan Aßmus {
31525fb0e67SStephan Aßmus 	BFont font(be_plain_font);
316c8ccdf52SStephan Aßmus 	float fontSize = ceilf((fVideoBounds.Width() * 0.9) / 36);
317c8ccdf52SStephan Aßmus 	outlineRadius = ceilf(fontSize / 28.0);
31825fb0e67SStephan Aßmus 	font.SetSize(fontSize);
31925fb0e67SStephan Aßmus 
32025fb0e67SStephan Aßmus 	rgb_color shadow;
32125fb0e67SStephan Aßmus 	shadow.red = 0;
32225fb0e67SStephan Aßmus 	shadow.green = 0;
32325fb0e67SStephan Aßmus 	shadow.blue = 0;
32425fb0e67SStephan Aßmus 	shadow.alpha = 200;
32525fb0e67SStephan Aßmus 
32625fb0e67SStephan Aßmus 	rgb_color color;
32725fb0e67SStephan Aßmus 	color.red = 255;
32825fb0e67SStephan Aßmus 	color.green = 255;
32925fb0e67SStephan Aßmus 	color.blue = 255;
33025fb0e67SStephan Aßmus 	color.alpha = 240;
33125fb0e67SStephan Aßmus 
332c8ccdf52SStephan Aßmus 	textRect = fVideoBounds;
333c8ccdf52SStephan Aßmus 	textRect.OffsetBy(outlineRadius, outlineRadius);
33425fb0e67SStephan Aßmus 
33525fb0e67SStephan Aßmus 	fTextView->SetText(NULL);
33625fb0e67SStephan Aßmus 	fTextView->SetFontAndColor(&font, B_FONT_ALL, &color);
33725fb0e67SStephan Aßmus 
33825fb0e67SStephan Aßmus 	fTextView->Insert(" ");
3394ad39ed7SStephan Aßmus 	parse_text(fText, fTextView, font, color, true);
34025fb0e67SStephan Aßmus 
341c8ccdf52SStephan Aßmus 	font.SetFalseBoldWidth(outlineRadius);
342*01e0d327SStephan Aßmus 	fShadowTextView->ForceFontAliasing(overlayMode);
34325fb0e67SStephan Aßmus 	fShadowTextView->SetText(NULL);
34425fb0e67SStephan Aßmus 	fShadowTextView->SetFontAndColor(&font, B_FONT_ALL, &shadow);
34525fb0e67SStephan Aßmus 
34625fb0e67SStephan Aßmus 	fShadowTextView->Insert(" ");
3474ad39ed7SStephan Aßmus 	parse_text(fText, fShadowTextView, font, shadow, false);
34825fb0e67SStephan Aßmus 
34925fb0e67SStephan Aßmus 	// This causes the BTextView to calculate the layout of the text
35025fb0e67SStephan Aßmus 	fTextView->SetTextRect(BRect(0, 0, 0, 0));
35125fb0e67SStephan Aßmus 	fTextView->SetTextRect(textRect);
35225fb0e67SStephan Aßmus 	fShadowTextView->SetTextRect(BRect(0, 0, 0, 0));
35325fb0e67SStephan Aßmus 	fShadowTextView->SetTextRect(textRect);
35425fb0e67SStephan Aßmus 
35525fb0e67SStephan Aßmus 	textRect = fTextView->TextRect();
356c8ccdf52SStephan Aßmus 	textRect.InsetBy(-outlineRadius, -outlineRadius);
35725fb0e67SStephan Aßmus 	textRect.OffsetTo(B_ORIGIN);
358c8ccdf52SStephan Aßmus 
359c8ccdf52SStephan Aßmus 	// Make sure the text rect really finishes behind the last line.
360c8ccdf52SStephan Aßmus 	// We don't want any accidental extra space.
361c8ccdf52SStephan Aßmus 	textRect.bottom = outlineRadius;
362c8ccdf52SStephan Aßmus 	int32 lineCount = fTextView->CountLines();
363c8ccdf52SStephan Aßmus 	for (int32 i = 0; i < lineCount; i++)
364c8ccdf52SStephan Aßmus 		textRect.bottom += fTextView->LineHeight(i);
365c8ccdf52SStephan Aßmus 	textRect.bottom += outlineRadius;
36625fb0e67SStephan Aßmus }
36725fb0e67SStephan Aßmus 
36825fb0e67SStephan Aßmus 
36925fb0e67SStephan Aßmus 
370