xref: /haiku/src/apps/mediaplayer/interface/SubtitleBitmap.cpp (revision ed74106bcbfbb30c8cc62b665f24669dee4cf326)
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 
1425fb0e67SStephan Aßmus 
1525fb0e67SStephan Aßmus SubtitleBitmap::SubtitleBitmap()
1625fb0e67SStephan Aßmus 	:
1725fb0e67SStephan Aßmus 	fBitmap(NULL),
1825fb0e67SStephan Aßmus 	fTextView(new BTextView("offscreen text")),
1925fb0e67SStephan Aßmus 	fShadowTextView(new BTextView("offscreen text shadow"))
2025fb0e67SStephan Aßmus {
2125fb0e67SStephan Aßmus 	fTextView->SetStylable(true);
2225fb0e67SStephan Aßmus 	fTextView->MakeEditable(false);
2325fb0e67SStephan Aßmus 	fTextView->SetWordWrap(false);
2425fb0e67SStephan Aßmus 	fTextView->SetAlignment(B_ALIGN_CENTER);
2525fb0e67SStephan Aßmus 
2625fb0e67SStephan Aßmus 	fShadowTextView->SetStylable(true);
2725fb0e67SStephan Aßmus 	fShadowTextView->MakeEditable(false);
2825fb0e67SStephan Aßmus 	fShadowTextView->SetWordWrap(false);
2925fb0e67SStephan Aßmus 	fShadowTextView->SetAlignment(B_ALIGN_CENTER);
3025fb0e67SStephan Aßmus }
3125fb0e67SStephan Aßmus 
3225fb0e67SStephan Aßmus 
3325fb0e67SStephan Aßmus SubtitleBitmap::~SubtitleBitmap()
3425fb0e67SStephan Aßmus {
3525fb0e67SStephan Aßmus 	delete fBitmap;
3625fb0e67SStephan Aßmus 	delete fTextView;
3725fb0e67SStephan Aßmus 	delete fShadowTextView;
3825fb0e67SStephan Aßmus }
3925fb0e67SStephan Aßmus 
4025fb0e67SStephan Aßmus 
4125fb0e67SStephan Aßmus void
4225fb0e67SStephan Aßmus SubtitleBitmap::SetText(const char* text)
4325fb0e67SStephan Aßmus {
4425fb0e67SStephan Aßmus 	if (text == fText)
4525fb0e67SStephan Aßmus 		return;
4625fb0e67SStephan Aßmus 
4725fb0e67SStephan Aßmus 	fText = text;
4825fb0e67SStephan Aßmus 
4925fb0e67SStephan Aßmus 	_GenerateBitmap();
5025fb0e67SStephan Aßmus }
5125fb0e67SStephan Aßmus 
5225fb0e67SStephan Aßmus 
5325fb0e67SStephan Aßmus void
5425fb0e67SStephan Aßmus SubtitleBitmap::SetVideoBounds(BRect bounds)
5525fb0e67SStephan Aßmus {
5625fb0e67SStephan Aßmus 	if (bounds == fVideoBounds)
5725fb0e67SStephan Aßmus 		return;
5825fb0e67SStephan Aßmus 
5925fb0e67SStephan Aßmus 	fVideoBounds = bounds;
6025fb0e67SStephan Aßmus 
6125fb0e67SStephan Aßmus 	_GenerateBitmap();
6225fb0e67SStephan Aßmus }
6325fb0e67SStephan Aßmus 
6425fb0e67SStephan Aßmus 
6525fb0e67SStephan Aßmus const BBitmap*
6625fb0e67SStephan Aßmus SubtitleBitmap::Bitmap() const
6725fb0e67SStephan Aßmus {
6825fb0e67SStephan Aßmus 	return fBitmap;
6925fb0e67SStephan Aßmus }
7025fb0e67SStephan Aßmus 
7125fb0e67SStephan Aßmus 
7225fb0e67SStephan Aßmus void
7325fb0e67SStephan Aßmus SubtitleBitmap::_GenerateBitmap()
7425fb0e67SStephan Aßmus {
7525fb0e67SStephan Aßmus 	if (!fVideoBounds.IsValid())
7625fb0e67SStephan Aßmus 		return;
7725fb0e67SStephan Aßmus 
7825fb0e67SStephan Aßmus 	delete fBitmap;
7925fb0e67SStephan Aßmus 
8025fb0e67SStephan Aßmus 	BRect bounds = _InsertText();
8125fb0e67SStephan Aßmus 
8225fb0e67SStephan Aßmus 	fBitmap = new BBitmap(bounds, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32);
8325fb0e67SStephan Aßmus 	memset(fBitmap->Bits(), 0, fBitmap->BitsLength());
8425fb0e67SStephan Aßmus 
8525fb0e67SStephan Aßmus 	if (fBitmap->Lock()) {
8625fb0e67SStephan Aßmus 		fBitmap->AddChild(fShadowTextView);
8725fb0e67SStephan Aßmus 		fShadowTextView->ResizeTo(bounds.Width(), bounds.Height());
8825fb0e67SStephan Aßmus 
8925fb0e67SStephan Aßmus 		fShadowTextView->SetViewColor(0, 0, 0, 0);
9025fb0e67SStephan Aßmus 		fShadowTextView->SetDrawingMode(B_OP_ALPHA);
9125fb0e67SStephan Aßmus 		fShadowTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
9225fb0e67SStephan Aßmus 
9325fb0e67SStephan Aßmus 		fShadowTextView->PushState();
9425fb0e67SStephan Aßmus 		fShadowTextView->Draw(bounds);
9525fb0e67SStephan Aßmus 		fShadowTextView->PopState();
9625fb0e67SStephan Aßmus 
9725fb0e67SStephan Aßmus 		fShadowTextView->Sync();
9825fb0e67SStephan Aßmus 		fShadowTextView->RemoveSelf();
9925fb0e67SStephan Aßmus 
10025fb0e67SStephan Aßmus 		fBitmap->AddChild(fTextView);
10125fb0e67SStephan Aßmus 		fTextView->ResizeTo(bounds.Width(), bounds.Height());
10225fb0e67SStephan Aßmus 
10325fb0e67SStephan Aßmus 		fTextView->SetViewColor(0, 0, 0, 0);
10425fb0e67SStephan Aßmus 		fTextView->SetDrawingMode(B_OP_ALPHA);
10525fb0e67SStephan Aßmus 		fTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
10625fb0e67SStephan Aßmus 
10725fb0e67SStephan Aßmus 		fTextView->PushState();
10825fb0e67SStephan Aßmus 		fTextView->Draw(bounds);
10925fb0e67SStephan Aßmus 		fTextView->PopState();
11025fb0e67SStephan Aßmus 
11125fb0e67SStephan Aßmus 		fTextView->Sync();
11225fb0e67SStephan Aßmus 		fTextView->RemoveSelf();
11325fb0e67SStephan Aßmus 
11425fb0e67SStephan Aßmus 		fBitmap->Unlock();
11525fb0e67SStephan Aßmus 	}
11625fb0e67SStephan Aßmus }
11725fb0e67SStephan Aßmus 
11825fb0e67SStephan Aßmus 
11925fb0e67SStephan Aßmus struct ParseState {
12025fb0e67SStephan Aßmus 	ParseState()
12125fb0e67SStephan Aßmus 		:
12225fb0e67SStephan Aßmus 		color((rgb_color){ 255, 255, 255, 255 }),
12325fb0e67SStephan Aßmus 		bold(false),
12425fb0e67SStephan Aßmus 		italic(false),
12525fb0e67SStephan Aßmus 		underlined(false),
12625fb0e67SStephan Aßmus 
12725fb0e67SStephan Aßmus 		previous(NULL)
12825fb0e67SStephan Aßmus 	{
12925fb0e67SStephan Aßmus 	}
13025fb0e67SStephan Aßmus 
13125fb0e67SStephan Aßmus 	ParseState(ParseState* previous)
13225fb0e67SStephan Aßmus 		:
13325fb0e67SStephan Aßmus 		color(previous->color),
13425fb0e67SStephan Aßmus 		bold(previous->bold),
13525fb0e67SStephan Aßmus 		italic(previous->italic),
13625fb0e67SStephan Aßmus 		underlined(previous->underlined),
13725fb0e67SStephan Aßmus 
13825fb0e67SStephan Aßmus 		previous(previous)
13925fb0e67SStephan Aßmus 	{
14025fb0e67SStephan Aßmus 	}
14125fb0e67SStephan Aßmus 	rgb_color	color;
14225fb0e67SStephan Aßmus 	bool		bold;
14325fb0e67SStephan Aßmus 	bool		italic;
14425fb0e67SStephan Aßmus 	bool		underlined;
14525fb0e67SStephan Aßmus 
14625fb0e67SStephan Aßmus 	ParseState*	previous;
14725fb0e67SStephan Aßmus };
14825fb0e67SStephan Aßmus 
14925fb0e67SStephan Aßmus 
15025fb0e67SStephan Aßmus static bool
15125fb0e67SStephan Aßmus find_next_tag(const BString& string, int32& tagPos, int32& tagLength,
15225fb0e67SStephan Aßmus 	ParseState*& state)
15325fb0e67SStephan Aßmus {
15425fb0e67SStephan Aßmus 	static const char* kTags[] = {
15525fb0e67SStephan Aßmus 		"<b>", "</b>",
15625fb0e67SStephan Aßmus 		"<i>", "</i>",
15725fb0e67SStephan Aßmus 		"<u>", "</u>",
15825fb0e67SStephan Aßmus 		"<font color=\"#", "</font>"
15925fb0e67SStephan Aßmus 	};
16025fb0e67SStephan Aßmus 	static const int32 kTagCount = sizeof(kTags) / sizeof(const char*);
16125fb0e67SStephan Aßmus 
16225fb0e67SStephan Aßmus 	int32 current = tagPos;
16325fb0e67SStephan Aßmus 	tagPos = string.Length();
16425fb0e67SStephan Aßmus 	tagLength = 0;
16525fb0e67SStephan Aßmus 
166*ed74106bSStephan Aßmus 	// Find the next tag closest from the current position
167*ed74106bSStephan Aßmus 	// This way of doing it allows broken input with overlapping tags even.
16825fb0e67SStephan Aßmus 	BString tag;
16925fb0e67SStephan Aßmus 	for (int32 i = 0; i < kTagCount; i++) {
170*ed74106bSStephan Aßmus 		int32 nextTag = string.IFindFirst(kTags[i], current);
17125fb0e67SStephan Aßmus 		if (nextTag >= current && nextTag < tagPos) {
17225fb0e67SStephan Aßmus 			tagPos = nextTag;
17325fb0e67SStephan Aßmus 			tag = kTags[i];
17425fb0e67SStephan Aßmus 		}
17525fb0e67SStephan Aßmus 	}
17625fb0e67SStephan Aßmus 
17725fb0e67SStephan Aßmus 	if (tag != "") {
178*ed74106bSStephan Aßmus 		// Tag found, ParseState will change.
17925fb0e67SStephan Aßmus 		tagLength = tag.Length();
18025fb0e67SStephan Aßmus 		if (tag == "<b>") {
18125fb0e67SStephan Aßmus 			state = new ParseState(state);
18225fb0e67SStephan Aßmus 			state->bold = true;
18325fb0e67SStephan Aßmus 		} else if (tag == "<i>") {
18425fb0e67SStephan Aßmus 			state = new ParseState(state);
18525fb0e67SStephan Aßmus 			state->italic = true;
18625fb0e67SStephan Aßmus 		} else if (tag == "<u>") {
18725fb0e67SStephan Aßmus 			state = new ParseState(state);
18825fb0e67SStephan Aßmus 			state->underlined = true;
18925fb0e67SStephan Aßmus 		} else if (tag == "<font color=\"#") {
19025fb0e67SStephan Aßmus 			state = new ParseState(state);
19125fb0e67SStephan Aßmus 			char number[16];
19225fb0e67SStephan Aßmus 			snprintf(number, sizeof(number), "0x%.6s",
19325fb0e67SStephan Aßmus 				string.String() + tagPos + tag.Length());
19425fb0e67SStephan Aßmus 			int colorInt;
19525fb0e67SStephan Aßmus 			if (sscanf(number, "%x", &colorInt) == 1) {
19625fb0e67SStephan Aßmus 				state->color.red = (colorInt & 0xff0000) >> 16;
19725fb0e67SStephan Aßmus 				state->color.green = (colorInt & 0x00ff00) >> 8;
19825fb0e67SStephan Aßmus 				state->color.blue = (colorInt & 0x0000ff);
19925fb0e67SStephan Aßmus 				tagLength += 8;
20025fb0e67SStephan Aßmus 			}
20125fb0e67SStephan Aßmus 		} else if (tag == "</b>" || tag == "</i>" || tag == "</u>"
20225fb0e67SStephan Aßmus 			|| tag == "</font>") {
203*ed74106bSStephan Aßmus 			// Closing tag, pop state
20425fb0e67SStephan Aßmus 			if (state->previous != NULL) {
20525fb0e67SStephan Aßmus 				ParseState* oldState = state;
20625fb0e67SStephan Aßmus 				state = state->previous;
20725fb0e67SStephan Aßmus 				delete oldState;
20825fb0e67SStephan Aßmus 			}
20925fb0e67SStephan Aßmus 		}
21025fb0e67SStephan Aßmus 		return true;
21125fb0e67SStephan Aßmus 	}
21225fb0e67SStephan Aßmus 	return false;
21325fb0e67SStephan Aßmus }
21425fb0e67SStephan Aßmus 
21525fb0e67SStephan Aßmus 
21625fb0e67SStephan Aßmus static void
21725fb0e67SStephan Aßmus apply_state(BTextView* textView, const ParseState* state, BFont font,
21825fb0e67SStephan Aßmus 	bool changeColor)
21925fb0e67SStephan Aßmus {
22025fb0e67SStephan Aßmus 	uint16 face = 0;
22125fb0e67SStephan Aßmus 	if (state->bold || state->italic || state->underlined) {
22225fb0e67SStephan Aßmus 		if (state->bold)
22325fb0e67SStephan Aßmus 			face |= B_BOLD_FACE;
22425fb0e67SStephan Aßmus 		if (state->italic)
22525fb0e67SStephan Aßmus 			face |= B_ITALIC_FACE;
22625fb0e67SStephan Aßmus 		if (state->underlined)
22725fb0e67SStephan Aßmus 			face |= B_UNDERSCORE_FACE;
22825fb0e67SStephan Aßmus 	} else
22925fb0e67SStephan Aßmus 		face = B_REGULAR_FACE;
23025fb0e67SStephan Aßmus 	font.SetFace(face);
23125fb0e67SStephan Aßmus 	if (changeColor)
23225fb0e67SStephan Aßmus 		textView->SetFontAndColor(&font, B_FONT_ALL, &state->color);
23325fb0e67SStephan Aßmus 	else
23425fb0e67SStephan Aßmus 		textView->SetFontAndColor(&font, B_FONT_ALL, NULL);
23525fb0e67SStephan Aßmus }
23625fb0e67SStephan Aßmus 
23725fb0e67SStephan Aßmus 
23825fb0e67SStephan Aßmus static void
23925fb0e67SStephan Aßmus parse_text(const BString& string, BTextView* textView, const BFont& font,
24025fb0e67SStephan Aßmus 	bool changeColor)
24125fb0e67SStephan Aßmus {
24225fb0e67SStephan Aßmus 	ParseState rootState;
24325fb0e67SStephan Aßmus 	ParseState* state = &rootState;
24425fb0e67SStephan Aßmus 
24525fb0e67SStephan Aßmus 	int32 pos = 0;
24625fb0e67SStephan Aßmus 	while (pos < string.Length()) {
24725fb0e67SStephan Aßmus 		int32 nextPos = pos;
24825fb0e67SStephan Aßmus 		int32 tagLength;
24925fb0e67SStephan Aßmus 		bool stateChanged = find_next_tag(string, nextPos, tagLength, state);
25025fb0e67SStephan Aßmus 		if (nextPos > pos) {
251*ed74106bSStephan Aßmus 			// Insert text between last and next tags
25225fb0e67SStephan Aßmus 			BString subString;
25325fb0e67SStephan Aßmus 			string.CopyInto(subString, pos, nextPos - pos);
25425fb0e67SStephan Aßmus 			textView->Insert(subString.String());
25525fb0e67SStephan Aßmus 		}
25625fb0e67SStephan Aßmus 		pos = nextPos + tagLength;
25725fb0e67SStephan Aßmus 		if (stateChanged)
25825fb0e67SStephan Aßmus 			apply_state(textView, state, font, changeColor);
25925fb0e67SStephan Aßmus 	}
260*ed74106bSStephan Aßmus 
261*ed74106bSStephan Aßmus 	// Cleanup states in case the input text had non-matching tags.
262*ed74106bSStephan Aßmus 	while (state->previous != NULL) {
263*ed74106bSStephan Aßmus 		ParseState* oldState = state->previous;
264*ed74106bSStephan Aßmus 		state = state->previous;
265*ed74106bSStephan Aßmus 		delete oldState;
266*ed74106bSStephan Aßmus 	}
26725fb0e67SStephan Aßmus }
26825fb0e67SStephan Aßmus 
26925fb0e67SStephan Aßmus 
27025fb0e67SStephan Aßmus BRect
27125fb0e67SStephan Aßmus SubtitleBitmap::_InsertText()
27225fb0e67SStephan Aßmus {
27325fb0e67SStephan Aßmus 	BFont font(be_plain_font);
27425fb0e67SStephan Aßmus 	float fontSize = ceilf((fVideoBounds.Width() * 0.9) / 35);
27525fb0e67SStephan Aßmus 	float falseBoldWidth = ceilf(fontSize / 28.0);
27625fb0e67SStephan Aßmus 	font.SetSize(fontSize);
27725fb0e67SStephan Aßmus 
27825fb0e67SStephan Aßmus 	rgb_color shadow;
27925fb0e67SStephan Aßmus 	shadow.red = 0;
28025fb0e67SStephan Aßmus 	shadow.green = 0;
28125fb0e67SStephan Aßmus 	shadow.blue = 0;
28225fb0e67SStephan Aßmus 	shadow.alpha = 200;
28325fb0e67SStephan Aßmus 
28425fb0e67SStephan Aßmus 	rgb_color color;
28525fb0e67SStephan Aßmus 	color.red = 255;
28625fb0e67SStephan Aßmus 	color.green = 255;
28725fb0e67SStephan Aßmus 	color.blue = 255;
28825fb0e67SStephan Aßmus 	color.alpha = 240;
28925fb0e67SStephan Aßmus 
29025fb0e67SStephan Aßmus 	BRect textRect = fVideoBounds;
29125fb0e67SStephan Aßmus 	textRect.OffsetBy(falseBoldWidth, falseBoldWidth);
29225fb0e67SStephan Aßmus 
29325fb0e67SStephan Aßmus 	fTextView->SetText(NULL);
29425fb0e67SStephan Aßmus 	fTextView->SetFontAndColor(&font, B_FONT_ALL, &color);
29525fb0e67SStephan Aßmus 
29625fb0e67SStephan Aßmus 	fTextView->Insert(" ");
29725fb0e67SStephan Aßmus 	parse_text(fText, fTextView, font, true);
29825fb0e67SStephan Aßmus 
29925fb0e67SStephan Aßmus 	font.SetFalseBoldWidth(falseBoldWidth);
30025fb0e67SStephan Aßmus 	fShadowTextView->SetText(NULL);
30125fb0e67SStephan Aßmus 	fShadowTextView->SetFontAndColor(&font, B_FONT_ALL, &shadow);
30225fb0e67SStephan Aßmus 
30325fb0e67SStephan Aßmus 	fShadowTextView->Insert(" ");
30425fb0e67SStephan Aßmus 	parse_text(fText, fShadowTextView, font, false);
30525fb0e67SStephan Aßmus 
30625fb0e67SStephan Aßmus 	// This causes the BTextView to calculate the layout of the text
30725fb0e67SStephan Aßmus 	fTextView->SetTextRect(BRect(0, 0, 0, 0));
30825fb0e67SStephan Aßmus 	fTextView->SetTextRect(textRect);
30925fb0e67SStephan Aßmus 	fShadowTextView->SetTextRect(BRect(0, 0, 0, 0));
31025fb0e67SStephan Aßmus 	fShadowTextView->SetTextRect(textRect);
31125fb0e67SStephan Aßmus 
31225fb0e67SStephan Aßmus 	textRect = fTextView->TextRect();
31325fb0e67SStephan Aßmus 	textRect.InsetBy(-falseBoldWidth, -falseBoldWidth);
31425fb0e67SStephan Aßmus 	textRect.OffsetTo(B_ORIGIN);
31525fb0e67SStephan Aßmus 	return textRect;
31625fb0e67SStephan Aßmus }
31725fb0e67SStephan Aßmus 
31825fb0e67SStephan Aßmus 
31925fb0e67SStephan Aßmus 
320