Source: lib/cea/cea708_window.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.cea.Cea708Window');
  7. goog.require('shaka.cea.CeaUtils');
  8. goog.require('shaka.cea.CeaUtils.StyledChar');
  9. goog.require('shaka.cea.ICaptionDecoder');
  10. goog.require('shaka.text.Cue');
  11. goog.require('shaka.util.Functional');
  12. /**
  13. * CEA-708 Window. Each CEA-708 service owns 8 of these.
  14. */
  15. shaka.cea.Cea708Window = class {
  16. /**
  17. * @param {number} windowNum
  18. */
  19. constructor(windowNum) {
  20. /**
  21. * A number from 0 - 7 indicating the window number in the
  22. * service that owns this window.
  23. * @private {number}
  24. */
  25. this.windowNum_ = windowNum;
  26. /**
  27. * Indicates whether this window is visible.
  28. * @private {boolean}
  29. */
  30. this.visible_ = false;
  31. /**
  32. * Indicates whether the horizontal and vertical anchors coordinates specify
  33. * a percentage of the screen, or physical coordinates on the screen.
  34. * @private {boolean}
  35. */
  36. this.relativeToggle_ = false;
  37. /**
  38. * Horizontal anchor. Loosely corresponds to a WebVTT viewport X anchor.
  39. * @private {number}
  40. */
  41. this.horizontalAnchor_ = 0;
  42. /**
  43. * Vertical anchor. Loosely corresponds to a WebVTT viewport Y anchor.
  44. * @private {number}
  45. */
  46. this.verticalAnchor_ = 0;
  47. /**
  48. * If valid, ranges from 0 to 8, specifying one of 9 locations on window:
  49. * 0________1________2
  50. * | | |
  51. * 3________4________5
  52. * | | |
  53. * 6________7________8
  54. * Diagram is valid as per CEA-708-E section 8.4.4.
  55. * Each of these locations corresponds to a WebVTT region's "region anchor".
  56. * @private {number}
  57. */
  58. this.anchorId_ = 0;
  59. /**
  60. * Indicates the number of rows in this window's buffer/memory.
  61. * @private {number}
  62. */
  63. this.rowCount_ = 0;
  64. /**
  65. * Indicates the number of columns in this window's buffer/memory.
  66. * @private {number}
  67. */
  68. this.colCount_ = 0;
  69. /**
  70. * Center by default.
  71. * @private {!shaka.cea.Cea708Window.TextJustification}
  72. */
  73. this.justification_ = shaka.cea.Cea708Window.TextJustification.CENTER;
  74. /**
  75. * An array of rows of styled characters, representing the
  76. * current text and styling of text in this window.
  77. * @private {!Array<!Array<?shaka.cea.CeaUtils.StyledChar>>}
  78. */
  79. this.memory_ = [];
  80. /**
  81. * @private {number}
  82. */
  83. this.startTime_ = 0;
  84. /**
  85. * Row that the current pen is pointing at.
  86. * @private {number}
  87. */
  88. this.row_ = 0;
  89. /**
  90. * Column that the current pen is pointing at.
  91. * @private {number}
  92. */
  93. this.col_ = 0;
  94. /**
  95. * Indicates whether the current pen position is italicized.
  96. * @private {boolean}
  97. */
  98. this.italics_ = false;
  99. /**
  100. * Indicates whether the current pen position is underlined.
  101. * @private {boolean}
  102. */
  103. this.underline_ = false;
  104. /**
  105. * Indicates the text color at the current pen position.
  106. * @private {string}
  107. */
  108. this.textColor_ = shaka.cea.CeaUtils.DEFAULT_TXT_COLOR;
  109. /**
  110. * Indicates the background color at the current pen position.
  111. * @private {string}
  112. */
  113. this.backgroundColor_ = shaka.cea.CeaUtils.DEFAULT_BG_COLOR;
  114. this.resetMemory();
  115. // TODO Support window positioning by mapping them to Regions.
  116. // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-708
  117. shaka.util.Functional.ignored(this.verticalAnchor_, this.relativeToggle_,
  118. this.horizontalAnchor_, this.anchorId_, this.windowNum_);
  119. }
  120. /**
  121. * @param {boolean} visible
  122. * @param {number} verticalAnchor
  123. * @param {number} horizontalAnchor
  124. * @param {number} anchorId
  125. * @param {boolean} relativeToggle
  126. * @param {number} rowCount
  127. * @param {number} colCount
  128. */
  129. defineWindow(visible, verticalAnchor, horizontalAnchor, anchorId,
  130. relativeToggle, rowCount, colCount) {
  131. this.visible_ = visible;
  132. this.verticalAnchor_ = verticalAnchor;
  133. this.horizontalAnchor_ = horizontalAnchor;
  134. this.anchorId_ = anchorId;
  135. this.relativeToggle_ = relativeToggle;
  136. this.rowCount_ = rowCount;
  137. this.colCount_ = colCount;
  138. }
  139. /**
  140. * Resets the memory buffer.
  141. */
  142. resetMemory() {
  143. this.memory_ = [];
  144. for (let i = 0; i < shaka.cea.Cea708Window.MAX_ROWS; i++) {
  145. this.memory_.push(this.createNewRow_());
  146. }
  147. }
  148. /**
  149. * Allocates and returns a new row.
  150. * @return {!Array<?shaka.cea.CeaUtils.StyledChar>}
  151. * @private
  152. */
  153. createNewRow_() {
  154. const row = [];
  155. for (let j = 0; j < shaka.cea.Cea708Window.MAX_COLS; j++) {
  156. row.push(null);
  157. }
  158. return row;
  159. }
  160. /**
  161. * Sets the unicode value for a char at the current pen location.
  162. * @param {string} char
  163. */
  164. setCharacter(char) {
  165. // Check if the pen is out of bounds.
  166. if (!this.isPenInBounds_()) {
  167. return;
  168. }
  169. const cea708Char = new shaka.cea.CeaUtils.StyledChar(
  170. char, this.underline_, this.italics_,
  171. this.backgroundColor_, this.textColor_);
  172. this.memory_[this.row_][this.col_] = cea708Char;
  173. // Increment column
  174. this.col_ ++;
  175. }
  176. /**
  177. * Erases a character from the buffer and moves the pen back.
  178. */
  179. backspace() {
  180. if (!this.isPenInBounds_()) {
  181. return;
  182. }
  183. // Check if a backspace can be done.
  184. if (this.col_ <= 0 && this.row_ <= 0) {
  185. return;
  186. }
  187. if (this.col_ <= 0) {
  188. // Move pen back a row.
  189. this.col_ = this.colCount_ - 1;
  190. this.row_--;
  191. } else {
  192. // Move pen back a column.
  193. this.col_--;
  194. }
  195. // Erase the character occupied at that position.
  196. this.memory_[this.row_][this.col_] = null;
  197. }
  198. /**
  199. * @private
  200. */
  201. isPenInBounds_() {
  202. const inRowBounds = this.row_ < this.rowCount_ && this.row_ >= 0;
  203. const inColBounds = this.col_ < this.colCount_ && this.col_ >= 0;
  204. return inRowBounds && inColBounds;
  205. }
  206. /**
  207. * @return {boolean}
  208. */
  209. isVisible() {
  210. return this.visible_;
  211. }
  212. /**
  213. * Moves up <count> rows in the buffer.
  214. * @param {number} count
  215. * @private
  216. */
  217. moveUpRows_(count) {
  218. let dst = 0; // Row each row should be moved to.
  219. // Move existing rows up by <count>.
  220. for (let i = count; i < shaka.cea.Cea708Window.MAX_ROWS; i++, dst++) {
  221. this.memory_[dst] = this.memory_[i];
  222. }
  223. // Create <count> new rows at the bottom.
  224. for (let i = 0; i < count; i++, dst++) {
  225. this.memory_[dst] = this.createNewRow_();
  226. }
  227. }
  228. /**
  229. * Handles CR. Increments row - if last row, "roll up" all rows by one.
  230. */
  231. carriageReturn() {
  232. if (this.row_ + 1 >= this.rowCount_) {
  233. this.moveUpRows_(1);
  234. this.col_ = 0;
  235. return;
  236. }
  237. this.row_++;
  238. this.col_ = 0;
  239. }
  240. /**
  241. * Handles HCR. Moves the pen to the beginning of the cur. row and clears it.
  242. */
  243. horizontalCarriageReturn() {
  244. this.memory_[this.row_] = this.createNewRow_();
  245. this.col_ = 0;
  246. }
  247. /**
  248. * @param {number} endTime
  249. * @param {number} serviceNumber Number of the service emitting this caption.
  250. * @return {?shaka.cea.ICaptionDecoder.ClosedCaption}
  251. */
  252. forceEmit(endTime, serviceNumber) {
  253. const stream = `svc${serviceNumber}`;
  254. const TextJustification = shaka.cea.Cea708Window.TextJustification;
  255. const topLevelCue = new shaka.text.Cue(
  256. this.startTime_, endTime, /* payload= */ '');
  257. if (this.justification_ === TextJustification.LEFT) {
  258. // LEFT justified.
  259. topLevelCue.textAlign = shaka.text.Cue.textAlign.LEFT;
  260. } else if (this.justification_ === TextJustification.RIGHT) {
  261. // RIGHT justified.
  262. topLevelCue.textAlign = shaka.text.Cue.textAlign.RIGHT;
  263. } else {
  264. // CENTER justified. Both FULL and CENTER are handled as CENTER justified.
  265. topLevelCue.textAlign = shaka.text.Cue.textAlign.CENTER;
  266. }
  267. const caption = shaka.cea.CeaUtils.getParsedCaption(
  268. topLevelCue, stream, this.memory_, this.startTime_, endTime);
  269. if (caption) {
  270. // If a caption is being emitted, then the next caption's start time
  271. // should be no less than this caption's end time.
  272. this.setStartTime(endTime);
  273. }
  274. return caption;
  275. }
  276. /**
  277. * @param {number} row
  278. * @param {number} col
  279. */
  280. setPenLocation(row, col) {
  281. this.row_ = row;
  282. this.col_ = col;
  283. }
  284. /**
  285. * @param {string} backgroundColor
  286. */
  287. setPenBackgroundColor(backgroundColor) {
  288. this.backgroundColor_ = backgroundColor;
  289. }
  290. /**
  291. * @param {string} textColor
  292. */
  293. setPenTextColor(textColor) {
  294. this.textColor_ = textColor;
  295. }
  296. /**
  297. * @param {boolean} underline
  298. */
  299. setPenUnderline(underline) {
  300. this.underline_ = underline;
  301. }
  302. /**
  303. * @param {boolean} italics
  304. */
  305. setPenItalics(italics) {
  306. this.italics_ = italics;
  307. }
  308. /** Reset the pen to 0,0 with default styling. */
  309. resetPen() {
  310. this.row_ = 0;
  311. this.col_ = 0;
  312. this.underline_ = false;
  313. this.italics_ = false;
  314. this.textColor_ = shaka.cea.CeaUtils.DEFAULT_TXT_COLOR;
  315. this.backgroundColor_ = shaka.cea.CeaUtils.DEFAULT_BG_COLOR;
  316. }
  317. /**
  318. * @param {!shaka.cea.Cea708Window.TextJustification} justification
  319. */
  320. setJustification(justification) {
  321. this.justification_ = justification;
  322. }
  323. /**
  324. * Sets the window to visible.
  325. */
  326. display() {
  327. this.visible_ = true;
  328. }
  329. /**
  330. * Sets the window to invisible.
  331. */
  332. hide() {
  333. this.visible_ = false;
  334. }
  335. /**
  336. * Toggles the visibility of the window.
  337. */
  338. toggle() {
  339. this.visible_ = !this.visible_;
  340. }
  341. /**
  342. * Sets the start time for the cue to be emitted.
  343. * @param {number} pts
  344. */
  345. setStartTime(pts) {
  346. this.startTime_ = pts;
  347. }
  348. };
  349. /**
  350. * Caption type.
  351. * @const @enum {number}
  352. */
  353. shaka.cea.Cea708Window.TextJustification = {
  354. LEFT: 0,
  355. RIGHT: 1,
  356. CENTER: 2,
  357. FULL: 3,
  358. };
  359. /**
  360. * Can be indexed 0-31 for 4:3 format, and 0-41 for 16:9 formats.
  361. * Thus the absolute maximum is 42 columns for the 16:9 format.
  362. * @private @const {number}
  363. */
  364. shaka.cea.Cea708Window.MAX_COLS = 42;
  365. /**
  366. * Maximum of 15 rows that can be indexed from 0 to 14.
  367. * @private @const {number}
  368. */
  369. shaka.cea.Cea708Window.MAX_ROWS = 15;