subtitleparser.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. /*
  2. ** Command & Conquer Renegade(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /****************************************************************************
  19. *
  20. * FILE
  21. * $Archive: /Commando/Code/BinkMovie/subtitleparser.cpp $
  22. *
  23. * DESCRIPTION
  24. * Subtitling control file parser
  25. *
  26. * PROGRAMMER
  27. * Denzil E. Long, Jr.
  28. *
  29. * VERSION INFO
  30. * $Author: Denzil_l $
  31. * $Modtime: 1/12/02 9:27p $
  32. * $Revision: 2 $
  33. *
  34. ****************************************************************************/
  35. #include "subtitleparser.h"
  36. #include "subtitle.h"
  37. #include "straw.h"
  38. #include "readline.h"
  39. #include "trim.h"
  40. #include <wchar.h>
  41. #include <stdlib.h>
  42. // Subtitle control file parsing tokens
  43. #define BEGINMOVIE_TOKEN L"BeginMovie"
  44. #define ENDMOVIE_TOKEN L"EndMovie"
  45. #define TIMEBIAS_TOKEN L"TimeBias"
  46. #define TIME_TOKEN L"Time"
  47. #define DURATION_TOKEN L"Duration"
  48. #define POSITION_TOKEN L"Position"
  49. #define COLOR_TOKEN L"Color"
  50. #define TEXT_TOKEN L"Text"
  51. unsigned long DecodeTimeString(wchar_t* string);
  52. void Parse_Time(wchar_t* string, SubTitleClass* subTitle);
  53. void Parse_Duration(wchar_t* string, SubTitleClass* subTitle);
  54. void Parse_Position(wchar_t* string, SubTitleClass* subTitle);
  55. void Parse_Color(wchar_t* string, SubTitleClass* subTitle);
  56. void Parse_Text(wchar_t* string, SubTitleClass* subTitle);
  57. SubTitleParserClass::TokenHook SubTitleParserClass::mTokenHooks[] =
  58. {
  59. {TIME_TOKEN, Parse_Time},
  60. {DURATION_TOKEN, Parse_Duration},
  61. {POSITION_TOKEN, Parse_Position},
  62. {COLOR_TOKEN, Parse_Color},
  63. {TEXT_TOKEN, Parse_Text},
  64. {NULL, NULL}
  65. };
  66. /******************************************************************************
  67. *
  68. * NAME
  69. * SubTitleParserClass::SubTitleParserClass
  70. *
  71. * DESCRIPTION
  72. *
  73. * INPUTS
  74. * Input - Control file input stream.
  75. *
  76. * RESULTS
  77. * NONE
  78. *
  79. ******************************************************************************/
  80. SubTitleParserClass::SubTitleParserClass(Straw& input)
  81. :
  82. mInput(input),
  83. mLineNumber(0)
  84. {
  85. // Check for Unicode byte-order mark.
  86. // All Unicode plaintext files are prefixed with the byte-order mark U+FEFF
  87. // or its mirror U+FFFE. This mark is used to indicate the byte order of a
  88. // text stream.
  89. wchar_t byteOrderMark = 0;
  90. mInput.Get(&byteOrderMark, sizeof(wchar_t));
  91. WWASSERT(byteOrderMark == 0xFEFF);
  92. if (byteOrderMark != 0xFEFF) {
  93. WWDEBUG_SAY(("Error: Subtitle control file is not unicode!\n"));
  94. }
  95. }
  96. /******************************************************************************
  97. *
  98. * NAME
  99. * SubTitleParserClass::~SubTitleParserClass
  100. *
  101. * DESCRIPTION
  102. *
  103. * INPUTS
  104. * NONE
  105. *
  106. * RESULTS
  107. * NONE
  108. *
  109. ******************************************************************************/
  110. SubTitleParserClass::~SubTitleParserClass()
  111. {
  112. }
  113. /******************************************************************************
  114. *
  115. * NAME
  116. * SubTitleParserClass::GetSubTitles
  117. *
  118. * DESCRIPTION
  119. *
  120. * INPUTS
  121. * NONE
  122. *
  123. * RESULTS
  124. *
  125. ******************************************************************************/
  126. DynamicVectorClass<SubTitleClass*>* SubTitleParserClass::Get_Sub_Titles(const char* moviename)
  127. {
  128. DynamicVectorClass<SubTitleClass*>* subTitleCollection = NULL;
  129. // Find the movie marker
  130. if (Find_Movie_Entry(moviename) == true) {
  131. // Allocate container to hold subtitles
  132. subTitleCollection = new DynamicVectorClass<SubTitleClass*>;
  133. WWASSERT(subTitleCollection != NULL);
  134. if (subTitleCollection != NULL) {
  135. for (;;) {
  136. // Retrieve a line from the control file
  137. wchar_t* string = Get_Next_Line();
  138. if ((string != NULL) && (wcslen(string) > 0)) {
  139. // Check for subtitle entry markers
  140. if ((string[0] == L'<') && (string[wcslen(string) - 1] == L'>')) {
  141. // Trim off markers
  142. string++;
  143. string[wcslen(string) - 1] = 0;
  144. wcstrim(string);
  145. // Ignore empty caption
  146. if (wcslen(string) == 0) {
  147. continue;
  148. }
  149. // Create a new SubTitleClass
  150. SubTitleClass* subTitle = new SubTitleClass();
  151. WWASSERT(subTitle != NULL);
  152. if (subTitle == NULL) {
  153. WWDEBUG_SAY(("***** Failed to create SubTitleClass!\n"));
  154. break;
  155. }
  156. if (Parse_Sub_Title(string, subTitle) == true) {
  157. subTitleCollection->Add(subTitle);
  158. }
  159. else {
  160. delete subTitle;
  161. }
  162. continue;
  163. }
  164. // Terminate if end movie token encountered.
  165. if (wcsnicmp(string, ENDMOVIE_TOKEN, wcslen(ENDMOVIE_TOKEN)) == 0) {
  166. break;
  167. }
  168. }
  169. }
  170. if (subTitleCollection->Count() == 0) {
  171. delete subTitleCollection;
  172. subTitleCollection = NULL;
  173. }
  174. }
  175. }
  176. return subTitleCollection;
  177. }
  178. /******************************************************************************
  179. *
  180. * NAME
  181. * SubTitleParserClass::FindMovieEntry
  182. *
  183. * DESCRIPTION
  184. * No description provided,
  185. *
  186. * INPUTS
  187. * Moviename - Pointer to name of movie to find subtitles for.
  188. *
  189. * RESULTS
  190. * Success - True if movie entry found; False if unable to find movie entry.
  191. *
  192. ******************************************************************************/
  193. bool SubTitleParserClass::Find_Movie_Entry(const char* moviename)
  194. {
  195. // Convert the moviename into Unicode
  196. WWASSERT(moviename != NULL);
  197. wchar_t wideName[32];
  198. mbstowcs(wideName, moviename, 32);
  199. do {
  200. // Retrieve line of text
  201. wchar_t* string = Get_Next_Line();
  202. // Terminate if no string read.
  203. if (string == NULL) {
  204. break;
  205. }
  206. // Look for begin movie token
  207. if (wcsnicmp(string, BEGINMOVIE_TOKEN, wcslen(BEGINMOVIE_TOKEN)) == 0) {
  208. // Get moviename following the token
  209. wchar_t* ptr = wcschr(string, L' ');
  210. // Check for matching moviename
  211. if (ptr != NULL) {
  212. wcstrim(ptr);
  213. if (wcsicmp(ptr, wideName) == 0) {
  214. WWDEBUG_SAY(("Found movie entry %s\n", moviename));
  215. return true;
  216. }
  217. }
  218. }
  219. } while (true);
  220. return false;
  221. }
  222. /******************************************************************************
  223. *
  224. * NAME
  225. * SubTitleParserClass::ParseSubTitle
  226. *
  227. * DESCRIPTION
  228. *
  229. * INPUTS
  230. * wchar_t* string
  231. * SubTitleClass* subTitle
  232. *
  233. * RESULTS
  234. * bool
  235. *
  236. ******************************************************************************/
  237. bool SubTitleParserClass::Parse_Sub_Title(wchar_t* string, SubTitleClass* subTitle)
  238. {
  239. // Parameter check
  240. WWASSERT(string != NULL);
  241. WWASSERT(subTitle != NULL);
  242. for (;;) {
  243. // Find token separator
  244. wchar_t* separator = wcschr(string, L'=');
  245. if (separator == NULL) {
  246. WWDEBUG_SAY(("Error on line %d: syntax error\n", Get_Line_Number()));
  247. return false;
  248. }
  249. // NULL terminate token part
  250. *separator++ = 0;
  251. // Tokens are to the left of the separator
  252. wchar_t* token = string;
  253. wcstrim(token);
  254. // Parameters are to the right of the separator
  255. wchar_t* param = separator;
  256. wcstrim(param);
  257. // Quoted parameters are treated as literals (ignore contents)
  258. if (param[0] == L'"') {
  259. // Skip leading quote
  260. param++;
  261. // Use next quote to mark end of parameter
  262. separator = wcschr(param, L'"');
  263. if (separator == NULL) {
  264. WWDEBUG_SAY(("Error on line %d: mismatched quotes\n", Get_Line_Number()));
  265. return false;
  266. }
  267. // NULL terminate parameter
  268. *separator++ = 0;
  269. // Skip any comma following a literal string since we used the trailing
  270. // quote to terminate the tokens parameters
  271. wcstrim(separator);
  272. if (*separator == L',') {
  273. separator++;
  274. }
  275. // Advance string past quoted parameter
  276. string = separator;
  277. }
  278. else {
  279. // Look for separator to next token
  280. separator = wcspbrk(param, L", ");
  281. if (separator != NULL) {
  282. *separator++ = 0;
  283. string = separator;
  284. }
  285. else {
  286. string = L"";
  287. }
  288. }
  289. // Error on empty tokens
  290. if (wcslen(token) == 0) {
  291. WWDEBUG_SAY(("Error on line %d: missing token\n", Get_Line_Number()));
  292. return false;
  293. }
  294. // Parse current token
  295. Parse_Token(token, param, subTitle);
  296. // Prepare for next token
  297. wcstrim(string);
  298. if (wcslen(string) == 0) {
  299. break;
  300. }
  301. }
  302. return true;
  303. }
  304. /******************************************************************************
  305. *
  306. * NAME
  307. * SubTitleParserClass::ParseToken
  308. *
  309. * DESCRIPTION
  310. *
  311. * INPUTS
  312. * wchar_t* token
  313. * wchar_t* param
  314. * SubTitleClass* subTitle
  315. *
  316. * RESULTS
  317. * NONE
  318. *
  319. ******************************************************************************/
  320. void SubTitleParserClass::Parse_Token(wchar_t* token, wchar_t* param, SubTitleClass* subTitle)
  321. {
  322. // Parameter check
  323. WWASSERT(token != NULL);
  324. WWASSERT(subTitle != NULL);
  325. if (token != NULL) {
  326. int index = 0;
  327. while (mTokenHooks[index].Token != NULL) {
  328. TokenHook& hook = mTokenHooks[index];
  329. if (wcsicmp(hook.Token, token) == 0) {
  330. WWASSERT(subTitle != NULL);
  331. hook.Handler(param, subTitle);
  332. return;
  333. }
  334. index++;
  335. }
  336. }
  337. }
  338. /******************************************************************************
  339. *
  340. * NAME
  341. * SubTitleParserClass::GetNextLine
  342. *
  343. * DESCRIPTION
  344. * Retrieve the next line of text from the control file.
  345. *
  346. * INPUTS
  347. * NONE
  348. *
  349. * RESULTS
  350. * String - Pointer to next line of text. NULL if error or EOF.
  351. *
  352. ******************************************************************************/
  353. wchar_t* SubTitleParserClass::Get_Next_Line(void)
  354. {
  355. bool eof = false;
  356. while (eof == false) {
  357. // Read in a line of text
  358. Read_Line(mInput, mBuffer, LINE_MAX, eof);
  359. mLineNumber++;
  360. // Remove whitespace
  361. wchar_t* string = wcstrim(mBuffer);
  362. // Skip comments and blank lines
  363. if ((wcslen(string) > 0) && (string[0] != L';')) {
  364. return string;
  365. }
  366. }
  367. return NULL;
  368. }
  369. // Convert a time string in the format hh:mm:ss:tt into 1/60 second ticks.
  370. unsigned long Decode_Time_String(wchar_t* string)
  371. {
  372. #define TICKS_PER_SECOND 60
  373. #define TICKS_PER_MINUTE (60 * TICKS_PER_SECOND)
  374. #define TICKS_PER_HOUR (60 * TICKS_PER_MINUTE)
  375. WWASSERT(string != NULL);
  376. wchar_t buffer[12];
  377. wcsncpy(buffer, string, 12);
  378. buffer[11] = 0;
  379. wchar_t* ptr = &buffer[0];
  380. // Isolate hours part
  381. wchar_t* separator = wcschr(ptr, L':');
  382. WWASSERT(separator != NULL);
  383. *separator++ = 0;
  384. unsigned long hours = wcstoul(ptr, NULL, 10);
  385. // Isolate minutes part
  386. ptr = separator;
  387. separator = wcschr(ptr, L':');
  388. WWASSERT(separator != NULL);
  389. *separator++ = 0;
  390. unsigned long minutes = wcstoul(ptr, NULL, 10);
  391. // Isolate seconds part
  392. ptr = separator;
  393. separator = wcschr(ptr, L':');
  394. WWASSERT(separator != NULL);
  395. *separator++ = 0;
  396. unsigned long seconds = wcstoul(ptr, NULL, 10);
  397. // Isolate hundredth part (1/100th of a second)
  398. ptr = separator;
  399. unsigned long hundredth = wcstoul(ptr, NULL, 10);
  400. unsigned long time = (hours * TICKS_PER_HOUR);
  401. time += (minutes * TICKS_PER_MINUTE);
  402. time += (seconds * TICKS_PER_SECOND);
  403. time += ((hundredth * TICKS_PER_SECOND) / 100);
  404. return time;
  405. }
  406. void Parse_Time(wchar_t* param, SubTitleClass* subTitle)
  407. {
  408. WWASSERT(param != NULL);
  409. WWASSERT(subTitle != NULL);
  410. unsigned long time = Decode_Time_String(param);
  411. subTitle->Set_Display_Time(time);
  412. }
  413. void Parse_Duration(wchar_t* param, SubTitleClass* subTitle)
  414. {
  415. WWASSERT(param != NULL);
  416. WWASSERT(subTitle != NULL);
  417. unsigned long time = Decode_Time_String(param);
  418. if (time > 0) {
  419. subTitle->Set_Display_Duration(time);
  420. }
  421. }
  422. void Parse_Position(wchar_t* param, SubTitleClass* subTitle)
  423. {
  424. static struct
  425. {
  426. const wchar_t* Name;
  427. SubTitleClass::Alignment Align;
  428. } _alignLookup[] = {
  429. {L"Left", SubTitleClass::Left},
  430. {L"Right", SubTitleClass::Right},
  431. {L"Center", SubTitleClass::Center},
  432. {NULL, SubTitleClass::Center}
  433. };
  434. WWASSERT(subTitle != NULL);
  435. WWASSERT(param != NULL);
  436. wchar_t* ptr = param;
  437. // Line position
  438. wchar_t* separator = wcschr(ptr, L':');
  439. if (separator != NULL) {
  440. *separator++ = 0;
  441. int linePos = wcstol(ptr, NULL, 0);
  442. subTitle->Set_Line_Position(linePos);
  443. ptr = separator;
  444. }
  445. // Justification
  446. SubTitleClass::Alignment align = SubTitleClass::Center;
  447. int index = 0;
  448. while (_alignLookup[index].Name != NULL) {
  449. if (wcsicmp(ptr, _alignLookup[index].Name) == 0) {
  450. align = _alignLookup[index].Align;
  451. break;
  452. }
  453. index++;
  454. }
  455. subTitle->Set_Alignment(align);
  456. }
  457. void Parse_Color(wchar_t* param, SubTitleClass* subTitle)
  458. {
  459. WWASSERT(param != NULL);
  460. WWASSERT(subTitle != NULL);
  461. wchar_t* ptr = param;
  462. wchar_t* separator = wcschr(ptr, L':');
  463. *separator++ = 0;
  464. unsigned char red = (unsigned char)wcstoul(ptr, NULL, 10);
  465. ptr = separator;
  466. separator = wcschr(ptr, L':');
  467. *separator++ = 0;
  468. unsigned char green = (unsigned char)wcstoul(ptr, NULL, 10);
  469. ptr = separator;
  470. unsigned char blue = (unsigned char)wcstoul(ptr, NULL, 10);
  471. subTitle->Set_RGB_Color(red, green, blue);
  472. }
  473. void Parse_Text(wchar_t* param, SubTitleClass* subTitle)
  474. {
  475. WWASSERT(param != NULL);
  476. WWASSERT(subTitle != NULL);
  477. subTitle->Set_Caption(param);
  478. }