diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 3b0ff89..7dd523f 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -20,7 +20,7 @@ jobs: python-version: '3.8' - name: Install Requirements - run: pip install .[dev] + run: pip install .[dev,mouthing,server] - name: Lint Code run: pylint signwriting diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1afeb29..7936df2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,7 +20,7 @@ jobs: python-version: '3.8' - name: Install Requirements - run: pip install .[dev] + run: pip install .[dev,mouthing,server] - name: Test Code run: pytest signwriting diff --git a/pyproject.toml b/pyproject.toml index cc8ccb9..9017197 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,11 @@ mouthing = [ "epitran", "g2pk" ] +server = [ + # For API server + "flask", + "Flask-RESTful", +] [tool.yapf] based_on_style = "google" @@ -33,6 +38,8 @@ disable = [ "C0114", # Missing module docstring "C0115", # Missing class docstring "C0116", # Missing function or method docstring + "C3001", # Lambda expression assigned to a variable + "R0917", # Too many positional arguments ] good-names = ["i", "f", "x", "y"] diff --git a/signwriting/tokenizer/base_tokenizer.py b/signwriting/tokenizer/base_tokenizer.py index 90cffba..6a33628 100644 --- a/signwriting/tokenizer/base_tokenizer.py +++ b/signwriting/tokenizer/base_tokenizer.py @@ -5,7 +5,7 @@ class BaseTokenizer: - # pylint: disable=too-many-arguments,too-many-instance-attributes + # pylint: disable=too-many-arguments, too-many-instance-attributes def __init__(self, tokens: List[str], starting_index=None, diff --git a/signwriting/utils/join_signs.py b/signwriting/utils/join_signs.py index 8db2cda..e63e940 100644 --- a/signwriting/utils/join_signs.py +++ b/signwriting/utils/join_signs.py @@ -1,4 +1,5 @@ from collections import namedtuple +from typing import List from signwriting.formats.fsw_to_sign import fsw_to_sign from signwriting.formats.sign_to_fsw import sign_to_fsw @@ -59,7 +60,7 @@ def join_signs_horizontal(*fsws: str, spacing: int = 0): Point = namedtuple("Point", ["x", "y"]) -def sign_from_symbols(symbols: list[SignSymbol], fix_x=True, fix_y=True) -> Sign: +def sign_from_symbols(symbols: List[SignSymbol], fix_x=True, fix_y=True) -> Sign: min_p = Point(x=999, y=999) max_p = Point(x=0, y=0) for symbol in symbols: diff --git a/signwriting/visualizer/test_assets/horizontal.png b/signwriting/visualizer/test_assets/horizontal.png new file mode 100644 index 0000000..5712988 Binary files /dev/null and b/signwriting/visualizer/test_assets/horizontal.png differ diff --git a/signwriting/visualizer/test_assets/vertical.png b/signwriting/visualizer/test_assets/vertical.png new file mode 100644 index 0000000..7fa0d56 Binary files /dev/null and b/signwriting/visualizer/test_assets/vertical.png differ diff --git a/signwriting/visualizer/test_visualize.py b/signwriting/visualizer/test_visualize.py index d1efe24..a404730 100644 --- a/signwriting/visualizer/test_visualize.py +++ b/signwriting/visualizer/test_visualize.py @@ -76,6 +76,14 @@ def test_image_with_fill_and_embedded_color(self): fill_color=(123,234,0,255)) self.assert_image_equal_with_reference(fsw, image) + def test_layout_signwriting(self): + fsw_list = ["AS14c20S27106M518x529S14c20481x471S27106503x489", + "AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468"] # Hello World + + for direction in ["horizontal", "vertical"]: + image = signwriting_to_image(fsw_list, direction=direction) + self.assert_image_equal_with_reference(direction, image) + if __name__ == '__main__': unittest.main() diff --git a/signwriting/visualizer/visualize.py b/signwriting/visualizer/visualize.py index 64e12f8..a6d5e63 100644 --- a/signwriting/visualizer/visualize.py +++ b/signwriting/visualizer/visualize.py @@ -1,6 +1,6 @@ from functools import lru_cache from pathlib import Path -from typing import Tuple +from typing import Tuple, List, Literal, Union from PIL import Image, ImageDraw, ImageFont @@ -26,9 +26,17 @@ def get_symbol_size(symbol: str): # pylint: disable=too-many-locals, too-many-arguments -def signwriting_to_image(fsw: str, antialiasing=True, trust_box=True, embedded_color=False, +def signwriting_to_image(fsw: Union[str, List[str]], antialiasing=True, trust_box=True, embedded_color=False, line_color: RGBA = (0, 0, 0, 255), - fill_color: RGBA = (255, 255, 255, 255)) -> Image: + fill_color: RGBA = (255, 255, 255, 255), + direction: Literal["horizontal", "vertical"] = "horizontal") -> Image.Image: + if isinstance(fsw, list): + images = [ + signwriting_to_image(fsw_string, antialiasing, trust_box, embedded_color, line_color, fill_color) + for fsw_string in fsw + ] + return layout_signwriting(images, direction) + sign = fsw_to_sign(fsw) if len(sign['symbols']) == 0: return Image.new('RGBA', (1, 1), (0, 0, 0, 0)) @@ -65,3 +73,28 @@ def signwriting_to_image(fsw: str, antialiasing=True, trust_box=True, embedded_c font=line_font, embedded_color=embedded_color) return img + + +def layout_signwriting(images: List[Image.Image], direction: str) -> Image.Image: + GAP = 20 + + if direction == "horizontal": + max_height = max(img.height for img in images) + total_width = sum(img.width for img in images) + GAP * (len(images) - 1) + size = (total_width, max_height) + paste_position = lambda offset, img: (offset, (max_height - img.height) // 2) + offset_increment = lambda img: img.width + GAP + else: + max_width = max(img.width for img in images) + total_height = sum(img.height for img in images) + GAP * (len(images) - 1) + size = (max_width, total_height) + paste_position = lambda offset, img: ((max_width - img.width) // 2, offset) + offset_increment = lambda img: img.height + GAP + + layout_image = Image.new("RGBA", size, (255, 255, 255, 0)) + offset = 0 + for img in images: + layout_image.paste(img, paste_position(offset, img)) + offset += offset_increment(img) + + return layout_image