2017-01-17 20:06:45 -06:00
// 17 january 2017
2016-05-05 17:23:54 -05:00
# include "uipriv_unix.h"
# include "draw.h"
2017-01-21 09:21:39 -06:00
// TODO
// - if the RTL override is at the beginning of a line, the preceding space is included?
2017-02-24 20:42:40 -06:00
// - nLines == 0: mostly works, except the width is wrong if the paragraph alignment is center or right...
2017-01-21 09:21:39 -06:00
2017-01-17 20:06:45 -06:00
struct uiDrawTextLayout {
PangoLayout * layout ;
2017-02-20 21:24:02 -06:00
GPtrArray * backgroundClosures ;
2017-01-20 15:36:44 -06:00
uiDrawTextLayoutLineMetrics * lineMetrics ;
2017-02-24 20:42:40 -06:00
// TODO change everything to use this
2017-02-10 14:49:36 -06:00
int nLines ;
2016-05-05 17:23:54 -05:00
} ;
2017-01-17 20:06:45 -06:00
// See https://developer.gnome.org/pango/1.30/pango-Cairo-Rendering.html#pango-Cairo-Rendering.description
// For the conversion, see https://developer.gnome.org/pango/1.30/pango-Glyph-Storage.html#pango-units-to-double and https://developer.gnome.org/pango/1.30/pango-Glyph-Storage.html#pango-units-from-double
# define pangoToCairo(pango) (pango_units_to_double(pango))
2017-02-20 19:41:14 -06:00
// cairoToPango() is in uipriv_unix.h because attrstr.c needs it
2016-05-05 17:23:54 -05:00
2017-01-17 20:06:45 -06:00
// we need a context for a few things
// the documentation suggests creating cairo_t-specific, GdkScreen-specific, or even GtkWidget-specific contexts, but we can't really do that because we want our uiDrawTextFonts and uiDrawTextLayouts to be context-independent
// we could use pango_font_map_create_context(pango_cairo_font_map_get_default()) but that will ignore GDK-specific settings
// so let's use gdk_pango_context_get() instead; even though it's for the default screen only, it's good enough for us
# define mkGenericPangoCairoContext() (gdk_pango_context_get())
2016-05-05 17:23:54 -05:00
2017-02-20 19:41:14 -06:00
const PangoStyle pangoItalics [ ] = {
2016-05-05 17:23:54 -05:00
[ uiDrawTextItalicNormal ] = PANGO_STYLE_NORMAL ,
[ uiDrawTextItalicOblique ] = PANGO_STYLE_OBLIQUE ,
[ uiDrawTextItalicItalic ] = PANGO_STYLE_ITALIC ,
} ;
2017-02-20 19:41:14 -06:00
const PangoStretch pangoStretches [ ] = {
2016-05-05 17:23:54 -05:00
[ uiDrawTextStretchUltraCondensed ] = PANGO_STRETCH_ULTRA_CONDENSED ,
[ uiDrawTextStretchExtraCondensed ] = PANGO_STRETCH_EXTRA_CONDENSED ,
[ uiDrawTextStretchCondensed ] = PANGO_STRETCH_CONDENSED ,
[ uiDrawTextStretchSemiCondensed ] = PANGO_STRETCH_SEMI_CONDENSED ,
[ uiDrawTextStretchNormal ] = PANGO_STRETCH_NORMAL ,
[ uiDrawTextStretchSemiExpanded ] = PANGO_STRETCH_SEMI_EXPANDED ,
[ uiDrawTextStretchExpanded ] = PANGO_STRETCH_EXPANDED ,
[ uiDrawTextStretchExtraExpanded ] = PANGO_STRETCH_EXTRA_EXPANDED ,
[ uiDrawTextStretchUltraExpanded ] = PANGO_STRETCH_ULTRA_EXPANDED ,
} ;
2017-01-20 15:36:44 -06:00
// TODO neither these nor the overall extents seem to include trailing whitespace... we need to figure that out too
static void computeLineMetrics ( uiDrawTextLayout * tl )
{
PangoLayoutIter * iter ;
PangoLayoutLine * pll ;
PangoRectangle lineStartPos , lineExtents ;
int i , n ;
uiDrawTextLayoutLineMetrics * m ;
2017-02-10 14:49:36 -06:00
n = tl - > nLines ; // TODO remove this variable
2017-01-20 15:36:44 -06:00
tl - > lineMetrics = ( uiDrawTextLayoutLineMetrics * ) uiAlloc ( n * sizeof ( uiDrawTextLayoutLineMetrics ) , " uiDrawTextLayoutLineMetrics[] (text layout) " ) ;
iter = pango_layout_get_iter ( tl - > layout ) ;
m = tl - > lineMetrics ;
for ( i = 0 ; i < n ; i + + ) {
int baselineY ;
// TODO we use this instead of _get_yrange() because of the block of text in that function's description about how line spacing is distributed in Pango; we have to worry about this when we start adding line spacing...
baselineY = pango_layout_iter_get_baseline ( iter ) ;
pll = pango_layout_iter_get_line_readonly ( iter ) ;
pango_layout_index_to_pos ( tl - > layout , pll - > start_index , & lineStartPos ) ;
pango_layout_line_get_extents ( pll , NULL , & lineExtents ) ;
// TODO unref pll?
// TODO is this correct for RTL glyphs?
m - > X = pangoToCairo ( lineStartPos . x ) ;
// TODO fix the whole combined not being updated shenanigans in the static build (here because ugh)
m - > Y = pangoToCairo ( baselineY - PANGO_ASCENT ( lineExtents ) ) ;
2017-02-07 18:14:51 -06:00
// TODO this does not include the last space if any
2017-01-20 15:36:44 -06:00
m - > Width = pangoToCairo ( lineExtents . width ) ;
m - > Height = pangoToCairo ( lineExtents . height ) ;
m - > BaselineY = pangoToCairo ( baselineY ) ;
m - > Ascent = pangoToCairo ( PANGO_ASCENT ( lineExtents ) ) ;
m - > Descent = pangoToCairo ( PANGO_DESCENT ( lineExtents ) ) ;
m - > Leading = 0 ; // TODO
m - > ParagraphSpacingBefore = 0 ; // TODO
m - > LineHeightSpace = 0 ; // TODO
m - > LineSpacing = 0 ; // TODO
m - > ParagraphSpacing = 0 ; // TODO
// don't worry about the return value; we're not using this after the last line
pango_layout_iter_next_line ( iter ) ;
m + + ;
}
pango_layout_iter_free ( iter ) ;
}
2017-02-11 20:25:41 -06:00
static const PangoAlignment pangoAligns [ ] = {
[ uiDrawTextLayoutAlignLeft ] = PANGO_ALIGN_LEFT ,
[ uiDrawTextLayoutAlignCenter ] = PANGO_ALIGN_CENTER ,
[ uiDrawTextLayoutAlignRight ] = PANGO_ALIGN_RIGHT ,
} ;
uiDrawTextLayout * uiDrawNewTextLayout ( uiDrawTextLayoutParams * p )
2016-05-05 17:23:54 -05:00
{
2017-01-17 20:06:45 -06:00
uiDrawTextLayout * tl ;
2016-05-05 17:23:54 -05:00
PangoContext * context ;
2017-01-17 20:06:45 -06:00
PangoFontDescription * desc ;
2017-02-20 18:28:19 -06:00
PangoAttrList * attrs ;
2017-01-17 20:06:45 -06:00
int pangoWidth ;
tl = uiNew ( uiDrawTextLayout ) ;
2016-05-05 17:23:54 -05:00
2017-01-17 20:06:45 -06:00
// in this case, the context is necessary to create the layout
// the layout takes a ref on the context so we can unref it afterward
2016-05-05 17:23:54 -05:00
context = mkGenericPangoCairoContext ( ) ;
2017-01-17 20:06:45 -06:00
tl - > layout = pango_layout_new ( context ) ;
2016-05-05 17:23:54 -05:00
g_object_unref ( context ) ;
2017-01-17 20:06:45 -06:00
// this is safe; pango_layout_set_text() copies the string
2017-02-11 20:25:41 -06:00
pango_layout_set_text ( tl - > layout , uiAttributedStringString ( p - > String ) , - 1 ) ;
2017-01-17 20:06:45 -06:00
desc = pango_font_description_new ( ) ;
2017-02-11 20:25:41 -06:00
pango_font_description_set_family ( desc , p - > DefaultFont - > Family ) ;
pango_font_description_set_style ( desc , pangoItalics [ p - > DefaultFont - > Italic ] ) ;
2017-01-17 20:06:45 -06:00
// for the most part, pango weights correlate to ours
// the differences:
// - Book — libui: 350, Pango: 380
// - Ultra Heavy — libui: 950, Pango: 1000
// TODO figure out what to do about this misalignment
2017-02-11 20:25:41 -06:00
pango_font_description_set_weight ( desc , p - > DefaultFont - > Weight ) ;
pango_font_description_set_stretch ( desc , pangoStretches [ p - > DefaultFont - > Stretch ] ) ;
2017-01-17 20:06:45 -06:00
// see https://developer.gnome.org/pango/1.30/pango-Fonts.html#pango-font-description-set-size and https://developer.gnome.org/pango/1.30/pango-Glyph-Storage.html#pango-units-from-double
2017-02-11 20:25:41 -06:00
pango_font_description_set_size ( desc , pango_units_from_double ( p - > DefaultFont - > Size ) ) ;
2017-01-17 20:06:45 -06:00
pango_layout_set_font_description ( tl - > layout , desc ) ;
// this is safe; the description is copied
pango_font_description_free ( desc ) ;
2016-05-05 17:23:54 -05:00
2017-02-11 20:25:41 -06:00
pangoWidth = cairoToPango ( p - > Width ) ;
if ( p - > Width < 0 )
2017-01-17 20:06:45 -06:00
pangoWidth = - 1 ;
pango_layout_set_width ( tl - > layout , pangoWidth ) ;
2016-05-05 17:23:54 -05:00
2017-02-11 20:25:41 -06:00
pango_layout_set_alignment ( tl - > layout , pangoAligns [ p - > Align ] ) ;
2017-02-20 21:24:02 -06:00
attrs = attrstrToPangoAttrList ( p , & ( tl - > backgroundClosures ) ) ;
2017-02-20 18:28:19 -06:00
pango_layout_set_attributes ( tl - > layout , attrs ) ;
pango_attr_list_unref ( attrs ) ;
2016-05-05 17:23:54 -05:00
2017-02-10 14:49:36 -06:00
tl - > nLines = pango_layout_get_line_count ( tl - > layout ) ;
2017-01-20 15:36:44 -06:00
computeLineMetrics ( tl ) ;
2017-01-17 20:06:45 -06:00
return tl ;
2016-05-05 17:23:54 -05:00
}
2017-01-17 20:06:45 -06:00
void uiDrawFreeTextLayout ( uiDrawTextLayout * tl )
2016-05-05 17:23:54 -05:00
{
2017-01-20 15:36:44 -06:00
uiFree ( tl - > lineMetrics ) ;
2017-02-20 21:24:02 -06:00
g_ptr_array_unref ( tl - > backgroundClosures ) ;
2017-01-17 20:06:45 -06:00
g_object_unref ( tl - > layout ) ;
uiFree ( tl ) ;
2016-05-05 17:23:54 -05:00
}
2017-01-17 20:06:45 -06:00
void uiDrawText ( uiDrawContext * c , uiDrawTextLayout * tl , double x , double y )
2016-05-05 17:23:54 -05:00
{
2017-02-20 21:24:02 -06:00
guint i ;
GClosure * closure ;
for ( i = 0 ; i < tl - > backgroundClosures - > len ; i + + ) {
closure = ( GClosure * ) g_ptr_array_index ( tl - > backgroundClosures , i ) ;
invokeBackgroundClosure ( closure , c , tl , x , y ) ;
}
// TODO have an implicit save/restore on each drawing functions instead? and is this correct?
cairo_set_source_rgb ( c - > cr , 0.0 , 0.0 , 0.0 ) ;
2017-01-17 20:06:45 -06:00
cairo_move_to ( c - > cr , x , y ) ;
pango_cairo_show_layout ( c - > cr , tl - > layout ) ;
2016-05-05 17:23:54 -05:00
}
2017-01-17 20:06:45 -06:00
void uiDrawTextLayoutExtents ( uiDrawTextLayout * tl , double * width , double * height )
2016-05-05 17:23:54 -05:00
{
2017-01-17 20:06:45 -06:00
PangoRectangle logical ;
2016-05-05 17:23:54 -05:00
2017-01-17 20:06:45 -06:00
pango_layout_get_extents ( tl - > layout , NULL , & logical ) ;
* width = pangoToCairo ( logical . width ) ;
* height = pangoToCairo ( logical . height ) ;
2016-05-05 17:23:54 -05:00
}
2017-01-17 20:06:45 -06:00
int uiDrawTextLayoutNumLines ( uiDrawTextLayout * tl )
2016-05-05 17:23:54 -05:00
{
2017-01-17 20:06:45 -06:00
return pango_layout_get_line_count ( tl - > layout ) ;
2016-05-05 17:23:54 -05:00
}
2017-01-17 20:06:45 -06:00
void uiDrawTextLayoutLineByteRange ( uiDrawTextLayout * tl , int line , size_t * start , size_t * end )
2016-05-05 17:23:54 -05:00
{
2017-01-17 20:06:45 -06:00
PangoLayoutLine * pll ;
pll = pango_layout_get_line_readonly ( tl - > layout , line ) ;
* start = pll - > start_index ;
* end = pll - > start_index + pll - > length ;
2017-01-20 15:36:44 -06:00
// TODO unref pll?
2016-05-05 17:23:54 -05:00
}
2017-01-17 20:06:45 -06:00
void uiDrawTextLayoutLineGetMetrics ( uiDrawTextLayout * tl , int line , uiDrawTextLayoutLineMetrics * m )
2016-05-05 17:23:54 -05:00
{
2017-01-20 15:36:44 -06:00
* m = tl - > lineMetrics [ line ] ;
2016-05-05 17:23:54 -05:00
}
2017-01-17 20:06:45 -06:00
// TODO
#if 0
2016-05-05 17:23:54 -05:00
{
2017-01-17 20:06:45 -06:00
PangoLayoutLine * pll ;
2016-05-05 17:23:54 -05:00
2017-01-17 20:06:45 -06:00
pll = pango_layout_get_line_readonly ( tl - > layout , line ) ;
// TODO unref?
2016-05-05 17:23:54 -05:00
}
2017-01-17 20:06:45 -06:00
# endif
2016-05-05 17:23:54 -05:00
2017-02-10 14:49:36 -06:00
// note: Pango will not let us place the cursor at the end of a line the same way other OSs do; see https://git.gnome.org/browse/pango/tree/pango/pango-layout.c?id=f4cbd27f4e5bf8490ea411190d41813e14f12165#n4204
// ideally there'd be a way to say "I don't need this hack; I'm well behaved" but GTK+ 2 and 3 AND Qt 4 and 5 all behave like this, with the behavior seeming to date back to TkTextView, so...
2017-02-08 20:19:49 -06:00
void uiDrawTextLayoutHitTest ( uiDrawTextLayout * tl , double x , double y , size_t * pos , int * line )
{
int p , trailing ;
2017-02-10 14:49:36 -06:00
int i ;
2017-02-08 20:19:49 -06:00
2017-02-24 13:25:16 -06:00
// this is layout-global, so it takes line origins into account
2017-02-08 20:19:49 -06:00
pango_layout_xy_to_index ( tl - > layout ,
cairoToPango ( x ) , cairoToPango ( y ) ,
& p , & trailing ) ;
// on a trailing hit, align to the nearest cluster
// fortunately Pango provides that info directly
if ( trailing ! = 0 )
p + = trailing ;
2017-02-10 14:49:36 -06:00
* pos = p ;
for ( i = 0 ; i < tl - > nLines ; i + + ) {
double ltop , lbottom ;
ltop = tl - > lineMetrics [ i ] . Y ;
lbottom = ltop + tl - > lineMetrics [ i ] . Height ;
// y will already >= ltop at this point since the past lbottom should == ltop
if ( y < lbottom )
break ;
2017-02-08 20:19:49 -06:00
}
2017-02-10 14:49:36 -06:00
if ( i = = pango_layout_get_line_count ( tl - > layout ) )
i - - ;
* line = i ;
2017-02-08 20:19:49 -06:00
}
double uiDrawTextLayoutByteLocationInLine ( uiDrawTextLayout * tl , size_t pos , int line )
{
PangoLayoutLine * pll ;
gboolean trailing ;
int pangox ;
2017-02-10 14:49:36 -06:00
if ( line < 0 | | line > = tl - > nLines )
return - 1 ;
2017-02-08 20:19:49 -06:00
pll = pango_layout_get_line_readonly ( tl - > layout , line ) ;
2017-02-10 14:49:36 -06:00
// note: >, not >=, because the position at end is valid!
if ( pos < pll - > start_index | | pos > ( pll - > start_index + pll - > length ) )
return - 1 ;
2017-02-08 20:19:49 -06:00
// this behavior seems correct
// there's also PadWrite's TextEditor::GetCaretRect() but that requires state...
// TODO where does this fail?
// TODO optimize everything to avoid calling strlen()
trailing = 0 ;
if ( pos ! = 0 & & pos ! = strlen ( pango_layout_get_text ( tl - > layout ) ) & & pos = = ( pll - > start_index + pll - > length ) ) {
pos - - ;
trailing = 1 ;
}
pango_layout_line_index_to_x ( pll , pos , trailing , & pangox ) ;
// TODO unref pll?
2017-02-24 13:25:16 -06:00
// this is relative to the beginning of the line
return pangoToCairo ( pangox ) + tl - > lineMetrics [ line ] . X ;
2017-02-08 20:19:49 -06:00
}
2017-02-10 15:53:08 -06:00
// note: we can't use gtk_render_insertion_cursor() because that doesn't take information about what line to render on
// we'll just recreate what it does
void caretDrawParams ( uiDrawContext * c , double height , struct caretDrawParams * p )
{
GdkColor * color ;
GdkRGBA rgba ;
gfloat aspectRatio ;
gint width , xoff ;
gtk_style_context_get_style ( c - > style ,
" cursor-color " , & color ,
" cursor-aspect-ratio " , & aspectRatio ,
NULL ) ;
if ( color ! = NULL ) {
p - > r = ( ( double ) ( color - > red ) ) / 65535.0 ;
p - > g = ( ( double ) ( color - > green ) ) / 65535.0 ;
p - > b = ( ( double ) ( color - > blue ) ) / 65535.0 ;
p - > a = 1.0 ;
gdk_color_free ( color ) ;
} else {
gtk_style_context_get_color ( c - > style , GTK_STATE_FLAG_NORMAL , & rgba ) ;
p - > r = rgba . red ;
p - > g = rgba . green ;
p - > b = rgba . blue ;
p - > a = rgba . alpha ;
}
// GTK+ itself uses integer arithmetic here; let's do the same
width = height * aspectRatio + 1 ;
// TODO this is for LTR
xoff = width / 2 ;
p - > xoff = xoff ;
p - > width = width ;
}
2017-02-11 13:45:58 -06:00
// TODO split this and the other font description stuff into their own file?
void fontdescFromPangoFontDescription ( PangoFontDescription * pdesc , uiDrawFontDescriptor * uidesc )
{
PangoStyle pitalic ;
PangoStretch pstretch ;
uidesc - > Family = uiUnixStrdupText ( pango_font_description_get_family ( pdesc ) ) ;
pitalic = pango_font_description_get_style ( pdesc ) ;
// TODO reverse the above misalignment if it is corrected
uidesc - > Weight = pango_font_description_get_weight ( pdesc ) ;
pstretch = pango_font_description_get_stretch ( pdesc ) ;
// absolute size does not matter because, as above, 1 device unit == 1 cairo point
uidesc - > Size = pango_units_to_double ( pango_font_description_get_size ( pdesc ) ) ;
for ( uidesc - > Italic = uiDrawTextItalicNormal ; uidesc - > Italic < uiDrawTextItalicItalic ; uidesc - > Italic + + )
if ( pangoItalics [ uidesc - > Italic ] = = pitalic )
break ;
for ( uidesc - > Stretch = uiDrawTextStretchUltraCondensed ; uidesc - > Stretch < uiDrawTextStretchUltraExpanded ; uidesc - > Stretch + + )
if ( pangoStretches [ uidesc - > Stretch ] = = pstretch )
break ;
}