Issue #59 resolved

Undercurl (underline, or even underscore) not fully rendered

Tomas Pospichal
created an issue

Meant for monospaced fonts ("fast rendering branch"), and after the main tb-issue57 fixes to cell sizing and alignment.

All this is highly dependent on font/size combination used. Modern fonts designed for terminal use ("Monospace" or "DejaVu Sans Mono") do not exhibit the problem.

(UC) Most often, the issue is an incomplete rendering of the "undercurl", such as the one used by SpellBad highlight group.

(UL) Sometime, even the underline decoration, used by the Underline highlight group, is not visible.

(US) Rarely, the underscore (or other low-positioned?) character may not be visible.

(UL) and (US) issues are resolved by ":set linespace=1" (sometimes, :set linespace=2 is needed). This appears to be a problem/design feature of the font (or its metric), and probably the reason why Vim has the 'linespace' option. Qt positions the glyph/decoration where the font tells it to, but parts falling outside the fixed-size cell (i.e. into the next line) do not get rendered or get overdrawn when the next line is drawn.

Examples: no UL: Liberation Mono at sizes <=10 or size 12 or 18, or (my system's) Courier New at sizes other than 14 and 20. Setting linespace=1 resolves the problem, except for Courier New regular style at size 14 or 20, where linespace=2 is needed. no US: in regular style only, Courier New at 8 or 12 pt.

The Courier New that I have installed exhibits a very strange metric (ascend or height or leading values depend on style; where is the baseline then?), which makes this font very atypical.

Undercurl (wavy line decoration): very often rendered incomplete and discontinuous, with only the top visible, so then it appears as a dashed line (at smaller sizes) or a row of "carets" (at larger sizes). Here setting the 'linespace' option to a positive value does not help: the lines spread apart, but the decoration appears clipped exactly as before.

Here Qt itself appears to position and clip the decoration (so as to be always below the baseline, and to not to exceed the design size of the font), and so setting the extra 'linespace' in Vim has no effect on this clipping.

Examples: Free Mono: UC good at 10 and 14pt, at other sizes disconnected/incomplete; Andale Mono: disconnected at 7,8,12, no problem at 9, 10, 11, 14 * Lucida Typewriter: disconnected at 7,8,9, good at 10, 11, 12, 14

Very often, the small sizes have the problem (it appears that Qt would require the font's 'descent' value to be at least 2 to fit the decoration in), but as the examples show, it is not a simple pattern (at larger sizes, Qt attempts to make the decoration larger, so a larger value of descent would be needed?).

The first thing to do would be to check that qvim is not itself setting drawing or clipping rectangles that are too small (or mis-positioned). But if the problem is not in qvim...

  • Can this clipping done by Qt be overruled/configured somehow? void QTextLine::setLeadingIncluded ( bool included ) is mentioned in http://doc.qt.nokia.com/4.7-snapshot/qtextline.html#leadingIncluded but the signature and the description do not give much hope this would allow any configurable values
  • Should one use a different/more flexible method for rendering the decoration?
  • Should a much simpler decoration be used for certain font families to improve within-the-same-font consistency?

Comments (21)

  1. equalsraf repo owner

    I do not think we are a setting too small of a a clipping rectangle(vertically). I tested this briefly when adding linespace support. The clipping rect grows(vertically) with the linespace size, but even with a large linespace value, sometimes the undercurl decoration is not correct. In particular setting a larger linespace fixes the underline issue but not the undercurl.

    • Currently the clipping is rect is based on the calculated Vim char_height and char_width
    • I can understand if the end of the line is clipped. But the vertical space should be enough, unless the actual clipping rect QTextLayout is using is not the same as the QPainter.
    • QTextLine::setLeadingIncluded() does not seem to produce any efect
  2. Tomas Pospichal reporter

    I also did not see any problem in qvim. Looking at the Qt sources, it does not seem surprising at all that the wavy decoration is getting clipped, Qt really tries to clip it based on the information in the font (it cannot go beyond the 'descent' value), with positioning based on 'underLine' info also stored in the font. What is more surprising no one else is finding the consequences of Qt handling undesirable. (in Qt: src_gui_painting_qpainter.cpp around line 6360).

    It may be that in proportional fonts (which is what interests much more than 99% of people who have any use for the 'spellchecking' wavy line) the 'descent' is not as small as in several of these monospaced fonts we are seeing, so the problem does not arise there.

    Because the sizes for positioning and clipping are derived directly from the font (or via FontEngine, which does not sound like something that could be easily sub-classed or subverted), I do not think this is going to be easy to fix if the decoration is done by Qt's QTextItem machinery.

  3. equalsraf repo owner

    The only trivial solution I see would be to render the underculr separately, on top of the already painted text. This should also ensure the line does not get segmented.

    Curiously this is exactly what vim-gtk does.

  4. equalsraf repo owner

    I can understand the undercurl(UC) that Qt is clipping and the segment underline(UL), at least in the slow branch. The one I don't understand is the underscore. I'm currently seeing no underscore for DejaVu Sans Mono 11, but 12 works ok. It seems to be missing by 1 pixel (also Courier New 8, but not 12).

    A curious detail seems to be that the leading() is -1 (for DejaVuSansMono size 11) and 0 (for size 12). The Qt docs don't mention any particular meaning for this. I assume this means the font engine wants to render the underscore on top of the next line. But since in Vim we must fit into the given grid this is not possible.

    Should we treat negative leading values as leading=0? The char_height would change from:

    gui.char_height = metric.lineSpacing() + p_linespace;
    

    into somethin like:

    int leading = metric.leading() > 0 ? metric.leading : 0 ;
    gui.char_height = p_linespace + metric.height() + leading;
    

    Not sure if the extra vertical space would cause additional issues, but it seems to work on my end.

  5. Tomas Pospichal reporter

    I know too little to see how it would be done, it is not trivial to me (is the cursor re-painting going to be simple?). But perhaps what you had in mind would also be usable in the slow-rendering branch?

    One possibility that would not require to change the current rendering procedure (only require adding code into the font-change hook): for problematic fonts, which can be identified by examining their metric (or simpler: by blacklisting their names?), one could ask Qt to render the decoration as QTextCharFormat::DashDotLine (which is apparently the normal spellchecking decoration on OS X), and this might avoid the clipping problem. I did not test this conjecture... Since Vim people do not switch fonts all the time (if ever), the resulting inter-font inconsistency is of very little concern in practice.

  6. Tomas Pospichal reporter

    Re:#6 DejaVu Sans Mono 11 is what I use most of the time, I am sure that it has visible underscore (without any increase of 'linespace'). Courier New has that problem at some sizes. Or did you mean DajaVu forced into the slow-rendering has no US?

    For several fonts that use negative leading: they are just trying to correct for relatively large height, so that lineSpacing is better reflecting the font's actual design size (that is the case of DejaVu Sans Mono 11), without having to cut down the font's 'descent' or 'ascent'.

    It could be that the "meaning" of leading() is implementation dependent... (should it go on top, or below?) Qt ignores it when it clips the wavy line (as if the leading went to the top, or they just did not care). For the fonts with the broken UC problem, those often have positive leading (FreeMono), so one would want to think it is applied below... Qt would not care, anyway.

    Looking at Courier New, which has style-dependent leading, it would seem it was designed on the premise that the leading is applied at the top, that is the only way to get a constant baseline. qvim does it the other way now, so the characters jump a little when the cursor passes (because the cursor is bold?).

    No, the gui.char_height is correct as it is now, and leading should not be clipped. One could think of also adding leading() to the char_ascent value (to get a straight baseline for Courier New), but it is not going to have any other positive effects, I would say.

    The negative effect: FreeMono has descent values 1 at sizes 8 and 9, but UL-position 2, meaning UL is below the lower bound of the character, if the leading (which =1 here) is applied to top, not bottom.

    So there is no logic to it, any choice is going to have some bad properties. In other words, real world. The leading values stored in the font show that there is little consistency, different fonts have metrics that were designed with a very different philosophy, some perhaps retaining some consistency with traditional fonts for printing, and some were designed so that they behave better with screen layout engines. And rounding everything to integers is making matters worse.

    Most of all, I do not think that any changes to the current character cell size are going to improve anything, quite on the contrary.

    Common mono-spaced fonts:

    • Monospace (=Bitstream Vera Sans Mono), DejaVu Sans Mono: identical metric, fonts with no issues on screen.
    • FreeMono, Liberation Mono: same width, very similar ascent, lineSpacing (cell size), but quite different leading, descent, height
    • Courier New: an abberation, the regular style differs from the others (esp. sizes 8, 11, 16)

    General constraints:

    • lineSpacing = height + leading
    • height = ascent + 1 + descent (1 is for the baseline)
    • UL-pos is the 'underline' position, measured below baseline (i.e. in the same way as 'descent')
    
    == Monospace: all styles, or DejaVu Sans Mono: all styles ==
    point size:	 6  7  8  9 10 11 12 14 16 18 20 22 24 26 28 36 48 72 
    width:	         5  5  7  7  8  9 10 11 13 14 16 17 19 21 22 29 39 58 
    lineSpacing: 	 9 10 13 14 15 17 19 22 24 28 31 34 37 41 43 56 75 112 
    leading:	 0  0  0  0  0 -1  0  0 -1  0  0  0 -1  1  0  0  1  0 
    height:		 9 10 13 14 15 18 19 22 25 28 31 34 38 40 43 56 74 112 
    ascent:		 7  8 10 11 12 14 15 18 20 22 25 27 30 32 34 45 59 89 
    descent:	 1  1  2  2  2  3  3  3  4  5  5  6  7  7  8 10 14 22 
    UL-pos:		 1  1  1  1  1  1  1  2  2  2  2  2  3  3  3  4  5  8
    
    
    == FreeMono: all styles ==
    point size:	 6  7  8  9 10 11 12 14 16 18 20 22 24 26 28 36 48 72 
    width:	         5  5  7  7  8  9 10 11 13 14 16 17 19 21 22 29 38 58 
    lineSpacing:	 9 10 12 13 14 16 17 21 23 26 29 32 35 38 40 52 70 105 
    leading:	 1  1  1  1  1  1  1  2  2  2  2  3  3  3  3  4  6  9 
    height:		 8  9 11 12 13 15 16 19 21 24 27 29 32 35 37 48 64 96 
    ascent:		 6  7  9 10 10 12 13 15 17 19 22 23 26 28 30 38 51 77 
    descent:	 1  1  1  1  2  2  2  3  3  4  4  5  5  6  6  9 12 18 
    UL-pos:		 1  1  2  2  2  2  2  3  3  4  4  4  5  5  6  7 10 14 
    
    == Liberation Mono: all styles ==
    point size:	 6  7  8  9 10 11 12 14 16 18 20 22 24 26 28 36 48 72 
    width:	         5  5  7  7  8  9 10 11 13 14 16 17 19 21 22 29 38 58 
    lineSpacing:	 9 10 12 14 15 17 18 22 24 27 31 33 36 40 42 54 73 109 
    leading:	 0 -1  0  0  0  1  0  0  1  0  1  0 -1  0  0  0  1  0 
    height:	 	 9 11 12 14 15 16 18 22 23 27 30 33 37 40 42 54 72 109 
    ascent:	 	 7  8  9 10 11 12 13 16 17 20 22 24 27 29 31 40 53 80 
    descent:	 1  2  2  3  3  3  4  5  5  6  7  8  9 10 10 13 18 28 
    UL-pos:	 	 2  3  3  3  4  4  5  5  6  7  8  8  9 10 10 14 18 27
    
    
    == Courier New: Regular (R) vs. Bold, Italic, Bold Italic (BI) ==
    point size:	 6  7  8  9 10 11 12 14 16 18 20 22 24 26 28 36 48 72 
    width:	         5  5  7  7  8  9 10 11 13 14 16 17 19 21 22 29 38 58 
    lineSpacing:	 9 10 12 14 15 17 18 22 24 27 31 33 36 40 42 54 73 109 
    
    leading:(R) 	-1 -1 -2  0  0 -1 -1  0 -1  0  1  0 -1  0  0  0  1  0 
    leading:(BI)	 0 -1  0  0  0  1  0  0  1  0  1  0 -1  0  0  0  1  0 
    
    height:(R)	10 11 14 14 15 18 19 22 25 27 30 33 37 40 42 54 72 109 
    height:(BI)	 9 11 12 14 15 16 18 22 23 27 30 33 37 40 42 54 72 109 
    
    ascent:(R)	 7  8 10 10 11 13 14 16 18 20 22 24 27 29 31 40 53 80 
    ascent:(BI)	 7  8  9 10 11 12 13 16 17 20 22 24 27 29 31 40 53 80 
    
    descent:(R)	 1  2  2  3  3  3  4  5  5  6  7  8  9 10 10 13 18 28 
    descent:(BI)	 2  2  3  3  3  4  4  5  6  6  7  8  9 10 10 13 18 28 
    
    UL-pos:		 2  2  3  3  3  4  4  5  5  6  7  7  8  9  9 12 16 24
    
  7. equalsraf repo owner

    No, I am seeing this problem with dejavu sans mono 11 in the fast branch.

    Some points to your comments

    • With the QPainter API we are calling, the extra space will go at the top, because the API takes the coordinates of the top of the cell. So it probably forces the ascent on top, and the leading on the bottom. We can try to change that to force the baseline coordinates instead.
    • Qt docs are not very clear they just state that "The y-coordinate of rectangle is used as the top of the font.". But hey don't say where the baseline is positioned.
    • Honestly, I dont think Vim is using the gui.char_ascent value at this point.

    So if I am understanding this correctly, the vertical size of the char cell is correct but the placement of the char(or the baseline) is not? And in this case we get clipped at the bottom.

    We can try to force the baseline position, potentially causing some clipping at the top. But what would be the position of the baseline within the cell?

    PS: I don't know how you generated the tables above, but I added some code in attachment.

  8. equalsraf repo owner

    Looking a bit further at this I tried the following:

    1. Kept the char_height unchanged
    2. Added leading to the char_ascent value
    3. Change calls to QPainter to force the baseline position to the char_ascent

    Got mixed results in the process

    • DejaVu Sans Mono: Underscore is now ok
    • Courier New: Underscore is now ok
    • Courier New: Underline still does not work(Courier New 12)
    • Broke the underculr code(this was intentional, I had no other choice :S)
    ============ Courier New ============
    pt size:         6   7   8   9  10  11  12  14  16  18  20  22  24  26  28  36  48  72 
    width:           5   5   7   7   8   9  10  11  13  14  16  17  19  21  22  29  38  58 
    lineSpacing:     9  10  12  14  15  17  18  22  24  27  31  33  36  40  42  54  73 109 
    leading:        -1  -1  -2   0   0  -1  -1   0  -1   0   1   0  -1   0   0   0   1   0 
    height:         10  11  14  14  15  18  19  22  25  27  30  33  37  40  42  54  72 109 
    ascent:          7   8  10  10  11  13  14  16  18  20  22  24  27  29  31  40  53  80 
    descent:         2   2   3   3   3   4   4   5   6   6   7   8   9  10  10  13  18  28 
    UL-pos:          2   2   3   3   3   4   4   5   5   6   7   7   8   9   9  12  16  24 
    
  9. Tomas Pospichal reporter

    I do not see any missing underscores at that size, but there are several strange things (my older qvim, compiled a couple of weeks ago, displays exactly the same thing as the one from two days ago, so something is not quite right). I can see that in the regular style, the underscore is sitting at the bottom of a cell, so if your qvim applied that -1 leading to reduce the cell hight, it is possible it would no longer be visible. (Strangely, bold underscore is one pixel higher). Also, the underline seems drawn one pixel lower than what it's position is supposed to be...

    I have taken a look at some sizes done by GTK2 gvim, and it seems they size things differently, one probably cannot take it as a guide (FreeMono in particular seems noticeably smaller than other typefaces, as if no leading was taken into consideration; but sizes for DejaVu Sans Mono 10 do not agree at all with the integer metric Qt is reporting, so one cannot make any solid conjectures based on these few observations).

    That vim has no use for the ascent value is easy to believe, it just considers the entire cell. But the 'ascent' pretty much determines where the baseline is (relative to line "boundaries"), so it would be quite important if one tried to typeset several different fonts on the same line.

    My program for printing out parts of font database is primitive (had to rename it to fontinfo_fixed), it does not do any amalgamation of fonts/styles with a similar metric, that was done by hand. I have only access to a single system where compiling with Qt libraries is easy, so there is little point in trying to make the program better...

  10. equalsraf repo owner

    I think gui_gtk.c:draw_under() draws the underline and undercurl independently from the font. Their underline is never painted bellow the cell box. Their underline position is always at the bottom of the cell, or if :set linespace is not 0, immediately below the font baseline. We can adopt a similar approach, but the underline position would be font independent, at most we could vary the line width.

    A little more insight into the whole underline issue. The Qt docs say that the underlinePos is the distance to the base line - but it seems to me that it is in fact the distance to the bottom of the baseline

    So for example for DejaVu Sans Mono(size 11) where with UL-pos(1) and descent(3), Qt paints

     ----------------------------------- This is the baseline (1 pixel)
     ----------------------------------- This is the pixel line  after the baseline
     ----------------------------------- This is the place where Qt draws the underline
     ----------------------------------- Last pixel of descent
    

    For DejaVu Sans Mono this is fine, the underline pos is within the descent(3). But for Courier New, if we assume the same reasoning is followed(descent(4), UL-pos(4)):

     ---------------------------------- This is the baseline (1 pixel)
     ---------------------------------- [distance 0]
     ---------------------------------- [distance 1]
     ---------------------------------- [distance 2]
     ---------------------------------- [distance 3] descent ends
     ---------------------------------- [distance 4] Qt is probably trying to paint the underline here
    

    I'm not sure who is to blame here: the font metrics, or Qt

    In general I see two alternatives:

    1. Increase the char_height, i.e. if the UL-pos is bellow the cell, add the necessary height to the descent.
    2. Like gtk, draw the underline/undercurl (hopefully the underscore is fixed in this branch) on our own

    The problem with (2) is that we can't be sure if we are placing the underline correctly(but it will be visible), e.g. the underline may be painted on top of the underscore?

    The problem with (1) is to come up with a good method to correct the height, i.e. is the difference (UL-pos - descent) enough, or do we have to take the lineWidth() into account. And in this case we still have the undercurl issue to solve.

  11. equalsraf repo owner

    I'm pushing changes into the tb-issue59 branch. In a nutshell:

    • Most of the text painting code has changed - but the cell metrics did not - to force painting at the text baseline
    • The undercurl is now draw as a dash-dotted line, in the underline position. If both UC and UL are enabled, UL is disabled. Undercurl now works for both branches.

    So far:

    • Underscore seems to work ok
    • The underline is still not visible for Courier New 12
    • The issues with a segmented underline can not be solved unless we draw the underline ourselves - as a single line - there is no other way to fix this for the slow branch, the fast branch seems to be operating correctly.
    • I added the dot-dash undercurl just as an experiment, I think it is the default in Mac - but it does not feel very natural to me, and it is less visible in my theme.
    • I have not noticed differences in terms of performance for the fast branch, but since we stopped using QTextLayout this is a possibility.
    • I see no other solution to fix underline, other than increasing the descent/height when the underlinesPos falls outside of the cell
    if ( underlinePos >= descent ) {
        height += underlinePos - descent + lineWidth
    }
    

    I'll have to take a look at this during the weekend.

  12. Tomas Pospichal reporter

    Re #(13): Yes, both GTK and Windows gvim makes undercurl sit at the bottom of the cell, and the wave is always 3px high, regardless of the size of the font (and the period seems to be the width of common fonts at 10pt). Which makes it look rather poor at very small and very large sizes, but is never clipped and avoids those visually disastrous consequences of Qt's sophisticated sizing combined with a very unsophisticated positioning and clipping. Seems like a Qt bug to me, but having seen the effect only in one application, I do not feel like I have enough evidence and understanding to report it as such.

    Qt's 2px high wave at small sizes looks quite good, and the idea of adjusting the size and width of the decoration to the font size is good, but the execution of the idea is poor and unfortunately hits many common fonts at common sizes.

    "the distance to the bottom of the baseline": one can take liberties with the word "distance" only so far. When the "distance" of baseline to baseline is not zero, Qt is simply wrong (either in the docs or in the implementation).

    GTK and Windows version differ in handling of 'linespace', Win puts it at the bottom, GTK splits it in half. It seems that you decision to use the GTK way is good, as there could be glyphs that are exceeding the ascent value. Although I doubt those outlying parts would actually get painted. But this is something that is of little concern now.

    It could be that in the traditional typesetting, leading was used at the "top", thus it should be added to the ascent; the case of Courier New seems to support that. But this font is not something that anything should be optimized for, its metrics are really strange for a monospaced font (its metrics really have the irregularities of proportional fonts, it is just the constancy of width and lineSpacing that makes it pass Qt's fixed-pitch test).

    Windows gvim actually displays similar problems with Courier New: the baseline is inconsistent across styles (but unlike in Qt, it is that the "bold" style differs from the others), and underscore may disappear in italics (at size 10, for instance) since it falls outside the cell. If you set 'linespace' to 0, that is. It could be that this font is the reason gvim sets 'linespace' to 1 on Win by default, perhaps because this font once was (or is) a popular choice on that platform.

  13. Tomas Pospichal reporter

    Just to put things in perspective: here are several fonts with integer metrics as reported by Qt (width, lineSpacing, and height), and then estimated vertical-cell sizes for GTK2 and Windows gvim that I gathered previously.

    • GTK2, Win32 values: estimated from the screen (not looking at the code)
    • in case of Windows, after setting 'linespace' option to 0
    • for larger values, it is not often so clear what the precise pixel value is; estimate 31+ means the actual value is either 31px or 32px
     == DejaVu Sans Mono: Book ==
     point size:	 6  7  8  9 10 11 12 14 16 18 20 
     M-width:	 5  5  7  7  8  9 10 11 13 14 16
    
     lineSpacing:	 9 10 13 14 15 17 19 22 24 28 31
     height:	 9 10 13 14 15 18 19 22 25 28 31
    
     Win32: 	 9    13    15    19 22       31
     GTK2:  	10 12 13 15 17 18 19 23       31+
    
    
     == FreeMono: Medium ==
     point size:	 6  7  8  9 10 11 12 14 16 18 20
     M-width:	 5  5  7  7  8  9 10 11 13 14 16
    
     lineSpacing:	 9 10 12 13 14 16 17 21 23 26 29
     height:	 8  9 11 12 13 15 16 19 21 24 27
    
     Win32: 	 9    12    14    17 20+      28+
     GTK2:  	 9 10 12 13 14 15 17 19       28
    
    
     == Liberation Mono: Regular ==
     point size:	 6  7  8  9 10 11 12 14 16 18 20
     M-width:	 5  5  7  7  8  9 10 11 13 14 16
    
     lineSpacing:	 9 10 12 14 15 17 18 22 24 27 31
     height:	 9 11 12 14 15 16 18 22 23 27 30
    
     GTK2:  	10 11 13 14 16 18 19 21+      31+
    

    There is nothing particular that could be learned from this, but already this small sample shows

    • there is nothing wrong with the values Qt is using
    • different rendering/layout engines do not use the "same" metric (each likely rounds the actual metric in their own way), but overall the sizes are pretty similar; GTK seem somewhat different (from Qt and Win) on DejaVu Sans Mono at small sizes

    The simplest way would be to use lineSpacing value for the cell size, and let people adjust 'linespace' option if they use a font for which glyphs are getting clipped. It is not clear if there is a practical way of taking care of all possible kinds of individual font problems (not relying on 'linespace'), without enlarging unnecessarily the cell size for most fonts.

    I think that underline is likely not of such a great practical importance in gvim. It is however quite unpleasant if the underscore does not show up, this would be a major problem for many people using Vim for coding (meaning many people).

    In the current tb-issue57, DejaVu Sans Mono (or Monospace) does not show the underscore in regular style at 14pt. That is a bit of a problem (since these are likely the default monospaced fonts on most modern systems), although unlikely to be used at this size by people with medium resolution displays.

    The weird Courier New, on the other hand, does not exhibit any problems at all.

    It may be that many will find the dash-dot underline something they have to get used to, but it serves the purpose and it is much more consistent now. Not only when switching fonts or sizes: the "alien" characters that were previously rendered "slow" used to have a very different (and confusing/missing) decorations, now they fit in very nicely.

  14. Tomas Pospichal reporter

    OK, DejaVu Sans Mono/Monospace at 14pt always had that problem with underscore in the regular style, this is not a new development... I just haven't noticed before. So everything improved, really.

  15. equalsraf repo owner

    Ok, I will merge this in the morning.

    I'm still seeing some issues:

    • For the slow branch the underline is segmented
    • The underline and the new undercurl(segmented line) are not always aligned(Courier) which means that we are still not getting the whole underlinePos right, but in this case I don't find it very significant

    This is probably as good as it gonna get for now. At least it seems to work well for most fonts, and small issues show up for some weird fonts.

  16. Log in to comment