diff --git a/lib/cea/cea708_service.js b/lib/cea/cea708_service.js index 6e141f681..6ef6001a4 100644 --- a/lib/cea/cea708_service.js +++ b/lib/cea/cea708_service.js @@ -568,7 +568,7 @@ shaka.cea.Cea708Service = class { // Create the window if it doesn't exist. const windowAlreadyExists = this.windows_[windowNum] !== null; if (!windowAlreadyExists) { - const window = new shaka.cea.Cea708Window(windowNum); + const window = new shaka.cea.Cea708Window(windowNum, this.serviceNumber_); window.setStartTime(pts); this.windows_[windowNum] = window; } diff --git a/lib/cea/cea708_window.js b/lib/cea/cea708_window.js index 71e40b672..8d071e2a2 100644 --- a/lib/cea/cea708_window.js +++ b/lib/cea/cea708_window.js @@ -9,7 +9,7 @@ goog.provide('shaka.cea.Cea708Window'); goog.require('shaka.cea.CeaUtils'); goog.require('shaka.cea.CeaUtils.StyledChar'); goog.require('shaka.text.Cue'); -goog.require('shaka.util.Functional'); +goog.require('shaka.text.CueRegion'); /** @@ -19,7 +19,13 @@ shaka.cea.Cea708Window = class { /** * @param {number} windowNum */ - constructor(windowNum) { + constructor(windowNum, parentService) { + /** + * Number for the parent service (1 - 63). + * @private {number} + */ + this.parentService_ = parentService; + /** * A number from 0 - 7 indicating the window number in the * service that owns this window. @@ -132,11 +138,6 @@ shaka.cea.Cea708Window = class { this.backgroundColor_ = shaka.cea.CeaUtils.DEFAULT_BG_COLOR; this.resetMemory(); - - // TODO Support window positioning by mapping them to Regions. - // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-708 - shaka.util.Functional.ignored(this.verticalAnchor_, this.relativeToggle_, - this.horizontalAnchor_, this.anchorId_, this.windowNum_); } /** @@ -306,6 +307,8 @@ shaka.cea.Cea708Window = class { topLevelCue.textAlign = shaka.text.Cue.textAlign.CENTER; } + this.adjustRegion_(topLevelCue.region); + const caption = shaka.cea.CeaUtils.getParsedCaption( topLevelCue, stream, this.memory_, this.startTime_, endTime); if (caption) { @@ -398,6 +401,75 @@ shaka.cea.Cea708Window = class { setStartTime(pts) { this.startTime_ = pts; } + + /** + * Support window positioning by mapping anchor related values to CueRegion. + * https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-708 + * @param {shaka.text.CueRegion} region + * @private + */ + adjustRegion_(region) { + if (this.parentService_) { + region.id += 'svc' + this.parentService_; + } + region.id += 'win' + this.windowNum_; + + region.height = this.rowCount_; + region.width = this.colCount_; + region.heightUnits = shaka.text.CueRegion.units.LINES; + region.widthUnits = shaka.text.CueRegion.units.LINES; + + region.viewportAnchorX = this.horizontalAnchor_; + region.viewportAnchorY = this.verticalAnchor_; + // WebVTT's region viewport anchors are technically always in percentages. + // However, we don't know the aspect ratio of the video at this point, + // which determines how we interpret the horizontal anchor. + // So, we expose the additonal flag to reflect whether these viewport anchor + // values can be used be used as is or should be converted to percentages. + region.viewportAnchorUnits = this.relativeToggle_ ? + shaka.text.CueRegion.units.PERCENTAGE : shaka.text.CueRegion.units.LINES; + + const AnchorId = shaka.cea.Cea708Window.AnchorId; + + switch (this.anchorId_) { + case AnchorId.UPPER_LEFT: + region.regionAnchorX = 0; + region.regionAnchorY = 0; + break; + case AnchorId.UPPER_CENTER: + region.regionAnchorX = 50; + region.regionAnchorY = 0; + break; + case AnchorId.UPPER_RIGHT: + region.regionAnchorX = 100; + region.regionAnchorY = 0; + break; + case AnchorId.MIDDLE_LEFT: + region.regionAnchorX = 0; + region.regionAnchorY = 50; + break; + case AnchorId.MIDDLE_CENTER: + region.regionAnchorX = 50; + region.regionAnchorY = 50; + break; + case AnchorId.MIDDLE_RIGHT: + region.regionAnchorX = 100; + region.regionAnchorY = 50; + break; + case AnchorId.LOWER_LEFT: + region.regionAnchorX = 0; + region.regionAnchorY = 100; + break; + case AnchorId.LOWER_CENTER: + region.regionAnchorX = 50; + region.regionAnchorY = 100; + break; + case AnchorId.LOWER_RIGHT: + region.regionAnchorX = 100; + region.regionAnchorY = 100; + break; + } + } }; /** @@ -411,6 +483,22 @@ shaka.cea.Cea708Window.TextJustification = { FULL: 3, }; +/** + * Possible AnchorId values. + * @const @enum {number} + */ +shaka.cea.Cea708Window.AnchorId = { + UPPER_LEFT: 0, + UPPER_CENTER: 1, + UPPER_RIGHT: 2, + MIDDLE_LEFT: 3, + MIDDLE_CENTER: 4, + MIDDLE_RIGHT: 5, + LOWER_LEFT: 6, + LOWER_CENTER: 7, + LOWER_RIGHT: 8, +}; + /** * Can be indexed 0-31 for 4:3 format, and 0-41 for 16:9 formats. * Thus the absolute maximum is 42 columns for the 16:9 format. diff --git a/test/cea/cea708_service_unit.js b/test/cea/cea708_service_unit.js index 550a08cc5..4cb2d7dd2 100644 --- a/test/cea/cea708_service_unit.js +++ b/test/cea/cea708_service_unit.js @@ -40,6 +40,18 @@ describe('Cea708Service', () => { /** @type {string} */ const stream = `svc${serviceNumber}`; + /** @type {number} */ + const windowId = 0; + + /** @type {number} */ + const rowCount = 16; + + /** @type {number} */ + const colCount = 32; + + /** @type {shaka.cea.Cea708Window.AnchorId} */ + const anchorId = shaka.cea.Cea708Window.AnchorId.UPPER_CENTER; + /** * Takes in a array of bytes and a presentation timestamp (in seconds), * and converts it into a CEA-708 DTVCC Packet. @@ -92,7 +104,8 @@ describe('Cea708Service', () => { const packet2 = createCea708PacketFromBytes(hideWindow, endTime); const text = 'test'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, windowId, rowCount, colCount, anchorId); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text), ]; @@ -129,7 +142,8 @@ describe('Cea708Service', () => { // [1]: // [2]: test const text = 'test'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, windowId, rowCount, colCount, anchorId); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text), CeaUtils.createLineBreakCue(startTime, endTime), @@ -174,7 +188,8 @@ describe('Cea708Service', () => { // Three nested cues, where the middle one should be underlined+italicized. const text = 'test'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, windowId, rowCount, colCount, anchorId); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text), CeaUtils.createStyledCue( @@ -217,7 +232,8 @@ describe('Cea708Service', () => { // Two nested cues, the second one should have colors. const text1 = 'test'; const text2 = 'color'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, windowId, rowCount, colCount, anchorId); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text1), CeaUtils.createStyledCue( @@ -269,7 +285,8 @@ describe('Cea708Service', () => { const text2 = '©¶÷'; const text3 = '⅞┐™'; const text4 = '[CC]'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, windowId, rowCount, colCount, anchorId); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text1), CeaUtils.createLineBreakCue(startTime, endTime), @@ -311,7 +328,8 @@ describe('Cea708Service', () => { // be replaced by an underline. const text1 = '_œ_'; const text2 = '[CC]__'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, windowId, rowCount, colCount, anchorId); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text1), CeaUtils.createLineBreakCue(startTime, endTime), @@ -343,7 +361,8 @@ describe('Cea708Service', () => { // The text in the current window should have been emitted, and then clear // should have been called. const text = 'test'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, windowId, rowCount, colCount, anchorId); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text), ]; @@ -378,7 +397,8 @@ describe('Cea708Service', () => { // Right-justified text is expected. const text = 'test'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, windowId, rowCount, colCount, anchorId); topLevelCue.textAlign = shaka.text.Cue.textAlign.RIGHT; topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text), @@ -413,7 +433,8 @@ describe('Cea708Service', () => { const text1 = 'te'; const text2 = 'st'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, windowId, rowCount, colCount, anchorId); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text1), CeaUtils.createLineBreakCue(startTime, endTime), @@ -456,7 +477,8 @@ describe('Cea708Service', () => { // HCR wipes the row and moves the pen to the row start. const text1 = 'te'; const text2 = 'st'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, windowId, rowCount, colCount, anchorId); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text1), CeaUtils.createLineBreakCue(startTime, endTime), @@ -489,7 +511,8 @@ describe('Cea708Service', () => { // Backspace should have erased the last 't' in 'test'. const text = 'tes'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, windowId, rowCount, colCount, anchorId); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text), ]; @@ -530,7 +553,8 @@ describe('Cea708Service', () => { // The form feed control code would have wiped the entire window // including new lines, and the text after is just 'test'. const text = 'test'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, windowId, rowCount, colCount, anchorId); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text), ]; @@ -605,15 +629,19 @@ describe('Cea708Service', () => { const text1 = 'test'; const text2 = 'testtest'; - const topLevelCue1 = new shaka.text.Cue( - /* startTime= */ time1, /* endTime= */ time2, ''); + const topLevelCue1 = CeaUtils.createWindowedCue( + /* startTime= */ time1, /* endTime= */ time2, '', + serviceNumber, windowId, rowCount, colCount, anchorId, + ); topLevelCue1.nestedCues = [ CeaUtils.createDefaultCue( /* startTime= */ time1, /* endTime= */ time2, /* payload= */ text1), ]; - const topLevelCue2 = new shaka.text.Cue( - /* startTime= */ time3, /* endTime= */ time4, ''); + const topLevelCue2 = CeaUtils.createWindowedCue( + /* startTime= */ time3, /* endTime= */ time4, '', + serviceNumber, windowId, rowCount, colCount, anchorId, + ); topLevelCue2.nestedCues = [ CeaUtils.createDefaultCue( /* startTime= */ time3, /* endTime= */ time4, /* payload= */ text2), @@ -652,8 +680,11 @@ describe('Cea708Service', () => { // Only one cue should have been emitted as per the explanation above. const text = 'test'; - const topLevelCue = new shaka.text.Cue( - /* startTime= */ time1, /* endTime= */ time2, ''); + const topLevelCue = CeaUtils.createWindowedCue( + /* startTime= */ time1, /* endTime= */ time2, '', + serviceNumber, windowId, rowCount, colCount, anchorId, + ); + topLevelCue.nestedCues = [ CeaUtils.createDefaultCue( /* startTime= */ time1, /* endTime= */ time2, /* payload= */ text), diff --git a/test/cea/cea708_window_unit.js b/test/cea/cea708_window_unit.js index e8f6c9891..b885544bb 100644 --- a/test/cea/cea708_window_unit.js +++ b/test/cea/cea708_window_unit.js @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ + describe('Cea708Window', () => { const CeaUtils = shaka.test.CeaUtils; @@ -29,7 +30,7 @@ describe('Cea708Window', () => { const endTime = 2; beforeEach(() => { - window = new shaka.cea.Cea708Window(/* windowNum= */ 0); + window = new shaka.cea.Cea708Window(/* windowNum= */ 0, serviceNumber); window.defineWindow( /* visible= */ true, /* verticalAnchor= */ 0, /* horAnchor= */ 0, /* anchorId= */ 0, /* relativeToggle= */ false, @@ -43,7 +44,8 @@ describe('Cea708Window', () => { window.setCharacter(c); } - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, 0, rowCount, colCount); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, text), ]; @@ -71,7 +73,8 @@ describe('Cea708Window', () => { const caption = window.forceEmit(endTime, serviceNumber); - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, 0, rowCount, colCount); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, text1), CeaUtils.createLineBreakCue(startTime, endTime), @@ -103,7 +106,8 @@ describe('Cea708Window', () => { const caption = window.forceEmit(endTime, serviceNumber); - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, 0, rowCount, colCount); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, text1), CeaUtils.createLineBreakCue(startTime, endTime), @@ -167,7 +171,8 @@ describe('Cea708Window', () => { } // These three stylings should correspond to three nested cues. - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, 0, rowCount, colCount); topLevelCue.nestedCues = [ CeaUtils.createStyledCue(startTime, endTime, text1, /* underline= */ true, /* italics= */ true, textColor1, @@ -190,7 +195,8 @@ describe('Cea708Window', () => { describe('handles justification of cues', () => { const text = 'test'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, 0, rowCount, colCount); it('justifies the text left', () => { for (const c of text) { window.setCharacter(c); @@ -268,7 +274,8 @@ describe('Cea708Window', () => { // The second text should have overwritten the first text, // and all the styles should have been cleared. - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, 0, rowCount, colCount); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, text2), ]; @@ -289,7 +296,8 @@ describe('Cea708Window', () => { } window.backspace(); - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, 0, rowCount, colCount); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, backspacedText), ]; @@ -321,7 +329,8 @@ describe('Cea708Window', () => { // There should be two spaces between the words on the first row, // and then the last row with text should appear 3 linebreaks later. - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, 0, rowCount, colCount); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, text1+' '+text2), CeaUtils.createLineBreakCue(startTime, endTime), @@ -346,7 +355,8 @@ describe('Cea708Window', () => { // Since column size is 32, the buffer should have only taken the first // 32 chars, and omitted the two extra ones at the end. - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, 0, rowCount, colCount); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, trimmedText), ]; @@ -369,5 +379,115 @@ describe('Cea708Window', () => { window.toggle(); // The window was hidden, but is now toggled to visible. expect(window.isVisible()).toBe(true); }); + + describe('correctly handles the window anchors', () => { + it('handles bottom of video window anchors', () => { + window = new shaka.cea.Cea708Window(/* windowNum= */ 0, + /* serviceNumber= */ 1); + window.defineWindow( + /* visible= */ true, /* verticalAnchor= */ 99, + /* horAnchor= */ 50, /* anchorId= */ 7, /* relativeToggle= */ true, + rowCount, colCount); + window.setStartTime(startTime); + + const text = 'test word'; + for (const c of text) { + window.setCharacter(c); + } + + const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + topLevelCue.nestedCues = [ + CeaUtils.createDefaultCue(startTime, endTime, text), + ]; + + const caption = window.forceEmit(endTime, serviceNumber); + + const region = new shaka.text.CueRegion(); + region.id = 'svc1win0'; + region.height = rowCount; + region.width = colCount; + region.heightUnits = shaka.text.CueRegion.units.LINES; + region.widthUnits = shaka.text.CueRegion.units.LINES; + region.viewportAnchorX = 50; + region.viewportAnchorY = 99; + region.regionAnchorX = 50; + region.regionAnchorY = 100; + region.viewportAnchorUnits = shaka.text.CueRegion.units.PERCENTAGE; + + expect(caption.cue.region).toEqual(region); + }); + + it('handles top of video window anchors', () => { + window = new shaka.cea.Cea708Window(/* windowNum= */ 0, + /* serviceNumber= */ 2); + window.defineWindow( + /* visible= */ true, /* verticalAnchor= */ 0, + /* horAnchor= */ 50, /* anchorId= */ 1, /* relativeToggle= */ true, + rowCount, colCount); + window.setStartTime(startTime); + + const text = 'test word'; + for (const c of text) { + window.setCharacter(c); + } + + const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + topLevelCue.nestedCues = [ + CeaUtils.createDefaultCue(startTime, endTime, text), + ]; + + const caption = window.forceEmit(endTime, serviceNumber); + + const region = new shaka.text.CueRegion(); + region.id = 'svc2win0'; + region.height = rowCount; + region.width = colCount; + region.heightUnits = shaka.text.CueRegion.units.LINES; + region.widthUnits = shaka.text.CueRegion.units.LINES; + region.viewportAnchorX = 50; + region.viewportAnchorY = 0; + region.regionAnchorX = 50; + region.regionAnchorY = 0; + region.viewportAnchorUnits = shaka.text.CueRegion.units.PERCENTAGE; + + expect(caption.cue.region).toEqual(region); + }); + + it('handles bottom right of video window anchors using line values', () => { + window = new shaka.cea.Cea708Window(/* windowNum= */ 0, + /* serviceNumbe= */ 3); + window.defineWindow( + /* visible= */ true, /* verticalAnchor= */ 74, + /* horAnchor= */ 209, /* anchorId= */ 8, /* relativeToggle= */ false, + rowCount, colCount); + window.setStartTime(startTime); + + const text = 'test word'; + for (const c of text) { + window.setCharacter(c); + } + + const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + topLevelCue.nestedCues = [ + CeaUtils.createDefaultCue(startTime, endTime, text), + ]; + + const caption = window.forceEmit(endTime, serviceNumber); + + const region = new shaka.text.CueRegion(); + region.id = 'svc3win0'; + region.height = rowCount; + region.width = colCount; + region.heightUnits = shaka.text.CueRegion.units.LINES; + region.widthUnits = shaka.text.CueRegion.units.LINES; + region.viewportAnchorX = 209; + region.viewportAnchorY = 74; + region.regionAnchorX = 100; + region.regionAnchorY = 100; + region.viewportAnchorUnits = shaka.text.CueRegion.units.LINES; + + expect(caption.cue.region).toEqual(region); + }); + }); }); diff --git a/test/cea/cea_decoder_unit.js b/test/cea/cea_decoder_unit.js index 65dc03d7e..6bd2a20e6 100644 --- a/test/cea/cea_decoder_unit.js +++ b/test/cea/cea_decoder_unit.js @@ -565,7 +565,8 @@ describe('CeaDecoder', () => { decoder.extract(hideWindow, endTime); const text = 'test'; - const topLevelCue = new shaka.text.Cue(startTime, endTime, ''); + const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '', + serviceNumber, 0, 11, 11); topLevelCue.nestedCues = [ CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text), ]; diff --git a/test/test/util/cea_utils.js b/test/test/util/cea_utils.js index 8da6e8a28..e923e2f6e 100644 --- a/test/test/util/cea_utils.js +++ b/test/test/util/cea_utils.js @@ -57,4 +57,73 @@ shaka.test.CeaUtils = class { cue.lineBreak = true; return cue; } + + /** + * Create shaka Cue with region updated to a specific value. + * @param {number} startTime + * @param {number} endTime + * @param {string} payload + * @param {number} serviceNumber + * @param {number} windowId + * @param {number} rowCount + * @param {number} colCount + * @param {number=} anchorId + * @return {!shaka.text.Cue} + */ + static createWindowedCue(startTime, endTime, payload, + serviceNumber, windowId, rowCount, colCount, anchorId) { + const cue = new shaka.text.Cue(startTime, endTime, payload); + const region = cue.region; + const AnchorId = shaka.cea.Cea708Window.AnchorId; + + region.id = 'svc' + serviceNumber + 'win' + windowId; + region.height = rowCount; + region.width = colCount; + region.heightUnits = shaka.text.CueRegion.units.LINES; + region.widthUnits = shaka.text.CueRegion.units.LINES; + region.viewportAnchorUnits = shaka.text.CueRegion.units.LINES; + + if (typeof anchorId === 'number') { + switch (anchorId) { + case AnchorId.UPPER_LEFT: + region.regionAnchorX = 0; + region.regionAnchorY = 0; + break; + case AnchorId.UPPER_CENTER: + region.regionAnchorX = 50; + region.regionAnchorY = 0; + break; + case AnchorId.UPPER_RIGHT: + region.regionAnchorX = 100; + region.regionAnchorY = 0; + break; + case AnchorId.MIDDLE_LEFT: + region.regionAnchorX = 0; + region.regionAnchorY = 50; + break; + case AnchorId.MIDDLE_CENTER: + region.regionAnchorX = 50; + region.regionAnchorY = 50; + break; + case AnchorId.MIDDLE_RIGHT: + region.regionAnchorX = 100; + region.regionAnchorY = 50; + break; + case AnchorId.LOWER_LEFT: + region.regionAnchorX = 0; + region.regionAnchorY = 100; + break; + case AnchorId.LOWER_CENTER: + region.regionAnchorX = 50; + region.regionAnchorY = 100; + break; + case AnchorId.LOWER_RIGHT: + region.regionAnchorX = 100; + region.regionAnchorY = 100; + break; + } + } + + return cue; + } };