xref: /haiku/src/apps/mediaplayer/interface/SubtitleBitmap.cpp (revision 4ad39ed76d033a772629cd2378c067a9c7cb85f9)
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 {
120*4ad39ed7SStephan Aßmus 	ParseState(rgb_color color)
12125fb0e67SStephan Aßmus 		:
122*4ad39ed7SStephan Aßmus 		color(color),
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 	}
141*4ad39ed7SStephan Aßmus 
14225fb0e67SStephan Aßmus 	rgb_color	color;
14325fb0e67SStephan Aßmus 	bool		bold;
14425fb0e67SStephan Aßmus 	bool		italic;
14525fb0e67SStephan Aßmus 	bool		underlined;
14625fb0e67SStephan Aßmus 
14725fb0e67SStephan Aßmus 	ParseState*	previous;
14825fb0e67SStephan Aßmus };
14925fb0e67SStephan Aßmus 
15025fb0e67SStephan Aßmus 
15125fb0e67SStephan Aßmus static bool
15225fb0e67SStephan Aßmus find_next_tag(const BString& string, int32& tagPos, int32& tagLength,
15325fb0e67SStephan Aßmus 	ParseState*& state)
15425fb0e67SStephan Aßmus {
15525fb0e67SStephan Aßmus 	static const char* kTags[] = {
15625fb0e67SStephan Aßmus 		"<b>", "</b>",
15725fb0e67SStephan Aßmus 		"<i>", "</i>",
15825fb0e67SStephan Aßmus 		"<u>", "</u>",
15925fb0e67SStephan Aßmus 		"<font color=\"#", "</font>"
16025fb0e67SStephan Aßmus 	};
16125fb0e67SStephan Aßmus 	static const int32 kTagCount = sizeof(kTags) / sizeof(const char*);
16225fb0e67SStephan Aßmus 
163*4ad39ed7SStephan Aßmus 	int32 startPos = tagPos;
16425fb0e67SStephan Aßmus 	tagPos = string.Length();
16525fb0e67SStephan Aßmus 	tagLength = 0;
16625fb0e67SStephan Aßmus 
167ed74106bSStephan Aßmus 	// Find the next tag closest from the current position
168ed74106bSStephan Aßmus 	// This way of doing it allows broken input with overlapping tags even.
16925fb0e67SStephan Aßmus 	BString tag;
17025fb0e67SStephan Aßmus 	for (int32 i = 0; i < kTagCount; i++) {
171*4ad39ed7SStephan Aßmus 		int32 nextTag = string.IFindFirst(kTags[i], startPos);
172*4ad39ed7SStephan Aßmus 		if (nextTag >= startPos && nextTag < tagPos) {
17325fb0e67SStephan Aßmus 			tagPos = nextTag;
17425fb0e67SStephan Aßmus 			tag = kTags[i];
17525fb0e67SStephan Aßmus 		}
17625fb0e67SStephan Aßmus 	}
17725fb0e67SStephan Aßmus 
178*4ad39ed7SStephan Aßmus 	if (tag.Length() == 0)
179*4ad39ed7SStephan Aßmus 		return false;
180*4ad39ed7SStephan Aßmus 
181ed74106bSStephan Aßmus 	// Tag found, ParseState will change.
18225fb0e67SStephan Aßmus 	tagLength = tag.Length();
18325fb0e67SStephan Aßmus 	if (tag == "<b>") {
18425fb0e67SStephan Aßmus 		state = new ParseState(state);
18525fb0e67SStephan Aßmus 		state->bold = true;
18625fb0e67SStephan Aßmus 	} else if (tag == "<i>") {
18725fb0e67SStephan Aßmus 		state = new ParseState(state);
18825fb0e67SStephan Aßmus 		state->italic = true;
18925fb0e67SStephan Aßmus 	} else if (tag == "<u>") {
19025fb0e67SStephan Aßmus 		state = new ParseState(state);
19125fb0e67SStephan Aßmus 		state->underlined = true;
19225fb0e67SStephan Aßmus 	} else if (tag == "<font color=\"#") {
19325fb0e67SStephan Aßmus 		state = new ParseState(state);
19425fb0e67SStephan Aßmus 		char number[16];
19525fb0e67SStephan Aßmus 		snprintf(number, sizeof(number), "0x%.6s",
19625fb0e67SStephan Aßmus 			string.String() + tagPos + tag.Length());
19725fb0e67SStephan Aßmus 		int colorInt;
19825fb0e67SStephan Aßmus 		if (sscanf(number, "%x", &colorInt) == 1) {
19925fb0e67SStephan Aßmus 			state->color.red = (colorInt & 0xff0000) >> 16;
20025fb0e67SStephan Aßmus 			state->color.green = (colorInt & 0x00ff00) >> 8;
20125fb0e67SStephan Aßmus 			state->color.blue = (colorInt & 0x0000ff);
202*4ad39ed7SStephan Aßmus 			// skip 'RRGGBB">' part, too
20325fb0e67SStephan Aßmus 			tagLength += 8;
20425fb0e67SStephan Aßmus 		}
20525fb0e67SStephan Aßmus 	} else if (tag == "</b>" || tag == "</i>" || tag == "</u>"
20625fb0e67SStephan Aßmus 		|| tag == "</font>") {
207ed74106bSStephan Aßmus 		// Closing tag, pop state
20825fb0e67SStephan Aßmus 		if (state->previous != NULL) {
20925fb0e67SStephan Aßmus 			ParseState* oldState = state;
21025fb0e67SStephan Aßmus 			state = state->previous;
21125fb0e67SStephan Aßmus 			delete oldState;
21225fb0e67SStephan Aßmus 		}
21325fb0e67SStephan Aßmus 	}
21425fb0e67SStephan Aßmus 	return true;
21525fb0e67SStephan Aßmus }
21625fb0e67SStephan Aßmus 
21725fb0e67SStephan Aßmus 
21825fb0e67SStephan Aßmus static void
21925fb0e67SStephan Aßmus apply_state(BTextView* textView, const ParseState* state, BFont font,
22025fb0e67SStephan Aßmus 	bool changeColor)
22125fb0e67SStephan Aßmus {
22225fb0e67SStephan Aßmus 	uint16 face = 0;
22325fb0e67SStephan Aßmus 	if (state->bold || state->italic || state->underlined) {
22425fb0e67SStephan Aßmus 		if (state->bold)
22525fb0e67SStephan Aßmus 			face |= B_BOLD_FACE;
22625fb0e67SStephan Aßmus 		if (state->italic)
22725fb0e67SStephan Aßmus 			face |= B_ITALIC_FACE;
228*4ad39ed7SStephan Aßmus 		// NOTE: This is probably not supported by the app_server (perhaps
229*4ad39ed7SStephan Aßmus 		// it is if the font contains a specific underline face).
23025fb0e67SStephan Aßmus 		if (state->underlined)
23125fb0e67SStephan Aßmus 			face |= B_UNDERSCORE_FACE;
23225fb0e67SStephan Aßmus 	} else
23325fb0e67SStephan Aßmus 		face = B_REGULAR_FACE;
23425fb0e67SStephan Aßmus 	font.SetFace(face);
23525fb0e67SStephan Aßmus 	if (changeColor)
23625fb0e67SStephan Aßmus 		textView->SetFontAndColor(&font, B_FONT_ALL, &state->color);
23725fb0e67SStephan Aßmus 	else
23825fb0e67SStephan Aßmus 		textView->SetFontAndColor(&font, B_FONT_ALL, NULL);
23925fb0e67SStephan Aßmus }
24025fb0e67SStephan Aßmus 
24125fb0e67SStephan Aßmus 
24225fb0e67SStephan Aßmus static void
24325fb0e67SStephan Aßmus parse_text(const BString& string, BTextView* textView, const BFont& font,
244*4ad39ed7SStephan Aßmus 	const rgb_color& color, bool changeColor)
24525fb0e67SStephan Aßmus {
246*4ad39ed7SStephan Aßmus 	ParseState rootState(color);
247*4ad39ed7SStephan Aßmus 		// Colors may change, but alpha channel will be preserved
248*4ad39ed7SStephan Aßmus 
24925fb0e67SStephan Aßmus 	ParseState* state = &rootState;
25025fb0e67SStephan Aßmus 
25125fb0e67SStephan Aßmus 	int32 pos = 0;
25225fb0e67SStephan Aßmus 	while (pos < string.Length()) {
25325fb0e67SStephan Aßmus 		int32 nextPos = pos;
25425fb0e67SStephan Aßmus 		int32 tagLength;
25525fb0e67SStephan Aßmus 		bool stateChanged = find_next_tag(string, nextPos, tagLength, state);
25625fb0e67SStephan Aßmus 		if (nextPos > pos) {
257ed74106bSStephan Aßmus 			// Insert text between last and next tags
25825fb0e67SStephan Aßmus 			BString subString;
25925fb0e67SStephan Aßmus 			string.CopyInto(subString, pos, nextPos - pos);
26025fb0e67SStephan Aßmus 			textView->Insert(subString.String());
26125fb0e67SStephan Aßmus 		}
26225fb0e67SStephan Aßmus 		pos = nextPos + tagLength;
26325fb0e67SStephan Aßmus 		if (stateChanged)
26425fb0e67SStephan Aßmus 			apply_state(textView, state, font, changeColor);
26525fb0e67SStephan Aßmus 	}
266ed74106bSStephan Aßmus 
267ed74106bSStephan Aßmus 	// Cleanup states in case the input text had non-matching tags.
268ed74106bSStephan Aßmus 	while (state->previous != NULL) {
269ed74106bSStephan Aßmus 		ParseState* oldState = state->previous;
270ed74106bSStephan Aßmus 		state = state->previous;
271ed74106bSStephan Aßmus 		delete oldState;
272ed74106bSStephan Aßmus 	}
27325fb0e67SStephan Aßmus }
27425fb0e67SStephan Aßmus 
27525fb0e67SStephan Aßmus 
27625fb0e67SStephan Aßmus BRect
27725fb0e67SStephan Aßmus SubtitleBitmap::_InsertText()
27825fb0e67SStephan Aßmus {
27925fb0e67SStephan Aßmus 	BFont font(be_plain_font);
28025fb0e67SStephan Aßmus 	float fontSize = ceilf((fVideoBounds.Width() * 0.9) / 35);
28125fb0e67SStephan Aßmus 	float falseBoldWidth = ceilf(fontSize / 28.0);
28225fb0e67SStephan Aßmus 	font.SetSize(fontSize);
28325fb0e67SStephan Aßmus 
28425fb0e67SStephan Aßmus 	rgb_color shadow;
28525fb0e67SStephan Aßmus 	shadow.red = 0;
28625fb0e67SStephan Aßmus 	shadow.green = 0;
28725fb0e67SStephan Aßmus 	shadow.blue = 0;
28825fb0e67SStephan Aßmus 	shadow.alpha = 200;
28925fb0e67SStephan Aßmus 
29025fb0e67SStephan Aßmus 	rgb_color color;
29125fb0e67SStephan Aßmus 	color.red = 255;
29225fb0e67SStephan Aßmus 	color.green = 255;
29325fb0e67SStephan Aßmus 	color.blue = 255;
29425fb0e67SStephan Aßmus 	color.alpha = 240;
29525fb0e67SStephan Aßmus 
29625fb0e67SStephan Aßmus 	BRect textRect = fVideoBounds;
29725fb0e67SStephan Aßmus 	textRect.OffsetBy(falseBoldWidth, falseBoldWidth);
29825fb0e67SStephan Aßmus 
29925fb0e67SStephan Aßmus 	fTextView->SetText(NULL);
30025fb0e67SStephan Aßmus 	fTextView->SetFontAndColor(&font, B_FONT_ALL, &color);
30125fb0e67SStephan Aßmus 
30225fb0e67SStephan Aßmus 	fTextView->Insert(" ");
303*4ad39ed7SStephan Aßmus 	parse_text(fText, fTextView, font, color, true);
30425fb0e67SStephan Aßmus 
30525fb0e67SStephan Aßmus 	font.SetFalseBoldWidth(falseBoldWidth);
30625fb0e67SStephan Aßmus 	fShadowTextView->SetText(NULL);
30725fb0e67SStephan Aßmus 	fShadowTextView->SetFontAndColor(&font, B_FONT_ALL, &shadow);
30825fb0e67SStephan Aßmus 
30925fb0e67SStephan Aßmus 	fShadowTextView->Insert(" ");
310*4ad39ed7SStephan Aßmus 	parse_text(fText, fShadowTextView, font, shadow, false);
31125fb0e67SStephan Aßmus 
31225fb0e67SStephan Aßmus 	// This causes the BTextView to calculate the layout of the text
31325fb0e67SStephan Aßmus 	fTextView->SetTextRect(BRect(0, 0, 0, 0));
31425fb0e67SStephan Aßmus 	fTextView->SetTextRect(textRect);
31525fb0e67SStephan Aßmus 	fShadowTextView->SetTextRect(BRect(0, 0, 0, 0));
31625fb0e67SStephan Aßmus 	fShadowTextView->SetTextRect(textRect);
31725fb0e67SStephan Aßmus 
31825fb0e67SStephan Aßmus 	textRect = fTextView->TextRect();
31925fb0e67SStephan Aßmus 	textRect.InsetBy(-falseBoldWidth, -falseBoldWidth);
32025fb0e67SStephan Aßmus 	textRect.OffsetTo(B_ORIGIN);
32125fb0e67SStephan Aßmus 	return textRect;
32225fb0e67SStephan Aßmus }
32325fb0e67SStephan Aßmus 
32425fb0e67SStephan Aßmus 
32525fb0e67SStephan Aßmus 
326