Compare commits
	
		
			No commits in common. "9cd511a2e2164561ad3f3225e64e372706b0fc93" and "13cf0da8c4e7efbb966444cb7e0964c1a7e8466d" have entirely different histories.
		
	
	
		
			9cd511a2e2
			...
			13cf0da8c4
		
	
		
							
								
								
									
										2
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								NEWS
									
									
									
									
									
								
							| @ -7,7 +7,7 @@ Unreleased | ||||
| 
 | ||||
|  * Escape no longer quits the program | ||||
| 
 | ||||
|  * X11: added support for font fallbacks and italic fonts | ||||
|  * X11: added italic font support | ||||
| 
 | ||||
|  * X11: fixed rendering of overflowing, partially visible list items | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										307
									
								
								nncmpp.c
									
									
									
									
									
								
							
							
						
						
									
										307
									
								
								nncmpp.c
									
									
									
									
									
								
							| @ -1313,24 +1313,6 @@ enum player_state { PLAYER_STOPPED, PLAYER_PLAYING, PLAYER_PAUSED }; | ||||
| // around a pointer to this, hence it is a simple global variable as well.
 | ||||
| // There is enough global state as it is.
 | ||||
| 
 | ||||
| #ifdef WITH_X11 | ||||
| 
 | ||||
| /// Wraps Xft fonts into a linked list with fallbacks.
 | ||||
| struct x11_font_link | ||||
| { | ||||
| 	struct x11_font_link *next; | ||||
| 	XftFont *font; | ||||
| }; | ||||
| 
 | ||||
| struct x11_font | ||||
| { | ||||
| 	struct x11_font_link *list;         ///< Fonts of varying Unicode coverage
 | ||||
| 	FcPattern *pattern;                 ///< Original unsubstituted pattern
 | ||||
| 	FcCharSet *unavailable;             ///< Couldn't find a font for these
 | ||||
| }; | ||||
| 
 | ||||
| #endif  // WITH_X11
 | ||||
| 
 | ||||
| static struct app_context | ||||
| { | ||||
| 	// Event loop:
 | ||||
| @ -1413,9 +1395,9 @@ static struct app_context | ||||
| 	Region x11_clip;                    ///< Invalidated region
 | ||||
| 	Picture x11_pixmap_picture;         ///< XRender wrap for x11_pixmap
 | ||||
| 	XftDraw *xft_draw;                  ///< Xft rendering context
 | ||||
| 	struct x11_font xft_regular;        ///< Regular font
 | ||||
| 	struct x11_font xft_bold;           ///< Bold font
 | ||||
| 	struct x11_font xft_italic;         ///< Italic font
 | ||||
| 	XftFont *xft_regular;               ///< Regular font
 | ||||
| 	XftFont *xft_bold;                  ///< Bold font
 | ||||
| 	XftFont *xft_italic;                ///< Italic font
 | ||||
| 	char *x11_selection;                ///< CLIPBOARD selection
 | ||||
| 
 | ||||
| 	XRenderColor x_fg[ATTRIBUTE_COUNT]; ///< Foreground per attribute
 | ||||
| @ -2079,19 +2061,14 @@ app_layout_header (void) | ||||
| 		app_layout_text (header, APP_ATTR (HEADER)); | ||||
| } | ||||
| 
 | ||||
| static struct widget * | ||||
| app_widget_by_id (int id) | ||||
| { | ||||
| 	LIST_FOR_EACH (struct widget, w, g.widgets.head) | ||||
| 		if (w->id == id) | ||||
| 			return w; | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| app_visible_items_height (void) | ||||
| { | ||||
| 	struct widget *list = app_widget_by_id (WIDGET_LIST); | ||||
| 	struct widget *list = NULL; | ||||
| 	LIST_FOR_EACH (struct widget, w, g.widgets.head) | ||||
| 		if (w->id == WIDGET_LIST) | ||||
| 			list = w; | ||||
| 
 | ||||
| 	hard_assert (list != NULL); | ||||
| 
 | ||||
| 	// The raw number of items that would have fit on the terminal
 | ||||
| @ -2971,7 +2948,11 @@ app_process_mouse (termo_mouse_event_t type, int x, int y, int button, | ||||
| 		 && g.ui_dragging != WIDGET_SCROLLBAR) | ||||
| 			return true; | ||||
| 
 | ||||
| 		struct widget *target = app_widget_by_id (g.ui_dragging); | ||||
| 		struct widget *target = NULL; | ||||
| 		LIST_FOR_EACH (struct widget, w, g.widgets.head) | ||||
| 			if (w->id == g.ui_dragging) | ||||
| 				target = w; | ||||
| 
 | ||||
| 		x -= target->x; | ||||
| 		y -= target->y; | ||||
| 		return app_process_left_mouse_click (target, x, y, modifiers); | ||||
| @ -4843,7 +4824,10 @@ spectrum_redraw (void) | ||||
| { | ||||
| 	// A full refresh would be too computationally expensive,
 | ||||
| 	// let's hack around it in this case
 | ||||
| 	struct widget *spectrum = app_widget_by_id (WIDGET_SPECTRUM); | ||||
| 	struct widget *spectrum = NULL; | ||||
| 	LIST_FOR_EACH (struct widget, w, g.widgets.head) | ||||
| 		if (w->id == WIDGET_SPECTRUM) | ||||
| 			spectrum = w; | ||||
| 	if (spectrum) | ||||
| 		spectrum->on_render (spectrum); | ||||
| 
 | ||||
| @ -5918,190 +5902,14 @@ static XRenderColor x11_default_fg = { .alpha = 0xffff }; | ||||
| static XRenderColor x11_default_bg = { 0xffff, 0xffff, 0xffff, 0xffff }; | ||||
| static XErrorHandler x11_default_error_handler; | ||||
| 
 | ||||
| static struct x11_font_link * | ||||
| x11_font_link_new (XftFont *font) | ||||
| { | ||||
| 	struct x11_font_link *self = xcalloc (1, sizeof *self); | ||||
| 	self->font = font; | ||||
| 	return self; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| x11_font_link_destroy (struct x11_font_link *self) | ||||
| { | ||||
| 	XftFontClose (g.dpy, self->font); | ||||
| 	free (self); | ||||
| } | ||||
| 
 | ||||
| static struct x11_font_link * | ||||
| x11_font_link_open (FcPattern *pattern) | ||||
| { | ||||
| 	XftFont *font = XftFontOpenPattern (g.dpy, pattern); | ||||
| 	if (!font) | ||||
| 	{ | ||||
| 		FcPatternDestroy (pattern); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	return x11_font_link_new (font); | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| x11_font_open (struct x11_font *self, FcPattern *pattern) | ||||
| { | ||||
| 	FcPattern *substituted = FcPatternDuplicate (pattern); | ||||
| 	FcConfigSubstitute (NULL, substituted, FcMatchPattern); | ||||
| 
 | ||||
| 	FcResult result = 0; | ||||
| 	FcPattern *match | ||||
| 		= XftFontMatch (g.dpy, DefaultScreen (g.dpy), substituted, &result); | ||||
| 	FcPatternDestroy (substituted); | ||||
| 	if (!match || !(self->list = x11_font_link_open (match))) | ||||
| 	{ | ||||
| 		FcPatternDestroy (pattern); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	self->pattern = pattern; | ||||
| 	self->unavailable = FcCharSetCreate (); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| x11_font_free (struct x11_font *self) | ||||
| { | ||||
| 	FcPatternDestroy (self->pattern); | ||||
| 	FcCharSetDestroy (self->unavailable); | ||||
| 	LIST_FOR_EACH (struct x11_font_link, iter, self->list) | ||||
| 		x11_font_link_destroy (iter); | ||||
| } | ||||
| 
 | ||||
| /// Find or instantiate a font that can render the character given by cp.
 | ||||
| static XftFont * | ||||
| x11_font_cover_codepoint (struct x11_font *self, ucs4_t cp) | ||||
| { | ||||
| 	if (FcCharSetHasChar (self->unavailable, cp)) | ||||
| 		return self->list->font; | ||||
| 
 | ||||
| 	struct x11_font_link **used = &self->list; | ||||
| 	for (; *used; used = &(*used)->next) | ||||
| 		if (XftCharExists (g.dpy, (*used)->font, cp)) | ||||
| 			return (*used)->font; | ||||
| 
 | ||||
| 	FcCharSet *set = FcCharSetCreate (); | ||||
| 	FcCharSetAddChar (set, cp); | ||||
| 	FcPattern *needle = FcPatternDuplicate (self->pattern); | ||||
| 	FcPatternAddCharSet (needle, FC_CHARSET, set); | ||||
| 	FcConfigSubstitute (NULL, needle, FcMatchPattern); | ||||
| 
 | ||||
| 	FcResult result = 0; | ||||
| 	FcPattern *match | ||||
| 		= XftFontMatch (g.dpy, DefaultScreen (g.dpy), needle, &result); | ||||
| 	FcCharSetDestroy (set); | ||||
| 	FcPatternDestroy (needle); | ||||
| 	if (!match) | ||||
| 		goto fail; | ||||
| 
 | ||||
| 	struct x11_font_link *new = x11_font_link_open (match); | ||||
| 	if (!new) | ||||
| 		goto fail; | ||||
| 
 | ||||
| 	// The reverse may happen simply due to race conditions.
 | ||||
| 	if (XftCharExists (g.dpy, new->font, cp)) | ||||
| 		return (*used = new)->font; | ||||
| 
 | ||||
| 	x11_font_link_destroy (new); | ||||
| fail: | ||||
| 	FcCharSetAddChar (self->unavailable, cp); | ||||
| 	return self->list->font; | ||||
| } | ||||
| 
 | ||||
| // TODO: Perhaps produce an array of FT_UInt glyph indexes, mainly so that
 | ||||
| //   x11_font_{hadvance,draw,render}() can use the same data, through the use
 | ||||
| //   of a new function that collects the spans in a data structure.
 | ||||
| static size_t | ||||
| x11_font_span (struct x11_font *self, const uint8_t *text, XftFont **font) | ||||
| { | ||||
| 	hard_assert (self->list != NULL); | ||||
| 
 | ||||
| 	// Xft similarly just stops on invalid UTF-8.
 | ||||
| 	ucs4_t cp = 0; | ||||
| 	const uint8_t *p = text; | ||||
| 	if (!(p = u8_next (&cp, p))) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	*font = x11_font_cover_codepoint (self, cp); | ||||
| 	for (const uint8_t *end = NULL; (end = u8_next (&cp, p)); p = end) | ||||
| 	{ | ||||
| 		if (x11_font_cover_codepoint (self, cp) != *font) | ||||
| 			break; | ||||
| 	} | ||||
| 	return p - text; | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| x11_font_draw (struct x11_font *self, XftColor *color, int x, int y, | ||||
| 	const char *text) | ||||
| { | ||||
| 	int advance = 0; | ||||
| 	size_t len = 0; | ||||
| 	XftFont *font = NULL; | ||||
| 	while ((len = x11_font_span (self, (const uint8_t *) text, &font))) | ||||
| 	{ | ||||
| 		if (color) | ||||
| 		{ | ||||
| 			XftDrawStringUtf8 (g.xft_draw, color, font, | ||||
| 				x + advance, y + self->list->font->ascent, | ||||
| 				(const FcChar8 *) text, len); | ||||
| 		} | ||||
| 
 | ||||
| 		XGlyphInfo extents = {}; | ||||
| 		XftTextExtentsUtf8 (g.dpy, font, (const FcChar8 *) text, len, &extents); | ||||
| 		text += len; | ||||
| 		advance += extents.xOff; | ||||
| 	} | ||||
| 	return advance; | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| x11_font_hadvance (struct x11_font *self, const char *text) | ||||
| { | ||||
| 	return x11_font_draw (self, NULL, 0, 0, text); | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| x11_font_render (struct x11_font *self, int op, Picture src, int srcx, int srcy, | ||||
| 	int x, int y, const char *text) | ||||
| { | ||||
| 	int advance = 0; | ||||
| 	size_t len = 0; | ||||
| 	XftFont *font = NULL; | ||||
| 	while ((len = x11_font_span (self, (const uint8_t *) text, &font))) | ||||
| 	{ | ||||
| 		if (src) | ||||
| 		{ | ||||
| 			XftTextRenderUtf8 (g.dpy, op, src, font, g.x11_pixmap_picture, | ||||
| 				srcx, srcy, x + advance, y + self->list->font->ascent, | ||||
| 				(const FcChar8 *) text, len); | ||||
| 		} | ||||
| 
 | ||||
| 		XGlyphInfo extents = {}; | ||||
| 		XftTextExtentsUtf8 (g.dpy, font, (const FcChar8 *) text, len, &extents); | ||||
| 		text += len; | ||||
| 		advance += extents.xOff; | ||||
| 	} | ||||
| 	return advance; | ||||
| } | ||||
| 
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||
| 
 | ||||
| static struct x11_font * | ||||
| x11_widget_font (struct widget *self) | ||||
| x11_font (struct widget *self) | ||||
| { | ||||
| 	if (self->attrs & A_BOLD) | ||||
| 		return &g.xft_bold; | ||||
| 		return g.xft_bold; | ||||
| 	if (self->attrs & A_ITALIC) | ||||
| 		return &g.xft_italic; | ||||
| 	return &g.xft_regular; | ||||
| 		return g.xft_italic; | ||||
| 	return g.xft_regular; | ||||
| } | ||||
| 
 | ||||
| static XRenderColor * | ||||
| @ -6171,12 +5979,16 @@ x11_render_label (struct widget *self) | ||||
| 		return; | ||||
| 
 | ||||
| 	// TODO: Try to avoid re-measuring on each render.
 | ||||
| 	struct x11_font *font = x11_widget_font (self); | ||||
| 	int advance = x11_font_hadvance (font, self->text); | ||||
| 	if (advance <= space) | ||||
| 	XftFont *font = x11_font (self); | ||||
| 	XGlyphInfo extents = {}; | ||||
| 	XftTextExtentsUtf8 (g.dpy, font, | ||||
| 		(const FcChar8 *) self->text, strlen (self->text), &extents); | ||||
| 	if (extents.xOff <= space) | ||||
| 	{ | ||||
| 		XftColor color = { .color = *x11_fg (self) }; | ||||
| 		x11_font_draw (font, &color, self->x, self->y, self->text); | ||||
| 		XftDrawStringUtf8 (g.xft_draw, &color, font, | ||||
| 			self->x, self->y + font->ascent, | ||||
| 			(const FcChar8 *) self->text, strlen (self->text)); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| @ -6184,15 +5996,16 @@ x11_render_label (struct widget *self) | ||||
| 	XRenderColor solid = *x11_fg (self), colors[3] = { solid, solid, solid }; | ||||
| 	colors[2].alpha = 0; | ||||
| 
 | ||||
| 	double portion = MIN (1, 2.0 * font->list->font->height / space); | ||||
| 	double portion = MIN (1, 2.0 * font->height / space); | ||||
| 	XFixed stops[3] = { 0, XDoubleToFixed (1 - portion), XDoubleToFixed (1) }; | ||||
| 	XLinearGradient gradient = { {}, { XDoubleToFixed (space), 0 } }; | ||||
| 
 | ||||
| 	// Note that this masking is a very expensive operation.
 | ||||
| 	Picture source = | ||||
| 		XRenderCreateLinearGradient (g.dpy, &gradient, stops, colors, 3); | ||||
| 	x11_font_render (font, PictOpOver, source, -self->x, 0, self->x, self->y, | ||||
| 		self->text); | ||||
| 	XftTextRenderUtf8 (g.dpy, PictOpOver, source, font, g.x11_pixmap_picture, | ||||
| 		-self->x, 0, self->x, self->y + font->ascent, | ||||
| 		(const FcChar8 *) self->text, strlen (self->text)); | ||||
| 	XRenderFreePicture (g.dpy, source); | ||||
| } | ||||
| 
 | ||||
| @ -6200,7 +6013,6 @@ static struct widget * | ||||
| x11_make_label (chtype attrs, const char *label) | ||||
| { | ||||
| 	// Xft renders combining marks by themselves, NFC improves it a bit.
 | ||||
| 	// We'd have to use HarfBuzz to do this correctly.
 | ||||
| 	size_t label_len = strlen (label) + 1, normalized_len = 0; | ||||
| 	uint8_t *normalized = u8_normalize (UNINORM_NFC, | ||||
| 		(const uint8_t *) label, label_len, NULL, &normalized_len); | ||||
| @ -6214,11 +6026,13 @@ x11_make_label (chtype attrs, const char *label) | ||||
| 	w->on_render = x11_render_label; | ||||
| 	w->attrs = attrs; | ||||
| 	memcpy (w + 1, normalized, normalized_len); | ||||
| 	free (normalized); | ||||
| 
 | ||||
| 	struct x11_font *font = x11_widget_font (w); | ||||
| 	w->width = x11_font_hadvance (font, w->text); | ||||
| 	w->height = font->list->font->height; | ||||
| 	XftFont *font = x11_font (w); | ||||
| 	XGlyphInfo extents = {}; | ||||
| 	XftTextExtentsUtf8 (g.dpy, font, normalized, normalized_len - 1, &extents); | ||||
| 	w->width = extents.xOff; | ||||
| 	w->height = font->height; | ||||
| 	free (normalized); | ||||
| 	return w; | ||||
| } | ||||
| 
 | ||||
| @ -6501,7 +6315,7 @@ x11_render_editor (struct widget *self) | ||||
| { | ||||
| 	x11_render_padding (self); | ||||
| 
 | ||||
| 	XftFont *font = x11_widget_font (self)->list->font; | ||||
| 	XftFont *font = x11_font (self); | ||||
| 	XftColor color = { .color = *x11_fg (self) }; | ||||
| 
 | ||||
| 	// A simplistic adaptation of line_editor_write() follows.
 | ||||
| @ -6515,7 +6329,6 @@ x11_render_editor (struct widget *self) | ||||
| 		x += extents.xOff + g.ui_vunit / 4; | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: Adapt x11_font_{hadvance,draw}().
 | ||||
| 	// TODO: Make this scroll around the caret, and fade like labels.
 | ||||
| 	XftDrawString32 (g.xft_draw, &color, font, x, y, | ||||
| 		g.editor.line, g.editor.len); | ||||
| @ -6575,9 +6388,9 @@ x11_destroy (void) | ||||
| 	XRenderFreePicture (g.dpy, g.x11_pixmap_picture); | ||||
| 	XFreePixmap (g.dpy, g.x11_pixmap); | ||||
| 	XftDrawDestroy (g.xft_draw); | ||||
| 	x11_font_free (&g.xft_regular); | ||||
| 	x11_font_free (&g.xft_bold); | ||||
| 	x11_font_free (&g.xft_italic); | ||||
| 	XftFontClose (g.dpy, g.xft_regular); | ||||
| 	XftFontClose (g.dpy, g.xft_bold); | ||||
| 	XftFontClose (g.dpy, g.xft_italic); | ||||
| 	cstr_set (&g.x11_selection, NULL); | ||||
| 
 | ||||
| 	poller_fd_reset (&g.x11_event); | ||||
| @ -7077,9 +6890,8 @@ x11_init_fonts (void) | ||||
| 	//   as well as Net/DoubleClick*.  See the XSETTINGS proposal for details.
 | ||||
| 	//   https://www.freedesktop.org/wiki/Specifications/XSettingsRegistry/
 | ||||
| 	const char *name = get_config_string (g.config.root, "settings.x11_font"); | ||||
| 
 | ||||
| 	if (!FcInit ()) | ||||
| 		print_warning ("FontConfig initialization failed"); | ||||
| 	int screen = DefaultScreen (g.dpy); | ||||
| 	FcResult result = 0; | ||||
| 
 | ||||
| 	FcPattern *query_regular = FcNameParse ((const FcChar8 *) name); | ||||
| 	FcPattern *query_bold = FcPatternDuplicate (query_regular); | ||||
| @ -7089,12 +6901,29 @@ x11_init_fonts (void) | ||||
| 	FcPatternAdd (query_italic, FC_STYLE, (FcValue) { | ||||
| 		.type = FcTypeString, .u.s = (FcChar8 *) "Italic" }, FcFalse); | ||||
| 
 | ||||
| 	if (!x11_font_open (&g.xft_regular, query_regular)) | ||||
| 	FcPattern *regular = XftFontMatch (g.dpy, screen, query_regular, &result); | ||||
| 	FcPatternDestroy (query_regular); | ||||
| 	if (!regular) | ||||
| 		exit_fatal ("cannot open font: %s (%d)", name, result); | ||||
| 	if (!(g.xft_regular = XftFontOpenPattern (g.dpy, regular))) | ||||
| 	{ | ||||
| 		FcPatternDestroy (regular); | ||||
| 		exit_fatal ("cannot open font: %s", name); | ||||
| 	if (!x11_font_open (&g.xft_bold, query_bold)) | ||||
| 		exit_fatal ("cannot open bold font: %s", name); | ||||
| 	if (!x11_font_open (&g.xft_italic, query_italic)) | ||||
| 		exit_fatal ("cannot open italic font: %s", name); | ||||
| 	} | ||||
| 
 | ||||
| 	FcPattern *bold = XftFontMatch (g.dpy, screen, query_bold, &result); | ||||
| 	FcPatternDestroy (query_bold); | ||||
| 	if (bold && !(g.xft_bold = XftFontOpenPattern (g.dpy, bold))) | ||||
| 		FcPatternDestroy (bold); | ||||
| 	if (!g.xft_bold) | ||||
| 		g.xft_bold = XftFontCopy (g.dpy, g.xft_regular); | ||||
| 
 | ||||
| 	FcPattern *italic = XftFontMatch (g.dpy, screen, query_italic, &result); | ||||
| 	FcPatternDestroy (query_italic); | ||||
| 	if (italic && !(g.xft_italic = XftFontOpenPattern (g.dpy, italic))) | ||||
| 		FcPatternDestroy (italic); | ||||
| 	if (!g.xft_italic) | ||||
| 		g.xft_italic = XftFontCopy (g.dpy, g.xft_regular); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| @ -7151,7 +6980,7 @@ x11_init (void) | ||||
| 	}; | ||||
| 
 | ||||
| 	// Approximate the average width of a character to half of the em unit.
 | ||||
| 	g.ui_vunit = g.xft_regular.list->font->height; | ||||
| 	g.ui_vunit = g.xft_regular->height; | ||||
| 	g.ui_hunit = g.ui_vunit / 2; | ||||
| 	// Base the window's size on the regular font size.
 | ||||
| 	// Roughly trying to match the 80x24 default dimensions of terminals.
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user