Quantcast

Mac OS X Text Drawing Method Comparison

classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Mac OS X Text Drawing Method Comparison

Jiang Jiang
Hi,

Here is a comparison of all the text drawing methods currently
available on Mac OS X. I think compare and summarize them
will help us building better Mac port of vim, for both Carbon and
Cocoa ones. So please review and comment it if you are
interested.

- Jiang

--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_mac" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---


Mac OS X Text Rendering Method Comparison
=========================================

To draw text in a Mac OS X application, we have the following options:

C interface
-----------

1. QuickDraw: DrawText(), TextMode(), etc. Deprecated.

2. ATSUI: create a font style, create a layout object, run that layout
   object with that style on a UniChar* string.

3. Quartz has a series of functions like CGContextShowText(),
   CGContextShowGlyphsAtPoint(), CGContextShowGlyphsWithAdvances() to
   do simple glyph rendering, but you have to retrieve CGGlyphs by hand
   or use private APIs of Core Graphics (CGFontGetGlyphsForUnicodes())
   to convert from Unicode characters.
   
4. Core Text, it's existed since Mac OS X 10.4, but only available for
   developers to use since 10.5. (Actually, Cocoa Text System itself
   used Core Text to do text layout and rendering on 10.4). It is told
   that Core Text has cleaner APIs than ATSUI, and it's expected to be
   a replacement for ATSUI.

Objective-C interface
---------------------

1. NSAttributedString/NSString (with attributes) has methods like
   drawAtPoint:, drawInRect:.

2. Manually create a NSTextStorage, NSTextContainer and NSLayoutManager,
   then use NSLayoutManager's drawGlyphsForGlyphRange:atPoint: and
   drawBackgroundForGlyphRange:atPoint: to render text. The text needs
   to render is stored by replacing characters in NSTextStorage.

3. Manually create a NSTextStorage, NSTextContainer and NSLayoutManager,
   but attach them to a NSTextView. Let the NSTextView to issue
   actual rendering requests, display rendered results.
   
To compare between NSAttributedString drawAtPoint: and NSLayoutManager,
"Text Layout Programming Guide for Cocoa" says, drawing directly with
attribute string should only be done for short pieces for text, if you
are working with a large portion of text, use a NSLayoutManager, it
"provides significant performance improvements because it caches glyph
layout and size information".

Comparison
----------

Here is a comparison of the above described rendering methods, Core
Text part is under NDA so I don't list it here.

------------+------------------+-------------+--------------+------------+-------------+------------+--------------
 Method     | Destination      | Coordinate  | Font         | String     | Glyph       | Layout     | Availability
            |                  | System      | Format       | Format     | Generation  | Method     |
------------+------------------+-------------+--------------+------------+-------------+------------+--------------
 QuickDraw  | QuickDraw        | Flipped (1) | Font Manager | C string   | Auto        | Simple     | 10.0
            | graphics port    |             | font ID      |            | (Hidden)    |            |
------------+------------------+-------------+--------------+------------+-------------+------------+--------------
 ATSUI      | QuickDraw port   | Non-flipped | ATSUFontID   | UniChar    | Auto        | Complex    | 10.1
            | Quartz context   |             |              | array      | (Hidden)    | (2)        |
------------+------------------+-------------+--------------+------------+-------------+------------+--------------
 CGContext- | Quartz context   | Non-flipped | CGFont (from | UniChar    | By hand or  | By         | 10.0 (3)
 ShowGlyphs |                  |             | ATS font)    | array      | private API | hand       |
------------+------------------+-------------+--------------+------------+-------------+------------+--------------
 Attributed | NSView           | Flipped or  | NSFont       | NS         | Auto        | Simple     | 10.0
 String     | (Quartz context) | Non-flipped |              | Attributed | (Hidden)    |            |
            |                  |             |              | String (4) |             |            |
------------+------------------+-------------+--------------+------------+-------------+------------+--------------
 NSLayout-  | NSView           | Flipped or  | NSFont       | NS         | NSGlyph-    | NS-        | 10.0
 Manager    | (Quartz context) | Non-flipped |              | Attributed | Generator   | Typesetter | (7)
            |                  |             |              | String     | (5)         | (6)        |
------------+------------------+-------------+--------------+------------+-------------+------------+--------------
 NSTextView | NSTextView       | Not         | NSFont       | NS         | NSGlyph-    | NS-        | 10.0
            |                  | applicable  |              | Attributed | Generator   | Typesetter | (7)
            |                  |             |              | String     |             |            |
------------+------------------+-------------+--------------+------------+-------------+------------+--------------

Note:

(1) "Flipped" means (0, 0) is in the top left corner, while "Non-flipped"
    means (0, 0) is in the bottom left corner. Please note that ATSUI
    can use kATSUFontMatrixTag to change the transform matrix used for
    text rendering, while CGContextShowGlyphs() APIs can use
    CGContextSetTextMatrix() to change that matrix.
   
(2) One thing really good for vim is, if we need to force all (but not
    wide) characters to be the same width, it's much easier to use ATSUI
    kATSUImposeWidthTag than customizing NSTypesetter.

(3) CGContextShowGlyphsWithAdvances() is available since 10.3.

(4) NSAttributedString or NSString with attributes.

(5) Unless specified explicitly, NSLayoutManager will use a default
    NSGlyphGenerator.
   
(6) Unless specified explicitly, NSLayoutManager will use a default
    NSGlyphGenerator.
   
(7) Some APIs for complex layout control is only available since 10.4.
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Mac OS X Text Drawing Method Comparison

Björn Winckler

Hi,

Here is a comparison of all the text drawing methods currently
available on Mac OS X. I think compare and summarize them
will help us building better Mac port of vim, for both Carbon and
Cocoa ones. So please review and comment it if you are
interested.

Interesting table Jiang...I only know about the NSTextStorage, NSLayoutManager, NSTypesetter, and NSTextView approach, but let me summarize what I know about that here.


1. NSAttributedString/NSString (with attributes) has methods like
   drawAtPoint:, drawInRect:.

I have tried this, but without any clever optimizations (never attempted to optimize myself) it was slow (text flickered a lot whilst scrolling).

2. Manually create a NSTextStorage, NSTextContainer and NSLayoutManager,
   then use NSLayoutManager's drawGlyphsForGlyphRange:atPoint: and
   drawBackgroundForGlyphRange:atPoint: to render text. The text needs
   to render is stored by replacing characters in NSTextStorage.

Never tried, but should be more or less identical to what MacVim uses now.

3. Manually create a NSTextStorage, NSTextContainer and NSLayoutManager,
   but attach them to a NSTextView. Let the NSTextView to issue
   actual rendering requests, display rendered results.

This is what MacVim does.


(2) One thing really good for vim is, if we need to force all (but not
    wide) characters to be the same width, it's much easier to use ATSUI
    kATSUImposeWidthTag than customizing NSTypesetter.

I disagree.  In NSFontDescriptor there is an attribute called NSFontFixedAdvanceAttribute.  Set this to whatever you want, create your font with the descriptor, and you'll have all characters having the same width.  It does not take care of line height though.  In NSParagraphStyle you can set maximum/minimum line height.  Theoretically, setting both of these to the same value should give you a unified line height, but I have found that this is not so.  In MacVim I overcome this problem by creating my own typesetter to handle line fragment generation.  This sounds harder than it is...it took me one day to implement (and there is not much code...take a look in MMTypesetter.m).

The problem that remains is font substitution:  Whenever a character is not in the current font, Cocoa will find a font that does have that character.  However, the font that Cocoa uses is sometimes "too wide".  I have started looking into this problem, and what I do is I inspect the font Cocoa wants to use and then modify it.  Also, at the same time I check if the character is wide and if so I set the NSFontFixedAdvanceAttribute to be twice the normal width.  There still is a lot of work to be done here, but I think this approach is feasible.  Of course, the font substitution problem is there whichever approach you take, so it has to be overcome anyway.

The only other way I can think of to deal with font substitution is "don't do it".  Instead, let the user specify a list of fonts to use; if a character is not in one font, try the next.  If its not in any of the fonts, just draw a square or something to indicate that the character cannot be rendered.  This way the user has maximum control over the fonts, but it also means that you (as a user) have to know which font has which characters.  (I certainly don't know this, and I'm guessing most other users don't either.)


/Björn

--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_mac" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Mac OS X Text Drawing Method Comparison

Jiang Jiang

Hi,

2007/8/14, björn <[hidden email]>:

> > (2) One thing really good for vim is, if we need to force all (but not
> >     wide) characters to be the same width, it's much easier to use ATSUI
> >     kATSUImposeWidthTag than customizing NSTypesetter.
>
> I disagree.  In NSFontDescriptor there is an attribute called
> NSFontFixedAdvanceAttribute.  Set this to whatever you want, create your
> font with the descriptor, and you'll have all characters having the same
> width.  It does not take care of line height though.  In NSParagraphStyle
> you can set maximum/minimum line height.  Theoretically, setting both of
> these to the same value should give you a unified line height, but I have
> found that this is not so.  In MacVim I overcome this problem by creating my
> own typesetter to handle line fragment generation.  This sounds harder than
> it is...it took me one day to implement (and there is not much code...take a
> look in MMTypesetter.m).

Line height is a interesting question, I found ([NSFont ascender] -
[NSFont descender]) not always equal to
[NSLayoutManager defaultLineHeightForFont: font], have
you noticed that? How is the latter one calculated?

The second question is about your approach to support wide characters, I
can see in MMTextStorage.m, setAttributes:range: will detect if the first
character of that range is a wide character, then doubles the width of the
font used as an attribute, but since setAttributes:range: is called quite
frequently, it may need to create NSFonts repeatedly, besides, to indicate
the occurrence of wide characters, gui_macvim_draw_string() became
much more complicate than gui_macvim_draw_string(), because it has
to append zero width space to those characters, and make sure sending
wide/non-wide characters to MMBackend one at a time, otherwise
setAttributes:range: of MMTextStorage can not do font replacement by
simply checking the first character. Seems too complicated to me.

iTerm used an even more heavyweight approach, it detects wide
characters in a subclass of NSTypesetter, iterate through the text
it needs to layout, send setLocation:forStartOfGlyphRange: to it's
NSLayoutManager one character at a time.

The code I just checked in on vim-cocoa repository provides an ATSUI
based approach to support wide characters rendering, basically, when
gui_mch_init_font(), we will create two ATSUStyle object, they are
identical except the second one sets a width twice as much as the first
one.

When gui_mch_draw_string() get called, it will create an ATSUTextLayout
object, iterate through the string provided as argument, apply different
ATSUStyle for different part of the string. For example, if we have:

 1  2  3  4   5   6   7  8
---------------------------
<C><C><C><WC><WC><WC><C><C>

Then it will call ATSUSetRunStyle() after character 3, 6, 8 with ATSUStyle 1,
ATSUStyle 2, ATSUStyle 1 respectively.

After it reach the end of that string, positions of all the glyphs is
fixed, simply call ATSUDrawText() to draw those glyphs.

- Jiang

--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_mac" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Mac OS X Text Drawing Method Comparison

Björn Winckler

Line height is a interesting question, I found ([NSFont ascender] -
[NSFont descender]) not always equal to
[NSLayoutManager defaultLineHeightForFont: font], have
you noticed that? How is the latter one calculated?

I don't know.  I simply use [NSLayoutManager defaultLineHeightForFont:] to set the line height, then I set the baseline to [NSFont descender] + MMBaselineOffset (which defaults to -1).  That looks good to me.  I gave up a long time ago trying to figure out how AppKit comes up with values like line height, there just doesn't seem to be a straight answer to many of these questions.

The second question is about your approach to support wide characters, I
can see in MMTextStorage.m, setAttributes:range: will detect if the first
character of that range is a wide character, then doubles the width of the
font used as an attribute, but since setAttributes:range: is called quite
frequently, it may need to create NSFonts repeatedly, besides, to indicate
the occurrence of wide characters, gui_macvim_draw_string() became
much more complicate than gui_macvim_draw_string(), because it has
to append zero width space to those characters, and make sure sending
wide/non-wide characters to MMBackend one at a time, otherwise
setAttributes:range: of MMTextStorage can not do font replacement by
simply checking the first character. Seems too complicated to me.

You are right, it will recreate the font for every character.  This seems like it should be slow, but after doing some tests I didn't find it so.  It should be possible to cache fonts, but I haven't looked into it yet.  The code that detects wide characters in setAttributes:range: is only there because I am using Cocoa font substitution still.  If I knew which wide font to use in advance then I could set the font when adding the text to the text storage.  But this requires that the user chooses the wide font and I thought it would be nice to be able to render all characters without the user having to do anything.  So at the moment, this is more of a font substitution problem than a text rendering problem.

As for gui_macvim_draw_string()...it is basically just a modified version of what happens in gui_outstr_nowrap().  The insertion of zero-width space characters seems like it might not be the best solution as it does not take care of composing characters (this is something I just realised today).  Anyway, once I figure out what to do with font substitution this method might simply disappear (and I'll go back to using gui_mch_draw_string() again).  Everything becomes a whole lot simpler if font substitution is disabled and you rely on the user to set the wide font.

iTerm used an even more heavyweight approach, it detects wide
characters in a subclass of NSTypesetter, iterate through the text
it needs to layout, send setLocation:forStartOfGlyphRange: to it's
NSLayoutManager one character at a time.

Yes, I tried a similar approach, but if you use setLocation:forStartOfGlyphRange: often then it will significantly slow down rendering.  (I used this call to position every glyph individually and this pretty much made the program unusable.)

The code I just checked in on vim-cocoa repository provides an ATSUI
based approach to support wide characters rendering, basically, when
gui_mch_init_font(), we will create two ATSUStyle object, they are
identical except the second one sets a width twice as much as the first
one.

When gui_mch_draw_string() get called, it will create an ATSUTextLayout
object, iterate through the string provided as argument, apply different
ATSUStyle for different part of the string. For example, if we have:

1  2  3  4   5   6   7  8
---------------------------
<C><C><C><WC><WC><WC><C><C>

Then it will call ATSUSetRunStyle() after character 3, 6, 8 with ATSUStyle 1,
ATSUStyle 2, ATSUStyle 1 respectively.

After it reach the end of that string, positions of all the glyphs is
fixed, simply call ATSUDrawText() to draw those glyphs.

This sounds like a good way to deal with this problem...I will have to look at your code when I get a chance.  I never attempted to get into ATSUI because it seemed too daunting, but I also think it might be the solution that will work best with Vim.  I am glad you are taking this approach! :)

However, you have said nothing on how you deal with font substitution, which is the biggest problem I am facing now.  To simply lay out characters with a fixed width is something I feel confident that I can get working now (although there is still some work to be done).  Are you relying on 'guifontwide' to be set?  What about characters which are not in 'guifont' or 'guifontwide'...are they rendered at all?  (Sorry for not looking at your code before asking these questions, but I haven't had the time to do so yet.)


/Björn
--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_mac" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Mac OS X Text Drawing Method Comparison

Jiang Jiang

Hi,

2007/8/14, björn <[hidden email]>:
> However, you have said nothing on how you deal with font substitution, which
> is the biggest problem I am facing now.  To simply lay out characters with a
> fixed width is something I feel confident that I can get working now
> (although there is still some work to be done).  Are you relying on
> 'guifontwide' to be set?  What about characters which are not in 'guifont'
> or 'guifontwide'...are they rendered at all?  (Sorry for not looking at your
> code before asking these questions, but I haven't had the time to do so
> yet.)

My current approach does not require 'guifontwide' to be set, simply set

ATSUSetTransientFontMatching(layout, TRUE);

before drawing, then ATSUI will use the default font behavior. But if
'guifontwide' is set, there are some APIs like ATSUSetFontFallbacks(),
ATSUCreateFontFallbacks() to override the default behavior and use
a specified font as the fallback font. But I haven't look into these APIs
yet, so I'm not sure how it works. (Default fallback looks fine, though)

- Jiang

--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_mac" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Mac OS X Text Drawing Method Comparison

Nico Weber-3
In reply to this post by Jiang Jiang

Hi,

> Line height is a interesting question, I found ([NSFont ascender] -
> [NSFont descender]) not always equal to
> [NSLayoutManager defaultLineHeightForFont: font], have
> you noticed that? How is the latter one calculated?

 From http://developer.apple.com/documentation/Carbon/Conceptual/ 
ATSUI_Concepts/atsui_chap4/chapter_4_section_12.html :

height = ascent + descend + leading

An image that shows this graphically is at http://www.unix.org.ua/ 
orelly/java/awt/ch03_02.htm :-P

Did you try writing a small app that renders text as fast as possible  
with each method, and measure the number of characters/second for  
each method?

Nico


--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_mac" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Mac OS X Text Drawing Method Comparison

Björn Winckler
In reply to this post by Jiang Jiang
2007/8/14, björn <[hidden email] >:
> However, you have said nothing on how you deal with font substitution, which
> is the biggest problem I am facing now.  To simply lay out characters with a
> fixed width is something I feel confident that I can get working now
> (although there is still some work to be done).  Are you relying on
> 'guifontwide' to be set?  What about characters which are not in 'guifont'
> or 'guifontwide'...are they rendered at all?  (Sorry for not looking at your
> code before asking these questions, but I haven't had the time to do so
> yet.)

My current approach does not require 'guifontwide' to be set, simply set

ATSUSetTransientFontMatching(layout, TRUE);

before drawing, then ATSUI will use the default font behavior. But if
'guifontwide' is set, there are some APIs like ATSUSetFontFallbacks(),
ATSUCreateFontFallbacks() to override the default behavior and use
a specified font as the fallback font. But I haven't look into these APIs
yet, so I'm not sure how it works. (Default fallback looks fine, though)

Very interesting.  With the font substitution in Cocoa some of the substituted fonts are too wide, e.g. the Greek letter omega is too wide if 'guifont=Monaco:h12' even though omega is not a wide character (i.e. Vim thinks it's a normal width character).  By 'too wide' I mean that it will overlap the following character in MacVim.  With ATSUI font substitution do you not have these problems?  That somehow strikes me as odd, since Cocoa uses ATSUI anyway.

(Try http://www.columbia.edu/kermit/utf8.html for lots of different utf-8 characters.)


/Björn


--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_mac" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Mac OS X Text Drawing Method Comparison

Jiang Jiang

2007/8/14, björn <[hidden email]>:
> Very interesting.  With the font substitution in Cocoa some of the
> substituted fonts are too wide, e.g. the Greek letter omega is too wide if
> 'guifont=Monaco:h12' even though omega is not a wide character (i.e. Vim
> thinks it's a normal width character).  By 'too wide' I mean that it will
> overlap the following character in MacVim.  With ATSUI font substitution do
> you not have these problems?  That somehow strikes me as odd, since Cocoa
> uses ATSUI anyway.

I see, allow user to suggest a font fallback list via set guifont= should
be a good idea. But how to decide gui.char_width is going to be a serious
problem. Should we iterate through all fonts in that list and find out a
maximum number of font advancement? or should we simply use the
advancement of the first font?

I think gvim on other platforms and similar editors like emacs should
face the same problem. It might be a good idea to look at how those
applications behave.

- Jiang

--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_mac" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Mac OS X Text Drawing Method Comparison

Björn Winckler

2007/8/14, björn <[hidden email]>:
> Very interesting.  With the font substitution in Cocoa some of the
> substituted fonts are too wide, e.g. the Greek letter omega is too wide if
> 'guifont=Monaco:h12' even though omega is not a wide character ( i.e. Vim
> thinks it's a normal width character).  By 'too wide' I mean that it will
> overlap the following character in MacVim.  With ATSUI font substitution do
> you not have these problems?  That somehow strikes me as odd, since Cocoa
> uses ATSUI anyway.

I see, allow user to suggest a font fallback list via set guifont= should
be a good idea. But how to decide gui.char_width is going to be a serious
problem. Should we iterate through all fonts in that list and find out a
maximum number of font advancement? or should we simply use the
advancement of the first font?

Just to be clear: does this mean you do see the same problem with your ATSUI approach (i.e. character overlap)?

The way I am dealing with it at this point in time is to let the user worry about such things.  I've exposed a user default called MMCellWidthMultiplier (a float) and I multiply this number with the width of the letter 'm' in the current font to get the cell width.

I think that a list of fonts is the only way to have some semblance of control over what is happening, but I really like the idea of automatic font substitution.  If we keep a list of fonts and iterate through to get the widest glyph in the font, I am guessing the spacing will be way too wide for most characters, but this might be worth a try.  Maybe a hybrid approach would work best; let the user specify a list and use these fonts if possible, otherwise fall back to font substitution.  That way, if some characters render as too wide, then the user can find an appropriate font (of the right size) and add it to the font list.  What do you think about that?

In any case, I think it will be very hard to get the font rendering to look good in every situation.

I think gvim on other platforms and similar editors like emacs should
face the same problem. It might be a good idea to look at how those
applications behave.

I haven't had a look myself, but I will let you know what I find out if I do.  I did check out the GTK port of Vim and it works pretty well, but it does suffer from the same problem as I described ( i.e. some characters overlap).


/Björn

--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_mac" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Mac OS X Text Drawing Method Comparison

Jiang Jiang

2007/8/14, björn <[hidden email]>:
> Just to be clear: does this mean you do see the same problem with your ATSUI
> approach (i.e. character overlap)?

Yes.

> The way I am dealing with it at this point in time is to let the user worry
> about such things.  I've exposed a user default called MMCellWidthMultiplier
> (a float) and I multiply this number with the width of the letter 'm' in the
> current font to get the cell width.

It's an acceptable way, and Terminal.app used this way.

> I think that a list of fonts is the only way to have some semblance of
> control over what is happening, but I really like the idea of automatic font
> substitution.  If we keep a list of fonts and iterate through to get the
> widest glyph in the font, I am guessing the spacing will be way too wide for
> most characters, but this might be worth a try.  Maybe a hybrid approach
> would work best; let the user specify a list and use these fonts if
> possible, otherwise fall back to font substitution.  That way, if some
> characters render as too wide, then the user can find an appropriate font
> (of the right size) and add it to the font list.  What do you think about
> that?

I think it's good enough, say, if the user think "omega" in a Greek font
is too wide as a single width character, he should really put that Greek
font in the first place of that list.

- Jiang

--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_mac" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---

Loading...