/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.cea.Cea608Memory');
goog.require('shaka.cea.CeaUtils');
goog.require('shaka.text.Cue');
/**
* CEA-608 captions memory/buffer.
*/
shaka.cea.Cea608Memory = class {
/**
* @param {number} fieldNum Field number.
* @param {number} channelNum Channel number.
*/
constructor(fieldNum, channelNum) {
/**
* Buffer for storing decoded characters.
* @private @const {!Array<!Array<!shaka.cea.CeaUtils.StyledChar>>}
*/
this.rows_ = [];
/**
* Current row.
* @private {number}
*/
this.row_ = 1;
/**
* Number of rows in the scroll window. Used for rollup mode.
* @private {number}
*/
this.scrollRows_ = 0;
/**
* Field number.
* @private {number}
*/
this.fieldNum_ = fieldNum;
/**
* Channel number.
* @private {number}
*/
this.channelNum_ = channelNum;
/**
* @private {boolean}
*/
this.underline_ = false;
/**
* @private {boolean}
*/
this.italics_ = false;
/**
* @private {string}
*/
this.textColor_ = shaka.cea.CeaUtils.DEFAULT_TXT_COLOR;
/**
* @private {string}
*/
this.backgroundColor_ = shaka.cea.CeaUtils.DEFAULT_BG_COLOR;
/**
* @private {?number}
*/
this.offset_ = null;
/**
* @private {?number}
*/
this.indent_ = null;
this.reset();
}
/**
* Emits a closed caption based on the state of the buffer.
* @param {number} startTime Start time of the cue.
* @param {number} endTime End time of the cue.
* @return {?shaka.extern.ICaptionDecoder.ClosedCaption}
*/
forceEmit(startTime, endTime) {
const Cea608Memory = shaka.cea.Cea608Memory;
const stream = `CC${((this.fieldNum_<< 1) | this.channelNum_) + 1}`;
const topLevelCue = new shaka.text.Cue(
startTime, endTime, /* payload= */ '');
topLevelCue.lineInterpretation =
shaka.text.Cue.lineInterpretation.PERCENTAGE;
let line = Cea608Memory.ROW_TO_LINE_CONVERSION_.get(this.row_);
if (line) {
topLevelCue.line = line;
}
if (this.indent_ != null && this.offset_ != null) {
topLevelCue.position = 10 + Math.min(70, this.indent_ * 10) +
this.offset_ * 2.5;
}
const ret = shaka.cea.CeaUtils.getParsedCaption(
topLevelCue, stream, this.rows_, startTime, endTime);
// If the text and its lines are larger than what we can show on the
// screen, we move the lines up so that the text does not come out of the
// video.
if (ret && (this.row_ + ret.cue.nestedCues.length - 3) > 15) {
const newLinePosition = this.row_ + 3 - ret.cue.nestedCues.length;
line = Cea608Memory.ROW_TO_LINE_CONVERSION_.get(newLinePosition);
if (line) {
topLevelCue.line = line;
}
}
return ret;
}
/**
* Resets the memory buffer.
*/
reset() {
this.resetAllRows();
this.row_ = 1;
}
/**
* @return {number}
*/
getRow() {
return this.row_;
}
/**
* @param {number} row
*/
setRow(row) {
this.row_ = row;
}
/**
* @return {number}
*/
getScrollSize() {
return this.scrollRows_;
}
/**
* @param {number} scrollRows
*/
setScrollSize(scrollRows) {
this.scrollRows_ = scrollRows;
}
/**
* Adds a character to the buffer.
* @param {!shaka.cea.Cea608Memory.CharSet} set Character set.
* @param {number} b CC byte to add.
*/
addChar(set, b) {
// Valid chars are in the range [0x20, 0x7f]
if (b < 0x20 || b > 0x7f) {
return;
}
let char = '';
switch (set) {
case shaka.cea.Cea608Memory.CharSet.BASIC_NORTH_AMERICAN:
if (shaka.cea.Cea608Memory.CharSet.BasicNorthAmericanChars.has(b)) {
char =
shaka.cea.Cea608Memory.CharSet.BasicNorthAmericanChars.get(b);
} else {
// Regular ASCII
char = String.fromCharCode(b);
}
break;
case shaka.cea.Cea608Memory.CharSet.SPECIAL_NORTH_AMERICAN:
char =
shaka.cea.Cea608Memory.CharSet.SpecialNorthAmericanChars.get(b);
break;
case shaka.cea.Cea608Memory.CharSet.SPANISH_FRENCH:
// Extended charset does a BS over preceding char, 6.4.2 EIA-608-B.
this.eraseChar();
char =
shaka.cea.Cea608Memory.CharSet.ExtendedSpanishFrench.get(b);
break;
case shaka.cea.Cea608Memory.CharSet.PORTUGUESE_GERMAN:
this.eraseChar();
char =
shaka.cea.Cea608Memory.CharSet.ExtendedPortugueseGerman.get(b);
break;
}
if (char) {
const styledChar = new shaka.cea.CeaUtils.StyledChar(
char, this.underline_, this.italics_,
this.backgroundColor_, this.textColor_);
this.rows_[this.row_].push(styledChar);
}
}
/**
* Erases a character from the buffer.
*/
eraseChar() {
this.rows_[this.row_].pop();
}
/**
* Moves rows of characters.
* @param {number} dst Destination row index.
* @param {number} src Source row index.
* @param {number} count Count of rows to move.
*/
moveRows(dst, src, count) {
if (src < 0 || dst < 0) {
return;
}
if (dst >= src) {
for (let i = count-1; i >= 0; i--) {
this.rows_[dst + i] = this.rows_[src + i].map((e) => e);
}
} else {
for (let i = 0; i < count; i++) {
this.rows_[dst + i] = this.rows_[src + i].map((e) => e);
}
}
}
/**
* Resets rows of characters.
* @param {number} idx Starting index.
* @param {number} count Count of rows to reset.
*/
resetRows(idx, count) {
for (let i = 0; i <= count; i++) {
this.rows_[idx + i] = [];
}
}
/**
* Resets the entire memory buffer.
*/
resetAllRows() {
this.resetRows(0, shaka.cea.Cea608Memory.CC_ROWS);
}
/**
* Erases entire memory buffer.
* Doesn't change scroll state or number of rows.
*/
eraseBuffer() {
this.row_ = (this.scrollRows_ > 0) ? this.scrollRows_ : 0;
this.resetAllRows();
}
/**
* @param {boolean} underline
*/
setUnderline(underline) {
this.underline_ = underline;
}
/**
* @param {boolean} italics
*/
setItalics(italics) {
this.italics_ = italics;
}
/**
* @param {string} color
*/
setTextColor(color) {
this.textColor_ = color;
}
/**
* @param {string} color
*/
setBackgroundColor(color) {
this.backgroundColor_ = color;
}
/**
* @param {number} offset
*/
setOffset(offset) {
this.offset_ = offset;
}
/**
* @param {?number} indent
*/
setIndent(indent) {
this.indent_ = indent;
}
};
/**
* Maximum number of rows in the buffer.
* @const {number}
*/
shaka.cea.Cea608Memory.CC_ROWS = 15;
/**
* Characters sets.
* @const @enum {number}
*/
shaka.cea.Cea608Memory.CharSet = {
BASIC_NORTH_AMERICAN: 0,
SPECIAL_NORTH_AMERICAN: 1,
SPANISH_FRENCH: 2,
PORTUGUESE_GERMAN: 3,
};
/**
* Basic North American char set deviates from ASCII with these exceptions.
* @private @const {!Map<number, string>}
*/
shaka.cea.Cea608Memory.CharSet.BasicNorthAmericanChars = new Map([
[0x27, '’'], [0x2a, 'á'], [0x5c, 'é'], [0x5c, 'é'], [0x5e, 'í'], [0x5f, 'ó'],
[0x60, 'ú'], [0x7b, 'ç'], [0x7c, '÷'], [0x7d, 'Ñ'], [0x7e, 'ñ'], [0x7f, '█'],
]);
/**
* Special North American char set.
* Note: Transparent Space is currently implemented as a regular space.
* @private @const {!Map<number, string>}
*/
shaka.cea.Cea608Memory.CharSet.SpecialNorthAmericanChars = new Map([
[0x30, '®'], [0x31, '°'], [0x32, '½'], [0x33, '¿'], [0x34, '™'], [0x35, '¢'],
[0x36, '£'], [0x37, '♪'], [0x38, 'à'], [0x39, ' '], [0x3a, 'è'], [0x3b, 'â'],
[0x3c, 'ê'], [0x3d, 'î'], [0x3e, 'ô'], [0x3f, 'û'],
]);
/**
* Extended Spanish/Misc/French char set.
* @private @const {!Map<number, string>}
*/
shaka.cea.Cea608Memory.CharSet.ExtendedSpanishFrench = new Map([
[0x20, 'Á'], [0x21, 'É'], [0x22, 'Ó'], [0x23, 'Ú'], [0x24, 'Ü'], [0x25, 'ü'],
[0x26, '‘'], [0x27, '¡'], [0x28, '*'], [0x29, '\''], [0x2a, '─'], [0x2b, '©'],
[0x2c, '℠'], [0x2d, '·'], [0x2e, '“'], [0x2f, '”'], [0x30, 'À'], [0x31, 'Â'],
[0x32, 'Ç'], [0x33, 'È'], [0x34, 'Ê'], [0x35, 'Ë'], [0x36, 'ë'], [0x37, 'Î'],
[0x38, 'Ï'], [0x39, 'ï'], [0x3a, 'Ô'], [0x3b, 'Ù'], [0x3c, 'ù'], [0x3d, 'Û'],
[0x3e, '«'], [0x3f, '»'],
]);
/**
* Extended Portuguese/German/Danish char set.
* @private @const {!Map<number, string>}
*/
shaka.cea.Cea608Memory.CharSet.ExtendedPortugueseGerman = new Map([
[0x20, 'Ã'], [0x21, 'ã'], [0x22, 'Í'], [0x23, 'Ì'], [0x24, 'ì'], [0x25, 'Ò'],
[0x26, 'ò'], [0x27, 'Õ'], [0x28, 'õ'], [0x29, '{'], [0x2a, '}'], [0x2b, '\\'],
[0x2c, '^'], [0x2d, '_'], [0x2e, '|'], [0x2f, '~'], [0x30, 'Ä'], [0x31, 'ä'],
[0x32, 'Ö'], [0x33, 'ö'], [0x34, 'ß'], [0x35, '¥'], [0x36, '¤'], [0x37, '│'],
[0x38, 'Å'], [0x39, 'å'], [0x3a, 'Ø'], [0x3b, 'ø'], [0x3c, '┌'], [0x3d, '┐'],
[0x3e, '└'], [0x3f, '┘'],
]);
/**
* @private @const {!Map<number, number>}
*/
shaka.cea.Cea608Memory.ROW_TO_LINE_CONVERSION_ = new Map([
[1, 10], [2, 15.33], [3, 20.66], [4, 26], [5, 31.33], [6, 36.66], [7, 42],
[8, 47.33], [9, 52.66], [10, 58], [11, 63.33], [12, 68.66], [13, 74],
[14, 79.33], [15, 84.66],
]);