dst/source/patch/boxdraw.d
2025-06-26 13:47:07 -05:00

386 lines
12 KiB
D

module patch.boxdraw;
import deimos.X11.X : Colormap;
import deimos.X11.Xlib : Display, Visual;
import xft_types : XftDraw, XftColor, XftGlyphFontSpec, XRenderColor;
import config : boxdraw, boxdraw_bold, boxdraw_braille;
import st : Glyph, Rune, GlyphAttribute;
import std.algorithm : min, max;
// External Xft functions
extern(C) {
void XftDrawRect(XftDraw* d, const(XftColor)* color, int x, int y, uint width, uint height);
int XftColorAllocValue(Display* dpy, Visual* visual, Colormap cmap, const(XRenderColor)* color, XftColor* result);
void XftColorFree(Display* dpy, Visual* visual, Colormap cmap, XftColor* color);
}
// Rounded non-negative integers division of n / d
private int DIV(int n, int d) { return (n + d / 2) / d; }
// Module variables
private Display* xdpy;
private Colormap xcmap;
private XftDraw* xd;
private Visual* xvis;
// Box drawing constants
enum {
BDL = (1<<8), // Box Draw Lines (light/double/heavy)
BDA = (1<<9), // Box Draw Arc (light)
BBD = (1<<10), // Box Block Down (lower) X/8
BBL = (2<<10), // Box Block Left X/8
BBU = (3<<10), // Box Block Upper X/8
BBR = (4<<10), // Box Block Right X/8
BBQ = (5<<10), // Box Block Quadrants
BRL = (6<<10), // Box Braille (data is lower byte of U28XX)
BBS = (1<<14), // Box Block Shades
BDB = (1<<15), // Box Draw is Bold
}
// Light/Double/Heavy x Left/Up/Right/Down/Horizontal/Vertical
enum {
LL = (1<<0),
LU = (1<<1),
LR = (1<<2),
LD = (1<<3),
LH = LL+LR,
LV = LU+LD,
DL = (1<<4),
DU = (1<<5),
DR = (1<<6),
DD = (1<<7),
DH = DL+DR,
DV = DU+DD,
HL = LL+DL,
HU = LU+DU,
HR = LR+DR,
HD = LD+DD,
HH = HL+HR,
HV = HU+HD,
}
// Quadrants Top/Bottom x Left/Right
enum {
TL = (1<<0),
TR = (1<<1),
BL = (1<<2),
BR = (1<<3),
}
// Data for U+2500 - U+259F except dashes/diagonals
__gshared immutable ushort[256] boxdata = [
// light lines
0x00: BDL + LH, // light horizontal
0x02: BDL + LV, // light vertical
0x0c: BDL + LD + LR, // light down and right
0x10: BDL + LD + LL, // light down and left
0x14: BDL + LU + LR, // light up and right
0x18: BDL + LU + LL, // light up and left
0x1c: BDL + LV + LR, // light vertical and right
0x24: BDL + LV + LL, // light vertical and left
0x2c: BDL + LH + LD, // light horizontal and down
0x34: BDL + LH + LU, // light horizontal and up
0x3c: BDL + LV + LH, // light vertical and horizontal
0x74: BDL + LL, // light left
0x75: BDL + LU, // light up
0x76: BDL + LR, // light right
0x77: BDL + LD, // light down
// heavy [+light] lines
0x01: BDL + HH,
0x03: BDL + HV,
0x0d: BDL + HR + LD,
0x0e: BDL + HD + LR,
0x0f: BDL + HD + HR,
0x11: BDL + HL + LD,
0x12: BDL + HD + LL,
0x13: BDL + HD + HL,
0x15: BDL + HR + LU,
0x16: BDL + HU + LR,
0x17: BDL + HU + HR,
0x19: BDL + HL + LU,
0x1a: BDL + HU + LL,
0x1b: BDL + HU + HL,
0x1d: BDL + HR + LV,
0x1e: BDL + HU + LD + LR,
0x1f: BDL + HD + LR + LU,
0x20: BDL + HV + LR,
0x21: BDL + HU + HR + LD,
0x22: BDL + HD + HR + LU,
0x23: BDL + HV + HR,
0x25: BDL + HL + LV,
0x26: BDL + HU + LD + LL,
0x27: BDL + HD + LU + LL,
0x28: BDL + HV + LL,
0x29: BDL + HU + HL + LD,
0x2a: BDL + HD + HL + LU,
0x2b: BDL + HV + HL,
0x2d: BDL + HL + LD + LR,
0x2e: BDL + HR + LL + LD,
0x2f: BDL + HH + LD,
0x30: BDL + HD + LH,
0x31: BDL + HD + HL + LR,
0x32: BDL + HR + HD + LL,
0x33: BDL + HH + HD,
0x35: BDL + HL + LU + LR,
0x36: BDL + HR + LU + LL,
0x37: BDL + HH + LU,
0x38: BDL + HU + LH,
0x39: BDL + HU + HL + LR,
0x3a: BDL + HU + HR + LL,
0x3b: BDL + HH + HU,
0x3d: BDL + HL + LV + LR,
0x3e: BDL + HR + LV + LL,
0x3f: BDL + HH + LV,
0x40: BDL + HU + LH + LD,
0x41: BDL + HD + LH + LU,
0x42: BDL + HV + LH,
0x43: BDL + HU + HL + LD + LR,
0x44: BDL + HU + HR + LD + LL,
0x45: BDL + HD + HL + LU + LR,
0x46: BDL + HD + HR + LU + LL,
0x47: BDL + HH + HU + LD,
0x48: BDL + HH + HD + LU,
0x49: BDL + HV + HL + LR,
0x4a: BDL + HV + HR + LL,
0x4b: BDL + HV + HH,
0x78: BDL + HL,
0x79: BDL + HU,
0x7a: BDL + HR,
0x7b: BDL + HD,
0x7c: BDL + HR + LL,
0x7d: BDL + HD + LU,
0x7e: BDL + HL + LR,
0x7f: BDL + HU + LD,
// double [+light] lines
0x50: BDL + DH,
0x51: BDL + DV,
0x52: BDL + DR + LD,
0x53: BDL + DD + LR,
0x54: BDL + DR + DD,
0x55: BDL + DL + LD,
0x56: BDL + DD + LL,
0x57: BDL + DL + DD,
0x58: BDL + DR + LU,
0x59: BDL + DU + LR,
0x5a: BDL + DU + DR,
0x5b: BDL + DL + LU,
0x5c: BDL + DU + LL,
0x5d: BDL + DL + DU,
0x5e: BDL + DR + LV,
0x5f: BDL + DV + LR,
0x60: BDL + DV + DR,
0x61: BDL + DL + LV,
0x62: BDL + DV + LL,
0x63: BDL + DV + DL,
0x64: BDL + DH + LD,
0x65: BDL + DD + LH,
0x66: BDL + DD + DH,
0x67: BDL + DH + LU,
0x68: BDL + DU + LH,
0x69: BDL + DH + DU,
0x6a: BDL + DH + LV,
0x6b: BDL + DV + LH,
0x6c: BDL + DH + DV,
// (light) arcs
0x6d: BDA + LD + LR,
0x6e: BDA + LD + LL,
0x6f: BDA + LU + LL,
0x70: BDA + LU + LR,
// Lower (Down) X/8 block (data is 8 - X)
0x81: BBD + 7, 0x82: BBD + 6, 0x83: BBD + 5, 0x84: BBD + 4,
0x85: BBD + 3, 0x86: BBD + 2, 0x87: BBD + 1, 0x88: BBD + 0,
// Left X/8 block (data is X)
0x89: BBL + 7, 0x8a: BBL + 6, 0x8b: BBL + 5, 0x8c: BBL + 4,
0x8d: BBL + 3, 0x8e: BBL + 2, 0x8f: BBL + 1,
// upper 1/2 (4/8), 1/8 block (X), right 1/2, 1/8 block (8-X)
0x80: BBU + 4, 0x94: BBU + 1,
0x90: BBR + 4, 0x95: BBR + 7,
// Quadrants
0x96: BBQ + BL,
0x97: BBQ + BR,
0x98: BBQ + TL,
0x99: BBQ + TL + BL + BR,
0x9a: BBQ + TL + BR,
0x9b: BBQ + TL + TR + BL,
0x9c: BBQ + TL + TR + BR,
0x9d: BBQ + TR,
0x9e: BBQ + BL + TR,
0x9f: BBQ + BL + TR + BR,
// Shades, data is an alpha value in 25% units (1/4, 1/2, 3/4)
0x91: BBS + 1, 0x92: BBS + 2, 0x93: BBS + 3,
];
// Public API
void boxdraw_xinit(Display* dpy, Colormap cmap, XftDraw* draw, Visual* vis) {
xdpy = dpy;
xcmap = cmap;
xd = draw;
xvis = vis;
}
int isboxdraw(Rune u) {
Rune block_ = u & ~0xff;
return (boxdraw && block_ == 0x2500 && boxdata[cast(ubyte)u]) ||
(boxdraw_braille && block_ == 0x2800);
}
ushort boxdrawindex(const(Glyph)* g) {
if (boxdraw_braille && (g.u & ~0xff) == 0x2800)
return BRL | cast(ubyte)g.u;
if (boxdraw_bold && (g.mode & GlyphAttribute.BOLD))
return BDB | boxdata[cast(ubyte)g.u];
return boxdata[cast(ubyte)g.u];
}
void drawboxes(int x, int y, int cw, int ch, XftColor* fg, XftColor* bg,
const(XftGlyphFontSpec)* specs, int len) {
for ( ; len-- > 0; x += cw, specs++)
drawbox(x, y, cw, ch, fg, bg, cast(ushort)specs.glyph);
}
// Implementation
private void drawbox(int x, int y, int w, int h, XftColor* fg, XftColor* bg, ushort bd) {
ushort cat = bd & ~(BDB | 0xff); // mask out bold and data
if (bd & (BDL | BDA)) {
// lines (light/double/heavy/arcs)
drawboxlines(x, y, w, h, fg, bd);
} else if (cat == BBD) {
// lower (8-X)/8 block
int d = DIV(cast(ubyte)bd * h, 8);
XftDrawRect(xd, fg, x, y + d, w, h - d);
} else if (cat == BBU) {
// upper X/8 block
XftDrawRect(xd, fg, x, y, w, DIV(cast(ubyte)bd * h, 8));
} else if (cat == BBL) {
// left X/8 block
XftDrawRect(xd, fg, x, y, DIV(cast(ubyte)bd * w, 8), h);
} else if (cat == BBR) {
// right (8-X)/8 block
int d = DIV(cast(ubyte)bd * w, 8);
XftDrawRect(xd, fg, x + d, y, w - d, h);
} else if (cat == BBQ) {
// Quadrants
int w2 = DIV(w, 2), h2 = DIV(h, 2);
if (bd & TL)
XftDrawRect(xd, fg, x, y, w2, h2);
if (bd & TR)
XftDrawRect(xd, fg, x + w2, y, w - w2, h2);
if (bd & BL)
XftDrawRect(xd, fg, x, y + h2, w2, h - h2);
if (bd & BR)
XftDrawRect(xd, fg, x + w2, y + h2, w - w2, h - h2);
} else if (bd & BBS) {
// Shades - data is 1/2/3 for 25%/50%/75% alpha, respectively
int d = cast(ubyte)bd;
XftColor xfc;
XRenderColor xrc;
xrc.alpha = 0xffff;
xrc.red = cast(ushort)DIV(fg.color.red * d + bg.color.red * (4 - d), 4);
xrc.green = cast(ushort)DIV(fg.color.green * d + bg.color.green * (4 - d), 4);
xrc.blue = cast(ushort)DIV(fg.color.blue * d + bg.color.blue * (4 - d), 4);
XftColorAllocValue(xdpy, xvis, xcmap, &xrc, &xfc);
XftDrawRect(xd, &xfc, x, y, w, h);
XftColorFree(xdpy, xvis, xcmap, &xfc);
} else if (cat == BRL) {
// braille, each data bit corresponds to one dot at 2x4 grid
int w1 = DIV(w, 2);
int h1 = DIV(h, 4), h2 = DIV(h, 2), h3 = DIV(3 * h, 4);
if (bd & 1) XftDrawRect(xd, fg, x, y, w1, h1);
if (bd & 2) XftDrawRect(xd, fg, x, y + h1, w1, h2 - h1);
if (bd & 4) XftDrawRect(xd, fg, x, y + h2, w1, h3 - h2);
if (bd & 8) XftDrawRect(xd, fg, x + w1, y, w - w1, h1);
if (bd & 16) XftDrawRect(xd, fg, x + w1, y + h1, w - w1, h2 - h1);
if (bd & 32) XftDrawRect(xd, fg, x + w1, y + h2, w - w1, h3 - h2);
if (bd & 64) XftDrawRect(xd, fg, x, y + h3, w1, h - h3);
if (bd & 128) XftDrawRect(xd, fg, x + w1, y + h3, w - w1, h - h3);
}
}
private void drawboxlines(int x, int y, int w, int h, XftColor* fg, ushort bd) {
// s: stem thickness. width/8 roughly matches underscore thickness.
// We draw bold as 1.5 * normal-stem and at least 1px thicker.
// doubles draw at least 3px, even when w or h < 3. bold needs 6px.
int mwh = min(w, h);
int base_s = max(1, DIV(mwh, 8));
int bold = (bd & BDB) && mwh >= 6; // possibly ignore boldness
int s = bold ? max(base_s + 1, DIV(3 * base_s, 2)) : base_s;
int w2 = DIV(w - s, 2), h2 = DIV(h - s, 2);
// the s-by-s square (x + w2, y + h2, s, s) is the center texel.
// The base length (per direction till edge) includes this square.
int light = bd & (LL | LU | LR | LD);
int double_ = bd & (DL | DU | DR | DD);
if (light) {
// d: additional (negative) length to not-draw the center
// texel - at arcs and avoid drawing inside (some) doubles
int arc = bd & BDA;
int multi_light = light & (light - 1);
int multi_double = double_ & (double_ - 1);
// light crosses double only at DH+LV, DV+LH (ref. shapes)
int d = arc || (multi_double && !multi_light) ? -s : 0;
if (bd & LL)
XftDrawRect(xd, fg, x, y + h2, w2 + s + d, s);
if (bd & LU)
XftDrawRect(xd, fg, x + w2, y, s, h2 + s + d);
if (bd & LR)
XftDrawRect(xd, fg, x + w2 - d, y + h2, w - w2 + d, s);
if (bd & LD)
XftDrawRect(xd, fg, x + w2, y + h2 - d, s, h - h2 + d);
}
// double lines - also align with light to form heavy when combined
if (double_) {
/*
* going clockwise, for each double-ray: p is additional length
* to the single-ray nearer to the previous direction, and n to
* the next. p and n adjust from the base length to lengths
* which consider other doubles - shorter to avoid intersections
* (p, n), or longer to draw the far-corner texel (n).
*/
int dl = bd & DL, du = bd & DU, dr = bd & DR, dd = bd & DD;
if (dl) {
int p = dd ? -s : 0, n = du ? -s : dd ? s : 0;
XftDrawRect(xd, fg, x, y + h2 + s, w2 + s + p, s);
XftDrawRect(xd, fg, x, y + h2 - s, w2 + s + n, s);
}
if (du) {
int p = dl ? -s : 0, n = dr ? -s : dl ? s : 0;
XftDrawRect(xd, fg, x + w2 - s, y, s, h2 + s + p);
XftDrawRect(xd, fg, x + w2 + s, y, s, h2 + s + n);
}
if (dr) {
int p = du ? -s : 0, n = dd ? -s : du ? s : 0;
XftDrawRect(xd, fg, x + w2 - p, y + h2 - s, w - w2 + p, s);
XftDrawRect(xd, fg, x + w2 - n, y + h2 + s, w - w2 + n, s);
}
if (dd) {
int p = dr ? -s : 0, n = dl ? -s : dr ? s : 0;
XftDrawRect(xd, fg, x + w2 + s, y + h2 - p, s, h - h2 + p);
XftDrawRect(xd, fg, x + w2 - s, y + h2 - n, s, h - h2 + n);
}
}
}