1*25fb0e67SStephan Aßmus /* 2*25fb0e67SStephan Aßmus * Copyright 2010, Stephan Aßmus <superstippi@gmx.de>. 3*25fb0e67SStephan Aßmus * Distributed under the terms of the MIT License. 4*25fb0e67SStephan Aßmus */ 5*25fb0e67SStephan Aßmus 6*25fb0e67SStephan Aßmus 7*25fb0e67SStephan Aßmus #include "SubtitleBitmap.h" 8*25fb0e67SStephan Aßmus 9*25fb0e67SStephan Aßmus #include <stdio.h> 10*25fb0e67SStephan Aßmus 11*25fb0e67SStephan Aßmus #include <Bitmap.h> 12*25fb0e67SStephan Aßmus #include <TextView.h> 13*25fb0e67SStephan Aßmus 14*25fb0e67SStephan Aßmus 15*25fb0e67SStephan Aßmus SubtitleBitmap::SubtitleBitmap() 16*25fb0e67SStephan Aßmus : 17*25fb0e67SStephan Aßmus fBitmap(NULL), 18*25fb0e67SStephan Aßmus fTextView(new BTextView("offscreen text")), 19*25fb0e67SStephan Aßmus fShadowTextView(new BTextView("offscreen text shadow")) 20*25fb0e67SStephan Aßmus { 21*25fb0e67SStephan Aßmus fTextView->SetStylable(true); 22*25fb0e67SStephan Aßmus fTextView->MakeEditable(false); 23*25fb0e67SStephan Aßmus fTextView->SetWordWrap(false); 24*25fb0e67SStephan Aßmus fTextView->SetAlignment(B_ALIGN_CENTER); 25*25fb0e67SStephan Aßmus 26*25fb0e67SStephan Aßmus fShadowTextView->SetStylable(true); 27*25fb0e67SStephan Aßmus fShadowTextView->MakeEditable(false); 28*25fb0e67SStephan Aßmus fShadowTextView->SetWordWrap(false); 29*25fb0e67SStephan Aßmus fShadowTextView->SetAlignment(B_ALIGN_CENTER); 30*25fb0e67SStephan Aßmus } 31*25fb0e67SStephan Aßmus 32*25fb0e67SStephan Aßmus 33*25fb0e67SStephan Aßmus SubtitleBitmap::~SubtitleBitmap() 34*25fb0e67SStephan Aßmus { 35*25fb0e67SStephan Aßmus delete fBitmap; 36*25fb0e67SStephan Aßmus delete fTextView; 37*25fb0e67SStephan Aßmus delete fShadowTextView; 38*25fb0e67SStephan Aßmus } 39*25fb0e67SStephan Aßmus 40*25fb0e67SStephan Aßmus 41*25fb0e67SStephan Aßmus void 42*25fb0e67SStephan Aßmus SubtitleBitmap::SetText(const char* text) 43*25fb0e67SStephan Aßmus { 44*25fb0e67SStephan Aßmus if (text == fText) 45*25fb0e67SStephan Aßmus return; 46*25fb0e67SStephan Aßmus 47*25fb0e67SStephan Aßmus fText = text; 48*25fb0e67SStephan Aßmus 49*25fb0e67SStephan Aßmus _GenerateBitmap(); 50*25fb0e67SStephan Aßmus } 51*25fb0e67SStephan Aßmus 52*25fb0e67SStephan Aßmus 53*25fb0e67SStephan Aßmus void 54*25fb0e67SStephan Aßmus SubtitleBitmap::SetVideoBounds(BRect bounds) 55*25fb0e67SStephan Aßmus { 56*25fb0e67SStephan Aßmus if (bounds == fVideoBounds) 57*25fb0e67SStephan Aßmus return; 58*25fb0e67SStephan Aßmus 59*25fb0e67SStephan Aßmus fVideoBounds = bounds; 60*25fb0e67SStephan Aßmus 61*25fb0e67SStephan Aßmus _GenerateBitmap(); 62*25fb0e67SStephan Aßmus } 63*25fb0e67SStephan Aßmus 64*25fb0e67SStephan Aßmus 65*25fb0e67SStephan Aßmus const BBitmap* 66*25fb0e67SStephan Aßmus SubtitleBitmap::Bitmap() const 67*25fb0e67SStephan Aßmus { 68*25fb0e67SStephan Aßmus return fBitmap; 69*25fb0e67SStephan Aßmus } 70*25fb0e67SStephan Aßmus 71*25fb0e67SStephan Aßmus 72*25fb0e67SStephan Aßmus void 73*25fb0e67SStephan Aßmus SubtitleBitmap::_GenerateBitmap() 74*25fb0e67SStephan Aßmus { 75*25fb0e67SStephan Aßmus if (!fVideoBounds.IsValid()) 76*25fb0e67SStephan Aßmus return; 77*25fb0e67SStephan Aßmus 78*25fb0e67SStephan Aßmus delete fBitmap; 79*25fb0e67SStephan Aßmus 80*25fb0e67SStephan Aßmus BRect bounds = _InsertText(); 81*25fb0e67SStephan Aßmus 82*25fb0e67SStephan Aßmus fBitmap = new BBitmap(bounds, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32); 83*25fb0e67SStephan Aßmus memset(fBitmap->Bits(), 0, fBitmap->BitsLength()); 84*25fb0e67SStephan Aßmus 85*25fb0e67SStephan Aßmus if (fBitmap->Lock()) { 86*25fb0e67SStephan Aßmus fBitmap->AddChild(fShadowTextView); 87*25fb0e67SStephan Aßmus fShadowTextView->ResizeTo(bounds.Width(), bounds.Height()); 88*25fb0e67SStephan Aßmus 89*25fb0e67SStephan Aßmus fShadowTextView->SetViewColor(0, 0, 0, 0); 90*25fb0e67SStephan Aßmus fShadowTextView->SetDrawingMode(B_OP_ALPHA); 91*25fb0e67SStephan Aßmus fShadowTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); 92*25fb0e67SStephan Aßmus 93*25fb0e67SStephan Aßmus fShadowTextView->PushState(); 94*25fb0e67SStephan Aßmus fShadowTextView->Draw(bounds); 95*25fb0e67SStephan Aßmus fShadowTextView->PopState(); 96*25fb0e67SStephan Aßmus 97*25fb0e67SStephan Aßmus fShadowTextView->Sync(); 98*25fb0e67SStephan Aßmus fShadowTextView->RemoveSelf(); 99*25fb0e67SStephan Aßmus 100*25fb0e67SStephan Aßmus fBitmap->AddChild(fTextView); 101*25fb0e67SStephan Aßmus fTextView->ResizeTo(bounds.Width(), bounds.Height()); 102*25fb0e67SStephan Aßmus 103*25fb0e67SStephan Aßmus fTextView->SetViewColor(0, 0, 0, 0); 104*25fb0e67SStephan Aßmus fTextView->SetDrawingMode(B_OP_ALPHA); 105*25fb0e67SStephan Aßmus fTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); 106*25fb0e67SStephan Aßmus 107*25fb0e67SStephan Aßmus fTextView->PushState(); 108*25fb0e67SStephan Aßmus fTextView->Draw(bounds); 109*25fb0e67SStephan Aßmus fTextView->PopState(); 110*25fb0e67SStephan Aßmus 111*25fb0e67SStephan Aßmus fTextView->Sync(); 112*25fb0e67SStephan Aßmus fTextView->RemoveSelf(); 113*25fb0e67SStephan Aßmus 114*25fb0e67SStephan Aßmus fBitmap->Unlock(); 115*25fb0e67SStephan Aßmus } 116*25fb0e67SStephan Aßmus } 117*25fb0e67SStephan Aßmus 118*25fb0e67SStephan Aßmus 119*25fb0e67SStephan Aßmus struct ParseState { 120*25fb0e67SStephan Aßmus ParseState() 121*25fb0e67SStephan Aßmus : 122*25fb0e67SStephan Aßmus color((rgb_color){ 255, 255, 255, 255 }), 123*25fb0e67SStephan Aßmus bold(false), 124*25fb0e67SStephan Aßmus italic(false), 125*25fb0e67SStephan Aßmus underlined(false), 126*25fb0e67SStephan Aßmus 127*25fb0e67SStephan Aßmus previous(NULL) 128*25fb0e67SStephan Aßmus { 129*25fb0e67SStephan Aßmus } 130*25fb0e67SStephan Aßmus 131*25fb0e67SStephan Aßmus ParseState(ParseState* previous) 132*25fb0e67SStephan Aßmus : 133*25fb0e67SStephan Aßmus color(previous->color), 134*25fb0e67SStephan Aßmus bold(previous->bold), 135*25fb0e67SStephan Aßmus italic(previous->italic), 136*25fb0e67SStephan Aßmus underlined(previous->underlined), 137*25fb0e67SStephan Aßmus 138*25fb0e67SStephan Aßmus previous(previous) 139*25fb0e67SStephan Aßmus { 140*25fb0e67SStephan Aßmus } 141*25fb0e67SStephan Aßmus rgb_color color; 142*25fb0e67SStephan Aßmus bool bold; 143*25fb0e67SStephan Aßmus bool italic; 144*25fb0e67SStephan Aßmus bool underlined; 145*25fb0e67SStephan Aßmus 146*25fb0e67SStephan Aßmus ParseState* previous; 147*25fb0e67SStephan Aßmus }; 148*25fb0e67SStephan Aßmus 149*25fb0e67SStephan Aßmus 150*25fb0e67SStephan Aßmus static bool 151*25fb0e67SStephan Aßmus find_next_tag(const BString& string, int32& tagPos, int32& tagLength, 152*25fb0e67SStephan Aßmus ParseState*& state) 153*25fb0e67SStephan Aßmus { 154*25fb0e67SStephan Aßmus static const char* kTags[] = { 155*25fb0e67SStephan Aßmus "<b>", "</b>", 156*25fb0e67SStephan Aßmus "<i>", "</i>", 157*25fb0e67SStephan Aßmus "<u>", "</u>", 158*25fb0e67SStephan Aßmus "<font color=\"#", "</font>" 159*25fb0e67SStephan Aßmus }; 160*25fb0e67SStephan Aßmus static const int32 kTagCount = sizeof(kTags) / sizeof(const char*); 161*25fb0e67SStephan Aßmus 162*25fb0e67SStephan Aßmus int32 current = tagPos; 163*25fb0e67SStephan Aßmus tagPos = string.Length(); 164*25fb0e67SStephan Aßmus tagLength = 0; 165*25fb0e67SStephan Aßmus 166*25fb0e67SStephan Aßmus BString tag; 167*25fb0e67SStephan Aßmus for (int32 i = 0; i < kTagCount; i++) { 168*25fb0e67SStephan Aßmus int32 nextTag = string.FindFirst(kTags[i], current); 169*25fb0e67SStephan Aßmus if (nextTag >= current && nextTag < tagPos) { 170*25fb0e67SStephan Aßmus tagPos = nextTag; 171*25fb0e67SStephan Aßmus tag = kTags[i]; 172*25fb0e67SStephan Aßmus } 173*25fb0e67SStephan Aßmus } 174*25fb0e67SStephan Aßmus 175*25fb0e67SStephan Aßmus if (tag != "") { 176*25fb0e67SStephan Aßmus tagLength = tag.Length(); 177*25fb0e67SStephan Aßmus if (tag == "<b>") { 178*25fb0e67SStephan Aßmus state = new ParseState(state); 179*25fb0e67SStephan Aßmus state->bold = true; 180*25fb0e67SStephan Aßmus } else if (tag == "<i>") { 181*25fb0e67SStephan Aßmus state = new ParseState(state); 182*25fb0e67SStephan Aßmus state->italic = true; 183*25fb0e67SStephan Aßmus } else if (tag == "<u>") { 184*25fb0e67SStephan Aßmus state = new ParseState(state); 185*25fb0e67SStephan Aßmus state->underlined = true; 186*25fb0e67SStephan Aßmus } else if (tag == "<font color=\"#") { 187*25fb0e67SStephan Aßmus state = new ParseState(state); 188*25fb0e67SStephan Aßmus char number[16]; 189*25fb0e67SStephan Aßmus snprintf(number, sizeof(number), "0x%.6s", 190*25fb0e67SStephan Aßmus string.String() + tagPos + tag.Length()); 191*25fb0e67SStephan Aßmus int colorInt; 192*25fb0e67SStephan Aßmus if (sscanf(number, "%x", &colorInt) == 1) { 193*25fb0e67SStephan Aßmus state->color.red = (colorInt & 0xff0000) >> 16; 194*25fb0e67SStephan Aßmus state->color.green = (colorInt & 0x00ff00) >> 8; 195*25fb0e67SStephan Aßmus state->color.blue = (colorInt & 0x0000ff); 196*25fb0e67SStephan Aßmus tagLength += 8; 197*25fb0e67SStephan Aßmus } 198*25fb0e67SStephan Aßmus } else if (tag == "</b>" || tag == "</i>" || tag == "</u>" 199*25fb0e67SStephan Aßmus || tag == "</font>") { 200*25fb0e67SStephan Aßmus // Pop state 201*25fb0e67SStephan Aßmus if (state->previous != NULL) { 202*25fb0e67SStephan Aßmus ParseState* oldState = state; 203*25fb0e67SStephan Aßmus state = state->previous; 204*25fb0e67SStephan Aßmus delete oldState; 205*25fb0e67SStephan Aßmus } 206*25fb0e67SStephan Aßmus } 207*25fb0e67SStephan Aßmus return true; 208*25fb0e67SStephan Aßmus } 209*25fb0e67SStephan Aßmus return false; 210*25fb0e67SStephan Aßmus } 211*25fb0e67SStephan Aßmus 212*25fb0e67SStephan Aßmus 213*25fb0e67SStephan Aßmus static void 214*25fb0e67SStephan Aßmus apply_state(BTextView* textView, const ParseState* state, BFont font, 215*25fb0e67SStephan Aßmus bool changeColor) 216*25fb0e67SStephan Aßmus { 217*25fb0e67SStephan Aßmus uint16 face = 0; 218*25fb0e67SStephan Aßmus if (state->bold || state->italic || state->underlined) { 219*25fb0e67SStephan Aßmus if (state->bold) 220*25fb0e67SStephan Aßmus face |= B_BOLD_FACE; 221*25fb0e67SStephan Aßmus if (state->italic) 222*25fb0e67SStephan Aßmus face |= B_ITALIC_FACE; 223*25fb0e67SStephan Aßmus if (state->underlined) 224*25fb0e67SStephan Aßmus face |= B_UNDERSCORE_FACE; 225*25fb0e67SStephan Aßmus } else 226*25fb0e67SStephan Aßmus face = B_REGULAR_FACE; 227*25fb0e67SStephan Aßmus font.SetFace(face); 228*25fb0e67SStephan Aßmus if (changeColor) 229*25fb0e67SStephan Aßmus textView->SetFontAndColor(&font, B_FONT_ALL, &state->color); 230*25fb0e67SStephan Aßmus else 231*25fb0e67SStephan Aßmus textView->SetFontAndColor(&font, B_FONT_ALL, NULL); 232*25fb0e67SStephan Aßmus } 233*25fb0e67SStephan Aßmus 234*25fb0e67SStephan Aßmus 235*25fb0e67SStephan Aßmus static void 236*25fb0e67SStephan Aßmus parse_text(const BString& string, BTextView* textView, const BFont& font, 237*25fb0e67SStephan Aßmus bool changeColor) 238*25fb0e67SStephan Aßmus { 239*25fb0e67SStephan Aßmus ParseState rootState; 240*25fb0e67SStephan Aßmus ParseState* state = &rootState; 241*25fb0e67SStephan Aßmus 242*25fb0e67SStephan Aßmus int32 pos = 0; 243*25fb0e67SStephan Aßmus while (pos < string.Length()) { 244*25fb0e67SStephan Aßmus int32 nextPos = pos; 245*25fb0e67SStephan Aßmus int32 tagLength; 246*25fb0e67SStephan Aßmus bool stateChanged = find_next_tag(string, nextPos, tagLength, state); 247*25fb0e67SStephan Aßmus if (nextPos > pos) { 248*25fb0e67SStephan Aßmus BString subString; 249*25fb0e67SStephan Aßmus string.CopyInto(subString, pos, nextPos - pos); 250*25fb0e67SStephan Aßmus textView->Insert(subString.String()); 251*25fb0e67SStephan Aßmus } 252*25fb0e67SStephan Aßmus pos = nextPos + tagLength; 253*25fb0e67SStephan Aßmus if (stateChanged) 254*25fb0e67SStephan Aßmus apply_state(textView, state, font, changeColor); 255*25fb0e67SStephan Aßmus } 256*25fb0e67SStephan Aßmus } 257*25fb0e67SStephan Aßmus 258*25fb0e67SStephan Aßmus 259*25fb0e67SStephan Aßmus BRect 260*25fb0e67SStephan Aßmus SubtitleBitmap::_InsertText() 261*25fb0e67SStephan Aßmus { 262*25fb0e67SStephan Aßmus BFont font(be_plain_font); 263*25fb0e67SStephan Aßmus float fontSize = ceilf((fVideoBounds.Width() * 0.9) / 35); 264*25fb0e67SStephan Aßmus float falseBoldWidth = ceilf(fontSize / 28.0); 265*25fb0e67SStephan Aßmus font.SetSize(fontSize); 266*25fb0e67SStephan Aßmus 267*25fb0e67SStephan Aßmus rgb_color shadow; 268*25fb0e67SStephan Aßmus shadow.red = 0; 269*25fb0e67SStephan Aßmus shadow.green = 0; 270*25fb0e67SStephan Aßmus shadow.blue = 0; 271*25fb0e67SStephan Aßmus shadow.alpha = 200; 272*25fb0e67SStephan Aßmus 273*25fb0e67SStephan Aßmus rgb_color color; 274*25fb0e67SStephan Aßmus color.red = 255; 275*25fb0e67SStephan Aßmus color.green = 255; 276*25fb0e67SStephan Aßmus color.blue = 255; 277*25fb0e67SStephan Aßmus color.alpha = 240; 278*25fb0e67SStephan Aßmus 279*25fb0e67SStephan Aßmus BRect textRect = fVideoBounds; 280*25fb0e67SStephan Aßmus textRect.OffsetBy(falseBoldWidth, falseBoldWidth); 281*25fb0e67SStephan Aßmus 282*25fb0e67SStephan Aßmus fTextView->SetText(NULL); 283*25fb0e67SStephan Aßmus fTextView->SetFontAndColor(&font, B_FONT_ALL, &color); 284*25fb0e67SStephan Aßmus 285*25fb0e67SStephan Aßmus fTextView->Insert(" "); 286*25fb0e67SStephan Aßmus parse_text(fText, fTextView, font, true); 287*25fb0e67SStephan Aßmus 288*25fb0e67SStephan Aßmus font.SetFalseBoldWidth(falseBoldWidth); 289*25fb0e67SStephan Aßmus fShadowTextView->SetText(NULL); 290*25fb0e67SStephan Aßmus fShadowTextView->SetFontAndColor(&font, B_FONT_ALL, &shadow); 291*25fb0e67SStephan Aßmus 292*25fb0e67SStephan Aßmus fShadowTextView->Insert(" "); 293*25fb0e67SStephan Aßmus parse_text(fText, fShadowTextView, font, false); 294*25fb0e67SStephan Aßmus 295*25fb0e67SStephan Aßmus // This causes the BTextView to calculate the layout of the text 296*25fb0e67SStephan Aßmus fTextView->SetTextRect(BRect(0, 0, 0, 0)); 297*25fb0e67SStephan Aßmus fTextView->SetTextRect(textRect); 298*25fb0e67SStephan Aßmus fShadowTextView->SetTextRect(BRect(0, 0, 0, 0)); 299*25fb0e67SStephan Aßmus fShadowTextView->SetTextRect(textRect); 300*25fb0e67SStephan Aßmus 301*25fb0e67SStephan Aßmus textRect = fTextView->TextRect(); 302*25fb0e67SStephan Aßmus textRect.InsetBy(-falseBoldWidth, -falseBoldWidth); 303*25fb0e67SStephan Aßmus textRect.OffsetTo(B_ORIGIN); 304*25fb0e67SStephan Aßmus return textRect; 305*25fb0e67SStephan Aßmus } 306*25fb0e67SStephan Aßmus 307*25fb0e67SStephan Aßmus 308*25fb0e67SStephan Aßmus 309