Repository: kivy/kivy

Issue:
 UnicodeDecodeError: 'utf8' codec can't decode byte 0xc3 in position 30: unexpected end of data
I have a crash on a label update, that seems to originate from 3851a7057ef367c55ca828dcbdf4d1dde33c251c

```
gabriel@desk-dev-1:~/something> python2 main.py
[INFO   ] [Logger      ] Record log in /home/gabriel/.kivy/logs/kivy_14-11-17_8.txt
[INFO   ] [Factory     ] 172 symbols loaded
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_pygame, img_pil, img_gif (img_sdl2, img_ffpyplayer ignored)
[INFO   ] [Text        ] Provider: pygame(['text_sdl2'] ignored)
[INFO   ] Kivy v1.9.0-dev
[INFO   ] [Python      ] v2.7.6 (default, Mar 22 2014, 22:59:56) 
[GCC 4.8.2]
[INFO   ] [Window      ] Provider: pygame(['window_egl_rpi'] ignored)
[INFO   ] [GL          ] OpenGL version <4.4.0 NVIDIA 331.38>
[INFO   ] [GL          ] OpenGL vendor <NVIDIA Corporation>
[INFO   ] [GL          ] OpenGL renderer <GeForce GTX 650/PCIe/SSE2>
[INFO   ] [GL          ] OpenGL parsed version: 4, 4
[INFO   ] [GL          ] Shading version <4.40 NVIDIA via Cg compiler>
[INFO   ] [GL          ] Texture max size <16384>
[INFO   ] [GL          ] Texture max units <32>
[INFO   ] [Window      ] virtual keyboard not allowed, single mode, not docked
[INFO   ] [OSC         ] using <multiprocessing> for socket
[INFO   ] [GL          ] NPOT texture support is available
[INFO   ] [Base        ] Start application main loop
[INFO   ] [OSC         ] listening for Tuio on 0.0.0.0:3333
[INFO   ] [Base        ] Leaving application in progress...
 Traceback (most recent call last):
   File "main.py", line 1405, in <module>
     app.run()
   File "/home/gabriel/kivy/kivy/app.py", line 825, in run
     runTouchApp()
   File "/home/gabriel/kivy/kivy/base.py", line 484, in runTouchApp
     EventLoop.window.mainloop()
   File "/home/gabriel/kivy/kivy/core/window/window_pygame.py", line 381, in mainloop
     self._mainloop()
   File "/home/gabriel/kivy/kivy/core/window/window_pygame.py", line 285, in _mainloop
     EventLoop.idle()
   File "/home/gabriel/kivy/kivy/base.py", line 333, in idle
     Clock.tick_draw()
   File "/home/gabriel/kivy/kivy/clock.py", line 490, in tick_draw
     self._process_events_before_frame()
   File "/home/gabriel/kivy/kivy/clock.py", line 640, in _process_events_before_frame
     event.tick(self._last_tick, remove)
   File "/home/gabriel/kivy/kivy/clock.py", line 374, in tick
     ret = callback(self._dt)
   File "/home/gabriel/kivy/kivy/uix/label.py", line 267, in texture_update
     self._label.refresh()
   File "/home/gabriel/kivy/kivy/core/text/__init__.py", line 567, in refresh
     sz = self.render()
   File "/home/gabriel/kivy/kivy/core/text/__init__.py", line 507, in render
     text = text.decode('utf8') if isinstance(text, bytes) else text
   File "/usr/lib/python2.7/encodings/utf_8.py", line 16, in decode
     return codecs.utf_8_decode(input, errors, True)
 UnicodeDecodeError: 'utf8' codec can't decode byte 0xc3 in position 30: unexpected end of data
```



Discussion:
workaround until it's fixed

```
<Label>:
    strip: True
```

My issue was that i was adding half an unicode char to the label, somehow this was not crashing before the aformentioned change.

my code is something along

```
self.text += self.target_text[len(self.text)]
```

which fails because it treats the string as an array of bytes on 2.7, i guess it works correctly on 3.

Howether, as discussed on irc, adding errors='ignore' or 'replace' in line 507 of core/text/**init**.py would solve the issue, but the better choice would certainly depend on the use case, so it would be nice to add it as an option and to pass the correct value from Label (in _font_properties i guess). I started this but didn't finish yet.


Resolving patch:
diff --git a/kivy/core/text/__init__.py b/kivy/core/text/__init__.py
--- a/kivy/core/text/__init__.py
+++ b/kivy/core/text/__init__.py
@@ -104,9 +104,12 @@ class LabelBase(object):
         `strip` : bool, defaults to False
             Whether each row of text has its leading and trailing spaces
             stripped. If `halign` is `justify` it is implicitly True.
+        `unicode_errors` : str, defaults to `'replace'`
+            How to handle unicode decode errors. Can be `'strict'`, `'replace'`
+            or `'ignore'`.
 
     .. versionchanged:: 1.9.0
-        `strip`, `shorten_from`, and `split_str` were added.
+        `strip`, `shorten_from`, `split_str`, and `unicode_errors` were added.
 
     .. versionchanged:: 1.9.0
         `padding_x` and `padding_y` has been fixed to work as expected.
@@ -139,14 +142,14 @@ def __init__(self, text='', font_size=12, font_name=DEFAULT_FONT,
                  bold=False, italic=False, halign='left', valign='bottom',
                  shorten=False, text_size=None, mipmap=False, color=None,
                  line_height=1.0, strip=False, shorten_from='center',
-                 split_str=' ', **kwargs):
+                 split_str=' ', unicode_errors='replace', **kwargs):
 
         options = {'text': text, 'font_size': font_size,
                    'font_name': font_name, 'bold': bold, 'italic': italic,
                    'halign': halign, 'valign': valign, 'shorten': shorten,
                    'mipmap': mipmap, 'line_height': line_height,
                    'strip': strip, 'shorten_from': shorten_from,
-                   'split_str': split_str}
+                   'split_str': split_str, 'unicode_errors': unicode_errors}
 
         options['color'] = color or (1, 1, 1, 1)
         options['padding'] = kwargs.get('padding', (0, 0))
@@ -504,7 +507,7 @@ def render(self, real=False):
             # all text will be stripped by default. unicode NO-BREAK SPACE
             # characters will be preserved, so we replace the leading and
             # trailing spaces with \u00a0
-            text = text.decode('utf8') if isinstance(text, bytes) else text
+            text = text.decode('utf8', errors=options['unicode_errors']) if isinstance(text, bytes) else text
             lspace = len(text) - len(text.lstrip())
             rspace = len(text) - len(text.rstrip())
             text = (u'\u00a0' * lspace) + text.strip() + (u'\u00a0' * rspace)
diff --git a/kivy/uix/label.py b/kivy/uix/label.py
--- a/kivy/uix/label.py
+++ b/kivy/uix/label.py
@@ -180,7 +180,7 @@ class Label(Widget):
                         'halign', 'valign', 'padding_x', 'padding_y',
                         'text_size', 'shorten', 'mipmap', 'markup',
                         'line_height', 'max_lines', 'strip', 'shorten_from',
-                        'split_str')
+                        'split_str', 'unicode_errors')
 
     def __init__(self, **kwargs):
         self._trigger_texture = Clock.create_trigger(self.texture_update, -1)
@@ -577,6 +577,16 @@ def on_ref_press(self, ref):
     :attr:`split_str` is a :class:`~kivy.properties.StringProperty` and
     defaults to `''` (the empty string).
     '''
+    
+    unicode_errors = OptionProperty('replace', options=('strict', 'replace', 'ignore'))
+    '''How to handle unicode decode errors. Can be `'strict'`, `'replace'` or
+    `'ignore'`.
+    
+    .. versionadded:: 1.9.0
+    
+    :attr:`unicode_errors` is an :class:`~kivy.properties.OptionProperty` and
+    defaults to `'replace'`.
+    '''
 
     markup = BooleanProperty(False)
     '''


Test patch: