# Source code for psd_tools.psd.tagged_blocks

    """
    Tagged block data structure.

    .. todo:: Support the following tagged blocks: ``Tag.PATTERN_DATA``,
        ``Tag.TYPE_TOOL_INFO``, ``Tag.LAYER``,
        ``Tag.ALPHA``

    """

    from __future__ import absolute_import, unicode_literals

    import io
    import logging
    from warnings import warn

    import attr

    from psd_tools.constants import (
        BlendMode,
        PlacedLayerType,
        SectionDivider,
        SheetColorType,
        Tag,
    )
    from psd_tools.psd.adjustments import ADJUSTMENT_TYPES
    from psd_tools.psd.base import (
        BaseElement,
        ByteElement,
        DictElement,
        EmptyElement,
        IntegerElement,
        ListElement,
        StringElement,
        ValueElement,
    )
    from psd_tools.psd.color import Color
    from psd_tools.psd.descriptor import DescriptorBlock, DescriptorBlock2
    from psd_tools.psd.effects_layer import EffectsLayer
    from psd_tools.psd.engine_data import EngineData, EngineData2
    from psd_tools.psd.filter_effects import FilterEffects
    from psd_tools.psd.linked_layer import LinkedLayers
    from psd_tools.psd.patterns import Patterns
    from psd_tools.psd.vector import VectorMaskSetting, VectorStrokeContentSetting
    from psd_tools.utils import (
        is_readable,
        new_registry,
        read_fmt,
        read_length_block,
        read_pascal_string,
        trimmed_repr,
        write_bytes,
        write_fmt,
        write_length_block,
        write_padding,
        write_pascal_string,
    )
    from psd_tools.validators import in_

    logger = logging.getLogger(__name__)

    TYPES, register = new_registry()

    TYPES.update(ADJUSTMENT_TYPES)
    TYPES.update(
        {
            Tag.ANIMATION_EFFECTS: DescriptorBlock,
            Tag.ARTBOARD_DATA1: DescriptorBlock,
            Tag.ARTBOARD_DATA2: DescriptorBlock,
            Tag.ARTBOARD_DATA3: DescriptorBlock,
            Tag.BLEND_CLIPPING_ELEMENTS: ByteElement,
            Tag.BLEND_FILL_OPACITY: ByteElement,
            Tag.BLEND_INTERIOR_ELEMENTS: ByteElement,
            Tag.COMPOSITOR_INFO: DescriptorBlock,
            Tag.CONTENT_GENERATOR_EXTRA_DATA: DescriptorBlock,
            Tag.EFFECTS_LAYER: EffectsLayer,
            Tag.EXPORT_SETTING1: DescriptorBlock,
            Tag.EXPORT_SETTING2: DescriptorBlock,
            Tag.FILTER_EFFECTS1: FilterEffects,
            Tag.FILTER_EFFECTS2: FilterEffects,
            Tag.FILTER_EFFECTS3: FilterEffects,
            Tag.FRAMED_GROUP: DescriptorBlock,
            Tag.KNOCKOUT_SETTING: ByteElement,
            Tag.LINKED_LAYER1: LinkedLayers,
            Tag.LINKED_LAYER2: LinkedLayers,
            Tag.LINKED_LAYER3: LinkedLayers,
            Tag.LINKED_LAYER_EXTERNAL: LinkedLayers,
            Tag.LAYER_ID: IntegerElement,
            Tag.LAYER_MASK_AS_GLOBAL_MASK: ByteElement,
            Tag.UNICODE_LAYER_NAME: StringElement,
            Tag.LAYER_VERSION: IntegerElement,
            Tag.OBJECT_BASED_EFFECTS_LAYER_INFO: DescriptorBlock2,
            Tag.OBJECT_BASED_EFFECTS_LAYER_INFO_V0: DescriptorBlock2,
            Tag.OBJECT_BASED_EFFECTS_LAYER_INFO_V1: DescriptorBlock2,
            Tag.PATTERNS1: Patterns,
            Tag.PATTERNS2: Patterns,
            Tag.PATTERNS3: Patterns,
            Tag.PATT: EmptyElement,
            Tag.PIXEL_SOURCE_DATA1: DescriptorBlock,
            Tag.SAVING_MERGED_TRANSPARENCY: EmptyElement,
            Tag.SAVING_MERGED_TRANSPARENCY16: EmptyElement,
            Tag.SAVING_MERGED_TRANSPARENCY32: EmptyElement,
            Tag.TEXT_ENGINE_DATA: EngineData2,
            Tag.TRANSPARENCY_SHAPES_LAYER: ByteElement,
            Tag.UNICODE_PATH_NAME: DescriptorBlock,
            Tag.USING_ALIGNED_RENDERING: IntegerElement,
            Tag.VECTOR_MASK_AS_GLOBAL_MASK: ByteElement,
            Tag.VECTOR_MASK_SETTING1: VectorMaskSetting,
            Tag.VECTOR_MASK_SETTING2: VectorMaskSetting,
            Tag.VECTOR_ORIGINATION_DATA: DescriptorBlock2,
            Tag.VECTOR_ORIGINATION_UNKNOWN: IntegerElement,
            Tag.VECTOR_STROKE_DATA: DescriptorBlock,
            Tag.VECTOR_STROKE_CONTENT_DATA: VectorStrokeContentSetting,
        }
    )

    @attr.s(repr=False, slots=True)
    class TaggedBlocks(DictElement):
        """
        Dict of tagged block items.

        See :py:class:`~psd_tools.constants.Tag` for available keys.

        Example::

            from psd_tools.constants import Tag

            # Iterate over fields
            for key in tagged_blocks:
                print(key)

            # Get a field
            value = tagged_blocks.get_data(Tag.TYPE_TOOL_OBJECT_SETTING)
        """

        def get_data(self, key, default=None):
            """
            Get data from the tagged blocks.

            Shortcut for the following::

                if key in tagged_blocks:
                    value = tagged_blocks[key].data
            """
            if key in self:
                value = self[key].data
                if isinstance(value, ValueElement):
                    return value.value
                else:
                    return value
            return default

        def set_data(self, key, *args, **kwargs):
            """
            Set data for the given key.

            Shortut for the following::

                key = getattr(Tag, key)
                kls = TYPES.get(key)
                self[key] = TaggedBlocks(key=key, data=kls(value))

            """
            key = self._key_converter(key)
            kls = TYPES.get(key)
            self[key] = TaggedBlock(key=key, data=kls(*args, **kwargs))

        @classmethod
        def read(cls, fp, version=1, padding=1, end_pos=None):
            items = []
            while is_readable(fp, 8):  # len(signature) + len(key) = 8
                if end_pos is not None and fp.tell() >= end_pos:
                    break
                block = TaggedBlock.read(fp, version, padding)
                if block is None:
                    break
                items.append((block.key, block))
            return cls(items)

        @classmethod
        def _key_converter(self, key):
            return getattr(key, "value", key)

        def _repr_pretty_(self, p, cycle):
            if cycle:
                return "{{...}"

            with p.group(2, "{", "}"):
                p.breakable("")
                for idx, key in enumerate(self._items):
                    if idx:
                        p.text(",")
                        p.breakable()
                    value = self._items[key]
                    try:
                        p.text(Tag(key).name)
                    except ValueError:
                        p.pretty(key)
                    p.text(": ")
                    if isinstance(value.data, bytes):
                        p.text(trimmed_repr(value.data))
                    else:
                        p.pretty(value.data)
                p.breakable("")

    @attr.s(repr=False, slots=True)
    class TaggedBlock(BaseElement):
        """
        Layer tagged block with extra info.

        .. py:attribute:: key

            4-character code. See :py:class:`~psd_tools.constants.Tag`

        .. py:attribute:: data

            Data.
        """

        _SIGNATURES = (b"8BIM", b"8B64")
        _BIG_KEYS = {
            Tag.USER_MASK,
            Tag.LAYER_16,
            Tag.LAYER_32,
            Tag.LAYER,
            Tag.SAVING_MERGED_TRANSPARENCY16,
            Tag.SAVING_MERGED_TRANSPARENCY32,
            Tag.SAVING_MERGED_TRANSPARENCY,
            Tag.SAVING_MERGED_TRANSPARENCY16,
            Tag.ALPHA,
            Tag.FILTER_MASK,
            Tag.LINKED_LAYER2,
            Tag.LINKED_LAYER3,
            Tag.LINKED_LAYER_EXTERNAL,
            Tag.FILTER_EFFECTS1,
            Tag.FILTER_EFFECTS2,
            Tag.FILTER_EFFECTS3,
            Tag.PIXEL_SOURCE_DATA2,
            Tag.UNICODE_PATH_NAME,
            Tag.EXPORT_SETTING1,
            Tag.EXPORT_SETTING2,
            Tag.COMPOSITOR_INFO,
            Tag.ARTBOARD_DATA2,
        }

        signature = attr.ib(default=b"8BIM", repr=False, validator=in_(_SIGNATURES))
        key = attr.ib(default=b"")
        data = attr.ib(default=b"", repr=True)

        @classmethod
        def read(cls, fp, version=1, padding=1):
            signature = read_fmt("4s", fp)[0]
            if signature not in cls._SIGNATURES:
                logger.warning("Invalid signature (%r)" % (signature))
                fp.seek(-4, 1)
                return None

            key = read_fmt("4s", fp)[0]
            try:
                key = Tag(key)
            except ValueError:
                message = "Unknown key: %r" % (key)
                warn(message)
                logger.warning(message)

            fmt = cls._length_format(key, version)
            raw_data = read_length_block(fp, fmt=fmt, padding=padding)
            kls = TYPES.get(key)
            if kls:
                data = kls.frombytes(raw_data, version=version)
                # _raw_data = data.tobytes(version=version,
                #                          padding=1 if padding == 4 else 4)
                # assert raw_data == _raw_data, '%r: %s vs %s' % (
                #     kls, trimmed_repr(raw_data), trimmed_repr(_raw_data)
                # )
            else:
                message = "Unknown tagged block: %r, %s" % (key, trimmed_repr(raw_data))
                warn(message)
                logger.warning(message)
                data = raw_data
            return cls(signature, key, data)

        def write(self, fp, version=1, padding=1):
            key = self.key if isinstance(self.key, bytes) else self.key.value
            written = write_fmt(fp, "4s4s", self.signature, key)

            def writer(f):
                if hasattr(self.data, "write"):
                    # It seems padding size applies at the block level here.
                    inner_padding = 1 if padding == 4 else 4
                    return self.data.write(f, padding=inner_padding, version=version)
                return write_bytes(f, self.data)

            fmt = self._length_format(self.key, version)
            written += write_length_block(fp, writer, fmt=fmt, padding=padding)
            return written

        @classmethod
        def _length_format(cls, key, version):
            return ("I", "Q")[int(version == 2 and key in cls._BIG_KEYS)]

    @register(Tag.ANNOTATIONS)
    @attr.s(repr=False, slots=True)
    class Annotations(ListElement):
        """
        List of Annotation, see :py:class: `.Annotation`.

        .. py:attribute:: major_version
        .. py:attribute:: minor_version
        """

        major_version = attr.ib(default=2, type=int)
        minor_version = attr.ib(default=1, type=int)

        @classmethod
        def read(cls, fp, **kwargs):
            major_version, minor_version, count = read_fmt("2HI", fp)
            items = []
            for _ in range(count):
                length = read_fmt("I", fp)[0] - 4
                if length > 0:
                    with io.BytesIO(fp.read(length)) as f:
                        items.append(Annotation.read(f))
            return cls(
                major_version=major_version, minor_version=minor_version, items=items
            )

        def write(self, fp, **kwargs):
            written = write_fmt(
                fp, "2HI", self.major_version, self.minor_version, len(self)
            )
            for item in self:
                data = item.tobytes()
                written += write_fmt(fp, "I", len(data) + 4)
                written += write_bytes(fp, data)
            written += write_padding(fp, written, 4)
            return written

    @attr.s(repr=False, slots=True)
    class Annotation(BaseElement):
        """
        Annotation structure.

        .. py:attribute:: kind
        .. py:attribute:: is_open
        """

        kind = attr.ib(default=b"txtA", type=bytes, validator=in_((b"txtA", b"sndM")))
        is_open = attr.ib(default=0, type=int)
        flags = attr.ib(default=0, type=int)
        optional_blocks = attr.ib(default=1, type=int)
        icon_location = attr.ib(factory=lambda: [0, 0, 0, 0], converter=list)
        popup_location = attr.ib(factory=lambda: [0, 0, 0, 0], converter=list)
        color = attr.ib(factory=Color)
        author = attr.ib(default="", type=str)
        name = attr.ib(default="", type=str)
        mod_date = attr.ib(default="", type=str)
        marker = attr.ib(default=b"txtC", type=bytes, validator=in_((b"txtC", b"sndM")))
        data = attr.ib(default=b"", type=bytes)

        @classmethod
        def read(cls, fp, **kwargs):
            kind, is_open, flags, optional_blocks = read_fmt("4s2BH", fp)
            icon_location = read_fmt("4i", fp)
            popup_location = read_fmt("4i", fp)
            color = Color.read(fp)
            author = read_pascal_string(fp, "macroman", padding=2)
            name = read_pascal_string(fp, "macroman", padding=2)
            mod_date = read_pascal_string(fp, "macroman", padding=2)
            length, marker = read_fmt("I4s", fp)
            data = read_length_block(fp)
            return cls(
                kind,
                is_open,
                flags,
                optional_blocks,
                icon_location,
                popup_location,
                color,
                author,
                name,
                mod_date,
                marker,
                data,
            )

        def write(self, fp, **kwargs):
            written = write_fmt(
                fp, "4s2BH", self.kind, self.is_open, self.flags, self.optional_blocks
            )
            written += write_fmt(fp, "4i", *self.icon_location)
            written += write_fmt(fp, "4i", *self.popup_location)
            written += self.color.write(fp)
            written += write_pascal_string(fp, self.author, "macroman", padding=2)
            written += write_pascal_string(fp, self.name, "macroman", padding=2)
            written += write_pascal_string(fp, self.mod_date, "macroman", padding=2)
            written += write_fmt(fp, "I4s", len(self.data) + 12, self.marker)
            written += write_length_block(fp, lambda f: write_bytes(f, self.data))
            return written

    @register(Tag.FOREIGN_EFFECT_ID)
    @register(Tag.LAYER_NAME_SOURCE_SETTING)
    @attr.s(repr=False, slots=True, eq=False, order=False)
    class Bytes(ValueElement):
        """
        Bytes structure.

        .. py:attribute:: value
        """

        value = attr.ib(default=b"\x00\x00\x00\x00", type=bytes)

        @classmethod
        def read(cls, fp, **kwargs):
            return cls(fp.read(4))

        def write(self, fp, **kwargs):
            return write_bytes(fp, self.value)

    @register(Tag.CHANNEL_BLENDING_RESTRICTIONS_SETTING)
    @attr.s(repr=False, slots=True)
    class ChannelBlendingRestrictionsSetting(ListElement):
        """
        ChannelBlendingRestrictionsSetting structure.

        List of restricted channel numbers (int).
        """

        @classmethod
        def read(cls, fp, **kwargs):
            items = []
            while is_readable(fp, 4):
                items.append(read_fmt("I", fp)[0])
            return cls(items)

        def write(self, fp, **kwargs):
            return write_fmt(fp, "%dI" % len(self), *self._items)

    @register(Tag.FILTER_MASK)
    @attr.s(repr=False, slots=True)
    class FilterMask(BaseElement):
        """
        FilterMask structure.

        .. py:attribute:: color
        .. py:attribute:: opacity
        """

        color = attr.ib(default=None)
        opacity = attr.ib(default=0, type=int)

        @classmethod
        def read(cls, fp, **kwargs):
            color = Color.read(fp)
            opacity = read_fmt("H", fp)[0]
            return cls(color, opacity)

        def write(self, fp, **kwargs):
            written = self.color.write(fp)
            written += write_fmt(fp, "H", self.opacity)
            return written

    @register(Tag.METADATA_SETTING)
    class MetadataSettings(ListElement):
        """
        MetadataSettings structure.
        """

        @classmethod
        def read(cls, fp, **kwargs):
            count = read_fmt("I", fp)[0]
            items = []
            for _ in range(count):
                items.append(MetadataSetting.read(fp))
            return cls(items)

        def write(self, fp, **kwargs):
            written = write_fmt(fp, "I", len(self))
            written += sum(item.write(fp) for item in self)
            return written

    @attr.s(repr=False, slots=True)
    class MetadataSetting(BaseElement):
        """
        MetadataSetting structure.
        """

        _KNOWN_KEYS = {b"cust", b"cmls", b"extn", b"mlst", b"tmln", b"sgrp"}
        _KNOWN_SIGNATURES = (b"8BIM", b"8ELE")
        signature = attr.ib(
            default=b"8BIM", type=bytes, repr=False, validator=in_(_KNOWN_SIGNATURES)
        )
        key = attr.ib(default=b"", type=bytes)
        copy_on_sheet = attr.ib(default=False, type=bool)
        data = attr.ib(default=b"", type=bytes)

        @classmethod
        def read(cls, fp, **kwargs):
            signature = read_fmt("4s", fp)[0]
            assert signature in cls._KNOWN_SIGNATURES, "Invalid signature %r" % signature
            key, copy_on_sheet = read_fmt("4s?3x", fp)
            data = read_length_block(fp)
            if key in (b"mdyn", b"sgrp"):
                with io.BytesIO(data) as f:
                    data = read_fmt("I", f)[0]
            elif key in cls._KNOWN_KEYS:
                data = DescriptorBlock.frombytes(data, padding=4)
            else:
                message = "Unknown metadata key %r" % (key)
                logger.warning(message)
                data = data
            return cls(signature, key, copy_on_sheet, data)

        def write(self, fp, **kwargs):
            written = write_fmt(fp, "4s4s?3x", self.signature, self.key, self.copy_on_sheet)

            def writer(f):
                if hasattr(self.data, "write"):
                    return self.data.write(f, padding=4)
                elif isinstance(self.data, int):
                    return write_fmt(fp, "I", self.data)
                return write_bytes(f, self.data)

            written += write_length_block(fp, writer)
            return written

    @register(Tag.PIXEL_SOURCE_DATA2)
    @attr.s(repr=False, slots=True)
    class PixelSourceData2(ListElement):
        """
        PixelSourceData2 structure.
        """

        @classmethod
        def read(cls, fp, **kwargs):
            items = []
            while is_readable(fp, 8):
                items.append(read_length_block(fp, fmt="Q"))
            return cls(items)

        def write(self, fp, padding=4, **kwargs):
            written = 0
            for item in self:
                written += write_length_block(
                    fp, lambda f, item=item: write_bytes(f, item), fmt="Q"
                )
            written += write_padding(fp, written, padding)
            return written

    @register(Tag.PLACED_LAYER1)
    @register(Tag.PLACED_LAYER2)
    @attr.s(repr=False, slots=True)
    class PlacedLayerData(BaseElement):
        """
        PlacedLayerData structure.
        """

        kind = attr.ib(default=b"plcL", type=bytes)
        version = attr.ib(default=3, type=int, validator=in_((3,)))
        uuid = attr.ib(default=b"", type=bytes)
        page = attr.ib(default=0, type=int)
        total_pages = attr.ib(default=0, type=int)
        anti_alias = attr.ib(default=0, type=int)
        layer_type = attr.ib(
            default=PlacedLayerType.UNKNOWN,
            converter=PlacedLayerType,
            validator=in_(PlacedLayerType),
        )
        transform = attr.ib(default=(0.0,) * 8, type=tuple)
        warp = attr.ib(default=None)

        @classmethod
        def read(cls, fp, **kwargs):
            kind, version = read_fmt("4sI", fp)
            uuid = read_pascal_string(fp, "macroman", padding=1)
            page, total_pages, anti_alias, layer_type = read_fmt("4I", fp)
            transform = read_fmt("8d", fp)
            warp = DescriptorBlock2.read(fp, padding=1)
            return cls(
                kind,
                version,
                uuid,
                page,
                total_pages,
                anti_alias,
                layer_type,
                transform,
                warp,
            )

        def write(self, fp, padding=4, **kwargs):
            written = write_fmt(fp, "4sI", self.kind, self.version)
            written += write_pascal_string(fp, self.uuid, "macroman", padding=1)
            written += write_fmt(
                fp,
                "4I",
                self.page,
                self.total_pages,
                self.anti_alias,
                self.layer_type.value,
            )
            written += write_fmt(fp, "8d", *self.transform)
            written += self.warp.write(fp, padding=1)
            written += write_padding(fp, written, padding)
            return written

    @register(Tag.PROTECTED_SETTING)
    class ProtectedSetting(IntegerElement):
        """
        ProtectedSetting structure.
        """

        @property
        def transparency(self):
            return bool(self.value & 0x01)

        @property
        def composite(self):
            return bool(self.value & 0x02)

        @property
        def position(self):
            return bool(self.value & 0x04)

    @register(Tag.REFERENCE_POINT)
    @attr.s(repr=False, slots=True)
    class ReferencePoint(ListElement):
        """
        ReferencePoint structure.
        """

        @classmethod
        def read(cls, fp, **kwargs):
            return cls(list(read_fmt("2d", fp)))

        def write(self, fp, **kwargs):
            return write_fmt(fp, "2d", *self._items)

    @register(Tag.SECTION_DIVIDER_SETTING)
    @register(Tag.NESTED_SECTION_DIVIDER_SETTING)
    @attr.s(repr=False, slots=True)
    class SectionDividerSetting(BaseElement):
        """
        SectionDividerSetting structure.

        .. py:attribute:: kind
        .. py:attribute:: blend_mode
        .. py:attribute:: sub_type
        """

        kind = attr.ib(
            default=SectionDivider.OTHER,
            converter=SectionDivider,
            validator=in_(SectionDivider),
        )
        signature = attr.ib(default=None, repr=False)
        blend_mode = attr.ib(default=None)
        sub_type = attr.ib(default=None)

        @classmethod
        def read(cls, fp, **kwargs):
            kind = SectionDivider(read_fmt("I", fp)[0])
            signature, blend_mode = None, None
            if is_readable(fp, 8):
                signature = read_fmt("4s", fp)[0]
                assert signature == b"8BIM", "Invalid signature %r" % signature
                blend_mode = BlendMode(read_fmt("4s", fp)[0])
            sub_type = None
            if is_readable(fp, 4):
                sub_type = read_fmt("I", fp)[0]
            return cls(kind, signature=signature, blend_mode=blend_mode, sub_type=sub_type)

        def write(self, fp, **kwargs):
            written = write_fmt(fp, "I", self.kind.value)
            if self.signature and self.blend_mode:
                written += write_fmt(fp, "4s4s", self.signature, self.blend_mode.value)
                if self.sub_type is not None:
                    written += write_fmt(fp, "I", self.sub_type)
            return written

    @register(Tag.SHEET_COLOR_SETTING)
    @attr.s(repr=False, slots=True, eq=False, order=False)
    class SheetColorSetting(ValueElement):
        """
        SheetColorSetting value.

        This setting represents color label in the layers panel in Photoshop UI.

        .. py:attribute:: value
        """

        value = attr.ib(default=0, converter=SheetColorType, type=SheetColorType)

        @classmethod
        def read(cls, fp, **kwargs):
            return cls(SheetColorType(*read_fmt("H6x", fp)))

        def write(self, fp, **kwargs):
            return write_fmt(fp, "H6x", self.value.value)

    @register(Tag.SMART_OBJECT_LAYER_DATA1)
    @register(Tag.SMART_OBJECT_LAYER_DATA2)
    @attr.s(repr=False, slots=True)
    class SmartObjectLayerData(BaseElement):
        """
        VersionedDescriptorBlock structure.

        .. py:attribute:: kind
        .. py:attribute:: version
        .. py:attribute:: data
        """

        kind = attr.ib(default=b"soLD", type=bytes, validator=in_((b"soLD",)))
        version = attr.ib(default=5, type=int, validator=in_((4, 5)))
        data = attr.ib(default=None, type=DescriptorBlock)

        @classmethod
        def read(cls, fp, **kwargs):
            kind, version = read_fmt("4sI", fp)
            data = DescriptorBlock.read(fp)
            return cls(kind, version, data)

        def write(self, fp, padding=4, **kwargs):
            written = write_fmt(fp, "4sI", self.kind, self.version)
            written += self.data.write(fp, padding=1)
            written += write_padding(fp, written, padding)
            return written

    @register(Tag.TYPE_TOOL_OBJECT_SETTING)
    @attr.s(repr=False, slots=True)
    class TypeToolObjectSetting(BaseElement):
        """
        TypeToolObjectSetting structure.

        .. py:attribute:: version
        .. py:attribute:: transform

            Tuple of affine transform parameters (xx, xy, yx, yy, tx, ty).

        .. py:attribute:: text_version
        .. py:attribute:: text_data
        .. py:attribute:: warp_version
        .. py:attribute:: warp
        .. py:attribute:: left
        .. py:attribute:: top
        .. py:attribute:: right
        .. py:attribute:: bottom
        """

        version = attr.ib(default=1, type=int)
        transform = attr.ib(default=(0.0,) * 6, type=tuple)
        text_version = attr.ib(default=1, type=int, validator=in_((50,)))
        text_data = attr.ib(default=None, type=DescriptorBlock)
        warp_version = attr.ib(default=1, type=int, validator=in_((1,)))
        warp = attr.ib(default=None, type=DescriptorBlock)
        left = attr.ib(default=0, type=int)
        top = attr.ib(default=0, type=int)
        right = attr.ib(default=0, type=int)
        bottom = attr.ib(default=0, type=int)

        @classmethod
        def read(cls, fp, **kwargs):
            version = read_fmt("H", fp)[0]
            transform = read_fmt("6d", fp)
            text_version = read_fmt("H", fp)[0]
            text_data = DescriptorBlock.read(fp)
            # Engine data.
            if b"EngineData" in text_data:
                try:
                    engine_data = text_data[b"EngineData"].value
                    engine_data = EngineData.frombytes(engine_data)
                    text_data[b"EngineData"].value = engine_data
                except Exception:
                    logger.warning("Failed to read engine data")
            warp_version = read_fmt("H", fp)[0]
            warp = DescriptorBlock.read(fp)
            left, top, right, bottom = read_fmt("4i", fp)
            return cls(
                version,
                transform,
                text_version,
                text_data,
                warp_version,
                warp,
                left,
                top,
                right,
                bottom,
            )

        def write(self, fp, padding=4, **kwargs):
            written = write_fmt(fp, "H6d", self.version, *self.transform)
            written += write_fmt(fp, "H", self.text_version)
            written += self.text_data.write(fp, padding=1)
            written += write_fmt(fp, "H", self.warp_version)
            written += self.warp.write(fp, padding=1)
            written += write_fmt(fp, "4i", self.left, self.top, self.right, self.bottom)
            written += write_padding(fp, written, padding)
            return written

    @register(Tag.USER_MASK)
    @attr.s(repr=False, slots=True)
    class UserMask(BaseElement):
        """
        UserMask structure.

        .. py:attribute:: color
        .. py:attribute:: opacity
        .. py:attribute:: flag
        """

        color = attr.ib(default=None)
        opacity = attr.ib(default=0, type=int)
        flag = attr.ib(default=128, type=int)

        @classmethod
        def read(cls, fp, **kwargs):
            color = Color.read(fp)
            opacity, flag = read_fmt("HBx", fp)
            return cls(color, opacity, flag)

        def write(self, fp, **kwargs):
            written = self.color.write(fp)
            written += write_fmt(fp, "HBx", self.opacity, self.flag)
            return written