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