Compare commits

...

10 commits

Author SHA1 Message Date
Peter Tillemans
e7d056748a add ligatures 2024-05-17 12:57:44 +02:00
Hiltjo Posthuma
68d1ad9b54 bump version to 0.9 2022-10-04 19:40:30 +02:00
Hiltjo Posthuma
0008519903 FAQ: document the color emojis crash issue which affected some systems is fixed
It is fixed in libXft 2.3.6:

https://gitlab.freedesktop.org/xorg/lib/libxft/-/blob/libXft-2.3.5/NEWS
2022-09-16 23:07:09 +02:00
Tom Schwindl
72fd32736a st: use `void' to indicate an empty parameter list 2022-08-18 17:14:10 +02:00
Hiltjo Posthuma
baa9357e96 Makefile: add manual path for OpenBSD 2022-05-01 18:38:40 +02:00
NRK
8629d9a1da code-golfing: cleanup osc color related code
* adds missing function prototype
* move xgetcolor() prototype to win.h (that's where all the other x.c
  func prototype seems to be declared at)
* check for snprintf error/truncation
* reduces code duplication for osc 10/11/12
* unify osc_color_response() and osc4_color_response() into a single function

the latter two was suggested by Quentin Rameau in his patch review on
the hackers list.
2022-04-19 11:43:37 +02:00
NRK
ef0551932f base64_digits: reduce scope, implicit zero, +1 size
the array is not accessed outside of base64dec() so it makes sense to
limit it's scope to the related function. the static-storage duration of
the array is kept intact.

this also removes unnecessary explicit zeroing from the start and end of
the array. anything that wasn't explicitly zero-ed will now be
implicitly zero-ed instead.

the validity of the new array can be easily confirmed via running this
trivial loop:

	for (int i = 0; i < 255; ++i)
		assert(base64_digits[i] == base64_digits_old[i]);

lastly, as pointed out by Roberto, the array needs to have 256 elements
in order to able access it as any unsigned char as an index; the
previous array had 255.

however, this array will only be accessed at indexes which are
isprint() || '=' (see `base64dec_getc()`), so reducing the size of the
array to the highest printable ascii char (127 AFAIK) + 1 might also be
a valid strategy.
2022-03-18 12:20:27 +01:00
NRK
af3bb68add avoid potential UB when using isprint()
all the ctype.h functions' argument must be representable as an unsigned
char or as EOF, otherwise the behavior is undefined.
2022-03-18 12:11:27 +01:00
Zacchary Dempsey-Plante
2aefa348ba make underlines and strikethroughs respect chscale 2022-03-13 10:45:34 +01:00
Santtu Lakkala
e823e2308f Delay redrawals on palette changes
Build on auto-sync and only mark window dirty on palette changes and let
the event handler do the actual draw.
2022-02-18 13:03:37 +01:00
14 changed files with 1497 additions and 208 deletions

2
.envrc Normal file
View file

@ -0,0 +1,2 @@
use guix
export CC="gcc -std=gnu99"

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.o
st

3
FAQ
View file

@ -248,3 +248,6 @@ fonts:
Please don't bother reporting this bug to st, but notify the upstream Xft Please don't bother reporting this bug to st, but notify the upstream Xft
developers about fixing this bug. developers about fixing this bug.
As of 2022-09-05 this now seems to be finally fixed in libXft 2.3.5:
https://gitlab.freedesktop.org/xorg/lib/libxft/-/blob/libXft-2.3.5/NEWS

View file

@ -4,7 +4,7 @@
include config.mk include config.mk
SRC = st.c x.c SRC = st.c x.c hb.c
OBJ = $(SRC:.c=.o) OBJ = $(SRC:.c=.o)
all: options st all: options st
@ -22,7 +22,8 @@ config.h:
$(CC) $(STCFLAGS) -c $< $(CC) $(STCFLAGS) -c $<
st.o: config.h st.h win.h st.o: config.h st.h win.h
x.o: arg.h config.h st.h win.h x.o: arg.h config.h st.h win.h hb.h
hb.o: st.h
$(OBJ): config.h config.mk $(OBJ): config.h config.mk

474
config.h Normal file
View file

@ -0,0 +1,474 @@
/* See LICENSE file for copyright and license details. */
/*
* appearance
*
* font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
*/
static char *font = "Fira Code:pixelsize=16:antialias=true:autohint=true";
static int borderpx = 2;
/*
* What program is execed by st depends of these precedence rules:
* 1: program passed with -e
* 2: scroll and/or utmp
* 3: SHELL environment variable
* 4: value of shell in /etc/passwd
* 5: value of shell in config.h
*/
static char *shell = "/bin/sh";
char *utmp = NULL;
/* scroll program: to enable use a string like "scroll" */
char *scroll = NULL;
char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400";
/* identification sequence returned in DA and DECID */
char *vtiden = "\033[?6c";
/* Kerning / character bounding-box multipliers */
static float cwscale = 1.0;
static float chscale = 1.0;
/*
* word delimiter string
*
* More advanced example: L" `'\"()[]{}"
*/
wchar_t *worddelimiters = L" ";
/* selection timeouts (in milliseconds) */
static unsigned int doubleclicktimeout = 300;
static unsigned int tripleclicktimeout = 600;
/* alt screens */
int allowaltscreen = 1;
/* allow certain non-interactive (insecure) window operations such as:
setting the clipboard text */
int allowwindowops = 0;
/*
* draw latency range in ms - from new content/keypress/etc until drawing.
* within this range, st draws when content stops arriving (idle). mostly it's
* near minlatency, but it waits longer for slow updates to avoid partial draw.
* low minlatency will tear/flicker more, as it can "detect" idle too early.
*/
static double minlatency = 2;
static double maxlatency = 33;
/*
* blinking timeout (set to 0 to disable blinking) for the terminal blinking
* attribute.
*/
static unsigned int blinktimeout = 800;
/*
* thickness of underline and bar cursors
*/
static unsigned int cursorthickness = 2;
/*
* bell volume. It must be a value between -100 and 100. Use 0 for disabling
* it
*/
static int bellvolume = 0;
/* default TERM value */
char *termname = "st-256color";
/*
* spaces per tab
*
* When you are changing this value, don't forget to adapt the »it« value in
* the st.info and appropriately install the st.info in the environment where
* you use this st version.
*
* it#$tabspaces,
*
* Secondly make sure your kernel is not expanding tabs. When running `stty
* -a` »tab0« should appear. You can tell the terminal to not expand tabs by
* running following command:
*
* stty tabs
*/
unsigned int tabspaces = 8;
/* Terminal colors (16 first used in escape sequence) */
static const char *colorname[] = {
/* 8 normal colors */
"black",
"red3",
"green3",
"yellow3",
"blue2",
"magenta3",
"cyan3",
"gray90",
/* 8 bright colors */
"gray50",
"red",
"green",
"yellow",
"#5c5cff",
"magenta",
"cyan",
"white",
[255] = 0,
/* more colors can be added after 255 to use with DefaultXX */
"#cccccc",
"#555555",
"gray90", /* default foreground colour */
"black", /* default background colour */
};
/*
* Default colors (colorname index)
* foreground, background, cursor, reverse cursor
*/
unsigned int defaultfg = 258;
unsigned int defaultbg = 259;
unsigned int defaultcs = 256;
static unsigned int defaultrcs = 257;
/*
* Default shape of cursor
* 2: Block ("")
* 4: Underline ("_")
* 6: Bar ("|")
* 7: Snowman ("")
*/
static unsigned int cursorshape = 2;
/*
* Default columns and rows numbers
*/
static unsigned int cols = 80;
static unsigned int rows = 24;
/*
* Default colour and shape of the mouse cursor
*/
static unsigned int mouseshape = XC_xterm;
static unsigned int mousefg = 7;
static unsigned int mousebg = 0;
/*
* Color used to display font attributes when fontconfig selected a font which
* doesn't match the ones requested.
*/
static unsigned int defaultattr = 11;
/*
* Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set).
* Note that if you want to use ShiftMask with selmasks, set this to an other
* modifier, set to 0 to not use it.
*/
static uint forcemousemod = ShiftMask;
/*
* Internal mouse shortcuts.
* Beware that overloading Button1 will disable the selection.
*/
static MouseShortcut mshortcuts[] = {
/* mask button function argument release */
{ XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 },
{ ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} },
{ XK_ANY_MOD, Button4, ttysend, {.s = "\031"} },
{ ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} },
{ XK_ANY_MOD, Button5, ttysend, {.s = "\005"} },
};
/* Internal keyboard shortcuts. */
#define MODKEY Mod1Mask
#define TERMMOD (ControlMask|ShiftMask)
static Shortcut shortcuts[] = {
/* mask keysym function argument */
{ XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} },
{ ControlMask, XK_Print, toggleprinter, {.i = 0} },
{ ShiftMask, XK_Print, printscreen, {.i = 0} },
{ XK_ANY_MOD, XK_Print, printsel, {.i = 0} },
{ TERMMOD, XK_Prior, zoom, {.f = +1} },
{ TERMMOD, XK_Next, zoom, {.f = -1} },
{ TERMMOD, XK_Home, zoomreset, {.f = 0} },
{ TERMMOD, XK_C, clipcopy, {.i = 0} },
{ TERMMOD, XK_V, clippaste, {.i = 0} },
{ TERMMOD, XK_Y, selpaste, {.i = 0} },
{ ShiftMask, XK_Insert, selpaste, {.i = 0} },
{ TERMMOD, XK_Num_Lock, numlock, {.i = 0} },
};
/*
* Special keys (change & recompile st.info accordingly)
*
* Mask value:
* * Use XK_ANY_MOD to match the key no matter modifiers state
* * Use XK_NO_MOD to match the key alone (no modifiers)
* appkey value:
* * 0: no value
* * > 0: keypad application mode enabled
* * = 2: term.numlock = 1
* * < 0: keypad application mode disabled
* appcursor value:
* * 0: no value
* * > 0: cursor application mode enabled
* * < 0: cursor application mode disabled
*
* Be careful with the order of the definitions because st searches in
* this table sequentially, so any XK_ANY_MOD must be in the last
* position for a key.
*/
/*
* If you want keys other than the X11 function keys (0xFD00 - 0xFFFF)
* to be mapped below, add them to this array.
*/
static KeySym mappedkeys[] = { -1 };
/*
* State bits to ignore when matching key or button events. By default,
* numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored.
*/
static uint ignoremod = Mod2Mask|XK_SWITCH_MOD;
/*
* This is the huge key array which defines all compatibility to the Linux
* world. Please decide about changes wisely.
*/
static Key key[] = {
/* keysym mask string appkey appcursor */
{ XK_KP_Home, ShiftMask, "\033[2J", 0, -1},
{ XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1},
{ XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1},
{ XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1},
{ XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0},
{ XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1},
{ XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1},
{ XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0},
{ XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1},
{ XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1},
{ XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0},
{ XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1},
{ XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1},
{ XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0},
{ XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1},
{ XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1},
{ XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0},
{ XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0},
{ XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0},
{ XK_KP_End, ControlMask, "\033[J", -1, 0},
{ XK_KP_End, ControlMask, "\033[1;5F", +1, 0},
{ XK_KP_End, ShiftMask, "\033[K", -1, 0},
{ XK_KP_End, ShiftMask, "\033[1;2F", +1, 0},
{ XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0},
{ XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0},
{ XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0},
{ XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0},
{ XK_KP_Insert, ShiftMask, "\033[4l", -1, 0},
{ XK_KP_Insert, ControlMask, "\033[L", -1, 0},
{ XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0},
{ XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0},
{ XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0},
{ XK_KP_Delete, ControlMask, "\033[M", -1, 0},
{ XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0},
{ XK_KP_Delete, ShiftMask, "\033[2K", -1, 0},
{ XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0},
{ XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0},
{ XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0},
{ XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0},
{ XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0},
{ XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0},
{ XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0},
{ XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0},
{ XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0},
{ XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0},
{ XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0},
{ XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0},
{ XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0},
{ XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0},
{ XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0},
{ XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0},
{ XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0},
{ XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0},
{ XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0},
{ XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0},
{ XK_Up, ShiftMask, "\033[1;2A", 0, 0},
{ XK_Up, Mod1Mask, "\033[1;3A", 0, 0},
{ XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0},
{ XK_Up, ControlMask, "\033[1;5A", 0, 0},
{ XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0},
{ XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0},
{ XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0},
{ XK_Up, XK_ANY_MOD, "\033[A", 0, -1},
{ XK_Up, XK_ANY_MOD, "\033OA", 0, +1},
{ XK_Down, ShiftMask, "\033[1;2B", 0, 0},
{ XK_Down, Mod1Mask, "\033[1;3B", 0, 0},
{ XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0},
{ XK_Down, ControlMask, "\033[1;5B", 0, 0},
{ XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0},
{ XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0},
{ XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0},
{ XK_Down, XK_ANY_MOD, "\033[B", 0, -1},
{ XK_Down, XK_ANY_MOD, "\033OB", 0, +1},
{ XK_Left, ShiftMask, "\033[1;2D", 0, 0},
{ XK_Left, Mod1Mask, "\033[1;3D", 0, 0},
{ XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0},
{ XK_Left, ControlMask, "\033[1;5D", 0, 0},
{ XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0},
{ XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0},
{ XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0},
{ XK_Left, XK_ANY_MOD, "\033[D", 0, -1},
{ XK_Left, XK_ANY_MOD, "\033OD", 0, +1},
{ XK_Right, ShiftMask, "\033[1;2C", 0, 0},
{ XK_Right, Mod1Mask, "\033[1;3C", 0, 0},
{ XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0},
{ XK_Right, ControlMask, "\033[1;5C", 0, 0},
{ XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0},
{ XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0},
{ XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0},
{ XK_Right, XK_ANY_MOD, "\033[C", 0, -1},
{ XK_Right, XK_ANY_MOD, "\033OC", 0, +1},
{ XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0},
{ XK_Return, Mod1Mask, "\033\r", 0, 0},
{ XK_Return, XK_ANY_MOD, "\r", 0, 0},
{ XK_Insert, ShiftMask, "\033[4l", -1, 0},
{ XK_Insert, ShiftMask, "\033[2;2~", +1, 0},
{ XK_Insert, ControlMask, "\033[L", -1, 0},
{ XK_Insert, ControlMask, "\033[2;5~", +1, 0},
{ XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0},
{ XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0},
{ XK_Delete, ControlMask, "\033[M", -1, 0},
{ XK_Delete, ControlMask, "\033[3;5~", +1, 0},
{ XK_Delete, ShiftMask, "\033[2K", -1, 0},
{ XK_Delete, ShiftMask, "\033[3;2~", +1, 0},
{ XK_Delete, XK_ANY_MOD, "\033[P", -1, 0},
{ XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0},
{ XK_BackSpace, XK_NO_MOD, "\177", 0, 0},
{ XK_BackSpace, Mod1Mask, "\033\177", 0, 0},
{ XK_Home, ShiftMask, "\033[2J", 0, -1},
{ XK_Home, ShiftMask, "\033[1;2H", 0, +1},
{ XK_Home, XK_ANY_MOD, "\033[H", 0, -1},
{ XK_Home, XK_ANY_MOD, "\033[1~", 0, +1},
{ XK_End, ControlMask, "\033[J", -1, 0},
{ XK_End, ControlMask, "\033[1;5F", +1, 0},
{ XK_End, ShiftMask, "\033[K", -1, 0},
{ XK_End, ShiftMask, "\033[1;2F", +1, 0},
{ XK_End, XK_ANY_MOD, "\033[4~", 0, 0},
{ XK_Prior, ControlMask, "\033[5;5~", 0, 0},
{ XK_Prior, ShiftMask, "\033[5;2~", 0, 0},
{ XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0},
{ XK_Next, ControlMask, "\033[6;5~", 0, 0},
{ XK_Next, ShiftMask, "\033[6;2~", 0, 0},
{ XK_Next, XK_ANY_MOD, "\033[6~", 0, 0},
{ XK_F1, XK_NO_MOD, "\033OP" , 0, 0},
{ XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0},
{ XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0},
{ XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0},
{ XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0},
{ XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0},
{ XK_F2, XK_NO_MOD, "\033OQ" , 0, 0},
{ XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0},
{ XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0},
{ XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0},
{ XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0},
{ XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0},
{ XK_F3, XK_NO_MOD, "\033OR" , 0, 0},
{ XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0},
{ XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0},
{ XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0},
{ XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0},
{ XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0},
{ XK_F4, XK_NO_MOD, "\033OS" , 0, 0},
{ XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0},
{ XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0},
{ XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0},
{ XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0},
{ XK_F5, XK_NO_MOD, "\033[15~", 0, 0},
{ XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0},
{ XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0},
{ XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0},
{ XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0},
{ XK_F6, XK_NO_MOD, "\033[17~", 0, 0},
{ XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0},
{ XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0},
{ XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0},
{ XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0},
{ XK_F7, XK_NO_MOD, "\033[18~", 0, 0},
{ XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0},
{ XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0},
{ XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0},
{ XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0},
{ XK_F8, XK_NO_MOD, "\033[19~", 0, 0},
{ XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0},
{ XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0},
{ XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0},
{ XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0},
{ XK_F9, XK_NO_MOD, "\033[20~", 0, 0},
{ XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0},
{ XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0},
{ XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0},
{ XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0},
{ XK_F10, XK_NO_MOD, "\033[21~", 0, 0},
{ XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0},
{ XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0},
{ XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0},
{ XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0},
{ XK_F11, XK_NO_MOD, "\033[23~", 0, 0},
{ XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0},
{ XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0},
{ XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0},
{ XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0},
{ XK_F12, XK_NO_MOD, "\033[24~", 0, 0},
{ XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0},
{ XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0},
{ XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0},
{ XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0},
{ XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0},
{ XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0},
{ XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0},
{ XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0},
{ XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0},
{ XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0},
{ XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0},
{ XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0},
{ XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0},
{ XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0},
{ XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0},
{ XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0},
{ XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0},
{ XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0},
{ XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0},
{ XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0},
{ XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0},
{ XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0},
{ XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0},
{ XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0},
{ XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0},
{ XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0},
{ XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0},
};
/*
* Selection types' masks.
* Use the same masks as usual.
* Button1Mask is always unset, to make masks match between ButtonPress.
* ButtonRelease and MotionNotify.
* If no match is found, regular selection is used.
*/
static uint selmasks[] = {
[SEL_RECTANGULAR] = Mod1Mask,
};
/*
* Printable characters in ASCII, used to estimate the advance width
* of single wide characters.
*/
static char ascii_printable[] =
" !\"#$%&'()*+,-./0123456789:;<=>?"
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
"`abcdefghijklmnopqrstuvwxyz{|}~";

View file

@ -1,10 +1,10 @@
# st version # st version
VERSION = 0.8.5 VERSION = 0.9
# Customize below to fit your system # Customize below to fit your system
# paths # paths
PREFIX = /usr/local PREFIX = /home/pti/.local
MANPREFIX = $(PREFIX)/share/man MANPREFIX = $(PREFIX)/share/man
X11INC = /usr/X11R6/include X11INC = /usr/X11R6/include
@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config
# includes and libs # includes and libs
INCS = -I$(X11INC) \ INCS = -I$(X11INC) \
`$(PKG_CONFIG) --cflags fontconfig` \ `$(PKG_CONFIG) --cflags fontconfig` \
`$(PKG_CONFIG) --cflags freetype2` `$(PKG_CONFIG) --cflags freetype2` \
`$(PKG_CONFIG) --cflags harfbuzz`
LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \
`$(PKG_CONFIG) --libs fontconfig` \ `$(PKG_CONFIG) --libs fontconfig` \
`$(PKG_CONFIG) --libs freetype2` `$(PKG_CONFIG) --libs freetype2` \
`$(PKG_CONFIG) --libs harfbuzz`
# flags # flags
STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
@ -30,6 +32,7 @@ STLDFLAGS = $(LIBS) $(LDFLAGS)
#LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \
# `$(PKG_CONFIG) --libs fontconfig` \ # `$(PKG_CONFIG) --libs fontconfig` \
# `$(PKG_CONFIG) --libs freetype2` # `$(PKG_CONFIG) --libs freetype2`
#MANPREFIX = ${PREFIX}/man
# compiler and linker # compiler and linker
# CC = c99 # CC = c99

125
hb.c Normal file
View file

@ -0,0 +1,125 @@
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <X11/Xft/Xft.h>
#include <X11/cursorfont.h>
#include <hb.h>
#include <hb-ft.h>
#include "st.h"
#include "hb.h"
#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END }
#define BUFFER_STEP 256
hb_font_t *hbfindfont(XftFont *match);
typedef struct {
XftFont *match;
hb_font_t *font;
} HbFontMatch;
typedef struct {
size_t capacity;
HbFontMatch *fonts;
} HbFontCache;
static HbFontCache hbfontcache = { 0, NULL };
typedef struct {
size_t capacity;
Rune *runes;
} RuneBuffer;
static RuneBuffer hbrunebuffer = { 0, NULL };
/*
* Poplulate the array with a list of font features, wrapped in FEATURE macro,
* e. g.
* FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g')
*/
hb_feature_t features[] = { };
void
hbunloadfonts()
{
for (int i = 0; i < hbfontcache.capacity; i++) {
hb_font_destroy(hbfontcache.fonts[i].font);
XftUnlockFace(hbfontcache.fonts[i].match);
}
if (hbfontcache.fonts != NULL) {
free(hbfontcache.fonts);
hbfontcache.fonts = NULL;
}
hbfontcache.capacity = 0;
}
hb_font_t *
hbfindfont(XftFont *match)
{
for (int i = 0; i < hbfontcache.capacity; i++) {
if (hbfontcache.fonts[i].match == match)
return hbfontcache.fonts[i].font;
}
/* Font not found in cache, caching it now. */
hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1));
FT_Face face = XftLockFace(match);
hb_font_t *font = hb_ft_font_create(face, NULL);
if (font == NULL)
die("Failed to load Harfbuzz font.");
hbfontcache.fonts[hbfontcache.capacity].match = match;
hbfontcache.fonts[hbfontcache.capacity].font = font;
hbfontcache.capacity += 1;
return font;
}
void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) {
ushort mode = USHRT_MAX;
unsigned int glyph_count;
int rune_idx, glyph_idx, end = start + length;
hb_font_t *font = hbfindfont(xfont);
if (font == NULL)
return;
hb_buffer_t *buffer = hb_buffer_create();
hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
/* Resize the buffer if required length is larger. */
if (hbrunebuffer.capacity < length) {
hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP;
hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune));
}
/* Fill buffer with codepoints. */
for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) {
hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u;
mode = glyphs[glyph_idx].mode;
if (mode & ATTR_WDUMMY)
hbrunebuffer.runes[rune_idx] = 0x0020;
}
hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length);
/* Shape the segment. */
hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t));
/* Get new glyph info. */
hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count);
hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count);
/* Fill the output. */
data->buffer = buffer;
data->glyphs = info;
data->positions = pos;
data->count = glyph_count;
}
void hbcleanup(HbTransformData *data) {
hb_buffer_destroy(data->buffer);
memset(data, 0, sizeof(HbTransformData));
}

14
hb.h Normal file
View file

@ -0,0 +1,14 @@
#include <X11/Xft/Xft.h>
#include <hb.h>
#include <hb-ft.h>
typedef struct {
hb_buffer_t *buffer;
hb_glyph_info_t *glyphs;
hb_glyph_position_t *positions;
unsigned int count;
} HbTransformData;
void hbunloadfonts();
void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int);
void hbcleanup(HbTransformData *);

13
manifest.scm Normal file
View file

@ -0,0 +1,13 @@
;; What follows is a "manifest" equivalent to the command line you gave.
;; You can store it in a file that you may then pass to any 'guix' command
;; that accepts a '--manifest' (or '-m') option.
(specifications->manifest
(list "gcc-toolchain"
"pkg-config"
"patch"
"libx11"
"libxft"
"fontconfig"
"freetype"
"harfbuzz"))

View file

@ -0,0 +1,633 @@
diff --git a/Makefile b/Makefile
index 470ac86..38240da 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
include config.mk
-SRC = st.c x.c
+SRC = st.c x.c hb.c
OBJ = $(SRC:.c=.o)
all: options st
@@ -22,7 +22,8 @@ config.h:
$(CC) $(STCFLAGS) -c $<
st.o: config.h st.h win.h
-x.o: arg.h config.h st.h win.h
+x.o: arg.h config.h st.h win.h hb.h
+hb.o: st.h
$(OBJ): config.h config.mk
diff --git a/config.mk b/config.mk
index 1e306f8..3e13e53 100644
--- a/config.mk
+++ b/config.mk
@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config
# includes and libs
INCS = -I$(X11INC) \
`$(PKG_CONFIG) --cflags fontconfig` \
- `$(PKG_CONFIG) --cflags freetype2`
+ `$(PKG_CONFIG) --cflags freetype2` \
+ `$(PKG_CONFIG) --cflags harfbuzz`
LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \
`$(PKG_CONFIG) --libs fontconfig` \
- `$(PKG_CONFIG) --libs freetype2`
+ `$(PKG_CONFIG) --libs freetype2` \
+ `$(PKG_CONFIG) --libs harfbuzz`
# flags
STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
diff --git a/hb.c b/hb.c
new file mode 100644
index 0000000..99412c8
--- /dev/null
+++ b/hb.c
@@ -0,0 +1,125 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <X11/Xft/Xft.h>
+#include <X11/cursorfont.h>
+#include <hb.h>
+#include <hb-ft.h>
+
+#include "st.h"
+#include "hb.h"
+
+#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END }
+#define BUFFER_STEP 256
+
+hb_font_t *hbfindfont(XftFont *match);
+
+typedef struct {
+ XftFont *match;
+ hb_font_t *font;
+} HbFontMatch;
+
+typedef struct {
+ size_t capacity;
+ HbFontMatch *fonts;
+} HbFontCache;
+
+static HbFontCache hbfontcache = { 0, NULL };
+
+typedef struct {
+ size_t capacity;
+ Rune *runes;
+} RuneBuffer;
+
+static RuneBuffer hbrunebuffer = { 0, NULL };
+
+/*
+ * Poplulate the array with a list of font features, wrapped in FEATURE macro,
+ * e. g.
+ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g')
+ */
+hb_feature_t features[] = { };
+
+void
+hbunloadfonts()
+{
+ for (int i = 0; i < hbfontcache.capacity; i++) {
+ hb_font_destroy(hbfontcache.fonts[i].font);
+ XftUnlockFace(hbfontcache.fonts[i].match);
+ }
+
+ if (hbfontcache.fonts != NULL) {
+ free(hbfontcache.fonts);
+ hbfontcache.fonts = NULL;
+ }
+ hbfontcache.capacity = 0;
+}
+
+hb_font_t *
+hbfindfont(XftFont *match)
+{
+ for (int i = 0; i < hbfontcache.capacity; i++) {
+ if (hbfontcache.fonts[i].match == match)
+ return hbfontcache.fonts[i].font;
+ }
+
+ /* Font not found in cache, caching it now. */
+ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1));
+ FT_Face face = XftLockFace(match);
+ hb_font_t *font = hb_ft_font_create(face, NULL);
+ if (font == NULL)
+ die("Failed to load Harfbuzz font.");
+
+ hbfontcache.fonts[hbfontcache.capacity].match = match;
+ hbfontcache.fonts[hbfontcache.capacity].font = font;
+ hbfontcache.capacity += 1;
+
+ return font;
+}
+
+void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) {
+ ushort mode = USHRT_MAX;
+ unsigned int glyph_count;
+ int rune_idx, glyph_idx, end = start + length;
+
+ hb_font_t *font = hbfindfont(xfont);
+ if (font == NULL)
+ return;
+
+ hb_buffer_t *buffer = hb_buffer_create();
+ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
+ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
+
+ /* Resize the buffer if required length is larger. */
+ if (hbrunebuffer.capacity < length) {
+ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP;
+ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune));
+ }
+
+ /* Fill buffer with codepoints. */
+ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) {
+ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u;
+ mode = glyphs[glyph_idx].mode;
+ if (mode & ATTR_WDUMMY)
+ hbrunebuffer.runes[rune_idx] = 0x0020;
+ }
+ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length);
+
+ /* Shape the segment. */
+ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t));
+
+ /* Get new glyph info. */
+ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count);
+ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count);
+
+ /* Fill the output. */
+ data->buffer = buffer;
+ data->glyphs = info;
+ data->positions = pos;
+ data->count = glyph_count;
+}
+
+void hbcleanup(HbTransformData *data) {
+ hb_buffer_destroy(data->buffer);
+ memset(data, 0, sizeof(HbTransformData));
+}
diff --git a/hb.h b/hb.h
new file mode 100644
index 0000000..3b0ef44
--- /dev/null
+++ b/hb.h
@@ -0,0 +1,14 @@
+#include <X11/Xft/Xft.h>
+#include <hb.h>
+#include <hb-ft.h>
+
+typedef struct {
+ hb_buffer_t *buffer;
+ hb_glyph_info_t *glyphs;
+ hb_glyph_position_t *positions;
+ unsigned int count;
+} HbTransformData;
+
+void hbunloadfonts();
+void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int);
+void hbcleanup(HbTransformData *);
diff --git a/st.c b/st.c
index 62def59..041c6d8 100644
--- a/st.c
+++ b/st.c
@@ -2640,7 +2640,8 @@ draw(void)
drawregion(0, 0, term.col, term.row);
xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
- term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
+ term.ocx, term.ocy, term.line[term.ocy][term.ocx],
+ term.line[term.ocy], term.col);
term.ocx = cx;
term.ocy = term.c.y;
xfinishdraw();
diff --git a/st.h b/st.h
index fd3b0d8..142fdfe 100644
--- a/st.h
+++ b/st.h
@@ -11,7 +11,8 @@
#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
#define DEFAULT(a, b) (a) = (a) ? (a) : (b)
#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
-#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \
+#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \
+ (a).fg != (b).fg || \
(a).bg != (b).bg)
#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
(t1.tv_nsec-t2.tv_nsec)/1E6)
diff --git a/win.h b/win.h
index 6de960d..94679e4 100644
--- a/win.h
+++ b/win.h
@@ -25,7 +25,7 @@ enum win_mode {
void xbell(void);
void xclipcopy(void);
-void xdrawcursor(int, int, Glyph, int, int, Glyph);
+void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
void xdrawline(Line, int, int, int);
void xfinishdraw(void);
void xloadcols(void);
diff --git a/x.c b/x.c
index 2a3bd38..66605ae 100644
--- a/x.c
+++ b/x.c
@@ -19,6 +19,7 @@ char *argv0;
#include "arg.h"
#include "st.h"
#include "win.h"
+#include "hb.h"
/* types used in config.h */
typedef struct {
@@ -141,8 +142,9 @@ typedef struct {
} DC;
static inline ushort sixd_to_16bit(int);
+static void xresetfontsettings(ushort mode, Font **font, int *frcflags);
static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
-static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
+static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
static void xdrawglyph(Glyph, int, int);
static void xclear(int, int, int, int);
static int xgeommasktogravity(int);
@@ -757,7 +759,7 @@ xresize(int col, int row)
xclear(0, 0, win.w, win.h);
/* resize to new width */
- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
+ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
}
ushort
@@ -1062,6 +1064,9 @@ xunloadfont(Font *f)
void
xunloadfonts(void)
{
+ /* Clear Harfbuzz font cache. */
+ hbunloadfonts();
+
/* Free the loaded fonts in the font cache. */
while (frclen > 0)
XftFontClose(xw.dpy, frc[--frclen].font);
@@ -1185,7 +1190,7 @@ xinit(int cols, int rows)
XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
/* font spec buffer */
- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
+ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
/* Xft rendering context */
xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
@@ -1239,6 +1244,22 @@ xinit(int cols, int rows)
xsel.xtarget = XA_STRING;
}
+void
+xresetfontsettings(ushort mode, Font **font, int *frcflags)
+{
+ *font = &dc.font;
+ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
+ *font = &dc.ibfont;
+ *frcflags = FRC_ITALICBOLD;
+ } else if (mode & ATTR_ITALIC) {
+ *font = &dc.ifont;
+ *frcflags = FRC_ITALIC;
+ } else if (mode & ATTR_BOLD) {
+ *font = &dc.bfont;
+ *frcflags = FRC_BOLD;
+ }
+}
+
int
xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
{
@@ -1253,128 +1274,156 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
FcPattern *fcpattern, *fontpattern;
FcFontSet *fcsets[] = { NULL };
FcCharSet *fccharset;
- int i, f, numspecs = 0;
+ int i, f, length = 0, start = 0, numspecs = 0;
+ float cluster_xp = xp, cluster_yp = yp;
+ HbTransformData shaped = { 0 };
+
+ /* Initial values. */
+ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP;
+ xresetfontsettings(mode, &font, &frcflags);
for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
- /* Fetch rune and mode for current glyph. */
- rune = glyphs[i].u;
- mode = glyphs[i].mode;
+ mode = glyphs[i].mode & ~ATTR_WRAP;
/* Skip dummy wide-character spacing. */
- if (mode == ATTR_WDUMMY)
+ if (mode & ATTR_WDUMMY && i < (len - 1))
continue;
- /* Determine font for glyph if different from previous glyph. */
- if (prevmode != mode) {
- prevmode = mode;
- font = &dc.font;
- frcflags = FRC_NORMAL;
- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
- font = &dc.ibfont;
- frcflags = FRC_ITALICBOLD;
- } else if (mode & ATTR_ITALIC) {
- font = &dc.ifont;
- frcflags = FRC_ITALIC;
- } else if (mode & ATTR_BOLD) {
- font = &dc.bfont;
- frcflags = FRC_BOLD;
+ if (
+ prevmode != mode
+ || ATTRCMP(glyphs[start], glyphs[i])
+ || selected(x + i, y) != selected(x + start, y)
+ || i == (len - 1)
+ ) {
+ /* Handle 1-character wide segments and end of line */
+ length = i - start;
+ if (i == start) {
+ length = 1;
+ } else if (i == (len - 1)) {
+ length = (i - start + 1);
}
- yp = winy + font->ascent;
- }
- /* Lookup character index with default font. */
- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
- if (glyphidx) {
- specs[numspecs].font = font->match;
- specs[numspecs].glyph = glyphidx;
- specs[numspecs].x = (short)xp;
- specs[numspecs].y = (short)yp;
- xp += runewidth;
- numspecs++;
- continue;
- }
-
- /* Fallback on font cache, search the font cache for match. */
- for (f = 0; f < frclen; f++) {
- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
- /* Everything correct. */
- if (glyphidx && frc[f].flags == frcflags)
- break;
- /* We got a default font for a not found glyph. */
- if (!glyphidx && frc[f].flags == frcflags
- && frc[f].unicodep == rune) {
- break;
+ /* Shape the segment. */
+ hbtransform(&shaped, font->match, glyphs, start, length);
+ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f);
+ cluster_xp = xp; cluster_yp = yp;
+ for (int code_idx = 0; code_idx < shaped.count; code_idx++) {
+ int idx = shaped.glyphs[code_idx].cluster;
+
+ if (glyphs[start + idx].mode & ATTR_WDUMMY)
+ continue;
+
+ /* Advance the drawing cursor if we've moved to a new cluster */
+ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
+ xp += runewidth;
+ cluster_xp = xp;
+ cluster_yp = yp;
+ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f);
+ }
+
+ if (shaped.glyphs[code_idx].codepoint != 0) {
+ /* If symbol is found, put it into the specs. */
+ specs[numspecs].font = font->match;
+ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint;
+ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
+ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
+ cluster_xp += shaped.positions[code_idx].x_advance / 64.;
+ cluster_yp += shaped.positions[code_idx].y_advance / 64.;
+ numspecs++;
+ } else {
+ /* If it's not found, try to fetch it through the font cache. */
+ rune = glyphs[start + idx].u;
+ for (f = 0; f < frclen; f++) {
+ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
+ /* Everything correct. */
+ if (glyphidx && frc[f].flags == frcflags)
+ break;
+ /* We got a default font for a not found glyph. */
+ if (!glyphidx && frc[f].flags == frcflags
+ && frc[f].unicodep == rune) {
+ break;
+ }
+ }
+
+ /* Nothing was found. Use fontconfig to find matching font. */
+ if (f >= frclen) {
+ if (!font->set)
+ font->set = FcFontSort(0, font->pattern,
+ 1, 0, &fcres);
+ fcsets[0] = font->set;
+
+ /*
+ * Nothing was found in the cache. Now use
+ * some dozen of Fontconfig calls to get the
+ * font for one single character.
+ *
+ * Xft and fontconfig are design failures.
+ */
+ fcpattern = FcPatternDuplicate(font->pattern);
+ fccharset = FcCharSetCreate();
+
+ FcCharSetAddChar(fccharset, rune);
+ FcPatternAddCharSet(fcpattern, FC_CHARSET,
+ fccharset);
+ FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
+
+ FcConfigSubstitute(0, fcpattern,
+ FcMatchPattern);
+ FcDefaultSubstitute(fcpattern);
+
+ fontpattern = FcFontSetMatch(0, fcsets, 1,
+ fcpattern, &fcres);
+
+ /* Allocate memory for the new cache entry. */
+ if (frclen >= frccap) {
+ frccap += 16;
+ frc = xrealloc(frc, frccap * sizeof(Fontcache));
+ }
+
+ frc[frclen].font = XftFontOpenPattern(xw.dpy,
+ fontpattern);
+ if (!frc[frclen].font)
+ die("XftFontOpenPattern failed seeking fallback font: %s\n",
+ strerror(errno));
+ frc[frclen].flags = frcflags;
+ frc[frclen].unicodep = rune;
+
+ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
+
+ f = frclen;
+ frclen++;
+
+ FcPatternDestroy(fcpattern);
+ FcCharSetDestroy(fccharset);
+ }
+
+ specs[numspecs].font = frc[f].font;
+ specs[numspecs].glyph = glyphidx;
+ specs[numspecs].x = (short)xp;
+ specs[numspecs].y = (short)yp;
+ numspecs++;
+ }
}
- }
- /* Nothing was found. Use fontconfig to find matching font. */
- if (f >= frclen) {
- if (!font->set)
- font->set = FcFontSort(0, font->pattern,
- 1, 0, &fcres);
- fcsets[0] = font->set;
+ /* Cleanup and get ready for next segment. */
+ hbcleanup(&shaped);
+ start = i;
- /*
- * Nothing was found in the cache. Now use
- * some dozen of Fontconfig calls to get the
- * font for one single character.
- *
- * Xft and fontconfig are design failures.
- */
- fcpattern = FcPatternDuplicate(font->pattern);
- fccharset = FcCharSetCreate();
-
- FcCharSetAddChar(fccharset, rune);
- FcPatternAddCharSet(fcpattern, FC_CHARSET,
- fccharset);
- FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
-
- FcConfigSubstitute(0, fcpattern,
- FcMatchPattern);
- FcDefaultSubstitute(fcpattern);
-
- fontpattern = FcFontSetMatch(0, fcsets, 1,
- fcpattern, &fcres);
-
- /* Allocate memory for the new cache entry. */
- if (frclen >= frccap) {
- frccap += 16;
- frc = xrealloc(frc, frccap * sizeof(Fontcache));
+ /* Determine font for glyph if different from previous glyph. */
+ if (prevmode != mode) {
+ prevmode = mode;
+ xresetfontsettings(mode, &font, &frcflags);
+ yp = winy + font->ascent;
}
-
- frc[frclen].font = XftFontOpenPattern(xw.dpy,
- fontpattern);
- if (!frc[frclen].font)
- die("XftFontOpenPattern failed seeking fallback font: %s\n",
- strerror(errno));
- frc[frclen].flags = frcflags;
- frc[frclen].unicodep = rune;
-
- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
-
- f = frclen;
- frclen++;
-
- FcPatternDestroy(fcpattern);
- FcCharSetDestroy(fccharset);
}
-
- specs[numspecs].font = frc[f].font;
- specs[numspecs].glyph = glyphidx;
- specs[numspecs].x = (short)xp;
- specs[numspecs].y = (short)yp;
- xp += runewidth;
- numspecs++;
}
return numspecs;
}
void
-xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
+xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen)
{
- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
width = charlen * win.cw;
Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
@@ -1510,21 +1559,24 @@ void
xdrawglyph(Glyph g, int x, int y)
{
int numspecs;
- XftGlyphFontSpec spec;
+ XftGlyphFontSpec *specs = xw.specbuf;
- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
- xdrawglyphfontspecs(&spec, g, numspecs, x, y);
+ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y);
+ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1);
}
void
-xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
+xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
{
Color drawcol;
/* remove the old cursor */
if (selected(ox, oy))
og.mode ^= ATTR_REVERSE;
- xdrawglyph(og, ox, oy);
+
+ /* Redraw the line where cursor was previously.
+ * It will restore the ligatures broken by the cursor. */
+ xdrawline(line, 0, oy, len);
if (IS_SET(MODE_HIDE))
return;
@@ -1652,18 +1704,16 @@ xdrawline(Line line, int x1, int y1, int x2)
Glyph base, new;
XftGlyphFontSpec *specs = xw.specbuf;
- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
i = ox = 0;
- for (x = x1; x < x2 && i < numspecs; x++) {
+ for (x = x1; x < x2; x++) {
new = line[x];
if (new.mode == ATTR_WDUMMY)
continue;
if (selected(x, y1))
new.mode ^= ATTR_REVERSE;
- if (i > 0 && ATTRCMP(base, new)) {
- xdrawglyphfontspecs(specs, base, i, ox, y1);
- specs += i;
- numspecs -= i;
+ if ((i > 0) && ATTRCMP(base, new)) {
+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox);
i = 0;
}
if (i == 0) {
@@ -1672,8 +1722,10 @@ xdrawline(Line line, int x1, int y1, int x2)
}
i++;
}
- if (i > 0)
- xdrawglyphfontspecs(specs, base, i, ox, y1);
+ if (i > 0) {
+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox);
+ }
}
void

121
st.c
View file

@ -161,6 +161,7 @@ static void csidump(void);
static void csihandle(void); static void csihandle(void);
static void csiparse(void); static void csiparse(void);
static void csireset(void); static void csireset(void);
static void osc_color_response(int, int, int);
static int eschandle(uchar); static int eschandle(uchar);
static void strdump(void); static void strdump(void);
static void strhandle(void); static void strhandle(void);
@ -349,25 +350,10 @@ utf8validate(Rune *u, size_t i)
return i; return i;
} }
static const char base64_digits[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
char char
base64dec_getc(const char **src) base64dec_getc(const char **src)
{ {
while (**src && !isprint(**src)) while (**src && !isprint((unsigned char)**src))
(*src)++; (*src)++;
return **src ? *((*src)++) : '='; /* emulate padding if string ends */ return **src ? *((*src)++) : '='; /* emulate padding if string ends */
} }
@ -377,6 +363,13 @@ base64dec(const char *src)
{ {
size_t in_len = strlen(src); size_t in_len = strlen(src);
char *result, *dst; char *result, *dst;
static const char base64_digits[256] = {
[43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
};
if (in_len % 4) if (in_len % 4)
in_len += 4 - (in_len % 4); in_len += 4 - (in_len % 4);
@ -946,7 +939,7 @@ ttyresize(int tw, int th)
} }
void void
ttyhangup() ttyhangup(void)
{ {
/* Send SIGHUP to shell */ /* Send SIGHUP to shell */
kill(pid, SIGHUP); kill(pid, SIGHUP);
@ -1843,39 +1836,28 @@ csireset(void)
} }
void void
osc4_color_response(int num) osc_color_response(int num, int index, int is_osc4)
{ {
int n; int n;
char buf[32]; char buf[32];
unsigned char r, g, b; unsigned char r, g, b;
if (xgetcolor(num, &r, &g, &b)) { if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) {
fprintf(stderr, "erresc: failed to fetch osc4 color %d\n", num); fprintf(stderr, "erresc: failed to fetch %s color %d\n",
is_osc4 ? "osc4" : "osc",
is_osc4 ? num : index);
return; return;
} }
n = snprintf(buf, sizeof buf, "\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
num, r, r, g, g, b, b); is_osc4 ? "4;" : "", num, r, r, g, g, b, b);
if (n < 0 || n >= sizeof(buf)) {
ttywrite(buf, n, 1); fprintf(stderr, "error: %s while printing %s response\n",
} n < 0 ? "snprintf failed" : "truncation occurred",
is_osc4 ? "osc4" : "osc");
void } else {
osc_color_response(int index, int num) ttywrite(buf, n, 1);
{
int n;
char buf[32];
unsigned char r, g, b;
if (xgetcolor(index, &r, &g, &b)) {
fprintf(stderr, "erresc: failed to fetch osc color %d\n", index);
return;
} }
n = snprintf(buf, sizeof buf, "\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
num, r, r, g, g, b, b);
ttywrite(buf, n, 1);
} }
void void
@ -1883,6 +1865,11 @@ strhandle(void)
{ {
char *p = NULL, *dec; char *p = NULL, *dec;
int j, narg, par; int j, narg, par;
const struct { int idx; char *str; } osc_table[] = {
{ defaultfg, "foreground" },
{ defaultbg, "background" },
{ defaultcs, "cursor" }
};
term.esc &= ~(ESC_STR_END|ESC_STR); term.esc &= ~(ESC_STR_END|ESC_STR);
strparse(); strparse();
@ -1917,43 +1904,22 @@ strhandle(void)
} }
return; return;
case 10: case 10:
if (narg < 2)
break;
p = strescseq.args[1];
if (!strcmp(p, "?"))
osc_color_response(defaultfg, 10);
else if (xsetcolorname(defaultfg, p))
fprintf(stderr, "erresc: invalid foreground color: %s\n", p);
else
redraw();
return;
case 11: case 11:
if (narg < 2)
break;
p = strescseq.args[1];
if (!strcmp(p, "?"))
osc_color_response(defaultbg, 11);
else if (xsetcolorname(defaultbg, p))
fprintf(stderr, "erresc: invalid background color: %s\n", p);
else
redraw();
return;
case 12: case 12:
if (narg < 2) if (narg < 2)
break; break;
p = strescseq.args[1]; p = strescseq.args[1];
if ((j = par - 10) < 0 || j >= LEN(osc_table))
break; /* shouldn't be possible */
if (!strcmp(p, "?")) if (!strcmp(p, "?")) {
osc_color_response(defaultcs, 12); osc_color_response(par, osc_table[j].idx, 0);
else if (xsetcolorname(defaultcs, p)) } else if (xsetcolorname(osc_table[j].idx, p)) {
fprintf(stderr, "erresc: invalid cursor color: %s\n", p); fprintf(stderr, "erresc: invalid %s color: %s\n",
else osc_table[j].str, p);
redraw(); } else {
tfulldirt();
}
return; return;
case 4: /* color set */ case 4: /* color set */
if (narg < 3) if (narg < 3)
@ -1963,9 +1929,9 @@ strhandle(void)
case 104: /* color reset */ case 104: /* color reset */
j = (narg > 1) ? atoi(strescseq.args[1]) : -1; j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
if (p && !strcmp(p, "?")) if (p && !strcmp(p, "?")) {
osc4_color_response(j); osc_color_response(j, 0, 1);
else if (xsetcolorname(j, p)) { } else if (xsetcolorname(j, p)) {
if (par == 104 && narg <= 1) if (par == 104 && narg <= 1)
return; /* color reset without parameter */ return; /* color reset without parameter */
fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
@ -1975,7 +1941,7 @@ strhandle(void)
* TODO if defaultbg color is changed, borders * TODO if defaultbg color is changed, borders
* are dirty * are dirty
*/ */
redraw(); tfulldirt();
} }
return; return;
} }
@ -2674,7 +2640,8 @@ draw(void)
drawregion(0, 0, term.col, term.row); drawregion(0, 0, term.col, term.row);
xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
term.ocx, term.ocy, term.line[term.ocy][term.ocx]); term.ocx, term.ocy, term.line[term.ocy][term.ocx],
term.line[term.ocy], term.col);
term.ocx = cx; term.ocx = cx;
term.ocy = term.c.y; term.ocy = term.c.y;
xfinishdraw(); xfinishdraw();

5
st.h
View file

@ -11,7 +11,8 @@
#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
#define DEFAULT(a, b) (a) = (a) ? (a) : (b) #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ #define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \
(a).fg != (b).fg || \
(a).bg != (b).bg) (a).bg != (b).bg)
#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
(t1.tv_nsec-t2.tv_nsec)/1E6) (t1.tv_nsec-t2.tv_nsec)/1E6)
@ -111,8 +112,6 @@ void *xmalloc(size_t);
void *xrealloc(void *, size_t); void *xrealloc(void *, size_t);
char *xstrdup(const char *); char *xstrdup(const char *);
int xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b);
/* config.h globals */ /* config.h globals */
extern char *utmp; extern char *utmp;
extern char *scroll; extern char *scroll;

3
win.h
View file

@ -25,11 +25,12 @@ enum win_mode {
void xbell(void); void xbell(void);
void xclipcopy(void); void xclipcopy(void);
void xdrawcursor(int, int, Glyph, int, int, Glyph); void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
void xdrawline(Line, int, int, int); void xdrawline(Line, int, int, int);
void xfinishdraw(void); void xfinishdraw(void);
void xloadcols(void); void xloadcols(void);
int xsetcolorname(int, const char *); int xsetcolorname(int, const char *);
int xgetcolor(int, unsigned char *, unsigned char *, unsigned char *);
void xseticontitle(char *); void xseticontitle(char *);
void xsettitle(char *); void xsettitle(char *);
int xsetcursor(int); int xsetcursor(int);

294
x.c
View file

@ -19,6 +19,7 @@ char *argv0;
#include "arg.h" #include "arg.h"
#include "st.h" #include "st.h"
#include "win.h" #include "win.h"
#include "hb.h"
/* types used in config.h */ /* types used in config.h */
typedef struct { typedef struct {
@ -141,8 +142,9 @@ typedef struct {
} DC; } DC;
static inline ushort sixd_to_16bit(int); static inline ushort sixd_to_16bit(int);
static void xresetfontsettings(ushort mode, Font **font, int *frcflags);
static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
static void xdrawglyph(Glyph, int, int); static void xdrawglyph(Glyph, int, int);
static void xclear(int, int, int, int); static void xclear(int, int, int, int);
static int xgeommasktogravity(int); static int xgeommasktogravity(int);
@ -757,7 +759,7 @@ xresize(int col, int row)
xclear(0, 0, win.w, win.h); xclear(0, 0, win.w, win.h);
/* resize to new width */ /* resize to new width */
xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
} }
ushort ushort
@ -1062,6 +1064,9 @@ xunloadfont(Font *f)
void void
xunloadfonts(void) xunloadfonts(void)
{ {
/* Clear Harfbuzz font cache. */
hbunloadfonts();
/* Free the loaded fonts in the font cache. */ /* Free the loaded fonts in the font cache. */
while (frclen > 0) while (frclen > 0)
XftFontClose(xw.dpy, frc[--frclen].font); XftFontClose(xw.dpy, frc[--frclen].font);
@ -1185,7 +1190,7 @@ xinit(int cols, int rows)
XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
/* font spec buffer */ /* font spec buffer */
xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
/* Xft rendering context */ /* Xft rendering context */
xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
@ -1239,6 +1244,22 @@ xinit(int cols, int rows)
xsel.xtarget = XA_STRING; xsel.xtarget = XA_STRING;
} }
void
xresetfontsettings(ushort mode, Font **font, int *frcflags)
{
*font = &dc.font;
if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
*font = &dc.ibfont;
*frcflags = FRC_ITALICBOLD;
} else if (mode & ATTR_ITALIC) {
*font = &dc.ifont;
*frcflags = FRC_ITALIC;
} else if (mode & ATTR_BOLD) {
*font = &dc.bfont;
*frcflags = FRC_BOLD;
}
}
int int
xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
{ {
@ -1253,128 +1274,156 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
FcPattern *fcpattern, *fontpattern; FcPattern *fcpattern, *fontpattern;
FcFontSet *fcsets[] = { NULL }; FcFontSet *fcsets[] = { NULL };
FcCharSet *fccharset; FcCharSet *fccharset;
int i, f, numspecs = 0; int i, f, length = 0, start = 0, numspecs = 0;
float cluster_xp = xp, cluster_yp = yp;
HbTransformData shaped = { 0 };
/* Initial values. */
mode = prevmode = glyphs[0].mode & ~ATTR_WRAP;
xresetfontsettings(mode, &font, &frcflags);
for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
/* Fetch rune and mode for current glyph. */ mode = glyphs[i].mode & ~ATTR_WRAP;
rune = glyphs[i].u;
mode = glyphs[i].mode;
/* Skip dummy wide-character spacing. */ /* Skip dummy wide-character spacing. */
if (mode == ATTR_WDUMMY) if (mode & ATTR_WDUMMY && i < (len - 1))
continue; continue;
/* Determine font for glyph if different from previous glyph. */ if (
if (prevmode != mode) { prevmode != mode
prevmode = mode; || ATTRCMP(glyphs[start], glyphs[i])
font = &dc.font; || selected(x + i, y) != selected(x + start, y)
frcflags = FRC_NORMAL; || i == (len - 1)
runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); ) {
if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { /* Handle 1-character wide segments and end of line */
font = &dc.ibfont; length = i - start;
frcflags = FRC_ITALICBOLD; if (i == start) {
} else if (mode & ATTR_ITALIC) { length = 1;
font = &dc.ifont; } else if (i == (len - 1)) {
frcflags = FRC_ITALIC; length = (i - start + 1);
} else if (mode & ATTR_BOLD) {
font = &dc.bfont;
frcflags = FRC_BOLD;
}
yp = winy + font->ascent;
}
/* Lookup character index with default font. */
glyphidx = XftCharIndex(xw.dpy, font->match, rune);
if (glyphidx) {
specs[numspecs].font = font->match;
specs[numspecs].glyph = glyphidx;
specs[numspecs].x = (short)xp;
specs[numspecs].y = (short)yp;
xp += runewidth;
numspecs++;
continue;
}
/* Fallback on font cache, search the font cache for match. */
for (f = 0; f < frclen; f++) {
glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
/* Everything correct. */
if (glyphidx && frc[f].flags == frcflags)
break;
/* We got a default font for a not found glyph. */
if (!glyphidx && frc[f].flags == frcflags
&& frc[f].unicodep == rune) {
break;
}
}
/* Nothing was found. Use fontconfig to find matching font. */
if (f >= frclen) {
if (!font->set)
font->set = FcFontSort(0, font->pattern,
1, 0, &fcres);
fcsets[0] = font->set;
/*
* Nothing was found in the cache. Now use
* some dozen of Fontconfig calls to get the
* font for one single character.
*
* Xft and fontconfig are design failures.
*/
fcpattern = FcPatternDuplicate(font->pattern);
fccharset = FcCharSetCreate();
FcCharSetAddChar(fccharset, rune);
FcPatternAddCharSet(fcpattern, FC_CHARSET,
fccharset);
FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
FcConfigSubstitute(0, fcpattern,
FcMatchPattern);
FcDefaultSubstitute(fcpattern);
fontpattern = FcFontSetMatch(0, fcsets, 1,
fcpattern, &fcres);
/* Allocate memory for the new cache entry. */
if (frclen >= frccap) {
frccap += 16;
frc = xrealloc(frc, frccap * sizeof(Fontcache));
} }
frc[frclen].font = XftFontOpenPattern(xw.dpy, /* Shape the segment. */
fontpattern); hbtransform(&shaped, font->match, glyphs, start, length);
if (!frc[frclen].font) runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f);
die("XftFontOpenPattern failed seeking fallback font: %s\n", cluster_xp = xp; cluster_yp = yp;
strerror(errno)); for (int code_idx = 0; code_idx < shaped.count; code_idx++) {
frc[frclen].flags = frcflags; int idx = shaped.glyphs[code_idx].cluster;
frc[frclen].unicodep = rune;
glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); if (glyphs[start + idx].mode & ATTR_WDUMMY)
continue;
f = frclen; /* Advance the drawing cursor if we've moved to a new cluster */
frclen++; if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
xp += runewidth;
cluster_xp = xp;
cluster_yp = yp;
runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f);
}
FcPatternDestroy(fcpattern); if (shaped.glyphs[code_idx].codepoint != 0) {
FcCharSetDestroy(fccharset); /* If symbol is found, put it into the specs. */
specs[numspecs].font = font->match;
specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint;
specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
cluster_xp += shaped.positions[code_idx].x_advance / 64.;
cluster_yp += shaped.positions[code_idx].y_advance / 64.;
numspecs++;
} else {
/* If it's not found, try to fetch it through the font cache. */
rune = glyphs[start + idx].u;
for (f = 0; f < frclen; f++) {
glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
/* Everything correct. */
if (glyphidx && frc[f].flags == frcflags)
break;
/* We got a default font for a not found glyph. */
if (!glyphidx && frc[f].flags == frcflags
&& frc[f].unicodep == rune) {
break;
}
}
/* Nothing was found. Use fontconfig to find matching font. */
if (f >= frclen) {
if (!font->set)
font->set = FcFontSort(0, font->pattern,
1, 0, &fcres);
fcsets[0] = font->set;
/*
* Nothing was found in the cache. Now use
* some dozen of Fontconfig calls to get the
* font for one single character.
*
* Xft and fontconfig are design failures.
*/
fcpattern = FcPatternDuplicate(font->pattern);
fccharset = FcCharSetCreate();
FcCharSetAddChar(fccharset, rune);
FcPatternAddCharSet(fcpattern, FC_CHARSET,
fccharset);
FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
FcConfigSubstitute(0, fcpattern,
FcMatchPattern);
FcDefaultSubstitute(fcpattern);
fontpattern = FcFontSetMatch(0, fcsets, 1,
fcpattern, &fcres);
/* Allocate memory for the new cache entry. */
if (frclen >= frccap) {
frccap += 16;
frc = xrealloc(frc, frccap * sizeof(Fontcache));
}
frc[frclen].font = XftFontOpenPattern(xw.dpy,
fontpattern);
if (!frc[frclen].font)
die("XftFontOpenPattern failed seeking fallback font: %s\n",
strerror(errno));
frc[frclen].flags = frcflags;
frc[frclen].unicodep = rune;
glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
f = frclen;
frclen++;
FcPatternDestroy(fcpattern);
FcCharSetDestroy(fccharset);
}
specs[numspecs].font = frc[f].font;
specs[numspecs].glyph = glyphidx;
specs[numspecs].x = (short)xp;
specs[numspecs].y = (short)yp;
numspecs++;
}
}
/* Cleanup and get ready for next segment. */
hbcleanup(&shaped);
start = i;
/* Determine font for glyph if different from previous glyph. */
if (prevmode != mode) {
prevmode = mode;
xresetfontsettings(mode, &font, &frcflags);
yp = winy + font->ascent;
}
} }
specs[numspecs].font = frc[f].font;
specs[numspecs].glyph = glyphidx;
specs[numspecs].x = (short)xp;
specs[numspecs].y = (short)yp;
xp += runewidth;
numspecs++;
} }
return numspecs; return numspecs;
} }
void void
xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen)
{ {
int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
width = charlen * win.cw; width = charlen * win.cw;
Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
@ -1493,12 +1542,12 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
/* Render underline and strikethrough. */ /* Render underline and strikethrough. */
if (base.mode & ATTR_UNDERLINE) { if (base.mode & ATTR_UNDERLINE) {
XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
width, 1); width, 1);
} }
if (base.mode & ATTR_STRUCK) { if (base.mode & ATTR_STRUCK) {
XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3,
width, 1); width, 1);
} }
@ -1510,21 +1559,24 @@ void
xdrawglyph(Glyph g, int x, int y) xdrawglyph(Glyph g, int x, int y)
{ {
int numspecs; int numspecs;
XftGlyphFontSpec spec; XftGlyphFontSpec *specs = xw.specbuf;
numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y);
xdrawglyphfontspecs(&spec, g, numspecs, x, y); xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1);
} }
void void
xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
{ {
Color drawcol; Color drawcol;
/* remove the old cursor */ /* remove the old cursor */
if (selected(ox, oy)) if (selected(ox, oy))
og.mode ^= ATTR_REVERSE; og.mode ^= ATTR_REVERSE;
xdrawglyph(og, ox, oy);
/* Redraw the line where cursor was previously.
* It will restore the ligatures broken by the cursor. */
xdrawline(line, 0, oy, len);
if (IS_SET(MODE_HIDE)) if (IS_SET(MODE_HIDE))
return; return;
@ -1652,18 +1704,16 @@ xdrawline(Line line, int x1, int y1, int x2)
Glyph base, new; Glyph base, new;
XftGlyphFontSpec *specs = xw.specbuf; XftGlyphFontSpec *specs = xw.specbuf;
numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
i = ox = 0; i = ox = 0;
for (x = x1; x < x2 && i < numspecs; x++) { for (x = x1; x < x2; x++) {
new = line[x]; new = line[x];
if (new.mode == ATTR_WDUMMY) if (new.mode == ATTR_WDUMMY)
continue; continue;
if (selected(x, y1)) if (selected(x, y1))
new.mode ^= ATTR_REVERSE; new.mode ^= ATTR_REVERSE;
if (i > 0 && ATTRCMP(base, new)) { if ((i > 0) && ATTRCMP(base, new)) {
xdrawglyphfontspecs(specs, base, i, ox, y1); numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
specs += i; xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox);
numspecs -= i;
i = 0; i = 0;
} }
if (i == 0) { if (i == 0) {
@ -1672,8 +1722,10 @@ xdrawline(Line line, int x1, int y1, int x2)
} }
i++; i++;
} }
if (i > 0) if (i > 0) {
xdrawglyphfontspecs(specs, base, i, ox, y1); numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox);
}
} }
void void