From 94fcdae7f819e07df8b47b0717fb54479e66d5d4 Mon Sep 17 00:00:00 2001
From: Julian Raphael Jautz <julian.jautz@student.tugraz.at>
Date: Mon, 12 Feb 2024 17:29:55 +0100
Subject: [PATCH] PAINTROID_684_apply_outline_to_text

TextTool feature: text with outline

-added new button for outline
-outline created by painting twice (second time in stroke mode)
---
 .../espresso/tools/TextToolIntegrationTest.kt | 148 +++++++++++-
 .../serialization/CommandSerializationTest.kt |   4 +-
 .../command/implementation/TextToolCommand.kt |  22 +-
 .../serialization/SerializableTypeface.kt     |   6 +-
 .../tools/implementation/TextTool.kt          |  58 ++++-
 .../tools/options/TextToolOptionsView.kt      |   8 +-
 .../ui/tools/DefaultTextToolOptionsView.kt    |  75 +++++-
 .../ic_pocketpaint_a_blue_outline.xml         |   7 +
 .../ic_pocketpaint_a_white_outline.xml        |   7 +
 .../res/drawable/outline_button_selector.xml  |   4 +
 .../layout/dialog_pocketpaint_text_tool.xml   | 215 ++++++++++++------
 Paintroid/src/main/res/values/string.xml      |   1 +
 12 files changed, 472 insertions(+), 83 deletions(-)
 create mode 100644 Paintroid/src/main/res/drawable/ic_pocketpaint_a_blue_outline.xml
 create mode 100644 Paintroid/src/main/res/drawable/ic_pocketpaint_a_white_outline.xml
 create mode 100644 Paintroid/src/main/res/drawable/outline_button_selector.xml

diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/tools/TextToolIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/tools/TextToolIntegrationTest.kt
index f17d6d50b0..3d0473fd3c 100644
--- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/tools/TextToolIntegrationTest.kt
+++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/tools/TextToolIntegrationTest.kt
@@ -27,7 +27,9 @@ import android.graphics.Paint
 import android.graphics.PointF
 import android.graphics.Typeface
 import android.widget.EditText
+import android.widget.RelativeLayout
 import androidx.core.content.res.ResourcesCompat
+import androidx.core.view.isVisible
 import androidx.recyclerview.widget.RecyclerView
 import androidx.test.espresso.Espresso
 import androidx.test.espresso.Espresso.onView
@@ -61,6 +63,7 @@ import org.catrobat.paintroid.tools.FontType
 import org.catrobat.paintroid.tools.ToolReference
 import org.catrobat.paintroid.tools.ToolType
 import org.catrobat.paintroid.tools.implementation.BOX_OFFSET
+import org.catrobat.paintroid.tools.implementation.DEFAULT_TEXT_OUTLINE_WIDTH
 import org.catrobat.paintroid.tools.implementation.MARGIN_TOP
 import org.catrobat.paintroid.tools.implementation.TEXT_SIZE_MAGNIFICATION_FACTOR
 import org.catrobat.paintroid.tools.implementation.TextTool
@@ -90,6 +93,8 @@ class TextToolIntegrationTest {
     private var underlinedToggleButton: MaterialButton? = null
     private var italicToggleButton: MaterialButton? = null
     private var boldToggleButton: MaterialButton? = null
+    private var outlineToggleButton: MaterialButton? = null
+    private var outlineWidthLayout: RelativeLayout? = null
     private var textSize: EditText? = null
     private var layerModel: LayerContracts.Model? = null
     private lateinit var activity: MainActivity
@@ -116,6 +121,8 @@ class TextToolIntegrationTest {
             activity.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_underlined)
         italicToggleButton = activity.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_italic)
         boldToggleButton = activity.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_bold)
+        outlineToggleButton = activity.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_outline)
+        outlineWidthLayout = activity.findViewById(R.id.pocketpaint_outline_width_layout)
         textSize = activity.findViewById(R.id.pocketpaint_font_size_text)
         textTool?.resetBoxPosition()
     }
@@ -125,6 +132,7 @@ class TextToolIntegrationTest {
         selectFormatting(FormattingOptions.ITALIC)
         selectFormatting(FormattingOptions.BOLD)
         selectFormatting(FormattingOptions.UNDERLINE)
+        selectFormatting(FormattingOptions.OUTLINE)
         enterTestText()
         onView(withId(R.id.pocketpaint_text_tool_dialog_input_text)).perform(click())
         onView(withId(R.id.pocketpaint_text_tool_dialog_input_text)).perform(
@@ -135,12 +143,14 @@ class TextToolIntegrationTest {
         italicToggleButton?.let { Assert.assertTrue(it.isChecked) }
         boldToggleButton?.let { Assert.assertTrue(it.isChecked) }
         underlinedToggleButton?.let { Assert.assertTrue(it.isChecked) }
+        outlineToggleButton?.let { Assert.assertTrue(it.isChecked) }
         Assert.assertEquals(TEST_TEXT_ADVANCED, textEditText?.text?.toString())
         onView(withId(R.id.pocketpaint_text_tool_dialog_input_text)).check(matches(isDisplayed()))
         onView(withId(R.id.pocketpaint_text_tool_dialog_list_font)).check(matches(isDisplayed()))
         onView(withId(R.id.pocketpaint_text_tool_dialog_toggle_underlined)).check(matches(isDisplayed()))
         onView(withId(R.id.pocketpaint_text_tool_dialog_toggle_italic)).check(matches(isDisplayed()))
         onView(withId(R.id.pocketpaint_text_tool_dialog_toggle_bold)).check(matches(isDisplayed()))
+        onView(withId(R.id.pocketpaint_text_tool_dialog_toggle_outline)).check(matches(isDisplayed()))
         onView(withId(R.id.pocketpaint_font_size_text)).check(matches(isDisplayed()))
     }
 
@@ -149,6 +159,7 @@ class TextToolIntegrationTest {
         selectFormatting(FormattingOptions.ITALIC)
         selectFormatting(FormattingOptions.BOLD)
         selectFormatting(FormattingOptions.UNDERLINE)
+        selectFormatting(FormattingOptions.OUTLINE)
         enterTestText()
         onDrawingSurfaceView()
             .perform(UiInteractions.touchAt(DrawingSurfaceLocationProvider.MIDDLE))
@@ -156,6 +167,7 @@ class TextToolIntegrationTest {
         italicToggleButton?.let { Assert.assertTrue(it.isChecked) }
         boldToggleButton?.let { Assert.assertTrue(it.isChecked) }
         underlinedToggleButton?.let { Assert.assertTrue(it.isChecked) }
+        outlineToggleButton?.let { Assert.assertTrue(it.isChecked) }
         Assert.assertEquals(TEST_TEXT, textEditText?.text?.toString())
         onView(withId(R.id.pocketpaint_text_tool_dialog_input_text))
             .check(matches(not(isDisplayed())))
@@ -167,6 +179,8 @@ class TextToolIntegrationTest {
             .check(matches(not(isDisplayed())))
         onView(withId(R.id.pocketpaint_text_tool_dialog_toggle_bold))
             .check(matches(not(isDisplayed())))
+        onView(withId(R.id.pocketpaint_text_tool_dialog_toggle_outline))
+            .check(matches(not(isDisplayed())))
         onView(withId(R.id.pocketpaint_font_size_text))
             .check(matches(not(isDisplayed())))
     }
@@ -247,6 +261,8 @@ class TextToolIntegrationTest {
         textTool?.let { Assert.assertFalse(it.underlined) }
         textTool?.let { Assert.assertFalse(it.italic) }
         textTool?.let { Assert.assertFalse(it.bold) }
+        textTool?.let { Assert.assertFalse(it.outlined) }
+        outlineWidthLayout?.let { Assert.assertFalse(it.isVisible) }
     }
 
     @Test
@@ -292,6 +308,20 @@ class TextToolIntegrationTest {
         Assert.assertFalse(toolMemberBold)
         boldToggleButton?.let { Assert.assertFalse(it.isChecked) }
         Assert.assertEquals(getFormattingOptionAsString(FormattingOptions.BOLD), boldToggleButton?.text.toString())
+        selectFormatting(FormattingOptions.OUTLINE)
+        textTool?.let { Assert.assertTrue(it.outlined) }
+        outlineToggleButton?.let { Assert.assertTrue(it.isChecked) }
+        Assert.assertEquals(
+            getFormattingOptionAsString(FormattingOptions.OUTLINE),
+            outlineToggleButton?.text.toString()
+        )
+        selectFormatting(FormattingOptions.OUTLINE)
+        textTool?.let { Assert.assertFalse(it.outlined) }
+        outlineToggleButton?.let { Assert.assertFalse(it.isChecked) }
+        Assert.assertEquals(
+            getFormattingOptionAsString(FormattingOptions.OUTLINE),
+            outlineToggleButton?.text.toString()
+        )
     }
 
     @Test
@@ -301,6 +331,7 @@ class TextToolIntegrationTest {
         selectFormatting(FormattingOptions.UNDERLINE)
         selectFormatting(FormattingOptions.ITALIC)
         selectFormatting(FormattingOptions.BOLD)
+        selectFormatting(FormattingOptions.OUTLINE)
         onToolBarView().performCloseToolOptionsView()
 
         val oldBoxWidth = toolMemberBoxWidth
@@ -318,6 +349,7 @@ class TextToolIntegrationTest {
         underlinedToggleButton?.let { Assert.assertTrue(it.isChecked) }
         italicToggleButton?.let { Assert.assertTrue(it.isChecked) }
         boldToggleButton?.let { Assert.assertTrue(it.isChecked) }
+        outlineToggleButton?.let { Assert.assertTrue(it.isChecked) }
         Assert.assertTrue(oldBoxWidth == toolMemberBoxWidth && oldBoxHeight == toolMemberBoxHeight)
     }
 
@@ -328,6 +360,7 @@ class TextToolIntegrationTest {
         selectFormatting(FormattingOptions.UNDERLINE)
         selectFormatting(FormattingOptions.ITALIC)
         selectFormatting(FormattingOptions.BOLD)
+        selectFormatting(FormattingOptions.OUTLINE)
 
         val toolMemberBoxPosition = toolMemberBoxPosition
         val expectedPosition = toolMemberBoxPosition?.y?.let { PointF(toolMemberBoxPosition.x, it) }
@@ -344,6 +377,7 @@ class TextToolIntegrationTest {
         underlinedToggleButton?.let { Assert.assertTrue(it.isChecked) }
         italicToggleButton?.let { Assert.assertTrue(it.isChecked) }
         boldToggleButton?.let { Assert.assertTrue(it.isChecked) }
+        outlineToggleButton?.let { Assert.assertTrue(it.isChecked) }
         Assert.assertEquals(expectedPosition, toolMemberBoxPosition)
         Assert.assertEquals(oldBoxWidth.toDouble(), toolMemberBoxWidth.toDouble(), EQUALS_DELTA)
         Assert.assertEquals(oldBoxHeight.toDouble(), toolMemberBoxHeight.toDouble(), EQUALS_DELTA)
@@ -750,6 +784,7 @@ class TextToolIntegrationTest {
                 selectFormatting(FormattingOptions.ITALIC)
                 selectFormatting(FormattingOptions.BOLD)
                 selectFormatting(FormattingOptions.UNDERLINE)
+                selectFormatting(FormattingOptions.OUTLINE)
             }
             val boxWidth = toolMemberBoxWidth
             val boxHeight = toolMemberBoxHeight
@@ -759,10 +794,120 @@ class TextToolIntegrationTest {
             selectFormatting(FormattingOptions.ITALIC)
             selectFormatting(FormattingOptions.BOLD)
             selectFormatting(FormattingOptions.UNDERLINE)
+            selectFormatting(FormattingOptions.OUTLINE)
             Assert.assertTrue(boxWidth < toolMemberBoxWidth && boxHeight < toolMemberBoxHeight)
         }
     }
 
+    @Test
+    fun testTextOutlineMode() {
+        enterTestText()
+        val canvasPoint = centerBox()
+        onTopBarView().performClickCheckmark()
+        val surfaceBitmapWidth = layerModel?.width
+        val pixelsDrawingSurface = surfaceBitmapWidth?.let { IntArray(it) }
+        if (surfaceBitmapWidth != null && canvasPoint != null) {
+            layerModel?.currentLayer?.bitmap?.getPixels(
+                pixelsDrawingSurface, 0, surfaceBitmapWidth, 0,
+                canvasPoint.y.toInt(), surfaceBitmapWidth, 1
+            )
+        }
+        val blackPixelAmountNoOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.BLACK) }
+        val whitePixelAmountNoOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.WHITE) }
+        onTopBarView().performUndo()
+
+        selectFormatting(FormattingOptions.OUTLINE)
+        textTool?.let { Assert.assertTrue(it.outlined) }
+
+        onTopBarView().performClickCheckmark()
+
+        if (surfaceBitmapWidth != null && canvasPoint != null) {
+            layerModel?.currentLayer?.bitmap?.getPixels(
+                pixelsDrawingSurface, 0, surfaceBitmapWidth, 0,
+                canvasPoint.y.toInt(), surfaceBitmapWidth, 1
+            )
+        }
+
+        val blackPixelAmountWithOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.BLACK) }
+        if (blackPixelAmountNoOutline != null && blackPixelAmountWithOutline != null) {
+            assert(blackPixelAmountNoOutline > blackPixelAmountWithOutline)
+            assert(blackPixelAmountWithOutline > 0)
+        }
+
+        val whitePixelAmountWithOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.WHITE) }
+        if (whitePixelAmountNoOutline != null && whitePixelAmountWithOutline != null) {
+            assert(whitePixelAmountNoOutline < whitePixelAmountWithOutline)
+        }
+    }
+
+    @Test
+    fun testTextOutlineWidth() {
+        val canvasPoint = centerBox()
+        selectFormatting(FormattingOptions.OUTLINE)
+        textTool?.let { Assert.assertTrue(it.outlined) }
+        outlineWidthLayout?.let { Assert.assertTrue(it.isVisible) }
+        val outlineWidthInput = onView(withId(R.id.pocketpaint_outline_width_text))
+        val outlineWidthSeekbar = onView(withId(R.id.pocketpaint_outline_width_seek_bar))
+        outlineWidthInput.check(matches(ViewMatchers.withText(DEFAULT_TEXT_OUTLINE_WIDTH.toString())))
+        outlineWidthSeekbar.check(matches(UiMatcher.withProgress(DEFAULT_TEXT_OUTLINE_WIDTH)))
+
+        enterTestText()
+
+        var testOutlineWidthText = "1"
+
+        outlineWidthInput.perform(
+            ViewActions.replaceText(testOutlineWidthText),
+            ViewActions.closeSoftKeyboard()
+        )
+        outlineWidthInput.check(matches(ViewMatchers.withText(testOutlineWidthText)))
+        outlineWidthSeekbar.check(matches(UiMatcher.withProgress(testOutlineWidthText.toInt())))
+
+        onTopBarView().performClickCheckmark()
+        val surfaceBitmapWidth = layerModel?.width
+        val pixelsDrawingSurface = surfaceBitmapWidth?.let { IntArray(it) }
+        if (surfaceBitmapWidth != null && canvasPoint != null) {
+            layerModel?.currentLayer?.bitmap?.getPixels(
+                pixelsDrawingSurface, 0, surfaceBitmapWidth, 0,
+                canvasPoint.y.toInt(), surfaceBitmapWidth, 1
+            )
+        }
+        val blackPixelAmountThinOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.BLACK) }
+        val whitePixelAmountThinOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.WHITE) }
+        onTopBarView().performUndo()
+
+        testOutlineWidthText = "60"
+
+        outlineWidthInput.perform(
+            ViewActions.replaceText(testOutlineWidthText),
+            ViewActions.closeSoftKeyboard()
+        )
+        outlineWidthInput.check(matches(ViewMatchers.withText(testOutlineWidthText)))
+        outlineWidthSeekbar.check(matches(UiMatcher.withProgress(testOutlineWidthText.toInt())))
+
+        onTopBarView().performClickCheckmark()
+
+        if (surfaceBitmapWidth != null && canvasPoint != null) {
+            layerModel?.currentLayer?.bitmap?.getPixels(
+                pixelsDrawingSurface, 0, surfaceBitmapWidth, 0,
+                canvasPoint.y.toInt(), surfaceBitmapWidth, 1
+            )
+        }
+
+        val blackPixelAmountThickOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.BLACK) }
+        if (blackPixelAmountThinOutline != null && blackPixelAmountThickOutline != null) {
+            assert(blackPixelAmountThinOutline > 0)
+            assert(blackPixelAmountThickOutline > 0)
+            assert(blackPixelAmountThickOutline > blackPixelAmountThinOutline)
+        }
+
+        val whitePixelAmountThickOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.WHITE) }
+        if (whitePixelAmountThinOutline != null && whitePixelAmountThickOutline != null) {
+            assert(whitePixelAmountThinOutline > 0)
+            assert(whitePixelAmountThickOutline > 0)
+            assert(whitePixelAmountThickOutline < whitePixelAmountThinOutline)
+        }
+    }
+
     private fun centerBox(): PointF? {
         val screenPoint =
             activityHelper?.displayWidth?.div(2.0f)
@@ -892,6 +1037,7 @@ class TextToolIntegrationTest {
             FormattingOptions.UNDERLINE -> activity.getString(R.string.text_tool_dialog_underline_shortcut)
             FormattingOptions.ITALIC -> activity.getString(R.string.text_tool_dialog_italic_shortcut)
             FormattingOptions.BOLD -> activity.getString(R.string.text_tool_dialog_bold_shortcut)
+            FormattingOptions.OUTLINE -> activity.getString(R.string.text_tool_dialog_outline_shortcut)
         }
     }
 
@@ -921,7 +1067,7 @@ class TextToolIntegrationTest {
     private val toolMemberMultilineText: Array<String>
         get() = textTool!!.multilineText
 
-    private enum class FormattingOptions { UNDERLINE, ITALIC, BOLD }
+    private enum class FormattingOptions { UNDERLINE, ITALIC, BOLD, OUTLINE }
 
     companion object {
         private const val TEST_TEXT = "123 www 123"
diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/junit/serialization/CommandSerializationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/junit/serialization/CommandSerializationTest.kt
index 2f11a6d7d1..893e0addab 100644
--- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/junit/serialization/CommandSerializationTest.kt
+++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/junit/serialization/CommandSerializationTest.kt
@@ -190,7 +190,9 @@ class CommandSerializationTest {
             underline = false,
             italic = true,
             textSize = 25f,
-            textSkewX = -0.25f
+            textSkewX = -0.25f,
+            outlined = false,
+            outlineWidth = 25
         )
 
         expectedModel.commands.add(
diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/command/implementation/TextToolCommand.kt b/Paintroid/src/main/java/org/catrobat/paintroid/command/implementation/TextToolCommand.kt
index 713420f81e..8e30024020 100644
--- a/Paintroid/src/main/java/org/catrobat/paintroid/command/implementation/TextToolCommand.kt
+++ b/Paintroid/src/main/java/org/catrobat/paintroid/command/implementation/TextToolCommand.kt
@@ -20,12 +20,16 @@
 package org.catrobat.paintroid.command.implementation
 
 import android.graphics.Canvas
+import android.graphics.Color
 import android.graphics.Paint
 import android.graphics.PointF
 import org.catrobat.paintroid.command.Command
 import org.catrobat.paintroid.command.serialization.SerializableTypeface
 import org.catrobat.paintroid.common.ITALIC_FONT_BOX_ADJUSTMENT
 import org.catrobat.paintroid.contract.LayerContracts
+import org.catrobat.paintroid.tools.implementation.OUTLINED_FONT_WIDTH_ADJUSTMENT
+
+const val TEXT_SIZE_MAGNIFICATION_FACTOR = 3f
 
 class TextToolCommand(
     multilineText: Array<String>,
@@ -74,14 +78,30 @@ class TextToolCommand(
             val scaledBoxWidth = boxWidth / widthScaling
             val scaledBoxHeight = boxHeight / heightScaling
 
+            val fillPaint = Paint(textPaint)
+            if (typeFaceInfo.outlined) fillPaint.color = Color.WHITE
             multilineText.forEachIndexed { index, textLine ->
                 canvas.drawText(
                     textLine,
                     scaledWidthOffset - scaledBoxWidth / 2 / if (typeFaceInfo.italic) ITALIC_FONT_BOX_ADJUSTMENT else 1f,
                     -(scaledBoxHeight / 2) + scaledHeightOffset - textAscent + lineHeight * index,
-                    textPaint
+                    fillPaint
                 )
             }
+            if (typeFaceInfo.outlined) {
+                val outlinePaint = Paint(textPaint)
+                val adjustedStrokeWidth = if (typeFaceInfo.outlineWidth == 0) 0f else java.lang.Float.max(textPaint.textSize / TEXT_SIZE_MAGNIFICATION_FACTOR * (typeFaceInfo.outlineWidth / OUTLINED_FONT_WIDTH_ADJUSTMENT), 1f)
+                outlinePaint.style = Paint.Style.STROKE
+                outlinePaint.strokeWidth = adjustedStrokeWidth
+                multilineText.forEachIndexed { index, textLine ->
+                    canvas.drawText(
+                        textLine,
+                        scaledWidthOffset - scaledBoxWidth / 2 / if (typeFaceInfo.italic) ITALIC_FONT_BOX_ADJUSTMENT else 1f,
+                        -(scaledBoxHeight / 2) + scaledHeightOffset - textAscent + lineHeight * index,
+                        outlinePaint
+                    )
+                }
+            }
             restore()
         }
     }
diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/SerializableTypeface.kt b/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/SerializableTypeface.kt
index 1aea05d5b6..45dd4499a4 100644
--- a/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/SerializableTypeface.kt
+++ b/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/SerializableTypeface.kt
@@ -23,7 +23,7 @@ import com.esotericsoftware.kryo.io.Input
 import com.esotericsoftware.kryo.io.Output
 import org.catrobat.paintroid.tools.FontType
 
-data class SerializableTypeface(val font: FontType, val bold: Boolean, val underline: Boolean, val italic: Boolean, val textSize: Float, val textSkewX: Float) {
+data class SerializableTypeface(val font: FontType, val bold: Boolean, val underline: Boolean, val italic: Boolean, val textSize: Float, val textSkewX: Float, val outlined: Boolean, val outlineWidth: Int) {
 
     class TypefaceSerializer(version: Int) : VersionSerializer<SerializableTypeface>(version) {
         override fun write(kryo: Kryo, output: Output, typeface: SerializableTypeface) {
@@ -34,6 +34,8 @@ data class SerializableTypeface(val font: FontType, val bold: Boolean, val under
                 writeBoolean(typeface.italic)
                 writeFloat(typeface.textSize)
                 writeFloat(typeface.textSkewX)
+                writeBoolean(typeface.outlined)
+                writeInt(typeface.outlineWidth)
             }
         }
 
@@ -42,7 +44,7 @@ data class SerializableTypeface(val font: FontType, val bold: Boolean, val under
 
         override fun readCurrentVersion(kryo: Kryo, input: Input, type: Class<out SerializableTypeface>): SerializableTypeface {
             return with(input) {
-                SerializableTypeface(FontType.valueOf(readString()), readBoolean(), readBoolean(), readBoolean(), readFloat(), readFloat())
+                SerializableTypeface(FontType.valueOf(readString()), readBoolean(), readBoolean(), readBoolean(), readFloat(), readFloat(), readBoolean(), readInt())
             }
         }
     }
diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TextTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TextTool.kt
index 5b9a1372a1..c8e303ffee 100644
--- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TextTool.kt
+++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TextTool.kt
@@ -19,6 +19,7 @@
 package org.catrobat.paintroid.tools.implementation
 
 import android.graphics.Canvas
+import android.graphics.Color
 import android.graphics.Paint
 import android.graphics.PointF
 import android.graphics.Typeface
@@ -38,6 +39,7 @@ import org.catrobat.paintroid.tools.ToolType
 import org.catrobat.paintroid.tools.Workspace
 import org.catrobat.paintroid.tools.options.TextToolOptionsView
 import org.catrobat.paintroid.tools.options.ToolOptionsViewController
+import java.lang.Float.max
 import kotlin.Exception
 import kotlin.math.abs
 
@@ -50,17 +52,23 @@ const val BOX_OFFSET = 20
 @VisibleForTesting
 const val MARGIN_TOP = 200f
 
+@VisibleForTesting
+const val DEFAULT_TEXT_OUTLINE_WIDTH = 25
+
 private const val ROTATION_ENABLED = true
 private const val RESIZE_POINTS_VISIBLE = true
 private const val ITALIC_TEXT_SKEW = -0.25f
 private const val DEFAULT_TEXT_SKEW = 0.0f
 private const val DEFAULT_TEXT_SIZE = 20
+const val OUTLINED_FONT_WIDTH_ADJUSTMENT = 350f
 private const val BUNDLE_TOOL_UNDERLINED = "BUNDLE_TOOL_UNDERLINED"
 private const val BUNDLE_TOOL_ITALIC = "BUNDLE_TOOL_ITALIC"
 private const val BUNDLE_TOOL_BOLD = "BUNDLE_TOOL_BOLD"
+private const val BUNDLE_TOOL_OUTLINED = "BUNDLE_TOOL_OUTLINED"
 private const val BUNDLE_TOOL_TEXT = "BUNDLE_TOOL_TEXT"
 private const val BUNDLE_TOOL_TEXT_SIZE = "BUNDLE_TOOL_TEXT_SIZE"
 private const val BUNDLE_TOOL_FONT = "BUNDLE_TOOL_FONT"
+private const val BUNDLE_TOOL_TEXT_OUTLINE_WIDTH = "BUNDLE_TOOL_TEXT_OUTLINE_WIDTH"
 private const val TAG = "Can't set custom font"
 
 class TextTool(
@@ -104,12 +112,17 @@ class TextTool(
     @JvmField
     var bold = false
 
+    @VisibleForTesting
+    @JvmField
+    var outlined = false
+
     private var textSize = DEFAULT_TEXT_SIZE
     private val stc: Typeface?
     private val dubai: Typeface?
     private var oldBoxWidth = 0f
     private var oldBoxHeight = 0f
     private var oldToolPosition: PointF? = null
+    private var outlineWidth = DEFAULT_TEXT_OUTLINE_WIDTH
 
     @get:VisibleForTesting
     val multilineText: Array<String>
@@ -191,6 +204,14 @@ class TextTool(
                 applyAttributes()
             }
 
+            override fun setOutline(outlined: Boolean) {
+                this@TextTool.outlined = outlined
+                storeAttributes()
+                resetPreview()
+                workspace.invalidate()
+                applyAttributes()
+            }
+
             override fun setTextSize(size: Int) {
                 textSize = size
                 textPaint.textSize = textSize * TEXT_SIZE_MAGNIFICATION_FACTOR
@@ -201,6 +222,10 @@ class TextTool(
             override fun hideToolOptions() {
                 this@TextTool.toolOptionsViewController.hide()
             }
+
+            override fun onOutlineWidthChanged(outlineWidth: Int) {
+                updateOutlineWidth(outlineWidth)
+            }
         }
         textToolOptionsView.setCallback(callback)
         toolOptionsViewController.showDelayed()
@@ -303,14 +328,30 @@ class TextTool(
         val scaledBoxWidth = boxWidth / widthScaling
         val scaledBoxHeight = boxHeight / heightScaling
 
+        val fillPaint = Paint(textPaint)
+        if (outlined) fillPaint.color = Color.WHITE
         multilineText.forEachIndexed { index, textLine ->
             canvas.drawText(
                 textLine,
                 scaledWidthOffset - scaledBoxWidth / 2 / if (italic) ITALIC_FONT_BOX_ADJUSTMENT else 1f,
                 -(scaledBoxHeight / 2) + scaledHeightOffset - textAscent + lineHeight * index,
-                textPaint
+                fillPaint
             )
         }
+        if (outlined) {
+            val outlinePaint = Paint(textPaint)
+            val adjustedStrokeWidth = if (outlineWidth == 0) 0f else max(textPaint.textSize / TEXT_SIZE_MAGNIFICATION_FACTOR * (outlineWidth / OUTLINED_FONT_WIDTH_ADJUSTMENT), 1f)
+            outlinePaint.style = Paint.Style.STROKE
+            outlinePaint.strokeWidth = adjustedStrokeWidth
+            multilineText.forEachIndexed { index, textLine ->
+                canvas.drawText(
+                    textLine,
+                    scaledWidthOffset - scaledBoxWidth / 2 / if (italic) ITALIC_FONT_BOX_ADJUSTMENT else 1f,
+                    -(scaledBoxHeight / 2) + scaledHeightOffset - textAscent + lineHeight * index,
+                    outlinePaint
+                )
+            }
+        }
         canvas.restore()
     }
 
@@ -350,9 +391,11 @@ class TextTool(
             putBoolean(BUNDLE_TOOL_UNDERLINED, underlined)
             putBoolean(BUNDLE_TOOL_ITALIC, italic)
             putBoolean(BUNDLE_TOOL_BOLD, bold)
+            putBoolean(BUNDLE_TOOL_OUTLINED, outlined)
             putString(BUNDLE_TOOL_TEXT, text)
             putInt(BUNDLE_TOOL_TEXT_SIZE, textSize)
             putString(BUNDLE_TOOL_FONT, font.name)
+            putInt(BUNDLE_TOOL_TEXT_OUTLINE_WIDTH, outlineWidth)
         }
     }
 
@@ -362,11 +405,13 @@ class TextTool(
             underlined = getBoolean(BUNDLE_TOOL_UNDERLINED, underlined)
             italic = getBoolean(BUNDLE_TOOL_ITALIC, italic)
             bold = getBoolean(BUNDLE_TOOL_BOLD, bold)
+            outlined = getBoolean(BUNDLE_TOOL_OUTLINED, outlined)
             text = getString(BUNDLE_TOOL_TEXT, text)
             textSize = getInt(BUNDLE_TOOL_TEXT_SIZE, textSize)
             font = FontType.valueOf(getString(BUNDLE_TOOL_FONT, font.name))
+            outlineWidth = getInt(BUNDLE_TOOL_TEXT_OUTLINE_WIDTH, outlineWidth)
         }
-        textToolOptionsView.setState(bold, italic, underlined, text, textSize, font)
+        textToolOptionsView.setState(bold, italic, underlined, outlined, text, textSize, font, outlineWidth)
         textPaint.isUnderlineText = underlined
         textPaint.isFakeBoldText = bold
         updateTypeface()
@@ -419,7 +464,9 @@ class TextTool(
             underlined,
             italic,
             textPaint.textSize,
-            textPaint.textSkewX
+            textPaint.textSkewX,
+            outlined,
+            outlineWidth
         )
 
         val command = commandFactory.createTextToolCommand(
@@ -447,4 +494,9 @@ class TextTool(
         super.changePaintColor(color, invalidate)
         changeTextColor()
     }
+
+    fun updateOutlineWidth(outlineWidth: Int) {
+        this.outlineWidth = outlineWidth
+        workspace.invalidate()
+    }
 }
diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/options/TextToolOptionsView.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/options/TextToolOptionsView.kt
index bd05b34d46..2885c56e1c 100644
--- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/options/TextToolOptionsView.kt
+++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/options/TextToolOptionsView.kt
@@ -26,9 +26,11 @@ interface TextToolOptionsView {
         bold: Boolean,
         italic: Boolean,
         underlined: Boolean,
+        outlined: Boolean,
         text: String,
         textSize: Int,
-        fontType: FontType
+        fontType: FontType,
+        outlineWidth: Int
     )
 
     fun setCallback(listener: Callback)
@@ -52,8 +54,12 @@ interface TextToolOptionsView {
 
         fun setBold(bold: Boolean)
 
+        fun setOutline(outlined: Boolean)
+
         fun setTextSize(size: Int)
 
         fun hideToolOptions()
+
+        fun onOutlineWidthChanged(outlineWidth: Int)
     }
 }
diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultTextToolOptionsView.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultTextToolOptionsView.kt
index 113feb7e01..aac51a054b 100644
--- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultTextToolOptionsView.kt
+++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultTextToolOptionsView.kt
@@ -21,7 +21,9 @@ package org.catrobat.paintroid.ui.tools
 import android.content.Context
 import android.graphics.Paint
 import android.text.Editable
+import android.text.InputFilter
 import android.text.TextWatcher
+import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.View.OnFocusChangeListener
@@ -29,17 +31,24 @@ import android.view.ViewGroup
 import android.view.inputmethod.InputMethodManager
 import android.widget.Checkable
 import android.widget.EditText
+import android.widget.RelativeLayout
+import android.widget.SeekBar
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.google.android.material.button.MaterialButton
 import org.catrobat.paintroid.R
 import org.catrobat.paintroid.tools.FontType
+import org.catrobat.paintroid.tools.helper.DefaultNumberRangeFilter
 import org.catrobat.paintroid.tools.options.TextToolOptionsView
+import java.util.Locale
 
 private const val DEFAULT_TEXTSIZE = "20"
 private const val MAX_TEXTSIZE = "300"
 private const val MIN_FONT_SIZE = 1
 private const val MAX_FONT_SIZE = 300
+private const val DEFAULT_OUTLINE_WIDTH = "25"
+private const val MIN_OUTLINE_THICKNESS = 0
+private const val MAX_OUTLINE_THICKNESS = 100
 
 class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView {
     private val context: Context = rootView.context
@@ -50,9 +59,13 @@ class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView {
     private val underlinedToggleButton: MaterialButton
     private val italicToggleButton: MaterialButton
     private val boldToggleButton: MaterialButton
+    private val outlineToggleButton: MaterialButton
     private val fontTypes: List<FontType>
     private val topLayout: View
     private val bottomLayout: View
+    private val outlineWidthLayout: RelativeLayout
+    private val outlineWidthText: EditText
+    private val outlineWidthSeekBar: SeekBar
 
     init {
         val inflater = LayoutInflater.from(context)
@@ -66,12 +79,45 @@ class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView {
         italicToggleButton =
             textToolView.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_italic)
         boldToggleButton = textToolView.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_bold)
+        outlineToggleButton = textToolView.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_outline)
         fontSizeText = textToolView.findViewById(R.id.pocketpaint_font_size_text)
+        outlineWidthLayout = textToolView.findViewById(R.id.pocketpaint_outline_width_layout)
+        outlineWidthText = textToolView.findViewById(R.id.pocketpaint_outline_width_text)
+        outlineWidthSeekBar = textToolView.findViewById(R.id.pocketpaint_outline_width_seek_bar)
         fontSizeText.setText(DEFAULT_TEXTSIZE)
         underlinedToggleButton.paintFlags =
             underlinedToggleButton.paintFlags or Paint.UNDERLINE_TEXT_FLAG
         @Suppress("SpreadOperator")
         fontTypes = FontType.values().toList()
+        outlineWidthLayout.visibility = View.GONE
+        outlineWidthText.filters = arrayOf<InputFilter>(DefaultNumberRangeFilter(MIN_OUTLINE_THICKNESS, MAX_OUTLINE_THICKNESS))
+        outlineWidthSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
+            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+                if (fromUser) {
+                    setOutlineWidthText(progress)
+                }
+            }
+
+            override fun onStartTrackingTouch(seekBar: SeekBar) = Unit
+
+            override fun onStopTrackingTouch(seekBar: SeekBar) = Unit
+        })
+
+        outlineWidthText.addTextChangedListener(object : TextWatcher {
+            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) =
+                Unit
+            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) = Unit
+            override fun afterTextChanged(s: Editable) {
+                try {
+                    val outlineWidthInPercent = s.toString().toInt()
+                    outlineWidthSeekBar.progress = outlineWidthInPercent
+                    updateOutlineWidth(outlineWidthInPercent)
+                } catch (e: java.lang.NumberFormatException) {
+                    Log.e("Error parsing outline", "result was null")
+                }
+            }
+        })
+        outlineWidthText.setText(DEFAULT_OUTLINE_WIDTH)
         initializeListeners()
         textEditText.requestFocus()
     }
@@ -110,6 +156,11 @@ class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView {
             notifyBoldChanged(bold)
             hideKeyboard()
         }
+        outlineToggleButton.setOnClickListener { v ->
+            val outline = (v as Checkable).isChecked
+            notifyOutlineChanged(outline)
+            hideKeyboard()
+        }
         fontSizeText.addTextChangedListener(object : TextWatcher {
             override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) = Unit
 
@@ -149,6 +200,15 @@ class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView {
         callback?.setBold(bold)
     }
 
+    private fun notifyOutlineChanged(outlined: Boolean) {
+        if (outlined) {
+            outlineWidthLayout.visibility = View.VISIBLE
+        } else {
+            outlineWidthLayout.visibility = View.GONE
+        }
+        callback?.setOutline(outlined)
+    }
+
     private fun notifyTextSizeChanged(textSize: Int) {
         callback?.setTextSize(textSize)
     }
@@ -161,16 +221,21 @@ class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView {
         bold: Boolean,
         italic: Boolean,
         underlined: Boolean,
+        outlined: Boolean,
         text: String,
         textSize: Int,
-        fontType: FontType
+        fontType: FontType,
+        outlineWidth: Int
     ) {
         boldToggleButton.isChecked = bold
         italicToggleButton.isChecked = italic
         underlinedToggleButton.isChecked = underlined
+        outlineToggleButton.isChecked = outlined
+        notifyOutlineChanged(outlined)
         textEditText.setText(text)
         (fontList.adapter as FontListAdapter).setSelectedIndex(fontTypes.indexOf(fontType))
         fontSizeText.setText(DEFAULT_TEXTSIZE)
+        outlineWidthText.setText(outlineWidth.toString())
     }
 
     override fun setCallback(listener: TextToolOptionsView.Callback) {
@@ -190,4 +255,12 @@ class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView {
     override fun getTopLayout(): View = topLayout
 
     override fun getBottomLayout(): View = bottomLayout
+
+    private fun setOutlineWidthText(widthInPercent: Int) {
+        outlineWidthText.setText(String.format(Locale.getDefault(), "%d", widthInPercent))
+    }
+
+    private fun updateOutlineWidth(outlineWidth: Int) {
+        callback?.onOutlineWidthChanged(outlineWidth)
+    }
 }
diff --git a/Paintroid/src/main/res/drawable/ic_pocketpaint_a_blue_outline.xml b/Paintroid/src/main/res/drawable/ic_pocketpaint_a_blue_outline.xml
new file mode 100644
index 0000000000..9fe0e82bf7
--- /dev/null
+++ b/Paintroid/src/main/res/drawable/ic_pocketpaint_a_blue_outline.xml
@@ -0,0 +1,7 @@
+<vector android:height="24dp" android:viewportHeight="48"
+    android:viewportWidth="48" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#ffffff"
+        android:pathData="M6.5,41.5 L20.862,6.5l5.332,0L41.5,41.5L35.862,41.5L31.5,30.9L15.862,30.9L11.755,41.5ZM17.291,27.128L29.969,27.128L26.066,17.435q-1.786,-4.417 -2.653,-7.258 -0.714,3.366 -2.015,6.685z" android:strokeColor="#00000000"/>
+    <path android:fillColor="#3d8091"
+        android:pathData="M20.506,6.016 L5.748,41.984l0.753,0 5.617,0l4.106,-10.6l14.922,0l4.361,10.6l6.763,0L26.541,6.016ZM21.217,6.983l4.629,0L40.732,41.017L36.217,41.017L31.855,30.417L15.499,30.417L11.393,41.017L7.254,41.017ZM23.91,10.044 L22.907,10.082c-0.471,2.219 -1.136,4.424 -1.995,6.613l-4.367,10.915l0.745,0 13.426,0l-4.167,-10.347c-1.187,-2.937 -2.067,-5.346 -2.639,-7.22zM23.46,11.796c0.564,1.692 1.173,3.459 2.124,5.81l3.638,9.037L18.037,26.643l3.846,-9.614l0,-0.002c0.679,-1.731 1.135,-3.481 1.577,-5.232z" android:strokeColor="#00000000"/>
+</vector>
diff --git a/Paintroid/src/main/res/drawable/ic_pocketpaint_a_white_outline.xml b/Paintroid/src/main/res/drawable/ic_pocketpaint_a_white_outline.xml
new file mode 100644
index 0000000000..5b03294a8f
--- /dev/null
+++ b/Paintroid/src/main/res/drawable/ic_pocketpaint_a_white_outline.xml
@@ -0,0 +1,7 @@
+<vector android:height="24dp" android:viewportHeight="48"
+    android:viewportWidth="48" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#3d8091"
+        android:pathData="M6.5,41.5 L20.862,6.5l5.332,0L41.5,41.5L35.862,41.5L31.5,30.9L15.862,30.9L11.755,41.5ZM17.291,27.128L29.969,27.128L26.066,17.435q-1.786,-4.417 -2.653,-7.258 -0.714,3.366 -2.015,6.685z" android:strokeColor="#00000000"/>
+    <path android:fillColor="#ffffff"
+        android:pathData="M20.506,6.016 L5.748,41.984l0.753,0 5.617,0l4.106,-10.6l14.922,0l4.361,10.6l6.763,0L26.541,6.016ZM21.217,6.983l4.629,0L40.732,41.017L36.217,41.017L31.855,30.417L15.499,30.417L11.393,41.017L7.254,41.017ZM23.91,10.044 L22.907,10.082c-0.471,2.219 -1.136,4.424 -1.995,6.613l-4.367,10.915l0.745,0 13.426,0l-4.167,-10.347c-1.187,-2.937 -2.067,-5.346 -2.639,-7.22zM23.46,11.796c0.564,1.692 1.173,3.459 2.124,5.81l3.638,9.037L18.037,26.643l3.846,-9.614l0,-0.002c0.679,-1.731 1.135,-3.481 1.577,-5.232z" android:strokeColor="#00000000"/>
+</vector>
diff --git a/Paintroid/src/main/res/drawable/outline_button_selector.xml b/Paintroid/src/main/res/drawable/outline_button_selector.xml
new file mode 100644
index 0000000000..5b3e7548db
--- /dev/null
+++ b/Paintroid/src/main/res/drawable/outline_button_selector.xml
@@ -0,0 +1,4 @@
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="true" android:drawable="@drawable/ic_pocketpaint_a_white_outline" />
+    <item android:state_checked="false" android:drawable="@drawable/ic_pocketpaint_a_blue_outline" />
+</selector>
\ No newline at end of file
diff --git a/Paintroid/src/main/res/layout/dialog_pocketpaint_text_tool.xml b/Paintroid/src/main/res/layout/dialog_pocketpaint_text_tool.xml
index 2d2cb40695..be293073ec 100644
--- a/Paintroid/src/main/res/layout/dialog_pocketpaint_text_tool.xml
+++ b/Paintroid/src/main/res/layout/dialog_pocketpaint_text_tool.xml
@@ -27,89 +27,158 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content">
 
-        <com.google.android.material.textfield.TextInputLayout
-            style="@style/TextFieldTheme"
+        <RelativeLayout
+            android:id="@+id/pocketpaint_text_button_layout"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentStart="true"
-            android:layout_alignParentTop="true"
-            android:layout_margin="10dp">
+            android:layout_height="wrap_content">
 
-            <com.google.android.material.textfield.TextInputEditText
-                android:id="@+id/pocketpaint_font_size_text"
+            <com.google.android.material.textfield.TextInputLayout
+                style="@style/TextFieldTheme"
                 android:layout_width="wrap_content"
-                android:layout_height="40dp"
-                android:background="@drawable/pocketpaint_round_rect_shape"
-                android:gravity="center"
-                android:imeOptions="actionDone"
-                android:importantForAutofill="no"
-                android:inputType="number"
-                android:minEms="3"
-                android:padding="4dp"
-                android:saveEnabled="false"
-                android:textColor="?attr/colorAccent"
-                android:textStyle="bold"
-                tools:ignore="LabelFor"
-                tools:text="100" />
-
-        </com.google.android.material.textfield.TextInputLayout>
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_margin="10dp">
 
+                <com.google.android.material.textfield.TextInputEditText
+                    android:id="@+id/pocketpaint_font_size_text"
+                    android:layout_width="wrap_content"
+                    android:layout_height="40dp"
+                    android:background="@drawable/pocketpaint_round_rect_shape"
+                    android:gravity="center"
+                    android:imeOptions="actionDone"
+                    android:importantForAutofill="no"
+                    android:inputType="number"
+                    android:minEms="3"
+                    android:padding="4dp"
+                    android:saveEnabled="false"
+                    android:textColor="?attr/colorAccent"
+                    android:textStyle="bold"
+                    tools:ignore="LabelFor"
+                    tools:text="100" />
+
+            </com.google.android.material.textfield.TextInputLayout>
+
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentTop="true"
+                android:layout_alignParentRight="true"
+                android:layout_margin="10dp"
+                android:layout_marginTop="@dimen/toolbar_height"
+                tools:ignore="RelativeOverlap,RtlHardcoded">
+
+                <com.google.android.material.button.MaterialButtonToggleGroup
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content">
+
+                    <com.google.android.material.button.MaterialButton
+                        android:id="@+id/pocketpaint_text_tool_dialog_toggle_outline"
+                        style="@style/CircularToggleButtonStyle"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"
+                        android:padding="8dp"
+                        android:text="@string/text_tool_dialog_outline_shortcut"
+                        android:drawableLeft="@drawable/outline_button_selector" />
+                </com.google.android.material.button.MaterialButtonToggleGroup>
+
+                <Space style="@style/PocketPaintToolHorizontalSpace" />
+
+                <com.google.android.material.button.MaterialButtonToggleGroup
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content">
+
+                    <com.google.android.material.button.MaterialButton
+                        android:id="@+id/pocketpaint_text_tool_dialog_toggle_underlined"
+                        style="@style/CircularToggleButtonStyle"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"
+                        android:text="@string/text_tool_dialog_underline_shortcut"
+                        android:textSize="20sp" />
+                </com.google.android.material.button.MaterialButtonToggleGroup>
+
+                <Space style="@style/PocketPaintToolHorizontalSpace" />
+
+                <com.google.android.material.button.MaterialButtonToggleGroup
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content">
+
+                    <com.google.android.material.button.MaterialButton
+                        android:id="@+id/pocketpaint_text_tool_dialog_toggle_italic"
+                        style="@style/CircularToggleButtonStyle"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"
+                        android:text="@string/text_tool_dialog_italic_shortcut"
+                        android:textSize="20sp"
+                        android:textStyle="italic" />
+                </com.google.android.material.button.MaterialButtonToggleGroup>
+
+                <Space style="@style/PocketPaintToolHorizontalSpace" />
+
+                <com.google.android.material.button.MaterialButtonToggleGroup
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content">
+
+                    <com.google.android.material.button.MaterialButton
+                        android:id="@+id/pocketpaint_text_tool_dialog_toggle_bold"
+                        style="@style/CircularToggleButtonStyle"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"
+                        android:text="@string/text_tool_dialog_bold_shortcut"
+                        android:textSize="20sp"
+                        android:textStyle="bold" />
+                </com.google.android.material.button.MaterialButtonToggleGroup>
+            </LinearLayout>
+        </RelativeLayout>
 
-        <LinearLayout
+        <RelativeLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            android:layout_alignParentRight="true"
-            android:layout_margin="10dp"
-            android:layout_marginTop="@dimen/toolbar_height"
-            tools:ignore="RelativeOverlap,RtlHardcoded">
+            android:id="@+id/pocketpaint_outline_width_layout"
+            android:layout_below="@id/pocketpaint_text_button_layout">
 
-            <com.google.android.material.button.MaterialButtonToggleGroup
+            <com.google.android.material.textfield.TextInputLayout
+                android:id="@+id/pocketpaint_outline_width_text_layout"
+                style="@style/TextFieldTheme"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content">
-
-                <com.google.android.material.button.MaterialButton
-                    android:id="@+id/pocketpaint_text_tool_dialog_toggle_underlined"
-                    style="@style/CircularToggleButtonStyle"
-                    android:layout_width="40dp"
-                    android:layout_height="40dp"
-                    android:text="@string/text_tool_dialog_underline_shortcut"
-                    android:textSize="20sp" />
-            </com.google.android.material.button.MaterialButtonToggleGroup>
-
-            <Space style="@style/PocketPaintToolHorizontalSpace" />
-
-            <com.google.android.material.button.MaterialButtonToggleGroup
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content">
-
-                <com.google.android.material.button.MaterialButton
-                    android:id="@+id/pocketpaint_text_tool_dialog_toggle_italic"
-                    style="@style/CircularToggleButtonStyle"
-                    android:layout_width="40dp"
-                    android:layout_height="40dp"
-                    android:text="@string/text_tool_dialog_italic_shortcut"
-                    android:textSize="20sp"
-                    android:textStyle="italic" />
-            </com.google.android.material.button.MaterialButtonToggleGroup>
-
-            <Space style="@style/PocketPaintToolHorizontalSpace" />
-
-            <com.google.android.material.button.MaterialButtonToggleGroup
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_margin="10dp">
+
+                <com.google.android.material.textfield.TextInputEditText
+                    android:id="@+id/pocketpaint_outline_width_text"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:background="@drawable/pocketpaint_round_rect_shape"
+                    android:gravity="center"
+                    android:imeOptions="actionDone"
+                    android:importantForAutofill="no"
+                    android:inputType="number"
+                    android:layoutDirection="ltr"
+                    android:minEms="3"
+                    android:padding="4dp"
+                    android:saveEnabled="false"
+                    android:textColor="?attr/colorAccent"
+                    android:textStyle="bold"
+                    tools:ignore="LabelFor"
+                    tools:text="100" />
+
+            </com.google.android.material.textfield.TextInputLayout>
+
+            <SeekBar
+                android:id="@+id/pocketpaint_outline_width_seek_bar"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content">
-
-                <com.google.android.material.button.MaterialButton
-                    android:id="@+id/pocketpaint_text_tool_dialog_toggle_bold"
-                    style="@style/CircularToggleButtonStyle"
-                    android:layout_width="40dp"
-                    android:layout_height="40dp"
-                    android:text="@string/text_tool_dialog_bold_shortcut"
-                    android:textSize="20sp"
-                    android:textStyle="bold" />
-            </com.google.android.material.button.MaterialButtonToggleGroup>
-        </LinearLayout>
-
+                android:layout_height="wrap_content"
+                android:layout_alignTop="@id/pocketpaint_outline_width_text_layout"
+                android:layout_alignBottom="@id/pocketpaint_outline_width_text_layout"
+                android:layout_alignParentEnd="true"
+                android:layout_marginEnd="10dp"
+                android:layout_toEndOf="@+id/pocketpaint_outline_width_text_layout"
+                android:max="100"
+                android:minHeight="30dip" />
+        </RelativeLayout>
     </RelativeLayout>
 
     <RelativeLayout
diff --git a/Paintroid/src/main/res/values/string.xml b/Paintroid/src/main/res/values/string.xml
index de2ea72fb6..9eb81a111e 100644
--- a/Paintroid/src/main/res/values/string.xml
+++ b/Paintroid/src/main/res/values/string.xml
@@ -117,6 +117,7 @@
     <string name="text_tool_dialog_underline_shortcut">U</string>
     <string name="text_tool_dialog_italic_shortcut">I</string>
     <string name="text_tool_dialog_bold_shortcut">B</string>
+    <string name="text_tool_dialog_outline_shortcut" translatable="false">O</string>
     <string name="text_tool_dialog_input_hint">Tap here to write</string>
     <string name="text_tool_dialog_font_monospace">Monospace</string>
     <string name="text_tool_dialog_font_serif">Serif</string>