Merge branch 'use-old-style-text-outline' into 'master'

Switch to using the old way of making text outlines

Closes #1016

See merge request openlp/openlp!432
This commit is contained in:
Tim Bentley 2022-03-01 07:49:44 +00:00
commit 52b5843d5d
4 changed files with 312 additions and 16 deletions

View File

@ -158,6 +158,45 @@ function _buildRadialGradient(width, startColor, endColor) {
return "radial-gradient(" + startColor + ", " + endColor + ") fixed";
}
/**
* Build a set of text shadows to form an outline
* @private
* @param {Number} size - The desired width of the outline
* @param {string} color - The color of the outline
* @returns {array} A list of shadows to be given to "text-shadow"
*/
function _buildTextOutline(size, color) {
let shadows = [];
// Outlines work from -(size) to +(size)
let from = size * -1;
// Loop through all the possible size iterations and add them to the array
for (let i = from; i <= size; i++) {
for (let j = from; j <= size; j++) {
shadows.push(color + " " + i + "pt " + j + "pt 0pt");
}
}
return shadows;
}
/**
* Build a text shadow
* @private
* @param {Number} offset - The offset of the shadow
* @param {string} color - The color that the shadow should be
* @returns {string} The text-shadow rule
*/
function _buildTextShadow(offset, size, color) {
let shadows = [];
let from = (size * -1) + offset;
let to = size + offset;
for (let i = from; i <= to; i++) {
for (let j = from; j <= to; j++) {
shadows.push(color + " " + i + "pt " + j + "pt 0pt");
}
}
return shadows.join(", ");
}
/**
* Get a style value from an element (computed or manual)
* @private
@ -464,11 +503,9 @@ var Display = {
return null;
}
if (Display._alertState === AlertState.Displaying) {
console.debug("Adding to queue");
Display.addAlertToQueue(text, settings);
}
else {
console.debug("Displaying immediately");
Display.showAlert(text, settings);
}
},
@ -541,7 +578,6 @@ var Display = {
*/
alertTransitionEndEvent: function (e) {
e.stopPropagation();
console.debug("Transition end event reached: " + Display._transitionState);
if (Display._transitionState === TransitionState.EntranceTransition) {
Display._transitionState = TransitionState.NoTransition;
}
@ -1018,13 +1054,11 @@ var Display = {
break;
case BackgroundType.Image:
backgroundContent = "url('" + Display._theme.background_filename + "')";
console.warn(backgroundContent);
break;
case BackgroundType.Video:
// never actually used since background type is overridden from video to transparent in window.py
backgroundContent = Display._theme.background_border_color;
backgroundHtml = "<video loop autoplay muted><source src='" + Display._theme.background_filename + "'></video>";
console.warn(backgroundHtml);
break;
default:
backgroundContent = "#000";
@ -1039,11 +1073,6 @@ var Display = {
return;
}
mainStyle = {};
if (!!Display._theme.font_main_outline) {
mainStyle["-webkit-text-stroke"] = "" + Display._theme.font_main_outline_size + "pt " +
Display._theme.font_main_outline_color;
mainStyle["-webkit-text-fill-color"] = Display._theme.font_main_color;
}
// These need to be fixed, in the Python they use a width passed in as a parameter
mainStyle.width = Display._theme.font_main_width + "px";
mainStyle.height = Display._theme.font_main_height + "px";
@ -1092,9 +1121,22 @@ var Display = {
default:
mainStyle["justify-content"] = "center";
}
if (Display._theme.hasOwnProperty('font_main_shadow_size') && !!Display._theme.font_main_shadow) {
mainStyle["text-shadow"] = Display._theme.font_main_shadow_color + " " + Display._theme.font_main_shadow_size + "pt " +
Display._theme.font_main_shadow_size + "pt";
/**
* This section draws the font outline. Previously we used the proprietary -webkit-text-stroke property
* but it draws the outline INSIDE the text, instead of OUTSIDE, so we had to go back to the old way
* of using multiple text-shadow rules to fake an outline.
*/
if (!!Display._theme.font_main_outline && Display._theme.hasOwnProperty('font_main_shadow_size') && !!Display._theme.font_main_shadow) {
let outlineShadows = _buildTextOutline(Display._theme.font_main_outline_size, Display._theme.font_main_outline_color);
let textShadow = _buildTextShadow(Display._theme.font_main_shadow_size, Display._theme.font_main_outline_size, Display._theme.font_main_shadow_color);
mainStyle["text-shadow"] = outlineShadows.join(", ") + ", " + textShadow;
}
else if (!!Display._theme.font_main_outline) {
let outlineShadows = _buildTextOutline(Display._theme.font_main_outline_size, Display._theme.font_main_outline_color);
mainStyle["text-shadow"] = outlineShadows.join(", ");
}
else if (Display._theme.hasOwnProperty('font_main_shadow_size') && !!Display._theme.font_main_shadow) {
mainStyle["text-shadow"] = _buildTextShadow(Display._theme.font_main_shadow_size, 0, Display._theme.font_main_shadow_color);
}
targetElement.style.cssText = "";
for (var mainKey in mainStyle) {

View File

@ -377,7 +377,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
self.theme.font_main_outline_size = self.main_area_page.outline_size
self.theme.font_main_shadow = self.main_area_page.is_shadow_enabled
self.theme.font_main_shadow_size = self.main_area_page.shadow_size
self.main_area_page.shadow_color = self.theme.font_main_shadow_color
self.theme.font_main_shadow_color = self.main_area_page.shadow_color
self.theme.font_main_bold = self.main_area_page.is_bold
self.theme.font_main_italics = self.main_area_page.is_italic
# footer page

View File

@ -53,6 +53,68 @@ describe("The function", function () {
expect(gradient).toBe("radial-gradient(#000, #fff) fixed");
});
it("_buildTextOutline should return an array of text-shadow values", function () {
let shadows = _buildTextOutline(2, "#fff");
expect(shadows).toEqual([
"#fff -2pt -2pt 0pt",
"#fff -2pt -1pt 0pt",
"#fff -2pt 0pt 0pt",
"#fff -2pt 1pt 0pt",
"#fff -2pt 2pt 0pt",
"#fff -1pt -2pt 0pt",
"#fff -1pt -1pt 0pt",
"#fff -1pt 0pt 0pt",
"#fff -1pt 1pt 0pt",
"#fff -1pt 2pt 0pt",
"#fff 0pt -2pt 0pt",
"#fff 0pt -1pt 0pt",
"#fff 0pt 0pt 0pt",
"#fff 0pt 1pt 0pt",
"#fff 0pt 2pt 0pt",
"#fff 1pt -2pt 0pt",
"#fff 1pt -1pt 0pt",
"#fff 1pt 0pt 0pt",
"#fff 1pt 1pt 0pt",
"#fff 1pt 2pt 0pt",
"#fff 2pt -2pt 0pt",
"#fff 2pt -1pt 0pt",
"#fff 2pt 0pt 0pt",
"#fff 2pt 1pt 0pt",
"#fff 2pt 2pt 0pt"
]);
});
it("_buildTextShadow should return a string of text-shadow", function () {
let shadow = _buildTextShadow(2, 2, "#acf");
expect(shadow).toEqual([
"#acf 0pt 0pt 0pt",
"#acf 0pt 1pt 0pt",
"#acf 0pt 2pt 0pt",
"#acf 0pt 3pt 0pt",
"#acf 0pt 4pt 0pt",
"#acf 1pt 0pt 0pt",
"#acf 1pt 1pt 0pt",
"#acf 1pt 2pt 0pt",
"#acf 1pt 3pt 0pt",
"#acf 1pt 4pt 0pt",
"#acf 2pt 0pt 0pt",
"#acf 2pt 1pt 0pt",
"#acf 2pt 2pt 0pt",
"#acf 2pt 3pt 0pt",
"#acf 2pt 4pt 0pt",
"#acf 3pt 0pt 0pt",
"#acf 3pt 1pt 0pt",
"#acf 3pt 2pt 0pt",
"#acf 3pt 3pt 0pt",
"#acf 3pt 4pt 0pt",
"#acf 4pt 0pt 0pt",
"#acf 4pt 1pt 0pt",
"#acf 4pt 2pt 0pt",
"#acf 4pt 3pt 0pt",
"#acf 4pt 4pt 0pt"
].join(", "));
});
it("_getStyle should return the correct style on an element", function () {
var div = _createDiv({"id": "style-test"});
div.style.setProperty("width", "100px");
@ -682,8 +744,7 @@ describe("Display.setTextSlides", function () {
Display.setTextSlides(slides);
const slidesDiv = $(".text-slides")[0];
expect(slidesDiv.style['-webkit-text-stroke']).toEqual('42pt red');
expect(slidesDiv.style['-webkit-text-fill-color']).toEqual('yellow');
expect(slidesDiv.style['text-shadow']).toEqual(_buildTextOutline(42, 'red').join(', '));
})
it("should correctly set text alignment,\

View File

@ -24,12 +24,45 @@ Test the ThemeForm class and related methods.
from pathlib import Path
from unittest.mock import patch, MagicMock
import pytest
from openlp.core.common.registry import Registry
from openlp.core.lib.theme import BackgroundType
from openlp.core.ui.themeform import ThemeForm
from openlp.core.ui.themelayoutform import ThemeLayoutForm
def _make_path(s):
return MagicMock(**{'__str__.return_value': s, 'exists.return_value': True})
THEME_BACKGROUNDS = {
'solid': [
('color', 'background_color', '#ff0')
],
'gradient': [
('gradient_type', 'background_direction', 'horizontal'),
('gradient_start', 'background_start_color', '#fff'),
('gradient_end', 'background_end_color', '#000')
],
'image': [
('image_color', 'background_border_color', '#f0f0f0'),
('image_path', 'background_source', '/path/to/image.png'),
('image_path', 'background_filename', '/path/to/image.png')
],
'video': [
('video_color', 'background_border_color', '#222'),
('video_path', 'background_source', '/path/to/video.mkv'),
('video_path', 'background_filename', '/path/to/video.mkv')
],
'stream': [
('stream_color', 'background_border_color', '#222'),
('stream_mrl', 'background_source', 'http:/127.0.0.1/stream.mkv'),
('stream_mrl', 'background_filename', 'http:/127.0.0.1/stream.mkv')
]
}
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_create_theme_wizard(mocked_setup, settings):
"""
@ -407,3 +440,163 @@ def test_initialise_page_area_position(mocked_setup, settings):
# THEN: Everything is working right
theme_form.set_position_page_values.assert_called_once()
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_update_theme_static(mocked_setup, settings):
"""
Test that the update_theme() method correctly sets all the "static" theme variables
"""
# GIVEN: An instance of a ThemeForm with some mocked out pages which return certain values
theme_form = ThemeForm(None)
theme_form.can_update_theme = True
theme_form.theme = MagicMock()
theme_form.background_page = MagicMock()
theme_form.main_area_page = MagicMock(font_name='Montserrat', font_color='#f00', font_size=50, line_spacing=12,
is_outline_enabled=True, outline_color='#00f', outline_size=3,
is_shadow_enabled=True, shadow_color='#111', shadow_size=5, is_bold=True,
is_italic=False)
theme_form.footer_area_page = MagicMock(font_name='Oxygen', font_color='#fff', font_size=20, is_bold=False,
is_italic=True)
theme_form.alignment_page = MagicMock(horizontal_align='left', vertical_align='top', is_transition_enabled=True,
transition_type='fade', transition_speed='normal',
transition_direction='horizontal', is_transition_reverse_enabled=False)
theme_form.area_position_page = MagicMock(use_main_default_location=True, use_footer_default_location=True)
# WHEN: ThemeForm.update_theme() is called
theme_form.update_theme()
# THEN: The theme should be correct
# Main area
assert theme_form.theme.font_main_name == 'Montserrat'
assert theme_form.theme.font_main_color == '#f00'
assert theme_form.theme.font_main_size == 50
assert theme_form.theme.font_main_line_adjustment == 12
assert theme_form.theme.font_main_outline is True
assert theme_form.theme.font_main_outline_color == '#00f'
assert theme_form.theme.font_main_outline_size == 3
assert theme_form.theme.font_main_shadow is True
assert theme_form.theme.font_main_shadow_color == '#111'
assert theme_form.theme.font_main_shadow_size == 5
assert theme_form.theme.font_main_bold is True
assert theme_form.theme.font_main_italics is False
assert theme_form.theme.font_main_override is False
theme_form.theme.set_default_header.assert_called_once_with()
# Footer
assert theme_form.theme.font_footer_name == 'Oxygen'
assert theme_form.theme.font_footer_color == '#fff'
assert theme_form.theme.font_footer_size == 20
assert theme_form.theme.font_footer_bold is False
assert theme_form.theme.font_footer_italics is True
assert theme_form.theme.font_footer_override is False
theme_form.theme.set_default_footer.assert_called_once_with()
# Alignment
assert theme_form.theme.display_horizontal_align == 'left'
assert theme_form.theme.display_vertical_align == 'top'
# Transitions
assert theme_form.theme.display_slide_transition is True
assert theme_form.theme.display_slide_transition_type == 'fade'
assert theme_form.theme.display_slide_transition_direction == 'horizontal'
assert theme_form.theme.display_slide_transition_speed == 'normal'
assert theme_form.theme.display_slide_transition_reverse is False
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_update_theme_overridden_areas(mocked_setup, settings):
"""
Test that the update_theme() method correctly sets all the positioning information for a custom position
"""
# GIVEN: An instance of a ThemeForm with some mocked out pages which return certain values
theme_form = ThemeForm(None)
theme_form.can_update_theme = True
theme_form.theme = MagicMock()
theme_form.background_page = MagicMock()
theme_form.main_area_page = MagicMock()
theme_form.footer_area_page = MagicMock()
theme_form.alignment_page = MagicMock()
theme_form.area_position_page = MagicMock(use_main_default_location=False, use_footer_default_location=False,
main_x=20, main_y=50, main_height=900, main_width=1880,
footer_x=20, footer_y=910, footer_height=70, footer_width=1880)
# WHEN: ThemeForm.update_theme() is called
theme_form.update_theme()
# THEN: The theme should be correct
assert theme_form.theme.font_main_override is True
assert theme_form.theme.font_main_x == 20
assert theme_form.theme.font_main_y == 50
assert theme_form.theme.font_main_height == 900
assert theme_form.theme.font_main_width == 1880
assert theme_form.theme.font_footer_override is True
assert theme_form.theme.font_footer_x == 20
assert theme_form.theme.font_footer_y == 910
assert theme_form.theme.font_footer_height == 70
assert theme_form.theme.font_footer_width == 1880
@pytest.mark.parametrize('background_type', THEME_BACKGROUNDS.keys())
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_update_theme_background(mocked_setup, background_type, settings):
"""
Test that the update_theme() method correctly sets all the theme background variables for each background type
"""
# GIVEN: An instance of a ThemeForm with some mocked out pages which return certain values
page_props = {page_prop: value for page_prop, _, value in THEME_BACKGROUNDS[background_type]}
theme_form = ThemeForm(None)
theme_form.can_update_theme = True
theme_form.theme = MagicMock()
theme_form.background_page = MagicMock(background_type=background_type, **page_props)
theme_form.main_area_page = MagicMock()
theme_form.footer_area_page = MagicMock()
theme_form.alignment_page = MagicMock()
theme_form.area_position_page = MagicMock()
# WHEN: ThemeForm.update_theme() is called
theme_form.update_theme()
# THEN: The theme should be correct
for _, theme_prop, value in THEME_BACKGROUNDS[background_type]:
assert getattr(theme_form.theme, theme_prop) == value, f'{theme_prop} should have been {value}'
@pytest.mark.skip('Being a bit problematic right now')
@pytest.mark.parametrize('background_type', THEME_BACKGROUNDS.keys())
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_set_background_page_values(mocked_setup, background_type, settings):
"""
Test that the set_background_page_values() method sets the background page values correctly
"""
# GIVEN: An instance of a ThemeForm with some mocked out pages and a mocked theme with values
theme_props = {theme_prop: value for _, theme_prop, value in THEME_BACKGROUNDS[background_type]}
theme_form = ThemeForm(None)
theme_form.theme = MagicMock(background_type=background_type, **theme_props)
theme_form.background_page = MagicMock()
theme_form.main_area_page = MagicMock()
theme_form.footer_area_page = MagicMock()
theme_form.alignment_page = MagicMock()
theme_form.area_position_page = MagicMock()
# WHEN: set_background_page_values() is called
theme_form.set_background_page_values()
# THEN: The correct values are set on the page
for page_prop, _, value in THEME_BACKGROUNDS[background_type]:
assert getattr(theme_form.background_page, page_prop) == value, (
f'{page_prop} should have been {value} but was {getattr(theme_form.background_page, page_prop)}'
)
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_update_theme_cannot_update(mocked_setup, settings):
"""
Test that the update_theme() method skips out early when the theme cannot be updated
"""
# GIVEN: An instance of a ThemeForm with some mocked out pages which return certain values
theme_form = ThemeForm(None)
theme_form.can_update_theme = False
# WHEN: ThemeForm.update_theme() is called
theme_form.update_theme()
# THEN: The theme should be correct
# TODO: Figure out a way to check this