diff --git a/src/assemblicon/highlevel/sink/__init__.py b/src/assemblicon/highlevel/sink/__init__.py index 3ccbd90..f8bbc1a 100644 --- a/src/assemblicon/highlevel/sink/__init__.py +++ b/src/assemblicon/highlevel/sink/__init__.py @@ -2,6 +2,7 @@ from .common import Sink from .tempart_sink import TempArtSink from .fdart_sink import FdArtSink from .icoart_sink import IcoArtSink +from .msixart_sink import MsixArtSink from .icnsart_sink import IcnsArtSink __all__ = [ @@ -9,5 +10,6 @@ __all__ = [ "TempArtSink", "FdArtSink", "IcoArtSink", + "MsixArtSink", "IcnsArtSink" ] diff --git a/src/assemblicon/highlevel/sink/msixart_sink.py b/src/assemblicon/highlevel/sink/msixart_sink.py new file mode 100644 index 0000000..f4c132c --- /dev/null +++ b/src/assemblicon/highlevel/sink/msixart_sink.py @@ -0,0 +1,105 @@ +from dataclasses import dataclass +from .common import Sink +from ..address import Address, AddressDomain +from ..environment import Environment +from ..utils import ( + Artifact, + MsixCategory, + MsixAppLogoKind, + MsixAppPlatedKind, + resolve_msix_scale, + check_vart, +) +from ...lowlevel import artio, artrdr +from ...logger import HIGHLEVEL_LOGGER as LOGGER + + +@dataclass(frozen=True) +class MsixArtSinkHint: + kind: MsixCategory + name: str + + +class MsixArtSink(Sink): + __kind: MsixCategory + __name: str + + def __init__(self, kind: MsixCategory, name: str) -> None: + self.__kind = kind + self.__name = name + + @property + def address(self) -> Address: + return Address(AddressDomain.WinMsixOutput, str(self.__kind), self.__name) + + def push(self, env: Environment, art: Artifact) -> None: + LOGGER.info( + 'Saving Windows MSIX artifact with kind "%s" and name "%s".', + self.__kind, + self.__name, + ) + check_vart(art) + + # different category need entirely different push strategies + match self.__kind: + case MsixCategory.Application: + self.__push_app(env, art) + case MsixCategory.Extension: + self.__push_ext(env, art) + case _: + raise ValueError(f"Unknown MSIX category: {self.__kind}") + + def __push_app(self, env: Environment, art: Artifact) -> None: + # push by sub-kind + for kind in MsixAppLogoKind: + size = kind.to_size() + + # push no suffix variant + # TODO: We currently brutely assume all artifacts are squared. + # This premise may be broken when meeting with wide tile logo and etc. + # But currently we doesn't support them. + # So these code need to be changed in future. + hw = size[0] + (thumbnail,) = artrdr.render_thumbnail(art, (hw,)) + dst = env.msix_app_output_artifact(kind, None, None, None, self.__name) + artio.save_artifact(thumbnail, dst) + + # push scale variants + hws = tuple(resolve_msix_scale(hw, scale) for scale in env.prologue.msix_scales) + for scale, thumbnail in zip( + env.prologue.msix_scales, artrdr.render_thumbnail(art, hws) + ): + dst = env.msix_app_output_artifact(kind, scale, None, None, self.__name) + artio.save_artifact(thumbnail, dst) + + # push special variants for Square44x44 + if kind == MsixAppLogoKind.Square44x44: + for targetsize, thumbnail in zip( + env.prologue.msix_app44_targetsizes, + artrdr.render_thumbnail(art, env.prologue.msix_app44_targetsizes), + ): + for plated in MsixAppPlatedKind: + dst = env.msix_app_output_artifact( + kind, None, targetsize, plated, self.__name + ) + artio.save_artifact(thumbnail, dst) + + def __push_ext(self, env: Environment, art: Artifact) -> None: + # push default with 44x44 size + # TODO: These hardcode code is also shitty + # It should be improved like ``MsixAppLogoKind`.to_size()` + hw = 44 + (thumbnail,) = artrdr.render_thumbnail(art, (hw,)) + dst = env.msix_ext_output_artifact(None, self.__name) + artio.save_artifact(thumbnail, dst) + + # push targetsize variants + for targetsize, thumbnail in zip( + env.prologue.msix_ext_targetsizes, + artrdr.render_thumbnail(art, env.prologue.msix_ext_targetsizes), + ): + dst = env.msix_ext_output_artifact(targetsize, self.__name) + artio.save_artifact(thumbnail, dst) + + def push_hint(self) -> MsixArtSinkHint: + return MsixArtSinkHint(self.__kind, self.__name) diff --git a/src/assemblicon/highlevel/source/__init__.py b/src/assemblicon/highlevel/source/__init__.py index 6b8653e..a1896d7 100644 --- a/src/assemblicon/highlevel/source/__init__.py +++ b/src/assemblicon/highlevel/source/__init__.py @@ -6,6 +6,7 @@ from .blender_source import BlenderSource from .tempart_source import TempArtSource from .fdart_source import FdArtSource from .icoart_source import IcoArtSource +from .msixart_source import MsixArtSource from .icnsart_source import IcnsArtSource @@ -18,5 +19,6 @@ __all__ = [ "TempArtSource", "FdArtSource", "IcoArtSource", + "MsixArtSource", "IcnsArtSource", ] diff --git a/src/assemblicon/highlevel/source/icnsart_source.py b/src/assemblicon/highlevel/source/icnsart_source.py index 10fd372..17878a6 100644 --- a/src/assemblicon/highlevel/source/icnsart_source.py +++ b/src/assemblicon/highlevel/source/icnsart_source.py @@ -19,8 +19,8 @@ class IcnsArtSource(Source): def address(self) -> Address: return Address(AddressDomain.MacIcnsOutput, str(self.__kind), self.__name) - def link_to(self, env: Environment, mac_sink: IcnsArtSink) -> None: - sink_hint = mac_sink.push_hint() + def link_to(self, env: Environment, icns_sink: IcnsArtSink) -> None: + sink_hint = icns_sink.push_hint() LOGGER.info( 'Duplicating macOS ICNS artifact from kind "%s" name "%s" to kind "%s" name "%s".', self.__kind, diff --git a/src/assemblicon/highlevel/source/icoart_source.py b/src/assemblicon/highlevel/source/icoart_source.py index 27c8b6b..3a17268 100644 --- a/src/assemblicon/highlevel/source/icoart_source.py +++ b/src/assemblicon/highlevel/source/icoart_source.py @@ -19,8 +19,8 @@ class IcoArtSource(Source): def address(self) -> Address: return Address(AddressDomain.WinIcoOutput, str(self.__kind), self.__name) - def link_to(self, env: Environment, win_sink: IcoArtSink) -> None: - sink_hint = win_sink.push_hint() + def link_to(self, env: Environment, ico_sink: IcoArtSink) -> None: + sink_hint = ico_sink.push_hint() LOGGER.info( 'Duplicating Windows ICO artifact from kind "%s" name "%s" to kind "%s" name "%s".', self.__kind, diff --git a/src/assemblicon/highlevel/source/msixart_source.py b/src/assemblicon/highlevel/source/msixart_source.py new file mode 100644 index 0000000..9521af4 --- /dev/null +++ b/src/assemblicon/highlevel/source/msixart_source.py @@ -0,0 +1,84 @@ +from .common import Source +from ..address import Address, AddressDomain +from ..environment import Environment +from ..sink.msixart_sink import MsixArtSink, MsixArtSinkHint +from ..utils import MsixCategory, MsixAppLogoKind, MsixAppPlatedKind +from ...lowlevel import artio +from ...logger import HIGHLEVEL_LOGGER as LOGGER + + +class MsixArtSource(Source): + __kind: MsixCategory + __name: str + + def __init__(self, kind: MsixCategory, name: str) -> None: + self.__kind = kind + self.__name = name + + @property + def address(self) -> Address: + return Address(AddressDomain.WinMsixOutput, str(self.__kind), self.__name) + + def link_to(self, env: Environment, msix_sink: MsixArtSink) -> None: + sink_hint = msix_sink.push_hint() + LOGGER.info( + 'Duplicating Windows MSIX artifact from kind "%s" name "%s" to kind "%s" name "%s".', + self.__kind, + self.__name, + sink_hint.kind, + sink_hint.name, + ) + + # different category need entirely different link strategies + match self.__kind: + case MsixCategory.Application: + self.__link_to_app(env, sink_hint) + case MsixCategory.Extension: + self.__link_to_ext(env, sink_hint) + case _: + raise ValueError(f"Unknown MSIX category: {self.__kind}") + + def __link_to_app(self, env: Environment, sink_hint: MsixArtSinkHint) -> None: + # link by sub-kind + for kind in MsixAppLogoKind: + # link no suffix variant + artio.link_artifact( + env.msix_app_output_artifact(kind, None, None, None, self.__name), + env.msix_app_output_artifact(kind, None, None, None, sink_hint.name), + ) + + # link scale variants + for scale in env.prologue.msix_scales: + artio.link_artifact( + env.msix_app_output_artifact(kind, scale, None, None, self.__name), + env.msix_app_output_artifact( + kind, scale, None, None, sink_hint.name + ), + ) + + # link special variants for Square44x44 + if kind == MsixAppLogoKind.Square44x44: + for targetsize in env.prologue.msix_app44_targetsizes: + for plated in MsixAppPlatedKind: + artio.link_artifact( + env.msix_app_output_artifact( + kind, None, targetsize, plated, self.__name + ), + env.msix_app_output_artifact( + kind, None, targetsize, plated, sink_hint.name + ), + ) + + def __link_to_ext(self, env: Environment, sink_hint: MsixArtSinkHint) -> None: + # link default + artio.link_artifact( + env.msix_ext_output_artifact(None, self.__name), + env.msix_ext_output_artifact(None, sink_hint.name), + ) + + # link targetsize variants + for targetsize in env.prologue.msix_ext_targetsizes: + artio.link_artifact( + env.msix_ext_output_artifact(targetsize, self.__name), + env.msix_ext_output_artifact(targetsize, sink_hint.name), + ) diff --git a/src/assemblicon/highlevel/utils.py b/src/assemblicon/highlevel/utils.py index 933de83..7c99ed6 100644 --- a/src/assemblicon/highlevel/utils.py +++ b/src/assemblicon/highlevel/utils.py @@ -20,6 +20,17 @@ def check_fd_hw(hw: int) -> None: ) +def resolve_msix_scale(hw: int, scale: int) -> int: + """ + Resolve given MSIX scale value with original width/height. + + :param hw: The height or width before scale for resolving. + :param scale: The scale value for resolving. + :returns: The height/width after applying scale value. + """ + return hw * scale // 100 + + def check_msix_scale(hw: int, scale: int) -> None: """ Check whether given MSIX scale value is valid. @@ -32,7 +43,7 @@ def check_msix_scale(hw: int, scale: int) -> None: if scale <= 100: raise ValueError(f"Invalid scale value for MSIX icon variant: {scale}") # the image scaled after this value must not exceed VART_HW - scaled_hw = hw * scale // 100 + scaled_hw = resolve_msix_scale(hw, scale) if scaled_hw > VART_HW: raise ValueError(f"Scale value is too large for MSIX icon variant: {scale}")