跳转至

WMTemplate

WMTemplate

WMTemplate is mainly used to load template, set related parameters, and insert watermark into text

WMTemplate

Class of WMTemplate

Example
Python
wmt = WMTemplate(
    template.confusables_chars,
    template.confusables_chars_length,
    template.method,
    confusables_chars_key)

wm_text = wmt.insert_watermark(
    text=text,
    wm_final=wm_final,
    start_at=start_at,
    loop=False)

wm_final = wmt.retrieve_watermark(
    wm_text=wm_text,
    wm_base=wmt.wm_base,
    wm_len=len(wm_final),
    start_at=start_at)
Source code in textwatermark/template.py
Python
class WMTemplate:
    """Class of WMTemplate

    Example
    --------
    ```py
    wmt = WMTemplate(
        template.confusables_chars,
        template.confusables_chars_length,
        template.method,
        confusables_chars_key)

    wm_text = wmt.insert_watermark(
        text=text,
        wm_final=wm_final,
        start_at=start_at,
        loop=False)

    wm_final = wmt.retrieve_watermark(
        wm_text=wm_text,
        wm_base=wmt.wm_base,
        wm_len=len(wm_final),
        start_at=start_at)
    ```
    """

    def __init__(
        self,
        confusables_chars: Union[dict, list],
        wm_base: int,
        method: WMMethod,
        confusables_chars_key: str = "",
    ):
        """Init of WMTemplate

        Args:
            confusables_chars (Union[dict, list]): The default template is in templates,
                you can your the modified custom template
            wm_base (int): Watermark convert base. Must be less than
                or equal to confusables_chars_length
            method (WMMethod):  Watermark insertion method,
                see defines.WMMethod for details
            confusables_chars_key (str): Key must be set when
                WMMethod is DECORATE_EACH_CHAR

        Note: Note of wm_base
            The value of wm_base will affect the range of watermark characters taken
                from confusables_chars

        """

        self.confusables_chars = confusables_chars
        """The default template is in templates,
                you can your the modified custom template """
        if wm_base == 0:
            self.wm_base = WMTemplate.get_wm_base_from_tpl(self.confusables_chars)
            """Watermark convert base. Must be less than or equal to confusables_chars_length """
        else:
            self.wm_base = wm_base

        self.method = method
        """Watermark insertion method, see defines.WMMethod for details """

        if (
            self.method in [WMMethod.DECORATE_EACH_CHAR, WMMethod.APPEND_AS_BINARY]
            and confusables_chars_key not in self.confusables_chars
        ):
            raise ValueError(
                "When WMMethod in [DECORATE_EACH_CHAR,APPEND_AS_BINARY], confusables_chars_key: "
                f"{confusables_chars_key} must can be found in confusables_chars's keys."
            )

        self.confusables_chars_key = confusables_chars_key
        """Key must be set when WMMethod is DECORATE_EACH_CHAR"""

    @staticmethod
    def get_wm_base_from_tpl(confusables_chars):
        """get wm_base from template's confusables chars

        Args:
            confusables_chars (dict): confusables chars template

        Returns:
            (int): wm_base

        """
        if isinstance(confusables_chars, list):
            wm_base = len(confusables_chars)
        elif isinstance(confusables_chars, dict):
            wm_base = len(list(confusables_chars.values())[0])
        else:
            raise TypeError("confusables_chars must be list or dict")

        wm_base = max(wm_base, 2)
        wm_base = min(wm_base, 36)

        return wm_base

    ################################# Clean watermark#################################

    def clean_html_tags(self, html):
        """Clean html tags from watermark.

        Args:
            html (str): HTML code

        Returns:
            (str): Cleaned HTML code
        """
        return re.sub(r"<[^<]+?>", "", html)

    def clean_text(self, text: str):
        """clean confusables chars from text

        Args:
            text (str): Text to clean

        Returns:
            (str): Cleaned text

        Raises:
            ValueError: If unknown method

        """
        text = self.clean_html_tags(text)

        match self.method:
            case WMMethod.FIND_AND_REPLACE:
                if not isinstance(self.confusables_chars, dict):
                    raise TypeError(
                        "Template defined error: Confusables_chars must be dict"
                        " when WMMethod equal to FIND_AND_REPLACE"
                    )

                for key, vals in self.confusables_chars.items():
                    if isinstance(vals, list):
                        vals = "".join(vals)
                    vals = vals.replace(key, "")
                    for val in vals:
                        text = text.replace(val, key)
            case WMMethod.DECORATE_EACH_CHAR:
                # use self.clean_html_tags by default
                pass
            case WMMethod.INSERT_INTO_POSITION | WMMethod.APPEND_TO_CHAR:
                for val in self.confusables_chars:
                    text = text.replace(val, "")
            case WMMethod.APPEND_AS_BINARY:
                if not isinstance(self.confusables_chars, dict):
                    raise TypeError(
                        "Template defined error: Confusables_chars must be dict"
                        " when WMMethod equal to APPEND_AS_BINARY"
                    )

                text = text.replace(
                    self.confusables_chars[self.confusables_chars_key], ""
                )
            case _:
                raise ValueError(f"Unknown WMMethod: {self.method}")
        # print(text)
        return text

    ################################# Insert watermark#################################

    def insert_watermark(
        self, text: str, wm_final: str, start_at: int = 0, loop: bool = False
    ):
        """Insert watermark into text

        Args:
            text (str): Text to be watermarked
            wm_final (str): The final watermark string
            start_at (int): The offset position for insert watermark
            loop (bool): Whether to insert the watermark in a loop

        Returns:
            (str): Watermarked text

        Raises:
            ValueError: If start_at is larger than text length
            ValueError: If there is not enough space to insert a watermark
            VaueError: If unknown watermark method

        """
        if start_at > len(text):
            raise ValueError(
                f"Start_at is {start_at}, larger than text length: {len(text)}"
            )

        text = self.clean_text(text=text)

        if start_at > 0:
            out_text = text[0:start_at]
            text = text[start_at:]
        else:
            out_text = ""

        match self.method:
            case WMMethod.FIND_AND_REPLACE:
                times = self.check_find_and_replace_space(text, len(wm_final))
                if times < 1:
                    raise ValueError(
                        f"There is not enough space to insert a watermark: {wm_final}"
                    )

                out_text += self._find_and_replace(
                    text=text, wm_final=wm_final, loop=loop
                )
            case WMMethod.DECORATE_EACH_CHAR:
                if len(wm_final) > len(text):
                    raise ValueError(
                        f"There is not enough space to insert a watermark: {wm_final}"
                    )

                out_text += self._decorate_each_char(
                    text=text, wm_final=wm_final, loop=loop
                )
            case WMMethod.INSERT_INTO_POSITION:
                out_text += self._insert_into_position(text=text, wm_final=wm_final)
            case WMMethod.APPEND_TO_CHAR:
                if len(wm_final) > len(text):
                    raise ValueError(
                        f"There is not enough space to insert a watermark: {wm_final}"
                    )

                out_text += self._append_to_char(
                    text=text, wm_final=wm_final, loop=loop
                )
            case WMMethod.APPEND_AS_BINARY:
                if len(wm_final) > len(text):
                    raise ValueError(
                        f"There is not enough space to insert a watermark: {wm_final}"
                    )

                out_text += self._append_as_binary(
                    text=text, wm_final=wm_final, loop=loop
                )

        return out_text

    def check_find_and_replace_space(self, text: str, wm_len: int):
        """Check if enough space for insert watermark

        Args:
            text (str): text to check
            wm_len (int): watermark length
        Returns:
            (int): number of times can insert watermark

        Note: Why use this method?
            Because WMMethod.FIND_AND_REPLACE mode must need enough space
            to find special char and replace to watermarked char
        """
        if not isinstance(self.confusables_chars, dict):
            raise TypeError(
                "Template defined error: Confusables_chars must be dict"
                " when WMMethod equal to FIND_AND_REPLACE"
            )

        actual_len = 0

        confusables_chars_keys = "".join(self.confusables_chars.keys())
        # 计算可插入的水印空间大小
        for char in text:
            if confusables_chars_keys.find(char) != -1:
                actual_len += 1
        times = actual_len // wm_len
        return times

    def _find_and_replace(self, text: str, wm_final: str, loop: bool):
        if not isinstance(self.confusables_chars, dict):
            raise TypeError(
                "Template defined error: Confusables_chars must be dict"
                " when WMMethod equal to FIND_AND_REPLACE"
            )

        confusables_chars_keys = "".join(self.confusables_chars.keys())
        wm_idx = 0
        wm_text = ""
        for idx in range(len(text)):
            if not loop and wm_idx >= len(wm_final):
                wm_text += text[idx:]
                break
            # 查找并替换成水印字符
            if confusables_chars_keys.find(text[idx]) != -1:
                offset = wm_final[wm_idx % len(wm_final)]
                wm_text += self.confusables_chars[text[idx]][int(offset)]
                wm_idx += 1
            else:
                wm_text += text[idx]
        return wm_text

    def _decorate_each_char(self, text: str, wm_final: str, loop: bool):
        if not isinstance(self.confusables_chars, dict):
            raise TypeError(
                "Template defined error: Confusables_chars must be dict"
                " when WMMethod equal to DECORATE_EACH_CHAR"
            )

        wm_idx = 0
        wm_text = ""
        for idx in range(len(text)):
            if not loop and wm_idx >= len(wm_final):
                wm_text += text[idx:]
                break
            offset = wm_final[wm_idx % len(wm_final)]
            wm_text += self.confusables_chars[self.confusables_chars_key][
                int(offset)
            ].replace("{char}", text[idx])

            wm_idx += 1

        return wm_text

    def _insert_into_position(self, text: str, wm_final: str):
        wm_out_str = ""
        for wm_char in wm_final:
            wm_out_str += self.confusables_chars[int(wm_char, self.wm_base)]
        return wm_out_str + text

    def _append_to_char(self, text: str, wm_final: str, loop: bool):
        wm_idx = 0
        wm_text = ""
        for idx in range(len(text)):
            if not loop and wm_idx >= len(wm_final):
                wm_text += text[idx:]
                break
            # 在每个字符串后添加字符
            offset = wm_final[wm_idx % len(wm_final)]
            wm_text += (
                text[idx] + self.confusables_chars[int(str(offset), self.wm_base)]
            )
            wm_idx += 1
        return wm_text

    def _append_as_binary(self, text: str, wm_final: str, loop: bool):
        if not isinstance(self.confusables_chars, dict):
            raise TypeError(
                "Template defined error: Confusables_chars must be dict"
                " when WMMethod equal to APPEND_AS_BINARY"
            )

        wm_idx = 0
        wm_text = ""
        for idx in range(len(text)):
            if not loop and wm_idx >= len(wm_final):
                wm_text += text[idx:]
                break
            # 在每个字符串后添加字符
            offset = wm_final[wm_idx % len(wm_final)]
            if offset == "0":
                wm_text += text[idx]
            else:
                wm_text += (
                    text[idx] + self.confusables_chars[self.confusables_chars_key]
                )
            wm_idx += 1
        return wm_text

    ################################# Retrieve watermark#################################

    def retrieve_watermark(
        self, wm_text: str, wm_base: int, wm_len: int, start_at: int = 0
    ):
        """Retrieve watermark from watermarked text

        Args:
            wm_text (str): Text which has be watermarked
            wm_base (int): The encoding base of the watermark string
            wm_len (int): The fixed length of watermark
            start_at (int): The offset position of insert watermark

        Returns:
            (str): The final watermark retrieved from text

        Raises:
            ValueError: If `start_at` is larger than `wm_text` length
            ValueError: If the length of retrieve watermark is not larger then `wm_len`
            ValueError: If unknown watermark method

        """
        if start_at > len(wm_text):
            raise ValueError(
                f"start_at: {start_at} is larger than wm_text length: {len(wm_text)}"
            )

        if start_at > 0:
            wm_text = wm_text[start_at:]

        wm_str = ""
        match self.method:
            case WMMethod.FIND_AND_REPLACE:
                wm_str = self._retrieve_find_and_replace(wm_text, wm_len)
            case WMMethod.DECORATE_EACH_CHAR:
                wm_str = self._retrieve_decorate_each_char(wm_text, wm_base, wm_len)
            case WMMethod.INSERT_INTO_POSITION:
                wm_str = self._retrieve_insert_into_position(wm_text, wm_base, wm_len)
            case WMMethod.APPEND_TO_CHAR:
                wm_str = self._retrieve_append_to_char(wm_text, wm_base, wm_len)
            case WMMethod.APPEND_AS_BINARY:
                wm_str = self._retrieve_append_as_binary(wm_text, wm_len)
            case _:
                raise ValueError(f"Unknown watermark method: {self.method}")

        if len(wm_str) < wm_len:
            raise ValueError(f"Watermark is: {wm_str}, length is less than {wm_len}")

        return wm_str[0:wm_len]

    def _retrieve_find_and_replace(self, wm_text: str, wm_len: int):
        if not isinstance(self.confusables_chars, dict):
            raise TypeError(
                "Template defined error: Confusables_chars must be dict"
                " when WMMethod equal to FIND_AND_REPLACE"
            )

        vals = self.confusables_chars.values()
        confusables_chars_length = len(list(vals)[0])
        confusables_chars_values = ""
        for val in vals:
            if isinstance(val, list):
                confusables_chars_values += "".join(val)
            if isinstance(val, str):
                confusables_chars_values += val
        wm_temp = ""
        for char in wm_text:
            distances = confusables_chars_values.find(char)
            if distances != -1:
                offset = int(distances % confusables_chars_length)
                # print(distances, offset)
                wm_temp += str(offset)
            if len(wm_temp) > wm_len:
                break
        return wm_temp

    def _retrieve_decorate_each_char(self, wm_text: str, wm_base: int, wm_len: int):
        if not isinstance(self.confusables_chars, dict):
            raise TypeError(
                "Template defined error: Confusables_chars must be dict"
                " when WMMethod equal to DECORATE_EACH_CHAR"
            )

        confusables_chars = self.confusables_chars[self.confusables_chars_key]
        for idx in range(len(confusables_chars)):
            offset = int(idx % wm_base)
            regex = confusables_chars[idx].replace("{char}", ".*?")
            wm_text = re.sub(regex, f"<wm_char>{offset}</wm_char>", wm_text)

        matched = re.findall(r"<wm_char>(.*?)</wm_char>", wm_text)
        return "".join(matched)[0:wm_len]

    def _retrieve_insert_into_position(self, wm_text: str, wm_base: int, wm_len: int):
        for idx in range(len(self.confusables_chars)):
            wm_val = np.base_repr(idx, wm_base)
            wm_text = wm_text.replace(
                self.confusables_chars[idx], f"<wm_char>{wm_val}</wm_char>"
            )

        matched = re.findall(r"<wm_char>(.*?)</wm_char>", wm_text)
        return "".join(matched)[0:wm_len]

    def _retrieve_append_to_char(self, wm_text: str, wm_base: int, wm_len: int):
        """retrieve watermark from append to char"""
        return self._retrieve_insert_into_position(wm_text, wm_base, wm_len)

    def _retrieve_append_as_binary(self, wm_text: str, wm_len: int):
        """retrieve watermark from append char as binary"""
        if not isinstance(self.confusables_chars, dict):
            raise TypeError(
                "Template defined error: Confusables_chars must be dict"
                " when WMMethod equal to APPEND_AS_BINARY"
            )

        confusables_char = self.confusables_chars[self.confusables_chars_key]
        wm_start = False
        wm_final = ""

        for idx, char in enumerate(wm_text):
            if len(wm_final) == wm_len:
                break
            if char == confusables_char and wm_start is False:
                wm_start = True
            if wm_start is False:
                continue

            if char == confusables_char:
                wm_final += "1"
                continue

            if char != confusables_char and (
                idx + 1 >= len(wm_text) or wm_text[idx + 1] != confusables_char
            ):
                wm_final += "0"
                continue
        if len(wm_final) != wm_len:
            raise ValueError(
                f"Retrieved watermark {wm_final}, length is not equal to {wm_len}"
            )

        return wm_final

confusables_chars = confusables_chars instance-attribute

The default template is in templates, you can your the modified custom template

confusables_chars_key = confusables_chars_key instance-attribute

Key must be set when WMMethod is DECORATE_EACH_CHAR

method = method instance-attribute

Watermark insertion method, see defines.WMMethod for details

wm_base = WMTemplate.get_wm_base_from_tpl(self.confusables_chars) instance-attribute

Watermark convert base. Must be less than or equal to confusables_chars_length

__init__(confusables_chars, wm_base, method, confusables_chars_key='')

Init of WMTemplate

Parameters:

Name Type Description Default
confusables_chars Union[dict, list]

The default template is in templates, you can your the modified custom template

required
wm_base int

Watermark convert base. Must be less than or equal to confusables_chars_length

required
method WMMethod

Watermark insertion method, see defines.WMMethod for details

required
confusables_chars_key str

Key must be set when WMMethod is DECORATE_EACH_CHAR

''
Note of wm_base

The value of wm_base will affect the range of watermark characters taken from confusables_chars

Source code in textwatermark/template.py
Python
def __init__(
    self,
    confusables_chars: Union[dict, list],
    wm_base: int,
    method: WMMethod,
    confusables_chars_key: str = "",
):
    """Init of WMTemplate

    Args:
        confusables_chars (Union[dict, list]): The default template is in templates,
            you can your the modified custom template
        wm_base (int): Watermark convert base. Must be less than
            or equal to confusables_chars_length
        method (WMMethod):  Watermark insertion method,
            see defines.WMMethod for details
        confusables_chars_key (str): Key must be set when
            WMMethod is DECORATE_EACH_CHAR

    Note: Note of wm_base
        The value of wm_base will affect the range of watermark characters taken
            from confusables_chars

    """

    self.confusables_chars = confusables_chars
    """The default template is in templates,
            you can your the modified custom template """
    if wm_base == 0:
        self.wm_base = WMTemplate.get_wm_base_from_tpl(self.confusables_chars)
        """Watermark convert base. Must be less than or equal to confusables_chars_length """
    else:
        self.wm_base = wm_base

    self.method = method
    """Watermark insertion method, see defines.WMMethod for details """

    if (
        self.method in [WMMethod.DECORATE_EACH_CHAR, WMMethod.APPEND_AS_BINARY]
        and confusables_chars_key not in self.confusables_chars
    ):
        raise ValueError(
            "When WMMethod in [DECORATE_EACH_CHAR,APPEND_AS_BINARY], confusables_chars_key: "
            f"{confusables_chars_key} must can be found in confusables_chars's keys."
        )

    self.confusables_chars_key = confusables_chars_key
    """Key must be set when WMMethod is DECORATE_EACH_CHAR"""

check_find_and_replace_space(text, wm_len)

Check if enough space for insert watermark

Parameters:

Name Type Description Default
text str

text to check

required
wm_len int

watermark length

required

Returns:

Type Description
int

number of times can insert watermark

Why use this method?

Because WMMethod.FIND_AND_REPLACE mode must need enough space to find special char and replace to watermarked char

Source code in textwatermark/template.py
Python
def check_find_and_replace_space(self, text: str, wm_len: int):
    """Check if enough space for insert watermark

    Args:
        text (str): text to check
        wm_len (int): watermark length
    Returns:
        (int): number of times can insert watermark

    Note: Why use this method?
        Because WMMethod.FIND_AND_REPLACE mode must need enough space
        to find special char and replace to watermarked char
    """
    if not isinstance(self.confusables_chars, dict):
        raise TypeError(
            "Template defined error: Confusables_chars must be dict"
            " when WMMethod equal to FIND_AND_REPLACE"
        )

    actual_len = 0

    confusables_chars_keys = "".join(self.confusables_chars.keys())
    # 计算可插入的水印空间大小
    for char in text:
        if confusables_chars_keys.find(char) != -1:
            actual_len += 1
    times = actual_len // wm_len
    return times

clean_html_tags(html)

Clean html tags from watermark.

Parameters:

Name Type Description Default
html str

HTML code

required

Returns:

Type Description
str

Cleaned HTML code

Source code in textwatermark/template.py
Python
def clean_html_tags(self, html):
    """Clean html tags from watermark.

    Args:
        html (str): HTML code

    Returns:
        (str): Cleaned HTML code
    """
    return re.sub(r"<[^<]+?>", "", html)

clean_text(text)

clean confusables chars from text

Parameters:

Name Type Description Default
text str

Text to clean

required

Returns:

Type Description
str

Cleaned text

Raises:

Type Description
ValueError

If unknown method

Source code in textwatermark/template.py
Python
def clean_text(self, text: str):
    """clean confusables chars from text

    Args:
        text (str): Text to clean

    Returns:
        (str): Cleaned text

    Raises:
        ValueError: If unknown method

    """
    text = self.clean_html_tags(text)

    match self.method:
        case WMMethod.FIND_AND_REPLACE:
            if not isinstance(self.confusables_chars, dict):
                raise TypeError(
                    "Template defined error: Confusables_chars must be dict"
                    " when WMMethod equal to FIND_AND_REPLACE"
                )

            for key, vals in self.confusables_chars.items():
                if isinstance(vals, list):
                    vals = "".join(vals)
                vals = vals.replace(key, "")
                for val in vals:
                    text = text.replace(val, key)
        case WMMethod.DECORATE_EACH_CHAR:
            # use self.clean_html_tags by default
            pass
        case WMMethod.INSERT_INTO_POSITION | WMMethod.APPEND_TO_CHAR:
            for val in self.confusables_chars:
                text = text.replace(val, "")
        case WMMethod.APPEND_AS_BINARY:
            if not isinstance(self.confusables_chars, dict):
                raise TypeError(
                    "Template defined error: Confusables_chars must be dict"
                    " when WMMethod equal to APPEND_AS_BINARY"
                )

            text = text.replace(
                self.confusables_chars[self.confusables_chars_key], ""
            )
        case _:
            raise ValueError(f"Unknown WMMethod: {self.method}")
    # print(text)
    return text

get_wm_base_from_tpl(confusables_chars) staticmethod

get wm_base from template's confusables chars

Parameters:

Name Type Description Default
confusables_chars dict

confusables chars template

required

Returns:

Type Description
int

wm_base

Source code in textwatermark/template.py
Python
@staticmethod
def get_wm_base_from_tpl(confusables_chars):
    """get wm_base from template's confusables chars

    Args:
        confusables_chars (dict): confusables chars template

    Returns:
        (int): wm_base

    """
    if isinstance(confusables_chars, list):
        wm_base = len(confusables_chars)
    elif isinstance(confusables_chars, dict):
        wm_base = len(list(confusables_chars.values())[0])
    else:
        raise TypeError("confusables_chars must be list or dict")

    wm_base = max(wm_base, 2)
    wm_base = min(wm_base, 36)

    return wm_base

insert_watermark(text, wm_final, start_at=0, loop=False)

Insert watermark into text

Parameters:

Name Type Description Default
text str

Text to be watermarked

required
wm_final str

The final watermark string

required
start_at int

The offset position for insert watermark

0
loop bool

Whether to insert the watermark in a loop

False

Returns:

Type Description
str

Watermarked text

Raises:

Type Description
ValueError

If start_at is larger than text length

ValueError

If there is not enough space to insert a watermark

VaueError

If unknown watermark method

Source code in textwatermark/template.py
Python
def insert_watermark(
    self, text: str, wm_final: str, start_at: int = 0, loop: bool = False
):
    """Insert watermark into text

    Args:
        text (str): Text to be watermarked
        wm_final (str): The final watermark string
        start_at (int): The offset position for insert watermark
        loop (bool): Whether to insert the watermark in a loop

    Returns:
        (str): Watermarked text

    Raises:
        ValueError: If start_at is larger than text length
        ValueError: If there is not enough space to insert a watermark
        VaueError: If unknown watermark method

    """
    if start_at > len(text):
        raise ValueError(
            f"Start_at is {start_at}, larger than text length: {len(text)}"
        )

    text = self.clean_text(text=text)

    if start_at > 0:
        out_text = text[0:start_at]
        text = text[start_at:]
    else:
        out_text = ""

    match self.method:
        case WMMethod.FIND_AND_REPLACE:
            times = self.check_find_and_replace_space(text, len(wm_final))
            if times < 1:
                raise ValueError(
                    f"There is not enough space to insert a watermark: {wm_final}"
                )

            out_text += self._find_and_replace(
                text=text, wm_final=wm_final, loop=loop
            )
        case WMMethod.DECORATE_EACH_CHAR:
            if len(wm_final) > len(text):
                raise ValueError(
                    f"There is not enough space to insert a watermark: {wm_final}"
                )

            out_text += self._decorate_each_char(
                text=text, wm_final=wm_final, loop=loop
            )
        case WMMethod.INSERT_INTO_POSITION:
            out_text += self._insert_into_position(text=text, wm_final=wm_final)
        case WMMethod.APPEND_TO_CHAR:
            if len(wm_final) > len(text):
                raise ValueError(
                    f"There is not enough space to insert a watermark: {wm_final}"
                )

            out_text += self._append_to_char(
                text=text, wm_final=wm_final, loop=loop
            )
        case WMMethod.APPEND_AS_BINARY:
            if len(wm_final) > len(text):
                raise ValueError(
                    f"There is not enough space to insert a watermark: {wm_final}"
                )

            out_text += self._append_as_binary(
                text=text, wm_final=wm_final, loop=loop
            )

    return out_text

retrieve_watermark(wm_text, wm_base, wm_len, start_at=0)

Retrieve watermark from watermarked text

Parameters:

Name Type Description Default
wm_text str

Text which has be watermarked

required
wm_base int

The encoding base of the watermark string

required
wm_len int

The fixed length of watermark

required
start_at int

The offset position of insert watermark

0

Returns:

Type Description
str

The final watermark retrieved from text

Raises:

Type Description
ValueError

If start_at is larger than wm_text length

ValueError

If the length of retrieve watermark is not larger then wm_len

ValueError

If unknown watermark method

Source code in textwatermark/template.py
Python
def retrieve_watermark(
    self, wm_text: str, wm_base: int, wm_len: int, start_at: int = 0
):
    """Retrieve watermark from watermarked text

    Args:
        wm_text (str): Text which has be watermarked
        wm_base (int): The encoding base of the watermark string
        wm_len (int): The fixed length of watermark
        start_at (int): The offset position of insert watermark

    Returns:
        (str): The final watermark retrieved from text

    Raises:
        ValueError: If `start_at` is larger than `wm_text` length
        ValueError: If the length of retrieve watermark is not larger then `wm_len`
        ValueError: If unknown watermark method

    """
    if start_at > len(wm_text):
        raise ValueError(
            f"start_at: {start_at} is larger than wm_text length: {len(wm_text)}"
        )

    if start_at > 0:
        wm_text = wm_text[start_at:]

    wm_str = ""
    match self.method:
        case WMMethod.FIND_AND_REPLACE:
            wm_str = self._retrieve_find_and_replace(wm_text, wm_len)
        case WMMethod.DECORATE_EACH_CHAR:
            wm_str = self._retrieve_decorate_each_char(wm_text, wm_base, wm_len)
        case WMMethod.INSERT_INTO_POSITION:
            wm_str = self._retrieve_insert_into_position(wm_text, wm_base, wm_len)
        case WMMethod.APPEND_TO_CHAR:
            wm_str = self._retrieve_append_to_char(wm_text, wm_base, wm_len)
        case WMMethod.APPEND_AS_BINARY:
            wm_str = self._retrieve_append_as_binary(wm_text, wm_len)
        case _:
            raise ValueError(f"Unknown watermark method: {self.method}")

    if len(wm_str) < wm_len:
        raise ValueError(f"Watermark is: {wm_str}, length is less than {wm_len}")

    return wm_str[0:wm_len]