diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index fa800948ca..3016505a44 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -289,6 +289,7 @@ set( COMMON_SRCS
     selcolor.cpp
     settings.cpp
     systemdirsappend.cpp
+    text_utils.cpp
     trigo.cpp
     utf8.cpp
     validators.cpp
diff --git a/common/gal/opengl/opengl_gal.cpp b/common/gal/opengl/opengl_gal.cpp
index 29d15096b4..a4eeed546b 100644
--- a/common/gal/opengl/opengl_gal.cpp
+++ b/common/gal/opengl/opengl_gal.cpp
@@ -31,6 +31,7 @@
 #include <gal/definitions.h>
 #include <gl_context_mgr.h>
 #include <geometry/shape_poly_set.h>
+#include <text_utils.h>
 
 #include <macros.h>
 
@@ -879,13 +880,16 @@ void OPENGL_GAL::BitmapText( const wxString& aText, const VECTOR2D& aPosition,
 {
     wxASSERT_MSG( !IsTextMirrored(), "No support for mirrored text using bitmap fonts." );
 
+    auto processedText = ProcessOverbars( aText );
+    const auto& text = processedText.first;
+    const auto& overbars = processedText.second;
+
     // Compute text size, so it can be properly justified
     VECTOR2D textSize;
     float commonOffset;
-    std::tie( textSize, commonOffset ) = computeBitmapTextSize( aText );
+    std::tie( textSize, commonOffset ) = computeBitmapTextSize( text );
 
     const double SCALE = GetGlyphSize().y / textSize.y;
-    int tildas = 0;
     bool overbar = false;
 
     int overbarLength = 0;
@@ -936,46 +940,39 @@ void OPENGL_GAL::BitmapText( const wxString& aText, const VECTOR2D& aPosition,
         break;
     }
 
-    for( unsigned int ii = 0; ii < aText.length(); ++ii )
+    int i = 0;
+
+    for( UTF8::uni_iter chIt = text.ubegin(), end = text.uend(); chIt < end; ++chIt )
     {
-        unsigned int c = aText[ii];
+        unsigned int c = *chIt;
 
         wxASSERT_MSG( LookupGlyph(c) != nullptr, wxT( "Missing character in bitmap font atlas." ) );
         wxASSERT_MSG( c != '\n' && c != '\r', wxT( "No support for multiline bitmap text yet" ) );
 
         // Handle overbar
-        if( c == '~' )
+        if( overbars[i] && !overbar )
         {
-            overbar = !overbar;
-            ++tildas;
-            continue;
+            overbar = true;     // beginning of an overbar
         }
-        else if( tildas > 0 )
+        else if( overbar && !overbars[i] )
         {
-            if( tildas % 2 == 1 )
-            {
-                if( overbar )                   // Overbar begins
-                    overbarLength = 0;
-                else if( overbarLength > 0 )    // Overbar finishes
-                    drawBitmapOverbar( overbarLength, overbarHeight );
-
-                --tildas;
-            }
-
-            // Draw tilda characters if there are any remaining
-            for( int jj = 0; jj < tildas / 2; ++jj )
-                overbarLength += drawBitmapChar( '~' );
-
-            tildas = 0;
+            overbar = false;    // end of an overbar
+            drawBitmapOverbar( overbarLength, overbarHeight );
+            overbarLength = 0;
         }
 
-        overbarLength += drawBitmapChar( c );
+        if( overbar )
+            overbarLength += drawBitmapChar( c );
+        else
+            drawBitmapChar( c );
+
+        ++i;
     }
 
     // Handle the case when overbar is active till the end of the drawn text
     currentManager->Translate( 0, commonOffset, 0 );
 
-    if( overbar )
+    if( overbar && overbarLength > 0 )
         drawBitmapOverbar( overbarLength, overbarHeight );
 
     Restore();
@@ -1664,32 +1661,15 @@ void OPENGL_GAL::drawBitmapOverbar( double aLength, double aHeight )
     Restore();
 }
 
-std::pair<VECTOR2D, float> OPENGL_GAL::computeBitmapTextSize( const wxString& aText ) const
+
+std::pair<VECTOR2D, float> OPENGL_GAL::computeBitmapTextSize( const UTF8& aText ) const
 {
     VECTOR2D textSize( 0, 0 );
     float commonOffset = std::numeric_limits<float>::max();
-    bool wasTilda = false;
 
-    for( unsigned int i = 0; i < aText.length(); ++i )
+    for( UTF8::uni_iter chIt = aText.ubegin(), end = aText.uend(); chIt < end; ++chIt )
     {
-        // Remove overbar control characters
-        if( aText[i] == '~' )
-        {
-            if( !wasTilda )
-            {
-                // Only double tildas are counted as characters, so skip it as it might
-                // be an overbar control character
-                wasTilda = true;
-                continue;
-            }
-            else
-            {
-                // Double tilda detected, reset the state and process as a normal character
-                wasTilda = false;
-            }
-        }
-
-        unsigned int c = aText[i];
+        unsigned int c = *chIt;
 
         const FONT_GLYPH_TYPE* glyph = LookupGlyph( c );
         wxASSERT( glyph );
@@ -1702,7 +1682,6 @@ std::pair<VECTOR2D, float> OPENGL_GAL::computeBitmapTextSize( const wxString& aT
             glyph = LookupGlyph( c );
         }
 
-
         if( glyph )
         {
             textSize.x  += glyph->advance;
diff --git a/common/gal/stroke_font.cpp b/common/gal/stroke_font.cpp
index ce93291971..ea08fa49ab 100644
--- a/common/gal/stroke_font.cpp
+++ b/common/gal/stroke_font.cpp
@@ -28,8 +28,10 @@
 
 #include <gal/stroke_font.h>
 #include <gal/graphics_abstraction_layer.h>
+#include <text_utils.h>
 #include <wx/string.h>
 
+
 using namespace KIGFX;
 
 const double STROKE_FONT::INTERLINE_PITCH_RATIO = 1.5;
@@ -241,9 +243,6 @@ void STROKE_FONT::Draw( const UTF8& aText, const VECTOR2D& aPosition, double aRo
 
 void STROKE_FONT::drawSingleLineText( const UTF8& aText )
 {
-    // By default the overbar is turned off
-    bool overbar = false;
-
     double      xOffset;
     VECTOR2D    glyphSize( m_gal->GetGlyphSize() );
     double      overbar_italic_comp = computeOverbarVerticalPosition() * ITALIC_TILT;
@@ -303,21 +302,13 @@ void STROKE_FONT::drawSingleLineText( const UTF8& aText )
     // must not be indented on subsequent letters to ensure that the bar segments
     // overlap.
     bool last_had_overbar = false;
+    auto processedText = ProcessOverbars( aText );
+    const auto& text = processedText.first;
+    const auto& overbars = processedText.second;
+    int i = 0;
 
-    for( UTF8::uni_iter chIt = aText.ubegin(), end = aText.uend(); chIt < end; ++chIt )
+    for( UTF8::uni_iter chIt = text.ubegin(), end = text.uend(); chIt < end; ++chIt )
     {
-        // Toggle overbar
-        if( *chIt == '~' )
-        {
-            if( ++chIt >= end )
-                break;
-
-            if( *chIt != '~' )      // It was a single tilda, it toggles overbar
-                overbar = !overbar;
-
-            // If it is a double tilda, just process the second one
-        }
-
         int dd = *chIt - ' ';
 
         if( dd >= (int) m_glyphBoundingBoxes.size() || dd < 0 )
@@ -326,7 +317,7 @@ void STROKE_FONT::drawSingleLineText( const UTF8& aText )
         GLYPH& glyph = m_glyphs[dd];
         BOX2D& bbox  = m_glyphBoundingBoxes[dd];
 
-        if( overbar )
+        if( overbars[i] )
         {
             double overbar_start_x = xOffset;
             double overbar_start_y = - computeOverbarVerticalPosition();
@@ -376,6 +367,7 @@ void STROKE_FONT::drawSingleLineText( const UTF8& aText )
         }
 
         xOffset += glyphSize.x * bbox.GetEnd().x;
+        ++i;
     }
 
     m_gal->Restore();
diff --git a/include/gal/opengl/opengl_gal.h b/include/gal/opengl/opengl_gal.h
index 5c8d402fc6..201302eeb6 100644
--- a/include/gal/opengl/opengl_gal.h
+++ b/include/gal/opengl/opengl_gal.h
@@ -420,7 +420,7 @@ private:
      * @return Pair containing text bounding box and common Y axis offset. The values are expressed
      * as a number of pixels on the bitmap font texture and need to be scaled before drawing.
      */
-    std::pair<VECTOR2D, float> computeBitmapTextSize( const wxString& aText ) const;
+    std::pair<VECTOR2D, float> computeBitmapTextSize( const UTF8& aText ) const;
 
     // Event handling
     /**