Function Repository Resource:

TextWatermark

Source Notebook

Hide an invisible textual watermark within a string

Contributed by: Jon McLoone

ResourceFunction["TextWatermark"][text,watermark]

embeds watermark in text without affecting the display of text.

ResourceFunction["TextWatermark"][text]

retrieves the watermark embedded in text.

Details and Options

ResourceFunction["TextWatermark"] uses a form of steganography.
The watermark is encoded into the text using invisible characters or by adjusting spacing:
Method"InvisibleSpace"how the watermark is encoded
"Sparsity"1how frequently additional characters are inserted
Method takes four possible values:
"InvisibleSpace"may insert an invisible space character (Unicode 8203) after every space (Unicode 13) in the original text
"InvisibleDense"may insert an invisible space character (Unicode 8203) after any character in the original text
"Space"may insert a space character (ASCII 13) after punctuation marks
"SpaceDense"may insert a space character (ASCII 13) after punctuation marks and other non-letter characters

Examples

Basic Examples (2) 

Insert the a watermark into the text of Alice in Wonderland:

In[1]:=
Snippet[text = ResourceFunction["TextWatermark"][
   ExampleData[{"Text", "AliceInWonderland"}], "Jon McLoone"], 3]
Out[1]=

Retrieve the watermark:

In[2]:=
ResourceFunction["TextWatermark"][text]
Out[2]=

Scope (1) 

The watermark is added repeatedly, so any sufficiently long sequence of the text will contain the watermark:

In[3]:=
text = ResourceFunction["TextWatermark"][
   ExampleData[{"Text", "AliceInWonderland"}], "Jon McLoone"];
In[4]:=
ResourceFunction["TextWatermark"][StringTake[text, {10000, 13000}]]
Out[4]=

Options (3) 

By default, information is encoded using the invisible space character (UNICODE 8203, Hex 200b). If the text is displayed in software which does not render invisible space then the encoding will be visible to the reader:

In[5]:=
Snippet[ResourceFunction["TextWatermark"][
   ExampleData[{"Text", "AliceInWonderland"}], "Jon McLoone"], 3] // FullForm
Out[6]=

Use Method"Space" to insert standard space characters (ASCII 32); however, the result will not appear identical to the original (note the lack of space after "to do.":

In[7]:=
Snippet[text = ResourceFunction["TextWatermark"][
   ExampleData[{"Text", "AliceInWonderland"}], "Jon McLoone", Method -> "Space"], 3]
Out[7]=
In[8]:=
ResourceFunction["TextWatermark"][text]
Out[8]=

TextWatermark inserts information greedily depending on the Method used. In this example, 26 invisible spaces are inserted in the first 300 characters:

In[9]:=
StringTake[
  ResourceFunction["TextWatermark"][
   ExampleData[{"Text", "AliceInWonderland"}], "Jon McLoone"], 300] // FullForm
Out[9]=

Use fewer opportunities to make the watermark less detectable. Higher "Sparsity" will require more carrier text to embed the watermark into:

In[10]:=
StringTake[
  ResourceFunction["TextWatermark"][
   ExampleData[{"Text", "AliceInWonderland"}], "Jon McLoone", "Sparsity" -> 26], 300] // FullForm
Out[10]=

Applications (1) 

The typical application is to create unique versions of secret text for each reader so that, if the text is leaked, it is possible to trace which reader was responsible:

In[11]:=
CloudDeploy[
 APIFunction[{}, ResourceFunction["TextWatermark"][
   ExampleData[{"Text", "AliceInWonderland"}], $RequesterWolframID], "HTML"], "textWatermarkDemo", Permissions -> {"Authenticated" -> "Execute"}]
Out[11]=

Possible Issues (2) 

If there is insufficient carrier text to encode the watermark into, TextWatermark will attempt to switch method from "InvisibleSpace" to "InvisibleSpaceDense" or "Space" to "SpaceDense", and if it still cannot succeed, will return $Failed

In[12]:=
ResourceFunction["TextWatermark"]["Short messsage", "Long watermark"]
Out[12]=

When decoding watermarks, it is not known which embedding method has been used, so each is performed in turn. There is a small risk that a spurious message is generated. This process is also slower than manually specifying the Method when decoding:

In[13]:=
text = ResourceFunction["TextWatermark"][
   ExampleData[{"Text", "AliceInWonderland"}], "Jon McLoone", Method -> "InvisibleSpace"];
In[14]:=
ResourceFunction["TextWatermark"][text, Method -> "InvisibleSpace"]
Out[14]=

Publisher

Jon McLoone

Version History

  • 1.0.1 – 10 February 2021
  • 1.0.0 – 05 February 2021

License Information