/** * We use this number to calculate the scale of the text. so it will be : 1 + 0.25 * FontSizes.value * So, if the user selects 400% the scale would be: 1 + 0.25 * 4 = 2. so the font size should be multiplied by 2. * The calculation of the size of the font is done in text-track-display and not in this module, because * the calculation in text-track-display also set the location of the container of the subtitiles according to the * font size. * @type {number} */ import { FontScaleOptions, FontSizeOptions, PKTextStyleObject, FontAlignmentOptions } from '../types'; const IMPLICIT_SCALE_PERCENTAGE: number = 0.25; /** * Creates a TextStyle object. * *

* Note that although this API is based on FCC guidelines, we cannot guarantee * that your application is in compliance with this or any other guideline. *

* * @constructor * @struct * @export */ class TextStyle { /** * Defined set of font families * @enum {Object.}} * @export */ public static FontFamily: { [font: string]: string } = { ARIAL: 'Arial', HELVETICA: 'Helvetica', VERDANA: 'Verdana', SANS_SERIF: 'sans-serif', TIMES_NEW_ROMAN: 'Times New Roman', TAHOMA: 'Tahoma', TREBUCHET_MS: 'Trebuchet MS', EB_GARAMOND: 'EB Garamond' }; /** * Defined in {@link https://goo.gl/ZcqOOM FCC 12-9}, paragraph 111, footnote * 448. Each value is an array of the three RGB values for that color. * @enum {Object.}} * @export */ public static StandardColors: { [coloer: string]: [number, number, number] } = { WHITE: [255, 255, 255], BLACK: [0, 0, 0], RED: [255, 0, 0], GREEN: [0, 255, 0], BLUE: [0, 0, 255], YELLOW: [255, 255, 0], MAGENTA: [255, 0, 255], CYAN: [0, 255, 255], DARK_BLUE: [0, 51, 102], LIGHT_YELLOW: [255, 255, 204], LIGHT_GRAY: [204, 204, 204], }; /** * Defined in {@link https://goo.gl/ZcqOOM FCC 12-9}, paragraph 111. * @enum {Object.}} * @export */ public static StandardOpacities: { [opacityLevel: string]: number } = { OPAQUE: 1, SEMI_HIGH: 0.75, SEMI_LOW: 0.25, TRANSPARENT: 0 }; /** * Defined in {@link https://goo.gl/ZcqOOM FCC 12-9}, paragraph 111. * The styles to achieve these effects are not specified anywhere. * * Each inner array represents a shadow, and is composed of RGB values for the * shadow color, followed by pixel values for x-offset, y-offset, and blur. * * @enum {!Array.} * @export */ public static EdgeStyles: { [edgeStyle: string]: Array<[number, number, number, number, number, number]> } = { NONE: [], RAISED: [ [34, 34, 34, 1, 1, 0], [34, 34, 34, 2, 2, 0], [34, 34, 34, 3, 3, 0] ], DEPRESSED: [ [204, 204, 204, 1, 1, 0], [204, 204, 204, 0, 1, 0], [34, 34, 34, -1, -1, 0], [34, 34, 34, 0, -1, 0] ], UNIFORM: [ [34, 34, 34, 0, 0, 4], [34, 34, 34, 0, 0, 4], [34, 34, 34, 0, 0, 4], [34, 34, 34, 0, 0, 4] ], DROP: [ [34, 34, 34, 2, 2, 3], [34, 34, 34, 2, 2, 4], [34, 34, 34, 2, 2, 5] ] }; /** * Possible font sizes are 100%, 115%, 125% */ public static FontSizes: { label: FontSizeOptions; value: FontScaleOptions; name?: string }[] = [ { value: -1, label: '100%', name: 'Small' }, { value: 0, label: '112.5%', name: 'Medium' }, { value: 2, label: '125%', name: 'Large' } ]; /** * Possible font weights are Light, Normal, SemiBold, Bold */ public static StandardFontWeights: { label: string; value: number }[] = [ { label: 'Light', value: 300 }, { label: 'Normal', value: 400 }, { label: 'SemiBold', value: 600 }, { label: 'Bold', value: 700 } ]; /** * Possible font alignments are left, center, right */ public static FontAlignment: { label: string; value: FontAlignmentOptions }[] = [ { label: 'Default', value: 'default' }, { label: 'Left', value: 'left' }, { label: 'Center', value: 'center' }, { label: 'Right', value: 'right' } ]; /** * Creates a CSS RGBA sctring for a given color and opacity values * @param {TextStyle.StandardColors} color - color value in RGB * @param {TextStyle.StandardOpacities} opacity - opacity value * @return {string} - CSS rgba string * @private */ public static toRGBA(color: [number, number, number], opacity: number): string { // shaka.asserts.assert(color.length == 3); return 'rgba(' + color.concat(opacity).join(',') + ')'; } public static fromJson(setting: PKTextStyleObject): TextStyle { const getValue = (newValue: any, defaultValue: any): any => { return typeof newValue !== 'undefined' && newValue !== null ? newValue : defaultValue; }; const textStyle = new TextStyle(); textStyle.fontEdge = getValue(setting.fontEdge, textStyle.fontEdge); textStyle.fontSize = getValue(setting.fontSize, textStyle.fontSize); textStyle.textAlign = getValue(setting.textAlign, textStyle.textAlign); textStyle.fontScale = getValue(setting.fontScale, textStyle.fontScale); textStyle.fontColor = getValue(setting.fontColor, textStyle.fontColor); textStyle.fontOpacity = getValue(setting.fontOpacity, textStyle.fontOpacity); textStyle.backgroundColor = getValue(setting.backgroundColor, textStyle.backgroundColor); textStyle.backgroundOpacity = getValue(setting.backgroundOpacity, textStyle.backgroundOpacity); textStyle.fontFamily = getValue(setting.fontFamily, textStyle.fontFamily); textStyle.fontWeight = getValue(setting.fontWeight, textStyle.fontWeight); return textStyle; } public static toJson(text: TextStyle): PKTextStyleObject { return { fontEdge: text.fontEdge, fontSize: text.fontSize, textAlign: text.textAlign, fontScale: text.fontScale, fontColor: text.fontColor, fontOpacity: text.fontOpacity, backgroundColor: text.backgroundColor, backgroundOpacity: text.backgroundOpacity, fontFamily: text.fontFamily, fontWeight: text.fontWeight }; } private _fontSizeIndex: number = 1; // 100% public set fontSize(fontSize: string) { const index = TextStyle.FontSizes.findIndex(({ label }) => label === fontSize); if (index !== -1) { this._fontSizeIndex = index; } } private _fontWeightIndex: number = 1; // 'Normal' public set fontWeight(weight: string | number) { if (typeof weight === 'string') { const index = TextStyle.StandardFontWeights.findIndex(({ label }) => label.toLowerCase() === weight.toLowerCase()); this._fontWeightIndex = index !== -1 ? index : 1; // Default 'Normal' } else if (typeof weight === 'number') { const index = TextStyle.StandardFontWeights.findIndex(({ value }) => value === weight); this._fontWeightIndex = index !== -1 ? index : 1; // Default 'Normal' } } public textAlign: FontAlignmentOptions = TextStyle.FontAlignment[0].value; /** * Percentage string matching a FontSizes entry */ public get fontSize(): FontSizeOptions { return TextStyle.FontSizes[this._fontSizeIndex].label; } public set fontScale(fontScale: number) { const index = TextStyle.FontSizes.findIndex(({ value }) => value === fontScale); if (index !== -1) { this._fontSizeIndex = index; } } public get fontWeight(): number { return TextStyle.StandardFontWeights[this._fontWeightIndex].value; } /** * Numeric value matching a FontSizes entry (for backward compatibility) */ public get fontScale(): FontScaleOptions { return TextStyle.FontSizes[this._fontSizeIndex].value; } /** * @type {TextStyle.FontFamily} */ public fontFamily: string = TextStyle.FontFamily.SANS_SERIF; /** * @type {TextStyle.StandardColors} */ public fontColor: [number, number, number] = TextStyle.StandardColors.WHITE; /** * @type {TextStyle.StandardOpacities} * @expose */ public fontOpacity: number = TextStyle.StandardOpacities.OPAQUE; /** * @type {TextStyle.StandardColors} */ public backgroundColor: [number, number, number] = TextStyle.StandardColors.BLACK; /** * @type {TextStyle.StandardOpacities} */ public backgroundOpacity: number = TextStyle.StandardOpacities.OPAQUE; /** * @type {TextStyle.EdgeStyles} * @expose */ public fontEdge: Array<[number, number, number, number, number, number]> = TextStyle.EdgeStyles.NONE; public getTextShadow(): string { // A given edge effect may be implemented with multiple shadows. // Collect them all into an array, then combine into one attribute. const shadows: Array = []; for (let i = 0; i < this.fontEdge.length; i++) { // shaka.asserts.assert(this.fontEdge[i].length == 6); const color: [number, number, number] = this.fontEdge[i].slice(0, 3) as any; const shadow: Array = this.fontEdge[i].slice(3, 6); shadows.push(TextStyle.toRGBA(color, this.fontOpacity) + ' ' + shadow.join('px ') + 'px'); } return shadows.join(','); } /** * Compute the CSS text necessary to represent this TextStyle. * Output does not contain any selectors. * * @return {string} - ::CUE CSS string */ public toCSS(): string { const attributes: Array = []; attributes.push('text-align: ' + this.textAlign); attributes.push('font-family: ' + this.fontFamily); attributes.push('color: ' + TextStyle.toRGBA(this.fontColor, this.fontOpacity)); attributes.push(`background: linear-gradient(0deg, ${TextStyle.toRGBA(this.backgroundColor, this.backgroundOpacity)}, ${TextStyle.toRGBA(this.backgroundColor, this.backgroundOpacity)})`); attributes.push('font-size: ' + this.fontSize); attributes.push('text-shadow: ' + this.getTextShadow()); attributes.push('font-weight: ' + this.fontWeight); return attributes.join('!important; '); } /** * clones the textStyle object * @returns {TextStyle} the cloned textStyle object */ public clone(): TextStyle { return TextStyle.fromJson(TextStyle.toJson(this)); } /** * comparing between 2 textStyle objects. * @param {TextStyle} textStyle - The textStyle to compare with. * @returns {boolean} - Whether the text styles are equal. */ public isEqual(textStyle: TextStyle): boolean { return JSON.stringify(TextStyle.toJson(this)) === JSON.stringify(TextStyle.toJson(textStyle)); } public get implicitFontScale(): number { const fontSizeValue = TextStyle.FontSizes[this._fontSizeIndex].value; return IMPLICIT_SCALE_PERCENTAGE * fontSizeValue + 1; } } export default TextStyle;