diff --git a/docs/tutorials/text-displayer.md b/docs/tutorials/text-displayer.md index 49386df52a..418b1834dd 100644 --- a/docs/tutorials/text-displayer.md +++ b/docs/tutorials/text-displayer.md @@ -28,6 +28,27 @@ const player = new shaka.Player(/* mediaElement= */ null, container); player.setVideoContainer(container); ``` +##### Font size scaling for readability + +For improved readability the option to scale text size is provided via CSS variable (where supported). The application must provide the following fallback setting in its CSS as a bare minimum: + +```css +:root { + --shaka-text-font-size-scaling: 1; +} +``` + +The scale factor can be changed in JS, for example, in response to the user selecting a percentage scale factor: + +```js +const fontSizeSelectElement = document.getElementById("fontScaling"); +const root = document.querySelector(":root"); +root.style.setProperty( + "--shaka-text-font-size-scaling", + fontSizeSelectElement.value / 100 +); +``` + ### Text displayer configuration Additional configuration for the text displayer can be passed by calling: diff --git a/lib/text/ui_text_displayer.js b/lib/text/ui_text_displayer.js index 372c8c23f9..a6ab3dead3 100644 --- a/lib/text/ui_text_displayer.js +++ b/lib/text/ui_text_displayer.js @@ -669,6 +669,26 @@ shaka.text.UITextDisplayer = class { return Cue.positionAlign.CENTER; } + /** + * Applies accessibility font size scaling to the provided fontSize. + * @param {string} fontSize size with units + * @return {string} scaled font size + */ + _accessibilityFontSizeScaling(fontSize) { + if (fontSize && fontSize !== '') { + const fontSizeInfo = + shaka.text.UITextDisplayer.getLengthValueInfo_(fontSize); + if (fontSizeInfo && window.CSS && + CSS.supports('font-size', 'var(--shaka-text-font-size-scaling)')) { + const {value, unit} = fontSizeInfo; + fontSize = `calc(${value}${unit} * ` + + 'var(--shaka-text-font-size-scaling))'; + } + } + + return fontSize; + } + /** * @param {!HTMLElement} cueElement * @param {!shaka.text.Cue} cue @@ -798,8 +818,9 @@ shaka.text.UITextDisplayer = class { style.fontWeight = cue.fontWeight.toString(); style.fontStyle = cue.fontStyle; style.letterSpacing = cue.letterSpacing; - style.fontSize = shaka.text.UITextDisplayer.convertLengthValue_( - cue.fontSize, cue, this.videoContainer_); + style.fontSize = this._accessibilityFontSizeScaling( + shaka.text.UITextDisplayer.convertLengthValue_( + cue.fontSize, cue, this.videoContainer_)); // The line attribute defines the positioning of the text container inside // the video container. diff --git a/test/text/ui_text_displayer_unit.js b/test/text/ui_text_displayer_unit.js index 7abb9bbb06..f42dceb32d 100644 --- a/test/text/ui_text_displayer_unit.js +++ b/test/text/ui_text_displayer_unit.js @@ -73,13 +73,23 @@ describe('UITextDisplayer', () => { textDisplayer.updateCaptions_(); } + function accessibilityScalingFontSize(cueSize) { + let result = cueSize; + if (window.CSS && + CSS.supports('font-size', 'var(--shaka-text-font-size-scaling)')) { + result = `calc(${cueSize}*var(--shaka-text-font-size-scaling))`; + } + return result; + } + it('correctly displays styles for cues', () => { /** @type {!shaka.text.Cue} */ const cue = new shaka.text.Cue(0, 100, 'Captain\'s log.'); cue.color = 'green'; cue.backgroundColor = 'black'; cue.direction = shaka.text.Cue.direction.HORIZONTAL_LEFT_TO_RIGHT; - cue.fontSize = '10px'; + const cueSampleFontSize = '10px'; + cue.fontSize = cueSampleFontSize; cue.fontWeight = shaka.text.Cue.fontWeight.NORMAL; cue.fontStyle = shaka.text.Cue.fontStyle.NORMAL; cue.lineHeight = '2'; @@ -98,7 +108,7 @@ describe('UITextDisplayer', () => { const expectCssObj = { 'color': 'green', 'direction': 'ltr', - 'font-size': '10px', + 'font-size': accessibilityScalingFontSize(cueSampleFontSize), 'font-style': 'normal', 'font-weight': 400, 'text-align': 'center', @@ -130,7 +140,8 @@ describe('UITextDisplayer', () => { nestedCue.writingMode = shaka.text.Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM; nestedCue.color = 'green'; nestedCue.backgroundColor = 'black'; - nestedCue.fontSize = '10px'; + const cueSampleFontSize = '10px'; + nestedCue.fontSize = cueSampleFontSize; nestedCue.fontWeight = shaka.text.Cue.fontWeight.NORMAL; nestedCue.fontStyle = shaka.text.Cue.fontStyle.NORMAL; nestedCue.lineHeight = '2'; @@ -148,7 +159,7 @@ describe('UITextDisplayer', () => { const expectCssObj = { 'color': 'green', - 'font-size': '10px', + 'font-size': accessibilityScalingFontSize(cueSampleFontSize), 'font-style': 'normal', 'font-weight': 400, 'text-align': 'center', @@ -174,7 +185,9 @@ describe('UITextDisplayer', () => { it('correctly displays styles for cellResolution units', () => { /** @type {!shaka.text.Cue} */ const cue = new shaka.text.Cue(0, 100, 'Captain\'s log.'); - cue.fontSize = '0.80c'; + + const fontSizeAsCellResolution = 0.80; + cue.fontSize = `${fontSizeAsCellResolution}c`; cue.linePadding = '0.50c'; cue.cellResolution = { columns: 60, @@ -187,7 +200,10 @@ describe('UITextDisplayer', () => { // Expected value is calculated based on ttp:cellResolution="60 20" // videoContainerHeight=450px and tts:fontSize="0.80c" on the default style. - const expectedFontSize = '18px'; + const calculatedFontSize = (450/20) * fontSizeAsCellResolution; + const expectedFontSize = `${calculatedFontSize}px`; + const expectedAccessibilityFontSize = accessibilityScalingFontSize( + expectedFontSize); // Expected value is calculated based on ttp:cellResolution="60 20" // videoContainerHeight=450px and ebutts:linePadding="0.5c" on the default @@ -199,7 +215,7 @@ describe('UITextDisplayer', () => { const cssObj = parseCssText(captions.style.cssText); expect(cssObj).toEqual( jasmine.objectContaining({ - 'font-size': expectedFontSize, + 'font-size': expectedAccessibilityFontSize, 'padding-left': expectedLinePadding, 'padding-right': expectedLinePadding, })); @@ -208,7 +224,8 @@ describe('UITextDisplayer', () => { it('correctly displays styles for percentages units', () => { /** @type {!shaka.text.Cue} */ const cue = new shaka.text.Cue(0, 100, 'Captain\'s log.'); - cue.fontSize = '90%'; + const cueSampleFontSize = 90; + cue.fontSize = `${cueSampleFontSize}%`; cue.cellResolution = { columns: 32, rows: 15, @@ -220,13 +237,16 @@ describe('UITextDisplayer', () => { // Expected value is calculated based on ttp:cellResolution="32 15" // videoContainerHeight=450px and tts:fontSize="90%" on the default style. - const expectedFontSize = '27px'; + const calculatedFontSize = (450/15) * (cueSampleFontSize/100); + const expectedFontSize = `${calculatedFontSize}px`; + const expectedAccessibilityFontSize = accessibilityScalingFontSize( + expectedFontSize); const textContainer = videoContainer.querySelector('.shaka-text-container'); const captions = textContainer.querySelector('div'); const cssObj = parseCssText(captions.style.cssText); expect(cssObj).toEqual( - jasmine.objectContaining({'font-size': expectedFontSize})); + jasmine.objectContaining({'font-size': expectedAccessibilityFontSize})); }); it('does not display duplicate cues', () => { diff --git a/ui/less/containers.less b/ui/less/containers.less index 77b0fc07ca..7061bd7fd6 100644 --- a/ui/less/containers.less +++ b/ui/less/containers.less @@ -6,6 +6,11 @@ /* All of the top-level containers into which various visible features go. */ +/* CSS Vars that can be overridden at the App */ +:root { + --shaka-text-font-size-scaling: 1; +} + /* A container for the entire video + controls combo. This is the auto-setup * div which we populate. */ .shaka-video-container {