Source: lib/cea/cea708_service.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. // cspell:ignore PNSTY
  7. goog.provide('shaka.cea.Cea708Service');
  8. goog.require('shaka.cea.Cea708Window');
  9. goog.require('shaka.cea.DtvccPacket');
  10. /**
  11. * CEA-708 closed captions service as defined by CEA-708-E. A decoder can own up
  12. * to 63 services. Each service owns eight windows.
  13. */
  14. shaka.cea.Cea708Service = class {
  15. /**
  16. * @param {number} serviceNumber
  17. */
  18. constructor(serviceNumber) {
  19. /**
  20. * Number for this specific service (1 - 63).
  21. * @private {number}
  22. */
  23. this.serviceNumber_ = serviceNumber;
  24. /**
  25. * Eight Cea708 Windows, as defined by the spec.
  26. * @private {!Array<?shaka.cea.Cea708Window>}
  27. */
  28. this.windows_ = [
  29. null, null, null, null, null, null, null, null,
  30. ];
  31. /**
  32. * The current window for which window command operate on.
  33. * @private {?shaka.cea.Cea708Window}
  34. */
  35. this.currentWindow_ = null;
  36. }
  37. /**
  38. * Processes a CEA-708 control code.
  39. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  40. * @return {!Array<shaka.extern.ICaptionDecoder.ClosedCaption>}
  41. * @throws {!shaka.util.Error}
  42. */
  43. handleCea708ControlCode(dtvccPacket) {
  44. const blockData = dtvccPacket.readByte();
  45. let controlCode = blockData.value;
  46. const pts = blockData.pts;
  47. // Read extended control code if needed.
  48. if (controlCode === shaka.cea.Cea708Service.EXT_CEA708_CTRL_CODE_BYTE1) {
  49. const extendedControlCodeBlock = dtvccPacket.readByte();
  50. controlCode = (controlCode << 16) | extendedControlCodeBlock.value;
  51. }
  52. // Control codes are in 1 of 4 logical groups:
  53. // CL (C0, C2), CR (C1, C3), GL (G0, G2), GR (G1, G2).
  54. if (controlCode >= 0x00 && controlCode <= 0x1f) {
  55. return this.handleC0_(dtvccPacket, controlCode, pts);
  56. } else if (controlCode >= 0x80 && controlCode <= 0x9f) {
  57. return this.handleC1_(dtvccPacket, controlCode, pts);
  58. } else if (controlCode >= 0x1000 && controlCode <= 0x101f) {
  59. this.handleC2_(dtvccPacket, controlCode & 0xff);
  60. } else if (controlCode >= 0x1080 && controlCode <= 0x109f) {
  61. this.handleC3_(dtvccPacket, controlCode & 0xff);
  62. } else if (controlCode >= 0x20 && controlCode <= 0x7f) {
  63. this.handleG0_(controlCode);
  64. } else if (controlCode >= 0xa0 && controlCode <= 0xff) {
  65. this.handleG1_(controlCode);
  66. } else if (controlCode >= 0x1020 && controlCode <= 0x107f) {
  67. this.handleG2_(controlCode & 0xff);
  68. } else if (controlCode >= 0x10a0 && controlCode <= 0x10ff) {
  69. this.handleG3_(controlCode & 0xff);
  70. }
  71. return [];
  72. }
  73. /**
  74. * Handles G0 group data.
  75. * @param {number} controlCode
  76. * @private
  77. */
  78. handleG0_(controlCode) {
  79. if (!this.currentWindow_) {
  80. return;
  81. }
  82. // G0 contains ASCII from 0x20 to 0x7f, with the exception that 0x7f
  83. // is replaced by a musical note.
  84. if (controlCode === 0x7f) {
  85. this.currentWindow_.setCharacter('♪');
  86. return;
  87. }
  88. this.currentWindow_.setCharacter(String.fromCharCode(controlCode));
  89. }
  90. /**
  91. * Handles G1 group data.
  92. * @param {number} controlCode
  93. * @private
  94. */
  95. handleG1_(controlCode) {
  96. if (!this.currentWindow_) {
  97. return;
  98. }
  99. // G1 is the Latin-1 Character Set from 0xa0 to 0xff.
  100. this.currentWindow_.setCharacter(String.fromCharCode(controlCode));
  101. }
  102. /**
  103. * Handles G2 group data.
  104. * @param {number} controlCode
  105. * @private
  106. */
  107. handleG2_(controlCode) {
  108. if (!this.currentWindow_) {
  109. return;
  110. }
  111. if (!shaka.cea.Cea708Service.G2Charset.has(controlCode)) {
  112. // If the character is unsupported, the spec says to put an underline.
  113. this.currentWindow_.setCharacter('_');
  114. return;
  115. }
  116. const char = shaka.cea.Cea708Service.G2Charset.get(controlCode);
  117. this.currentWindow_.setCharacter(char);
  118. }
  119. /**
  120. * Handles G3 group data.
  121. * @param {number} controlCode
  122. * @private
  123. */
  124. handleG3_(controlCode) {
  125. if (!this.currentWindow_) {
  126. return;
  127. }
  128. // As of CEA-708-E, the G3 group only contains 1 character. It's a
  129. // [CC] character which has no unicode value on 0xa0.
  130. if (controlCode != 0xa0) {
  131. // Similar to G2, the spec decrees an underline if char is unsupported.
  132. this.currentWindow_.setCharacter('_');
  133. return;
  134. }
  135. this.currentWindow_.setCharacter('[CC]');
  136. }
  137. /**
  138. * Handles C0 group data.
  139. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  140. * @param {number} controlCode
  141. * @param {number} pts
  142. * @return {!Array<shaka.extern.ICaptionDecoder.ClosedCaption>}
  143. * @private
  144. */
  145. handleC0_(dtvccPacket, controlCode, pts) {
  146. // All these commands pertain to the current window, so ensure it exists.
  147. if (!this.currentWindow_) {
  148. return [];
  149. }
  150. if (controlCode == 0x18) {
  151. const firstByte = dtvccPacket.readByte().value;
  152. const secondByte = dtvccPacket.readByte().value;
  153. const toHexString = (byteArray) => {
  154. return byteArray.map((byte) => {
  155. return ('0' + (byte & 0xFF).toString(16)).slice(-2);
  156. }).join('');
  157. };
  158. const unicode = toHexString([firstByte, secondByte]);
  159. // Takes a unicode hex string and creates a single character.
  160. const char = String.fromCharCode(parseInt(unicode, 16));
  161. this.currentWindow_.setCharacter(char);
  162. return [];
  163. }
  164. const window = this.currentWindow_;
  165. let parsedClosedCaption = null;
  166. // Note: This decoder ignores the "ETX" (end of text) control code. Since
  167. // this is JavaScript, a '\0' is not needed to terminate a string.
  168. switch (controlCode) {
  169. case shaka.cea.Cea708Service.ASCII_BACKSPACE:
  170. window.backspace();
  171. break;
  172. case shaka.cea.Cea708Service.ASCII_CARRIAGE_RETURN:
  173. // Force out the buffer, since the top row could be lost.
  174. if (window.isVisible()) {
  175. parsedClosedCaption = window.forceEmit(pts, this.serviceNumber_);
  176. }
  177. window.carriageReturn();
  178. break;
  179. case shaka.cea.Cea708Service.ASCII_HOR_CARRIAGE_RETURN:
  180. // Force out the buffer, a row will be erased.
  181. if (window.isVisible()) {
  182. parsedClosedCaption = window.forceEmit(pts, this.serviceNumber_);
  183. }
  184. window.horizontalCarriageReturn();
  185. break;
  186. case shaka.cea.Cea708Service.ASCII_FORM_FEED:
  187. // Clear window and move pen to (0,0).
  188. // Force emit if the window is visible.
  189. if (window.isVisible()) {
  190. parsedClosedCaption = window.forceEmit(pts, this.serviceNumber_);
  191. }
  192. window.resetMemory();
  193. window.setPenLocation(0, 0);
  194. break;
  195. }
  196. return parsedClosedCaption ? [parsedClosedCaption] : [];
  197. }
  198. /**
  199. * Processes C1 group data.
  200. * These are caption commands.
  201. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  202. * @param {number} captionCommand
  203. * @param {number} pts in seconds
  204. * @return {!Array<shaka.extern.ICaptionDecoder.ClosedCaption>}
  205. * @throws {!shaka.util.Error} a possible out-of-range buffer read.
  206. * @private
  207. */
  208. handleC1_(dtvccPacket, captionCommand, pts) {
  209. // Note: This decoder ignores delay and delayCancel control codes in the C1.
  210. // group. These control codes delay processing of data for a set amount of
  211. // time, however this decoder processes that data immediately.
  212. if (captionCommand >= 0x80 && captionCommand <= 0x87) {
  213. const windowNum = captionCommand & 0x07;
  214. this.setCurrentWindow_(windowNum);
  215. } else if (captionCommand === 0x88) {
  216. const bitmap = dtvccPacket.readByte().value;
  217. return this.clearWindows_(bitmap, pts);
  218. } else if (captionCommand === 0x89) {
  219. const bitmap = dtvccPacket.readByte().value;
  220. this.displayWindows_(bitmap, pts);
  221. } else if (captionCommand === 0x8a) {
  222. const bitmap = dtvccPacket.readByte().value;
  223. return this.hideWindows_(bitmap, pts);
  224. } else if (captionCommand === 0x8b) {
  225. const bitmap = dtvccPacket.readByte().value;
  226. return this.toggleWindows_(bitmap, pts);
  227. } else if (captionCommand === 0x8c) {
  228. const bitmap = dtvccPacket.readByte().value;
  229. return this.deleteWindows_(bitmap, pts);
  230. } else if (captionCommand === 0x8f) {
  231. return this.reset_(pts);
  232. } else if (captionCommand === 0x90) {
  233. this.setPenAttributes_(dtvccPacket);
  234. } else if (captionCommand === 0x91) {
  235. this.setPenColor_(dtvccPacket);
  236. } else if (captionCommand === 0x92) {
  237. this.setPenLocation_(dtvccPacket);
  238. } else if (captionCommand === 0x97) {
  239. this.setWindowAttributes_(dtvccPacket);
  240. } else if (captionCommand >= 0x98 && captionCommand <= 0x9f) {
  241. const windowNum = (captionCommand & 0x0f) - 8;
  242. this.defineWindow_(dtvccPacket, windowNum, pts);
  243. }
  244. return [];
  245. }
  246. /**
  247. * Handles C2 group data.
  248. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  249. * @param {number} controlCode
  250. * @private
  251. */
  252. handleC2_(dtvccPacket, controlCode) {
  253. // As of the CEA-708-E spec there are no commands on the C2 table, but if
  254. // seen, then the appropriate number of bytes must be skipped as per spec.
  255. if (controlCode >= 0x08 && controlCode <= 0x0f) {
  256. dtvccPacket.skip(1);
  257. } else if (controlCode >= 0x10 && controlCode <= 0x17) {
  258. dtvccPacket.skip(2);
  259. } else if (controlCode >= 0x18 && controlCode <= 0x1f) {
  260. dtvccPacket.skip(3);
  261. }
  262. }
  263. /**
  264. * Handles C3 group data.
  265. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  266. * @param {number} controlCode
  267. * @private
  268. */
  269. handleC3_(dtvccPacket, controlCode) {
  270. // As of the CEA-708-E spec there are no commands on the C3 table, but if
  271. // seen, then the appropriate number of bytes must be skipped as per spec.
  272. if (controlCode >= 0x80 && controlCode <= 0x87) {
  273. dtvccPacket.skip(4);
  274. } else if (controlCode >= 0x88 && controlCode <= 0x8f) {
  275. dtvccPacket.skip(5);
  276. }
  277. }
  278. /**
  279. * @param {number} windowNum
  280. * @private
  281. */
  282. setCurrentWindow_(windowNum) {
  283. // If the window isn't created, ignore the command.
  284. if (!this.windows_[windowNum]) {
  285. return;
  286. }
  287. this.currentWindow_ = this.windows_[windowNum];
  288. }
  289. /**
  290. * Yields each non-null window specified in the 8-bit bitmap.
  291. * @param {number} bitmap 8 bits corresponding to each of the 8 windows.
  292. * @return {!Array<number>}
  293. * @private
  294. */
  295. getSpecifiedWindowIds_(bitmap) {
  296. const ids = [];
  297. for (let i = 0; i < 8; i++) {
  298. const windowSpecified = (bitmap & 0x01) === 0x01;
  299. if (windowSpecified && this.windows_[i]) {
  300. ids.push(i);
  301. }
  302. bitmap >>= 1;
  303. }
  304. return ids;
  305. }
  306. /**
  307. * @param {number} windowsBitmap
  308. * @param {number} pts
  309. * @return {!Array<shaka.extern.ICaptionDecoder.ClosedCaption>}
  310. * @private
  311. */
  312. clearWindows_(windowsBitmap, pts) {
  313. const parsedClosedCaptions = [];
  314. // Clears windows from the 8 bit bitmap.
  315. for (const windowId of this.getSpecifiedWindowIds_(windowsBitmap)) {
  316. // If window visible and being cleared, emit buffer and reset start time!
  317. const window = this.windows_[windowId];
  318. if (window.isVisible()) {
  319. const newParsedClosedCaption =
  320. window.forceEmit(pts, this.serviceNumber_);
  321. if (newParsedClosedCaption) {
  322. parsedClosedCaptions.push(newParsedClosedCaption);
  323. }
  324. }
  325. window.resetMemory();
  326. }
  327. return parsedClosedCaptions;
  328. }
  329. /**
  330. * @param {number} windowsBitmap
  331. * @param {number} pts
  332. * @private
  333. */
  334. displayWindows_(windowsBitmap, pts) {
  335. // Displays windows from the 8 bit bitmap.
  336. for (const windowId of this.getSpecifiedWindowIds_(windowsBitmap)) {
  337. const window = this.windows_[windowId];
  338. if (!window.isVisible()) {
  339. // We are turning on the visibility, set the start time.
  340. window.setStartTime(pts);
  341. }
  342. window.display();
  343. }
  344. }
  345. /**
  346. * @param {number} windowsBitmap
  347. * @param {number} pts
  348. * @return {!Array<shaka.extern.ICaptionDecoder.ClosedCaption>}
  349. * @private
  350. */
  351. hideWindows_(windowsBitmap, pts) {
  352. let parsedClosedCaption = null;
  353. // Hides windows from the 8 bit bitmap.
  354. for (const windowId of this.getSpecifiedWindowIds_(windowsBitmap)) {
  355. const window = this.windows_[windowId];
  356. if (window.isVisible()) {
  357. // We are turning off the visibility, emit!
  358. parsedClosedCaption = window.forceEmit(pts, this.serviceNumber_);
  359. }
  360. window.hide();
  361. }
  362. return parsedClosedCaption ? [parsedClosedCaption] : [];
  363. }
  364. /**
  365. * @param {number} windowsBitmap
  366. * @param {number} pts
  367. * @return {!Array<shaka.extern.ICaptionDecoder.ClosedCaption>}
  368. * @private
  369. */
  370. toggleWindows_(windowsBitmap, pts) {
  371. let parsedClosedCaption = null;
  372. // Toggles windows from the 8 bit bitmap.
  373. for (const windowId of this.getSpecifiedWindowIds_(windowsBitmap)) {
  374. const window = this.windows_[windowId];
  375. if (window.isVisible()) {
  376. // We are turning off the visibility, emit!
  377. parsedClosedCaption = window.forceEmit(pts, this.serviceNumber_);
  378. } else {
  379. // We are turning on visibility, set the start time.
  380. window.setStartTime(pts);
  381. }
  382. window.toggle();
  383. }
  384. return parsedClosedCaption ? [parsedClosedCaption] : [];
  385. }
  386. /**
  387. * @param {number} windowsBitmap
  388. * @param {number} pts
  389. * @return {!Array<shaka.extern.ICaptionDecoder.ClosedCaption>}
  390. * @private
  391. */
  392. deleteWindows_(windowsBitmap, pts) {
  393. const parsedClosedCaptions = [];
  394. // Deletes windows from the 8 bit bitmap.
  395. for (const windowId of this.getSpecifiedWindowIds_(windowsBitmap)) {
  396. const window = this.windows_[windowId];
  397. if (window.isVisible()) {
  398. // We are turning off the visibility, emit!
  399. const newParsedClosedCaption =
  400. window.forceEmit(pts, this.serviceNumber_);
  401. if (newParsedClosedCaption) {
  402. parsedClosedCaptions.push(newParsedClosedCaption);
  403. }
  404. }
  405. // Delete the window from the list of windows
  406. this.windows_[windowId] = null;
  407. }
  408. return parsedClosedCaptions;
  409. }
  410. /**
  411. * Emits anything currently present in any of the windows, and then
  412. * deletes all windows, cancels all delays, reinitializes the service.
  413. * @param {number} pts
  414. * @return {!Array<shaka.extern.ICaptionDecoder.ClosedCaption>}
  415. * @private
  416. */
  417. reset_(pts) {
  418. const allWindowsBitmap = 0xff; // All windows should be deleted.
  419. const captions = this.deleteWindows_(allWindowsBitmap, pts);
  420. this.clear();
  421. return captions;
  422. }
  423. /**
  424. * Clears the state of the service completely.
  425. */
  426. clear() {
  427. this.currentWindow_ = null;
  428. this.windows_ = [null, null, null, null, null, null, null, null];
  429. }
  430. /**
  431. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  432. * @throws {!shaka.util.Error}
  433. * @private
  434. */
  435. setPenAttributes_(dtvccPacket) {
  436. // Two bytes follow. For the purpose of this decoder, we are only concerned
  437. // with byte 2, which is of the form |I|U|EDTYP|FNTAG|.
  438. // I (1 bit): Italics toggle.
  439. // U (1 bit): Underline toggle.
  440. // EDTYP (3 bits): Edge type (unused in this decoder).
  441. // FNTAG (3 bits): Font tag (unused in this decoder).
  442. // More info at https://en.wikipedia.org/wiki/CEA-708#SetPenAttributes_(0x90_+_2_bytes)
  443. dtvccPacket.skip(1); // Skip first byte
  444. const attrByte2 = dtvccPacket.readByte().value;
  445. if (!this.currentWindow_) {
  446. return;
  447. }
  448. const italics = (attrByte2 & 0x80) > 0;
  449. const underline = (attrByte2 & 0x40) > 0;
  450. this.currentWindow_.setPenItalics(italics);
  451. this.currentWindow_.setPenUnderline(underline);
  452. }
  453. /**
  454. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  455. * @throws {!shaka.util.Error}
  456. * @private
  457. */
  458. setPenColor_(dtvccPacket) {
  459. // Read foreground and background properties.
  460. const foregroundByte = dtvccPacket.readByte().value;
  461. const backgroundByte = dtvccPacket.readByte().value;
  462. dtvccPacket.skip(1); // Edge color not supported, skip it.
  463. if (!this.currentWindow_) {
  464. return;
  465. }
  466. // Byte semantics are described at the following link:
  467. // https://en.wikipedia.org/wiki/CEA-708#SetPenColor_(0x91_+_3_bytes)
  468. // Foreground color properties: |FOP|F_R|F_G|F_B|.
  469. const foregroundBlue = foregroundByte & 0x03;
  470. const foregroundGreen = (foregroundByte & 0x0c) >> 2;
  471. const foregroundRed = (foregroundByte & 0x30) >> 4;
  472. // Background color properties: |BOP|B_R|B_G|B_B|.
  473. const backgroundBlue = backgroundByte & 0x03;
  474. const backgroundGreen = (backgroundByte & 0x0c) >> 2;
  475. const backgroundRed = (backgroundByte & 0x30) >> 4;
  476. const foregroundColor = this.rgbColorToHex_(
  477. foregroundRed, foregroundGreen, foregroundBlue);
  478. const backgroundColor = this.rgbColorToHex_(
  479. backgroundRed, backgroundGreen, backgroundBlue);
  480. this.currentWindow_.setPenTextColor(foregroundColor);
  481. this.currentWindow_.setPenBackgroundColor(backgroundColor);
  482. }
  483. /**
  484. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  485. * @throws {!shaka.util.Error}
  486. * @private
  487. */
  488. setPenLocation_(dtvccPacket) {
  489. // Following 2 bytes take the following form:
  490. // b1 = |0|0|0|0|ROW| and b2 = |0|0|COLUMN|
  491. const locationByte1 = dtvccPacket.readByte().value;
  492. const locationByte2 = dtvccPacket.readByte().value;
  493. if (!this.currentWindow_) {
  494. return;
  495. }
  496. const row = locationByte1 & 0x0f;
  497. const col = locationByte2 & 0x3f;
  498. this.currentWindow_.setPenLocation(row, col);
  499. }
  500. /**
  501. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  502. * @throws {!shaka.util.Error}
  503. * @private
  504. */
  505. setWindowAttributes_(dtvccPacket) {
  506. // 4 bytes follow, with the following form:
  507. // Byte 1 contains fill-color information. Unused in this decoder.
  508. // Byte 2 contains border color information. Unused in this decoder.
  509. // Byte 3 contains justification information. In this decoder, we only use
  510. // the last 2 bits, which specifies text justification on the screen.
  511. // Byte 4 is special effects. Unused in this decoder.
  512. // More info at https://en.wikipedia.org/wiki/CEA-708#SetWindowAttributes_(0x97_+_4_bytes)
  513. dtvccPacket.skip(1); // Fill color not supported, skip.
  514. dtvccPacket.skip(1); // Border colors not supported, skip.
  515. const b3 = dtvccPacket.readByte().value;
  516. dtvccPacket.skip(1); // Effects not supported, skip.
  517. if (!this.currentWindow_) {
  518. return;
  519. }
  520. // Word wrap is outdated as of CEA-708-E, so we ignore those bits.
  521. // Extract the text justification and set it on the window.
  522. const justification =
  523. /** @type {!shaka.cea.Cea708Window.TextJustification} */ (b3 & 0x03);
  524. this.currentWindow_.setJustification(justification);
  525. }
  526. /**
  527. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  528. * @param {number} windowNum
  529. * @param {number} pts
  530. * @throws {!shaka.util.Error}
  531. * @private
  532. */
  533. defineWindow_(dtvccPacket, windowNum, pts) {
  534. // Create the window if it doesn't exist.
  535. const windowAlreadyExists = this.windows_[windowNum] !== null;
  536. if (!windowAlreadyExists) {
  537. const window = new shaka.cea.Cea708Window(windowNum, this.serviceNumber_);
  538. window.setStartTime(pts);
  539. this.windows_[windowNum] = window;
  540. }
  541. // 6 Bytes follow, with the following form:
  542. // b1 = |0|0|V|R|C|PRIOR| , b2 = |P|VERT_ANCHOR| , b3 = |HOR_ANCHOR|
  543. // b4 = |ANC_ID|ROW_CNT| , b5 = |0|0|COL_COUNT| , b6 = |0|0|WNSTY|PNSTY|
  544. // Semantics of these bytes at https://en.wikipedia.org/wiki/CEA-708#DefineWindow07_(0x98-0x9F,_+_6_bytes)
  545. const b1 = dtvccPacket.readByte().value;
  546. const b2 = dtvccPacket.readByte().value;
  547. const b3 = dtvccPacket.readByte().value;
  548. const b4 = dtvccPacket.readByte().value;
  549. const b5 = dtvccPacket.readByte().value;
  550. const b6 = dtvccPacket.readByte().value;
  551. // As per 8.4.7 of CEA-708-E, row locks and column locks are to be ignored.
  552. // So this decoder will ignore these values.
  553. const visible = (b1 & 0x20) > 0;
  554. const verticalAnchor = b2 & 0x7f;
  555. const relativeToggle = (b2 & 0x80) > 0;
  556. const horAnchor = b3;
  557. const rowCount = (b4 & 0x0f) + 1; // Spec says to add 1.
  558. const anchorId = (b4 & 0xf0) >> 4;
  559. const colCount = (b5 & 0x3f) + 1; // Spec says to add 1.
  560. // If pen style = 0 AND window previously existed, keep its pen style.
  561. // Otherwise, change the pen style (For now, just reset to the default pen).
  562. // TODO add support for predefined pen styles and fonts.
  563. const penStyle = b6 & 0x07;
  564. if (!windowAlreadyExists || penStyle !== 0) {
  565. this.windows_[windowNum].resetPen();
  566. }
  567. this.windows_[windowNum].defineWindow(visible, verticalAnchor,
  568. horAnchor, anchorId, relativeToggle, rowCount, colCount);
  569. // Set the current window to the newly defined window.
  570. this.currentWindow_ = this.windows_[windowNum];
  571. }
  572. /**
  573. * Maps 64 possible CEA-708 colors to 8 CSS colors.
  574. * @param {number} red value from 0-3
  575. * @param {number} green value from 0-3
  576. * @param {number} blue value from 0-3
  577. * @return {string}
  578. * @private
  579. */
  580. rgbColorToHex_(red, green, blue) {
  581. // Rather than supporting 64 colors, this decoder supports 8 colors and
  582. // gets the closest color, as per 9.19 of CEA-708-E. This is because some
  583. // colors on television such as white, are often sent with lower intensity
  584. // and often appear dull/greyish on the browser, making them hard to read.
  585. // As per CEA-708-E 9.19, these mappings will map 64 colors to 8 colors.
  586. const colorMapping = {0: 0, 1: 0, 2: 1, 3: 1};
  587. red = colorMapping[red];
  588. green = colorMapping[green];
  589. blue = colorMapping[blue];
  590. const colorCode = (red << 2) | (green << 1) | blue;
  591. return shaka.cea.Cea708Service.Colors[colorCode];
  592. }
  593. };
  594. /**
  595. * @private @const {number}
  596. */
  597. shaka.cea.Cea708Service.ASCII_BACKSPACE = 0x08;
  598. /**
  599. * @private @const {number}
  600. */
  601. shaka.cea.Cea708Service.ASCII_FORM_FEED = 0x0c;
  602. /**
  603. * @private @const {number}
  604. */
  605. shaka.cea.Cea708Service.ASCII_CARRIAGE_RETURN = 0x0d;
  606. /**
  607. * @private @const {number}
  608. */
  609. shaka.cea.Cea708Service.ASCII_HOR_CARRIAGE_RETURN = 0x0e;
  610. /**
  611. * For extended control codes in block_data on CEA-708, byte 1 is 0x10.
  612. * @private @const {number}
  613. */
  614. shaka.cea.Cea708Service.EXT_CEA708_CTRL_CODE_BYTE1 = 0x10;
  615. /**
  616. * Holds characters mapping for bytes that are G2 control codes.
  617. * @private @const {!Map<number, string>}
  618. */
  619. shaka.cea.Cea708Service.G2Charset = new Map([
  620. [0x20, ' '], [0x21, '\xa0'], [0x25, '…'], [0x2a, 'Š'], [0x2c, 'Œ'],
  621. [0x30, '█'], [0x31, '‘'], [0x32, '’'], [0x33, '“'], [0x34, '”'],
  622. [0x35, '•'], [0x39, '™'], [0x3a, 'š'], [0x3c, 'œ'], [0x3d, '℠'],
  623. [0x3f, 'Ÿ'], [0x76, '⅛'], [0x77, '⅜'], [0x78, '⅝'], [0x79, '⅞'],
  624. [0x7a, '│'], [0x7b, '┐'], [0x7c, '└'], [0x7d, '─'], [0x7e, '┘'], [0x7f, '┌'],
  625. ]);
  626. /**
  627. * An array of 8 colors that 64 colors can be quantized to. Order here matters.
  628. * @private @const {!Array<string>}
  629. */
  630. shaka.cea.Cea708Service.Colors = [
  631. 'black', 'blue', 'green', 'cyan',
  632. 'red', 'magenta', 'yellow', 'white',
  633. ];
  634. /**
  635. * CEA-708 closed captions byte.
  636. * @typedef {{
  637. * pts: number,
  638. * type: number,
  639. * value: number,
  640. * order: number
  641. * }}
  642. *
  643. * @property {number} pts
  644. * Presentation timestamp (in second) at which this packet was received.
  645. * @property {number} type
  646. * Type of the byte. Either 2 or 3, DTVCC Packet Data or a DTVCC Packet Start.
  647. * @property {number} value The byte containing data relevant to the packet.
  648. * @property {number} order
  649. * A number indicating the order this packet was received in a sequence
  650. * of packets. Used to break ties in a stable sorting algorithm
  651. */
  652. shaka.cea.Cea708Service.Cea708Byte;