diff --git a/watermarker/src/commonMain/kotlin/fileWatermarker/TextWatermarker.kt b/watermarker/src/commonMain/kotlin/fileWatermarker/TextWatermarker.kt index dd76986..e30ad45 100644 --- a/watermarker/src/commonMain/kotlin/fileWatermarker/TextWatermarker.kt +++ b/watermarker/src/commonMain/kotlin/fileWatermarker/TextWatermarker.kt @@ -400,9 +400,10 @@ class TextWatermarker( "'${char.toUnicodeRepresentation()}'" } - return "The file contains characters of the watermark " + - "transcoding alphabet. Adding a Watermarking would potentially make the " + - "file unusable! Maybe the file already contains a watermark?\n\n" + + return "The input contains characters of the watermark " + + "transcoding alphabet. It is only possible to add a watermark to and input " + + "that doesn't contain any watermark. Adding another watermark would potentially " + + "make the input unusable! Maybe the input already contains a watermark?\n\n" + "Contained Chars:\n" + "$containedChars." } diff --git a/watermarker/src/commonTest/kotlin/unitTest/fileWatermarker/TextWatermarkerTest.kt b/watermarker/src/commonTest/kotlin/unitTest/fileWatermarker/TextWatermarkerTest.kt index 2c6658a..56a540b 100644 --- a/watermarker/src/commonTest/kotlin/unitTest/fileWatermarker/TextWatermarkerTest.kt +++ b/watermarker/src/commonTest/kotlin/unitTest/fileWatermarker/TextWatermarkerTest.kt @@ -141,9 +141,10 @@ class TextWatermarkerTest { // Arrange val error = TextWatermarker.ContainsAlphabetCharsError(sequenceOf('a', 'b')) val expected = - "Error (TextWatermarker.addWatermark): The file contains characters of the watermark " + - "transcoding alphabet. Adding a Watermarking would potentially make the file " + - "unusable! Maybe the file already contains a watermark?\n" + + "Error (TextWatermarker.addWatermark): The input contains characters of the watermark" + + " transcoding alphabet. It is only possible to add a watermark to and input that" + + " doesn't contain any watermark. Adding another watermark would potentially make " + + "the input unusable! Maybe the input already contains a watermark?\n" + "\n" + "Contained Chars:\n" + "['\\u0061','\\u0062']." diff --git a/webinterface/src/jsMain/kotlin/WatermarkTextEmbedTab.kt b/webinterface/src/jsMain/kotlin/WatermarkTextEmbedTab.kt index b48699f..c5f3ffe 100644 --- a/webinterface/src/jsMain/kotlin/WatermarkTextEmbedTab.kt +++ b/webinterface/src/jsMain/kotlin/WatermarkTextEmbedTab.kt @@ -7,6 +7,8 @@ import de.fraunhofer.isst.trend.watermarker.Watermarker import de.fraunhofer.isst.trend.watermarker.fileWatermarker.TextWatermarker +import de.fraunhofer.isst.trend.watermarker.returnTypes.Result +import de.fraunhofer.isst.trend.watermarker.watermarks.TextWatermark import de.fraunhofer.isst.trend.watermarker.watermarks.Watermark import io.kvision.core.Placement import io.kvision.core.TooltipOptions @@ -170,23 +172,45 @@ class WatermarkTextEmbedTab : SimplePanel() { submitButton.onClick { if (textFormPanel.validate()) { // Modal - val watermarkedText = + val modal = Modal("Result") + + val watermarkedResult = addWatermarkToText( textFormPanel.getData().watermark, textFormPanel.getData().text, ) - val modal = Modal("Successful") - modal.add(span("The following text includes your watermark:")) - modal.add(div(watermarkedText, className = "selectable card-text")) - modal.addButton( - Button("Copy to Clipboard") { - onClick { - window.navigator.clipboard.writeText(watermarkedText) - Toast.success("Successful copied to clipboard!") - } - }, - ) + if (watermarkedResult.isSuccess) { + val watermarkedText = watermarkedResult.value ?: "" + + modal.add( + div( + "Successfully embedded your watermark in the " + + "cover text", + className = "alert alert-success", + ), + ) + modal.add(span("The following text includes your watermark:")) + modal.add(div(watermarkedText, className = "selectable card-text")) + modal.addButton( + Button("Copy to Clipboard") { + onClick { + window.navigator.clipboard.writeText(watermarkedText) + Toast.success("Successful copied to clipboard!") + } + }, + ) + } else { + modal.add( + div( + "An error occurs during the watermarking " + + "process:
" + watermarkedResult.getMessage(), + rich = true, + className = "alert alert-danger", + ), + ) + } + modal.addButton( Button("Close") { onClick { @@ -249,20 +273,14 @@ class WatermarkTextEmbedTab : SimplePanel() { (if (watermarkerInput.value == null) min else capacityObservable.value) } - /** Adds a [watermark] string to [text] and returns the watermarked text */ + /** Adds a [watermarkString] string to [text] and returns the watermarked text result */ private fun addWatermarkToText( - watermark: String, + watermarkString: String, text: String, - ): String { + ): Result { val watermarker = Watermarker() - val result = watermarker.textAddWatermark(text, watermark.encodeToByteArray().asList()) - - return if (result.isSuccess) { - result.value ?: "" - } else { - // TODO: Proper error handling - result.toString() - } + val watermark = TextWatermark.new(watermarkString) + return watermarker.textAddWatermark(text, watermark) } /** diff --git a/webinterface/src/jsMain/kotlin/WatermarkTextExtractTab.kt b/webinterface/src/jsMain/kotlin/WatermarkTextExtractTab.kt index 3b99f70..f6a551f 100644 --- a/webinterface/src/jsMain/kotlin/WatermarkTextExtractTab.kt +++ b/webinterface/src/jsMain/kotlin/WatermarkTextExtractTab.kt @@ -6,6 +6,14 @@ */ import de.fraunhofer.isst.trend.watermarker.Watermarker +import de.fraunhofer.isst.trend.watermarker.fileWatermarker.DefaultTranscoding +import de.fraunhofer.isst.trend.watermarker.helper.toUnicodeRepresentation +import de.fraunhofer.isst.trend.watermarker.returnTypes.Result +import de.fraunhofer.isst.trend.watermarker.watermarks.TextWatermark +import de.fraunhofer.isst.trend.watermarker.watermarks.Watermark +import de.fraunhofer.isst.trend.watermarker.watermarks.toTextWatermarks +import io.kvision.collapse.collapse +import io.kvision.collapse.forCollapse import io.kvision.core.Placement import io.kvision.core.TooltipOptions import io.kvision.core.Trigger @@ -13,13 +21,21 @@ import io.kvision.core.enableTooltip import io.kvision.form.FormMethod import io.kvision.form.formPanel import io.kvision.form.text.TextArea +import io.kvision.html.Button import io.kvision.html.ButtonStyle +import io.kvision.html.br import io.kvision.html.button +import io.kvision.html.div +import io.kvision.html.li +import io.kvision.html.p import io.kvision.html.span -import io.kvision.modal.Alert +import io.kvision.html.strong +import io.kvision.html.ul import io.kvision.modal.Confirm +import io.kvision.modal.Modal import io.kvision.panel.HPanel import io.kvision.panel.SimplePanel +import io.kvision.panel.simplePanel import io.kvision.utils.em import kotlinx.serialization.Serializable @@ -66,11 +82,120 @@ class WatermarkTextExtractTab : SimplePanel() { onClick { if (extractTextFormPanel.validate()) { println("Starting watermark extraction process ...") - val watermarkedText = + val watermarkedResult = extractWatermark( extractTextFormPanel.getData().text, + ).toTextWatermarks() + + val modal = Modal("Result") + + // Success + if (watermarkedResult.status.isSuccess) { + if (watermarkedResult.value.isNullOrEmpty()) { + modal.add( + div( + "Could not find any valid watermark in the" + + "text.", + className = "alert alert-secondary", + ), + ) + } else { + modal.add( + div( + "Successfully extracted the watermark(s)!", + className = "alert alert-success", + ), + ) + + val countedWatermarkList = + getWatermarkStringList(watermarkedResult) + .groupingBy { it } + .eachCount() + + // Most frequent watermark + modal.add( + span( + "Most frequent " + + "watermark: " + + countedWatermarkList.maxByOrNull { + it.value + }?.key + "

", + rich = true, + ), + ) + + // Details section + val watermarkDetailsPanel = + simplePanel { + button( + "More details", + style = ButtonStyle.SECONDARY, + ).forCollapse("watermark-details") + + collapse("watermark-details") { + // Print all watermarks + strong("Detailed watermark list:") + ul { + for ((key, value) in countedWatermarkList) { + li("$key ($value times)") + } + } + br() + + // Show watermarking type + strong("Watermarking type(s): ") + p( + watermarkedResult.value!!.map { watermark -> + watermark.finish().getSource() + }.toSet().toString(), + ) + + // Show input text with hidden chars + strong( + "Raw data with hidden alphabet chars:", + ) + br() + span( + showWatermarkChars( + extractTextFormPanel.getData().text, + ), + rich = true, + className = "break-all", + ) + } + } + modal.add(watermarkDetailsPanel) + } + // Warning + } else if (watermarkedResult.status.isWarning) { + modal.add( + div( + "Some problems occur during the extraction: " + + watermarkedResult.status.getMessage(), + className = "alert alert-warning", + ), + ) + modal.add(strong("Extracted Data: ")) + modal.add(p(getWatermarkStringList(watermarkedResult).toString())) + // Error + } else if (watermarkedResult.status.isError) { + modal.add( + div( + "An error occurs during the extraction: " + + watermarkedResult.status.getMessage(), + className = "alert alert-danger", + ), ) - Alert.show("Successful", watermarkedText) + } + + modal.addButton( + Button("Close") { + onClick { + modal.hide() + } + }, + ) + modal.show() } } } @@ -91,17 +216,49 @@ class WatermarkTextExtractTab : SimplePanel() { } /** Extracts a watermark from a [text] and returns it */ - private fun extractWatermark(text: String): String { + private fun extractWatermark(text: String): Result> { val watermarker = Watermarker() - val result = watermarker.textGetWatermarks(text) - - return if (result.isSuccess) { - result.value!!.map { watermark -> - watermark.watermarkContent.toByteArray().decodeToString() - }.toString() - } else { - // TODO: Proper error handling - result.toString() + return watermarker.textGetWatermarks(text, squash = false) + } + + /** + * Replaces all whitespaces of the transcoding alphabet of the watermarking library in + * [watermarkedText] with its Unicode representation. [html] defines if the result is a + * styled HTML string (true) or a plain text without formatting (false). + */ + private fun showWatermarkChars( + watermarkedText: String, + html: Boolean = true, + ): String { + val alphabet = DefaultTranscoding.alphabet + DefaultTranscoding.SEPARATOR_CHAR + var resultText = watermarkedText + var className: String + + for (char in alphabet) { + if (html) { + className = + if (char == DefaultTranscoding.SEPARATOR_CHAR) { + "separator-highlight" + } else { + "whitespace-highlight" + } + + resultText = + resultText.replace( + char.toString(), + "" + + "${char.toUnicodeRepresentation()}", + ) + } else { + resultText = resultText.replace(char.toString(), char.toUnicodeRepresentation()) + } } + return resultText } + + /** Creates a list of Strings based on a [watermarkedResult] */ + private fun getWatermarkStringList(watermarkedResult: Result>) = + watermarkedResult.value!!.map { watermark -> + watermark.text + } } diff --git a/webinterface/src/jsMain/resources/css/custom.css b/webinterface/src/jsMain/resources/css/custom.css index 8987204..38a5c91 100644 --- a/webinterface/src/jsMain/resources/css/custom.css +++ b/webinterface/src/jsMain/resources/css/custom.css @@ -12,3 +12,18 @@ padding: 1em; margin-top: 0.5em; } + +.break-all { + word-break: break-all; +} + +.whitespace-highlight { + background-color: lightblue; + padding: 1px; +} + +.separator-highlight { + background-color: black; + color: white; + padding: 1px; +}