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