Repository: kivy/kivy

Issue:
Label with line_height < 0 are broken
Test case:

```
from kivy.base import runTouchApp
from kivy.uix.label import Label
runTouchApp(Label(
    text="AAAApppp\nppppAAAAA",
    line_height=0.5,
    text_size=(None, None)))
```

Result:
![image](https://cloud.githubusercontent.com/assets/37904/4371391/d8414d90-4316-11e4-9de7-eac64581d612.png)

Label with line_height < 0 are broken
Test case:

```
from kivy.base import runTouchApp
from kivy.uix.label import Label
runTouchApp(Label(
    text="AAAApppp\nppppAAAAA",
    line_height=0.5,
    text_size=(None, None)))
```

Result:
![image](https://cloud.githubusercontent.com/assets/37904/4371391/d8414d90-4316-11e4-9de7-eac64581d612.png)



Discussion:
@matham Is it your part?

Yes, I'll look into it.

@tito, I checked and this is how it was displayed in 1.7.2 and 1.8.0. Thinking about this, I'm not sure what other outcome you think is more appropriate. You set to line size to .5, so the lines will overlap each other. I think the line size is meant more values >= 1.

And that's what i was trying too. I got a design where the line are compacted, currently, i'm forcing the text_size instead, cause using None just fail :)

![decor_4objets_ref](https://cloud.githubusercontent.com/assets/37904/4448556/6be33fac-4814-11e4-9149-66d84ebe750b.png)

This is one example of the result i want to achieve (the titles in white).

Just saying, it might not be here before, but it still must be considered as a bug :)

Ah, I think I understand the problem now - the bottom gets cut off. I thought the problem you had in mind was that the lines overlap :). Ok, I'll look into it.

@tito Maybe change issue title to "Label with line_height < 1 are broken"

I think I got it fixed. We need to reserve the first line height always, and then multiply by the scaling factor. So in kivy.core.text.__init__.py, instead of:

``` python
# no width specified, faster method
if uw is None:
    index = 0
    for line in self.text.split('\n'):
        index += 1
        if max_lines > 0 and index > max_lines:
            break
        lw, lh = get_extents(line)
        lh = lh * options['line_height'] # -> Must be removed
        if real:
            x = 0; print self._size
            if halign[0] == 'c':
                # center
                x = int((self.width - lw) / 2.)
            elif halign[0] == 'r':
                # right
                x = int(self.width - lw)
            if len(line):
                render_text(line, x, y)
            y += int(lh) # -> Need to scale
        else:
            w = max(w, int(lw))
            self._internal_height = int(lh) # -> Need to reserve first line
    h = self._internal_height if uh is None else uh
```

We can do this:

``` python
# no width specified, faster method
if uw is None:
    index = 0
    for line in self.text.split('\n'):
        index += 1
        if max_lines > 0 and index > max_lines:
            break
        lw, lh = get_extents(line)
        if real:
            x = 0; print self._size
            if halign[0] == 'c':
                # center
                x = int((self.width - lw) / 2.)
            elif halign[0] == 'r':
                # right
                x = int(self.width - lw)
            if len(line):
                render_text(line, x, y)
            y += int(lh * options['line_height'])
        else:
            w = max(w, int(lw))
            if self._internal_height == 0: # -> First line
              self._internal_height = int(lh)
            else: self._internal_height += int(lh) * (options['line_height'])
    h = self._internal_height if uh is None else uh
```

I'll submit a pull request in a few minutes.

EDIT: Never mind. I was working from 1.8.0. The current version is completely different.
The line_height feature is likely removed. While there are new things like space_width and unicode errors handling.

After looking deeper into it, I realise line_height has not completely disappeared. It's been passed along with the options dict to layout_text in another file. However the wrong logic is still used, while the code size has increased, making the change harder.

It all comes down to knowing that the first line is rendered differently.
First line. We never truncate the first line of text.
Other lines. To avoid the cut, y the position of the text must be adjusted with internal_height < 1, regardless of the fact that we might overlap with the previous line.

Now _internal_height which was calculated at every line in a loop is removed in favor of _internal_size. The logic being that the size of lines is constant across all lines... which seems inaccurate to me. The previous code structure was better to handle the case that there might be a font size change in the same paragraph.

There is no way to fix this without rolling back on the layout algorithm. Maybe it would be useful to not consider line height as the height of the text but as line spacing, like any word processor does. The above patch I proposed use that logic.

@matham Is it your part?

Yes, I'll look into it.

@tito, I checked and this is how it was displayed in 1.7.2 and 1.8.0. Thinking about this, I'm not sure what other outcome you think is more appropriate. You set to line size to .5, so the lines will overlap each other. I think the line size is meant more values >= 1.

And that's what i was trying too. I got a design where the line are compacted, currently, i'm forcing the text_size instead, cause using None just fail :)

![decor_4objets_ref](https://cloud.githubusercontent.com/assets/37904/4448556/6be33fac-4814-11e4-9149-66d84ebe750b.png)

This is one example of the result i want to achieve (the titles in white).

Just saying, it might not be here before, but it still must be considered as a bug :)

Ah, I think I understand the problem now - the bottom gets cut off. I thought the problem you had in mind was that the lines overlap :). Ok, I'll look into it.

@tito Maybe change issue title to "Label with line_height < 1 are broken"

I think I got it fixed. We need to reserve the first line height always, and then multiply by the scaling factor. So in kivy.core.text.__init__.py, instead of:

``` python
# no width specified, faster method
if uw is None:
    index = 0
    for line in self.text.split('\n'):
        index += 1
        if max_lines > 0 and index > max_lines:
            break
        lw, lh = get_extents(line)
        lh = lh * options['line_height'] # -> Must be removed
        if real:
            x = 0; print self._size
            if halign[0] == 'c':
                # center
                x = int((self.width - lw) / 2.)
            elif halign[0] == 'r':
                # right
                x = int(self.width - lw)
            if len(line):
                render_text(line, x, y)
            y += int(lh) # -> Need to scale
        else:
            w = max(w, int(lw))
            self._internal_height = int(lh) # -> Need to reserve first line
    h = self._internal_height if uh is None else uh
```

We can do this:

``` python
# no width specified, faster method
if uw is None:
    index = 0
    for line in self.text.split('\n'):
        index += 1
        if max_lines > 0 and index > max_lines:
            break
        lw, lh = get_extents(line)
        if real:
            x = 0; print self._size
            if halign[0] == 'c':
                # center
                x = int((self.width - lw) / 2.)
            elif halign[0] == 'r':
                # right
                x = int(self.width - lw)
            if len(line):
                render_text(line, x, y)
            y += int(lh * options['line_height'])
        else:
            w = max(w, int(lw))
            if self._internal_height == 0: # -> First line
              self._internal_height = int(lh)
            else: self._internal_height += int(lh) * (options['line_height'])
    h = self._internal_height if uh is None else uh
```

I'll submit a pull request in a few minutes.

EDIT: Never mind. I was working from 1.8.0. The current version is completely different.
The line_height feature is likely removed. While there are new things like space_width and unicode errors handling.

After looking deeper into it, I realise line_height has not completely disappeared. It's been passed along with the options dict to layout_text in another file. However the wrong logic is still used, while the code size has increased, making the change harder.

It all comes down to knowing that the first line is rendered differently.
First line. We never truncate the first line of text.
Other lines. To avoid the cut, y the position of the text must be adjusted with internal_height < 1, regardless of the fact that we might overlap with the previous line.

Now _internal_height which was calculated at every line in a loop is removed in favor of _internal_size. The logic being that the size of lines is constant across all lines... which seems inaccurate to me. The previous code structure was better to handle the case that there might be a font size change in the same paragraph.

There is no way to fix this without rolling back on the layout algorithm. Maybe it would be useful to not consider line height as the height of the text but as line spacing, like any word processor does. The above patch I proposed use that logic.


Resolving patch:
diff --git a/kivy/core/text/markup.py b/kivy/core/text/markup.py
--- a/kivy/core/text/markup.py
+++ b/kivy/core/text/markup.py
@@ -410,18 +410,20 @@ def _real_render(self):
             psp = pph = 0
             for word in layout_line.words:
                 options = self.options = word.options
+                # the word height is not scaled by line_height, only lh was
+                wh = options['line_height'] * word.lh
                 # calculate sub/super script pos
                 if options['script'] == 'superscript':
                     script_pos = max(0, psp if psp else self.get_descent())
                     psp = script_pos
-                    pph = word.lh
+                    pph = wh
                 elif options['script'] == 'subscript':
-                    script_pos = min(lh - word.lh, ((psp + pph) - word.lh)
-                                     if pph else (lh - word.lh))
-                    pph = word.lh
+                    script_pos = min(lh - wh, ((psp + pph) - wh)
+                                     if pph else (lh - wh))
+                    pph = wh
                     psp = script_pos
                 else:
-                    script_pos = (lh - word.lh) / 1.25
+                    script_pos = (lh - wh) / 1.25
                     psp = pph = 0
                 if len(word.text):
                     render_text(word.text, x, y + script_pos)
@@ -431,7 +433,7 @@ def _real_render(self):
                 if ref is not None:
                     if not ref in refs:
                         refs[ref] = []
-                    refs[ref].append((x, y, x + word.lw, y + word.lh))
+                    refs[ref].append((x, y, x + word.lw, y + wh))
 
                 # Should we record anchors?
                 anchor = options['_anchor']


Test patch: