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
SubtitleBitmap()1725fb0e67SStephan Aßmus SubtitleBitmap::SubtitleBitmap()
1825fb0e67SStephan Aßmus :
1925fb0e67SStephan Aßmus fBitmap(NULL),
2025fb0e67SStephan Aßmus fTextView(new BTextView("offscreen text")),
2101e0d327SStephan Aßmus fShadowTextView(new BTextView("offscreen text shadow")),
22e65a6eb2SStephan Aßmus fCharsPerLine(36),
2301e0d327SStephan Aßmus fUseSoftShadow(true),
2401e0d327SStephan Aßmus fOverlayMode(false)
2525fb0e67SStephan Aßmus {
2625fb0e67SStephan Aßmus fTextView->SetStylable(true);
2725fb0e67SStephan Aßmus fTextView->MakeEditable(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->SetAlignment(B_ALIGN_CENTER);
3325fb0e67SStephan Aßmus }
3425fb0e67SStephan Aßmus
3525fb0e67SStephan Aßmus
~SubtitleBitmap()3625fb0e67SStephan Aßmus SubtitleBitmap::~SubtitleBitmap()
3725fb0e67SStephan Aßmus {
3825fb0e67SStephan Aßmus delete fBitmap;
3925fb0e67SStephan Aßmus delete fTextView;
4025fb0e67SStephan Aßmus delete fShadowTextView;
4125fb0e67SStephan Aßmus }
4225fb0e67SStephan Aßmus
4325fb0e67SStephan Aßmus
44e65a6eb2SStephan Aßmus bool
SetText(const char * text)4525fb0e67SStephan Aßmus SubtitleBitmap::SetText(const char* text)
4625fb0e67SStephan Aßmus {
4725fb0e67SStephan Aßmus if (text == fText)
48e65a6eb2SStephan Aßmus return false;
4925fb0e67SStephan Aßmus
5025fb0e67SStephan Aßmus fText = text;
5125fb0e67SStephan Aßmus
5225fb0e67SStephan Aßmus _GenerateBitmap();
53e65a6eb2SStephan Aßmus return true;
5425fb0e67SStephan Aßmus }
5525fb0e67SStephan Aßmus
5625fb0e67SStephan Aßmus
5725fb0e67SStephan Aßmus void
SetVideoBounds(BRect bounds)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
6501e0d327SStephan Aßmus fUseSoftShadow = true;
6601e0d327SStephan Aßmus _GenerateBitmap();
6701e0d327SStephan Aßmus }
6801e0d327SStephan Aßmus
6901e0d327SStephan Aßmus
7001e0d327SStephan Aßmus void
SetOverlayMode(bool overlayMode)7101e0d327SStephan Aßmus SubtitleBitmap::SetOverlayMode(bool overlayMode)
7201e0d327SStephan Aßmus {
7301e0d327SStephan Aßmus if (overlayMode == fOverlayMode)
7401e0d327SStephan Aßmus return;
7501e0d327SStephan Aßmus
7601e0d327SStephan Aßmus fOverlayMode = overlayMode;
7701e0d327SStephan Aßmus
7825fb0e67SStephan Aßmus _GenerateBitmap();
7925fb0e67SStephan Aßmus }
8025fb0e67SStephan Aßmus
8125fb0e67SStephan Aßmus
82e65a6eb2SStephan Aßmus void
SetCharsPerLine(float charsPerLine)83e65a6eb2SStephan Aßmus SubtitleBitmap::SetCharsPerLine(float charsPerLine)
84e65a6eb2SStephan Aßmus {
85e65a6eb2SStephan Aßmus if (charsPerLine == fCharsPerLine)
86e65a6eb2SStephan Aßmus return;
87e65a6eb2SStephan Aßmus
88e65a6eb2SStephan Aßmus fCharsPerLine = charsPerLine;
89e65a6eb2SStephan Aßmus
90e65a6eb2SStephan Aßmus fUseSoftShadow = true;
91e65a6eb2SStephan Aßmus _GenerateBitmap();
92e65a6eb2SStephan Aßmus }
93e65a6eb2SStephan Aßmus
94e65a6eb2SStephan Aßmus
9525fb0e67SStephan Aßmus const BBitmap*
Bitmap() const9625fb0e67SStephan Aßmus SubtitleBitmap::Bitmap() const
9725fb0e67SStephan Aßmus {
9825fb0e67SStephan Aßmus return fBitmap;
9925fb0e67SStephan Aßmus }
10025fb0e67SStephan Aßmus
10125fb0e67SStephan Aßmus
10225fb0e67SStephan Aßmus void
_GenerateBitmap()10325fb0e67SStephan Aßmus SubtitleBitmap::_GenerateBitmap()
10425fb0e67SStephan Aßmus {
10525fb0e67SStephan Aßmus if (!fVideoBounds.IsValid())
10625fb0e67SStephan Aßmus return;
10725fb0e67SStephan Aßmus
10825fb0e67SStephan Aßmus delete fBitmap;
10925fb0e67SStephan Aßmus
110c8ccdf52SStephan Aßmus BRect bounds;
111c8ccdf52SStephan Aßmus float outlineRadius;
11201e0d327SStephan Aßmus _InsertText(bounds, outlineRadius, fOverlayMode);
11301e0d327SStephan Aßmus
11401e0d327SStephan Aßmus bigtime_t startTime = 0;
11501e0d327SStephan Aßmus if (!fOverlayMode && fUseSoftShadow)
11601e0d327SStephan Aßmus startTime = system_time();
11725fb0e67SStephan Aßmus
11825fb0e67SStephan Aßmus fBitmap = new BBitmap(bounds, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32);
11925fb0e67SStephan Aßmus memset(fBitmap->Bits(), 0, fBitmap->BitsLength());
12025fb0e67SStephan Aßmus
12125fb0e67SStephan Aßmus if (fBitmap->Lock()) {
12225fb0e67SStephan Aßmus fBitmap->AddChild(fShadowTextView);
12325fb0e67SStephan Aßmus fShadowTextView->ResizeTo(bounds.Width(), bounds.Height());
12425fb0e67SStephan Aßmus
12525fb0e67SStephan Aßmus fShadowTextView->SetViewColor(0, 0, 0, 0);
12625fb0e67SStephan Aßmus fShadowTextView->SetDrawingMode(B_OP_ALPHA);
12725fb0e67SStephan Aßmus fShadowTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
12825fb0e67SStephan Aßmus
12925fb0e67SStephan Aßmus fShadowTextView->PushState();
13025fb0e67SStephan Aßmus fShadowTextView->Draw(bounds);
13125fb0e67SStephan Aßmus fShadowTextView->PopState();
13225fb0e67SStephan Aßmus
13301e0d327SStephan Aßmus if (!fOverlayMode && fUseSoftShadow) {
13425fb0e67SStephan Aßmus fShadowTextView->Sync();
13501e0d327SStephan Aßmus StackBlurFilter filter;
136c8ccdf52SStephan Aßmus filter.Filter(fBitmap, outlineRadius * 2);
13701e0d327SStephan Aßmus }
13801e0d327SStephan Aßmus
13901e0d327SStephan Aßmus fShadowTextView->RemoveSelf();
140c8ccdf52SStephan Aßmus
14125fb0e67SStephan Aßmus fBitmap->AddChild(fTextView);
14225fb0e67SStephan Aßmus fTextView->ResizeTo(bounds.Width(), bounds.Height());
14301e0d327SStephan Aßmus if (!fOverlayMode && fUseSoftShadow)
144c8ccdf52SStephan Aßmus fTextView->MoveTo(-outlineRadius / 2, -outlineRadius / 2);
14501e0d327SStephan Aßmus else
14601e0d327SStephan Aßmus fTextView->MoveTo(0, 0);
14725fb0e67SStephan Aßmus
14825fb0e67SStephan Aßmus fTextView->SetViewColor(0, 0, 0, 0);
14925fb0e67SStephan Aßmus fTextView->SetDrawingMode(B_OP_ALPHA);
15025fb0e67SStephan Aßmus fTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
15125fb0e67SStephan Aßmus
15225fb0e67SStephan Aßmus fTextView->PushState();
15325fb0e67SStephan Aßmus fTextView->Draw(bounds);
15425fb0e67SStephan Aßmus fTextView->PopState();
15525fb0e67SStephan Aßmus
15625fb0e67SStephan Aßmus fTextView->Sync();
15725fb0e67SStephan Aßmus fTextView->RemoveSelf();
15825fb0e67SStephan Aßmus
15925fb0e67SStephan Aßmus fBitmap->Unlock();
16025fb0e67SStephan Aßmus }
16101e0d327SStephan Aßmus
16201e0d327SStephan Aßmus if (!fOverlayMode && fUseSoftShadow && system_time() - startTime > 10000)
16301e0d327SStephan Aßmus fUseSoftShadow = false;
16425fb0e67SStephan Aßmus }
16525fb0e67SStephan Aßmus
16625fb0e67SStephan Aßmus
16725fb0e67SStephan Aßmus struct ParseState {
ParseStateParseState1684ad39ed7SStephan Aßmus ParseState(rgb_color color)
16925fb0e67SStephan Aßmus :
1704ad39ed7SStephan Aßmus color(color),
17125fb0e67SStephan Aßmus bold(false),
17225fb0e67SStephan Aßmus italic(false),
17325fb0e67SStephan Aßmus underlined(false),
17425fb0e67SStephan Aßmus
17525fb0e67SStephan Aßmus previous(NULL)
17625fb0e67SStephan Aßmus {
17725fb0e67SStephan Aßmus }
17825fb0e67SStephan Aßmus
ParseStateParseState17925fb0e67SStephan Aßmus ParseState(ParseState* previous)
18025fb0e67SStephan Aßmus :
18125fb0e67SStephan Aßmus color(previous->color),
18225fb0e67SStephan Aßmus bold(previous->bold),
18325fb0e67SStephan Aßmus italic(previous->italic),
18425fb0e67SStephan Aßmus underlined(previous->underlined),
18525fb0e67SStephan Aßmus
18625fb0e67SStephan Aßmus previous(previous)
18725fb0e67SStephan Aßmus {
18825fb0e67SStephan Aßmus }
1894ad39ed7SStephan Aßmus
19025fb0e67SStephan Aßmus rgb_color color;
19125fb0e67SStephan Aßmus bool bold;
19225fb0e67SStephan Aßmus bool italic;
19325fb0e67SStephan Aßmus bool underlined;
19425fb0e67SStephan Aßmus
19525fb0e67SStephan Aßmus ParseState* previous;
19625fb0e67SStephan Aßmus };
19725fb0e67SStephan Aßmus
19825fb0e67SStephan Aßmus
19925fb0e67SStephan Aßmus static bool
find_next_tag(const BString & string,int32 & tagPos,int32 & tagLength,ParseState * & state)20025fb0e67SStephan Aßmus find_next_tag(const BString& string, int32& tagPos, int32& tagLength,
20125fb0e67SStephan Aßmus ParseState*& state)
20225fb0e67SStephan Aßmus {
20325fb0e67SStephan Aßmus static const char* kTags[] = {
20425fb0e67SStephan Aßmus "<b>", "</b>",
20525fb0e67SStephan Aßmus "<i>", "</i>",
20625fb0e67SStephan Aßmus "<u>", "</u>",
20725fb0e67SStephan Aßmus "<font color=\"#", "</font>"
20825fb0e67SStephan Aßmus };
20925fb0e67SStephan Aßmus static const int32 kTagCount = sizeof(kTags) / sizeof(const char*);
21025fb0e67SStephan Aßmus
2114ad39ed7SStephan Aßmus int32 startPos = tagPos;
21225fb0e67SStephan Aßmus tagPos = string.Length();
21325fb0e67SStephan Aßmus tagLength = 0;
21425fb0e67SStephan Aßmus
215ed74106bSStephan Aßmus // Find the next tag closest from the current position
216ed74106bSStephan Aßmus // This way of doing it allows broken input with overlapping tags even.
21725fb0e67SStephan Aßmus BString tag;
21825fb0e67SStephan Aßmus for (int32 i = 0; i < kTagCount; i++) {
2194ad39ed7SStephan Aßmus int32 nextTag = string.IFindFirst(kTags[i], startPos);
2204ad39ed7SStephan Aßmus if (nextTag >= startPos && nextTag < tagPos) {
22125fb0e67SStephan Aßmus tagPos = nextTag;
22225fb0e67SStephan Aßmus tag = kTags[i];
22325fb0e67SStephan Aßmus }
22425fb0e67SStephan Aßmus }
22525fb0e67SStephan Aßmus
2264ad39ed7SStephan Aßmus if (tag.Length() == 0)
2274ad39ed7SStephan Aßmus return false;
2284ad39ed7SStephan Aßmus
229ed74106bSStephan Aßmus // Tag found, ParseState will change.
23025fb0e67SStephan Aßmus tagLength = tag.Length();
23125fb0e67SStephan Aßmus if (tag == "<b>") {
23225fb0e67SStephan Aßmus state = new ParseState(state);
23325fb0e67SStephan Aßmus state->bold = true;
23425fb0e67SStephan Aßmus } else if (tag == "<i>") {
23525fb0e67SStephan Aßmus state = new ParseState(state);
23625fb0e67SStephan Aßmus state->italic = true;
23725fb0e67SStephan Aßmus } else if (tag == "<u>") {
23825fb0e67SStephan Aßmus state = new ParseState(state);
23925fb0e67SStephan Aßmus state->underlined = true;
24025fb0e67SStephan Aßmus } else if (tag == "<font color=\"#") {
24125fb0e67SStephan Aßmus state = new ParseState(state);
24225fb0e67SStephan Aßmus char number[16];
24325fb0e67SStephan Aßmus snprintf(number, sizeof(number), "0x%.6s",
24425fb0e67SStephan Aßmus string.String() + tagPos + tag.Length());
24525fb0e67SStephan Aßmus int colorInt;
24625fb0e67SStephan Aßmus if (sscanf(number, "%x", &colorInt) == 1) {
24725fb0e67SStephan Aßmus state->color.red = (colorInt & 0xff0000) >> 16;
24825fb0e67SStephan Aßmus state->color.green = (colorInt & 0x00ff00) >> 8;
24925fb0e67SStephan Aßmus state->color.blue = (colorInt & 0x0000ff);
2504ad39ed7SStephan Aßmus // skip 'RRGGBB">' part, too
25125fb0e67SStephan Aßmus tagLength += 8;
25225fb0e67SStephan Aßmus }
25325fb0e67SStephan Aßmus } else if (tag == "</b>" || tag == "</i>" || tag == "</u>"
25425fb0e67SStephan Aßmus || tag == "</font>") {
255ed74106bSStephan Aßmus // Closing tag, pop state
25625fb0e67SStephan Aßmus if (state->previous != NULL) {
25725fb0e67SStephan Aßmus ParseState* oldState = state;
25825fb0e67SStephan Aßmus state = state->previous;
25925fb0e67SStephan Aßmus delete oldState;
26025fb0e67SStephan Aßmus }
26125fb0e67SStephan Aßmus }
26225fb0e67SStephan Aßmus return true;
26325fb0e67SStephan Aßmus }
26425fb0e67SStephan Aßmus
26525fb0e67SStephan Aßmus
26625fb0e67SStephan Aßmus static void
apply_state(BTextView * textView,const ParseState * state,BFont font,bool changeColor)26725fb0e67SStephan Aßmus apply_state(BTextView* textView, const ParseState* state, BFont font,
26825fb0e67SStephan Aßmus bool changeColor)
26925fb0e67SStephan Aßmus {
27025fb0e67SStephan Aßmus uint16 face = 0;
27125fb0e67SStephan Aßmus if (state->bold || state->italic || state->underlined) {
27225fb0e67SStephan Aßmus if (state->bold)
27325fb0e67SStephan Aßmus face |= B_BOLD_FACE;
27425fb0e67SStephan Aßmus if (state->italic)
27525fb0e67SStephan Aßmus face |= B_ITALIC_FACE;
27625fb0e67SStephan Aßmus if (state->underlined)
27725fb0e67SStephan Aßmus face |= B_UNDERSCORE_FACE;
27825fb0e67SStephan Aßmus } else
27925fb0e67SStephan Aßmus face = B_REGULAR_FACE;
28025fb0e67SStephan Aßmus font.SetFace(face);
28125fb0e67SStephan Aßmus if (changeColor)
28225fb0e67SStephan Aßmus textView->SetFontAndColor(&font, B_FONT_ALL, &state->color);
28325fb0e67SStephan Aßmus else
28425fb0e67SStephan Aßmus textView->SetFontAndColor(&font, B_FONT_ALL, NULL);
28525fb0e67SStephan Aßmus }
28625fb0e67SStephan Aßmus
28725fb0e67SStephan Aßmus
28825fb0e67SStephan Aßmus static void
parse_text(const BString & string,BTextView * textView,const BFont & font,const rgb_color & color,bool changeColor)28925fb0e67SStephan Aßmus parse_text(const BString& string, BTextView* textView, const BFont& font,
2904ad39ed7SStephan Aßmus const rgb_color& color, bool changeColor)
29125fb0e67SStephan Aßmus {
2924ad39ed7SStephan Aßmus ParseState rootState(color);
2934ad39ed7SStephan Aßmus // Colors may change, but alpha channel will be preserved
2944ad39ed7SStephan Aßmus
29525fb0e67SStephan Aßmus ParseState* state = &rootState;
29625fb0e67SStephan Aßmus
29725fb0e67SStephan Aßmus int32 pos = 0;
29825fb0e67SStephan Aßmus while (pos < string.Length()) {
29925fb0e67SStephan Aßmus int32 nextPos = pos;
30025fb0e67SStephan Aßmus int32 tagLength;
30125fb0e67SStephan Aßmus bool stateChanged = find_next_tag(string, nextPos, tagLength, state);
30225fb0e67SStephan Aßmus if (nextPos > pos) {
303ed74106bSStephan Aßmus // Insert text between last and next tags
30425fb0e67SStephan Aßmus BString subString;
30525fb0e67SStephan Aßmus string.CopyInto(subString, pos, nextPos - pos);
30625fb0e67SStephan Aßmus textView->Insert(subString.String());
30725fb0e67SStephan Aßmus }
30825fb0e67SStephan Aßmus pos = nextPos + tagLength;
30925fb0e67SStephan Aßmus if (stateChanged)
31025fb0e67SStephan Aßmus apply_state(textView, state, font, changeColor);
31125fb0e67SStephan Aßmus }
312ed74106bSStephan Aßmus
313ed74106bSStephan Aßmus // Cleanup states in case the input text had non-matching tags.
314ed74106bSStephan Aßmus while (state->previous != NULL) {
3157050e3cdSMichael Lotz ParseState* oldState = state;
316ed74106bSStephan Aßmus state = state->previous;
317ed74106bSStephan Aßmus delete oldState;
318ed74106bSStephan Aßmus }
31925fb0e67SStephan Aßmus }
32025fb0e67SStephan Aßmus
32125fb0e67SStephan Aßmus
322c8ccdf52SStephan Aßmus void
_InsertText(BRect & textRect,float & outlineRadius,bool overlayMode)32301e0d327SStephan Aßmus SubtitleBitmap::_InsertText(BRect& textRect, float& outlineRadius,
32401e0d327SStephan Aßmus bool overlayMode)
32525fb0e67SStephan Aßmus {
32625fb0e67SStephan Aßmus BFont font(be_plain_font);
327e65a6eb2SStephan Aßmus float fontSize = ceilf((fVideoBounds.Width() * 0.9) / fCharsPerLine);
328c8ccdf52SStephan Aßmus outlineRadius = ceilf(fontSize / 28.0);
32925fb0e67SStephan Aßmus font.SetSize(fontSize);
33025fb0e67SStephan Aßmus
33125fb0e67SStephan Aßmus rgb_color shadow;
33225fb0e67SStephan Aßmus shadow.red = 0;
33325fb0e67SStephan Aßmus shadow.green = 0;
33425fb0e67SStephan Aßmus shadow.blue = 0;
33525fb0e67SStephan Aßmus shadow.alpha = 200;
33625fb0e67SStephan Aßmus
33725fb0e67SStephan Aßmus rgb_color color;
33825fb0e67SStephan Aßmus color.red = 255;
33925fb0e67SStephan Aßmus color.green = 255;
34025fb0e67SStephan Aßmus color.blue = 255;
34125fb0e67SStephan Aßmus color.alpha = 240;
34225fb0e67SStephan Aßmus
343c8ccdf52SStephan Aßmus textRect = fVideoBounds;
344c8ccdf52SStephan Aßmus textRect.OffsetBy(outlineRadius, outlineRadius);
34525fb0e67SStephan Aßmus
34625fb0e67SStephan Aßmus fTextView->SetText(NULL);
34725fb0e67SStephan Aßmus fTextView->SetFontAndColor(&font, B_FONT_ALL, &color);
348*a5df3d52SMáximo Castañeda fTextView->ResizeTo(fVideoBounds.Width(), fVideoBounds.Height());
34925fb0e67SStephan Aßmus
35025fb0e67SStephan Aßmus fTextView->Insert(" ");
3514ad39ed7SStephan Aßmus parse_text(fText, fTextView, font, color, true);
35225fb0e67SStephan Aßmus
353c8ccdf52SStephan Aßmus font.SetFalseBoldWidth(outlineRadius);
35401e0d327SStephan Aßmus fShadowTextView->ForceFontAliasing(overlayMode);
35525fb0e67SStephan Aßmus fShadowTextView->SetText(NULL);
35625fb0e67SStephan Aßmus fShadowTextView->SetFontAndColor(&font, B_FONT_ALL, &shadow);
357*a5df3d52SMáximo Castañeda fShadowTextView->ResizeTo(fVideoBounds.Width(), fVideoBounds.Height());
35825fb0e67SStephan Aßmus
35925fb0e67SStephan Aßmus fShadowTextView->Insert(" ");
3604ad39ed7SStephan Aßmus parse_text(fText, fShadowTextView, font, shadow, false);
36125fb0e67SStephan Aßmus
36225fb0e67SStephan Aßmus // This causes the BTextView to calculate the layout of the text
36325fb0e67SStephan Aßmus fTextView->SetTextRect(BRect(0, 0, 0, 0));
36425fb0e67SStephan Aßmus fTextView->SetTextRect(textRect);
36525fb0e67SStephan Aßmus fShadowTextView->SetTextRect(BRect(0, 0, 0, 0));
36625fb0e67SStephan Aßmus fShadowTextView->SetTextRect(textRect);
36725fb0e67SStephan Aßmus
36825fb0e67SStephan Aßmus textRect = fTextView->TextRect();
369c8ccdf52SStephan Aßmus textRect.InsetBy(-outlineRadius, -outlineRadius);
37025fb0e67SStephan Aßmus textRect.OffsetTo(B_ORIGIN);
371c8ccdf52SStephan Aßmus
372c8ccdf52SStephan Aßmus // Make sure the text rect really finishes behind the last line.
373c8ccdf52SStephan Aßmus // We don't want any accidental extra space.
374c8ccdf52SStephan Aßmus textRect.bottom = outlineRadius;
375c8ccdf52SStephan Aßmus int32 lineCount = fTextView->CountLines();
376c8ccdf52SStephan Aßmus for (int32 i = 0; i < lineCount; i++)
377c8ccdf52SStephan Aßmus textRect.bottom += fTextView->LineHeight(i);
378c8ccdf52SStephan Aßmus textRect.bottom += outlineRadius;
37925fb0e67SStephan Aßmus }
38025fb0e67SStephan Aßmus
38125fb0e67SStephan Aßmus
38225fb0e67SStephan Aßmus
383