xbanan/xbanan/Font.cpp

1027 lines
28 KiB
C++

#include "Base.h"
#include "Definitions.h"
#include "Font.h"
#include "SafeGetters.h"
#include "Utils.h"
#include <BAN/Endianness.h>
#include <BAN/ScopeGuard.h>
#include <LibDEFLATE/Decompressor.h>
#include <X11/X.h>
#include <ctype.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
static BAN::HashMap<BAN::String, BAN::String> s_available_fonts;
static BAN::HashMap<BAN::String, BAN::WeakPtr<PCFFont>> s_loaded_fonts;
// https://fontforge.org/docs/techref/pcf-format.html
#define PCF_PROPERTIES (1 << 0)
#define PCF_ACCELERATORS (1 << 1)
#define PCF_METRICS (1 << 2)
#define PCF_BITMAPS (1 << 3)
#define PCF_INK_METRICS (1 << 4)
#define PCF_BDF_ENCODINGS (1 << 5)
#define PCF_SWIDTHS (1 << 6)
#define PCF_GLYPH_NAMES (1 << 7)
#define PCF_BDF_ACCELERATORS (1 << 8)
#define PCF_COMPRESSED_METRICS 0x00000100
struct pcf_table_entry
{
BAN::LittleEndian<uint32_t> type;
BAN::LittleEndian<uint32_t> format;
BAN::LittleEndian<uint32_t> size;
BAN::LittleEndian<uint32_t> offset;
};
struct pcf_header
{
char magic[4];
BAN::LittleEndian<uint32_t> table_count;
pcf_table_entry tables[];
};
struct pcf_metrics_uncompressed
{
int16_t left_sided_bearing;
int16_t right_side_bearing;
int16_t character_width;
int16_t character_ascent;
int16_t character_descent;
uint16_t character_attributes;
};
struct pcf_metrics_compressed
{
uint8_t left_sided_bearing;
uint8_t right_side_bearing;
uint8_t character_width;
uint8_t character_ascent;
uint8_t character_descent;
};
struct pcf_metrics
{
BAN::LittleEndian<uint32_t> format;
union
{
struct
{
int16_t count;
pcf_metrics_compressed entries[];
} compressed;
struct
{
int32_t count;
pcf_metrics_uncompressed entries[];
} uncompressed;
};
};
struct pcf_bitmap
{
BAN::LittleEndian<uint32_t> format;
int32_t glyph_count;
int32_t glyph_offsets[/* glyph_count */];
//int32_t bitmap_sizes[4];
//char bitmap_data[bitmapsizes[format & 3]];
};
struct pcf_encoding
{
BAN::LittleEndian<uint32_t> format;
int16_t min_char_or_byte2;
int16_t max_char_or_byte2;
int16_t min_byte1;
int16_t max_byte1;
int16_t default_char;
int16_t glyph_indices[];
};
static BAN::ErrorOr<BAN::Vector<uint8_t>> gzip_decompress_file(const BAN::String& path)
{
int fd = open(path.data(), O_RDONLY);
if (fd == -1)
return BAN::Error::from_errno(errno);
BAN::ScopeGuard _1([fd] { close(fd); });
struct stat st;
if (fstat(fd, &st) == -1)
{
close(fd);
return BAN::Error::from_errno(errno);
}
void* addr = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (addr == MAP_FAILED)
return BAN::Error::from_errno(errno);
BAN::ScopeGuard _2([&st, addr] { munmap(addr, st.st_size); });
auto file_data = BAN::ConstByteSpan(static_cast<const uint8_t*>(addr), st.st_size);
LibDEFLATE::Decompressor decompressor(file_data, LibDEFLATE::StreamType::GZip);
return TRY(decompressor.decompress());
}
static constexpr uint64_t reverse_bits(uint64_t value, uint8_t count)
{
uint64_t reverse = 0;
for (uint8_t bit = 0; bit < count; bit++)
reverse |= ((value >> bit) & 1) << (count - bit - 1);
return reverse;
}
static BAN::ErrorOr<BAN::RefPtr<PCFFont>> parse_font(const BAN::String& path)
{
const auto font_data_vec = TRY(gzip_decompress_file(path));
const auto font_data = BAN::ConstByteSpan(font_data_vec.span());
const auto& header = *reinterpret_cast<const pcf_header*>(font_data.data());
if (strncmp(header.magic, "\1fcp", 4) != 0)
{
dwarnln("pcf font invalid magic");
return BAN::Error::from_errno(EINVAL);
}
BAN::ConstByteSpan metrics_data;
BAN::ConstByteSpan bitmap_data;
BAN::ConstByteSpan encoding_data;
for (size_t i = 0; i < header.table_count; i++)
{
const auto& table = header.tables[i];
switch (table.type)
{
case PCF_METRICS:
metrics_data = font_data.slice(table.offset, table.size);
break;
case PCF_BITMAPS:
bitmap_data = font_data.slice(table.offset, table.size);
break;
case PCF_BDF_ENCODINGS:
encoding_data = font_data.slice(table.offset, table.size);
break;
}
}
if (metrics_data.empty() || bitmap_data.empty() || encoding_data.empty())
{
dwarnln("pcf font missing required table");
return BAN::Error::from_errno(EINVAL);
}
auto* font_ptr = new PCFFont;
if (font_ptr == nullptr)
return BAN::Error::from_errno(ENOMEM);
auto font = BAN::RefPtr<PCFFont>::adopt(font_ptr);
{
const auto& metrics_table = metrics_data.as<const pcf_metrics>();
const bool little_endian = !(metrics_table.format & (1 << 2));
const auto read_T =
[little_endian]<typename T>(T value) -> T
{
return little_endian
? BAN::little_endian_to_host(value)
: BAN::big_endian_to_host(value);
};
if (metrics_table.format & PCF_COMPRESSED_METRICS)
{
const auto count = read_T(metrics_table.compressed.count);
TRY(font->glyphs.resize(count));
for (size_t i = 0; i < count; i++)
{
const auto& entry = metrics_table.compressed.entries[i];
font->glyphs[i] = { .char_info = {
.leftSideBearing = static_cast<INT16>(entry.left_sided_bearing - 0x80),
.rightSideBearing = static_cast<INT16>(entry.right_side_bearing - 0x80),
.characterWidth = static_cast<INT16>(entry.character_width - 0x80),
.ascent = static_cast<INT16>(entry.character_ascent - 0x80),
.descent = static_cast<INT16>(entry.character_descent - 0x80),
.attributes = 0,
}};
}
}
else
{
const auto count = read_T(metrics_table.uncompressed.count);
TRY(font->glyphs.resize(count));
for (size_t i = 0; i < count; i++)
{
const auto& entry = metrics_table.uncompressed.entries[i];
font->glyphs[i] = { .char_info = {
.leftSideBearing = read_T(entry.left_sided_bearing),
.rightSideBearing = read_T(entry.right_side_bearing),
.characterWidth = read_T(entry.character_width),
.ascent = read_T(entry.character_ascent),
.descent = read_T(entry.character_descent),
.attributes = read_T(entry.character_attributes),
}};
}
}
}
{
const auto& bitmap_table = bitmap_data.as<const pcf_bitmap>();
const bool little_endian = !(bitmap_table.format & (1 << 2));
const auto read_T =
[little_endian]<typename T>(T value) -> T
{
return little_endian
? BAN::little_endian_to_host(value)
: BAN::big_endian_to_host(value);
};
const auto count = read_T(bitmap_table.glyph_count);
if (count != font->glyphs.size())
{
dwarnln("bitmap entry count and metric entry count mismatch");
return BAN::Error::from_errno(EINVAL);
}
const uint8_t scanline_pad = 1 << ((bitmap_table.format >> 0) & 3);
const uint8_t data_type = 1 << ((bitmap_table.format >> 4) & 3);
const uint8_t* bitmap_base = reinterpret_cast<const uint8_t*>(bitmap_table.glyph_offsets + count + 4);
for (size_t i = 0; i < count; i++)
{
const uint8_t* glyph_bitmap = bitmap_base + read_T(bitmap_table.glyph_offsets[i]);
const auto& char_info = font->glyphs[i].char_info;
const auto width = char_info.rightSideBearing - char_info.leftSideBearing;
const auto height = char_info.ascent + char_info.descent;
const auto bytes_per_scanline = (width + scanline_pad * 8 - 1) / (scanline_pad * 8) * scanline_pad;
font->glyphs[i].bitmap_offset = font->bitmap.size();
for (int32_t i = 0; i < height; i++)
{
const uint8_t* row_bytes = glyph_bitmap + i * bytes_per_scanline;
for (int32_t j = 0; j * data_type * 8 < width; j++)
{
uint64_t normalized;
switch (data_type)
{
case 1: normalized = row_bytes[j]; break;
case 2: normalized = read_T(reinterpret_cast<const uint16_t*>(row_bytes)[j]); break;
case 4: normalized = read_T(reinterpret_cast<const uint32_t*>(row_bytes)[j]); break;
case 8: normalized = read_T(reinterpret_cast<const uint64_t*>(row_bytes)[j]); break;
}
if (bitmap_table.format & (1 << 3))
normalized = reverse_bits(normalized, data_type * 8);
for (int32_t k = 0; k < data_type; k++)
TRY(font->bitmap.push_back(normalized >> (k * 8)));
}
}
}
(void)font->bitmap.shrink_to_fit();
}
{
const auto& encoding_table = encoding_data.as<const pcf_encoding>();
const bool little_endian = !(encoding_table.format & (1 << 2));
const auto read_T =
[little_endian]<typename T>(T value) -> T
{
return little_endian
? BAN::little_endian_to_host(value)
: BAN::big_endian_to_host(value);
};
font->min_char_or_byte2 = read_T(encoding_table.min_char_or_byte2);
font->max_char_or_byte2 = read_T(encoding_table.max_char_or_byte2);
font->min_byte1 = read_T(encoding_table.min_byte1);
font->max_byte1 = read_T(encoding_table.max_byte1);
font->default_char = read_T(encoding_table.default_char);
const auto rows = font->max_byte1 - font->min_byte1 + 1;
const auto cols = font->max_char_or_byte2 - font->min_char_or_byte2 + 1;
font->all_chars_exist = true;
for (size_t row = 0; row < rows; row++)
{
for (size_t col = 0; col < cols; col++)
{
const uint16_t glyph = read_T(encoding_table.glyph_indices[row * cols + col]);
if (glyph == 0xFFFF || glyph >= font->glyphs.size())
{
font->all_chars_exist = false;
continue;
}
const uint8_t byte1 = row + font->min_byte1;
const uint8_t byte2 = col + font->min_char_or_byte2;
const uint16_t codepoint = (byte1 << 8) | byte2;
TRY(font->map.push_back({
.codepoint = codepoint,
.glyph_index = glyph,
}));
}
}
(void)font->map.shrink_to_fit();
}
font->min_bounds = {
.leftSideBearing = INT16_MAX,
.rightSideBearing = INT16_MAX,
.characterWidth = INT16_MAX,
.ascent = INT16_MAX,
.descent = INT16_MAX,
};
font->max_bounds = {
.leftSideBearing = INT16_MIN,
.rightSideBearing = INT16_MIN,
.characterWidth = INT16_MIN,
.ascent = INT16_MIN,
.descent = INT16_MIN,
};
for (const auto& glyph : font->glyphs)
{
const auto& ci = glyph.char_info;
font->min_bounds.leftSideBearing = BAN::Math::min(font->min_bounds.leftSideBearing, ci.leftSideBearing);
font->min_bounds.rightSideBearing = BAN::Math::min(font->min_bounds.rightSideBearing, ci.rightSideBearing);
font->min_bounds.characterWidth = BAN::Math::min(font->min_bounds.characterWidth, ci.characterWidth);
font->min_bounds.ascent = BAN::Math::min(font->min_bounds.ascent, ci.ascent);
font->min_bounds.descent = BAN::Math::min(font->min_bounds.descent, ci.descent);
font->max_bounds.leftSideBearing = BAN::Math::max(font->max_bounds.leftSideBearing, ci.leftSideBearing);
font->max_bounds.rightSideBearing = BAN::Math::max(font->max_bounds.rightSideBearing, ci.rightSideBearing);
font->max_bounds.characterWidth = BAN::Math::max(font->max_bounds.characterWidth, ci.characterWidth);
font->max_bounds.ascent = BAN::Math::max(font->max_bounds.ascent, ci.ascent);
font->max_bounds.descent = BAN::Math::max(font->max_bounds.descent, ci.descent);
}
font->font_ascent = font->max_bounds.ascent;
font->font_descent = font->max_bounds.descent;
return font;
}
__attribute__((constructor))
static void initialize_fonts()
{
const char* font_path = "fonts/misc";
do
{
char fonts_dir[PATH_MAX];
sprintf(fonts_dir, "%s/fonts.dir", font_path);
FILE* fp = fopen(fonts_dir, "r");
if (fp == NULL)
{
perror("fopen");
break;
}
for (;;)
{
char buffer[1024];
if (fgets(buffer, sizeof(buffer), fp) == NULL)
break;
if (buffer[0] == '\n')
continue;
char file[512], name[512], dummy;
if (sscanf(buffer, "%s %s%c", file, name, &dummy) != 3 || dummy != '\n')
continue;
auto path = MUST(BAN::String::formatted("{}/{}", font_path, file));
struct stat st;
if (stat(path.data(), &st) != 0)
continue;
MUST(s_available_fonts.insert(BAN::String(name), BAN::move(path)));
}
fclose(fp);
} while (false);
do
{
char fonts_alias[PATH_MAX];
sprintf(fonts_alias, "%s/fonts.alias", font_path);
FILE* fp = fopen(fonts_alias, "r");
if (fp == NULL)
{
perror("fopen");
break;
}
for (;;)
{
char buffer[1024];
if (fgets(buffer, sizeof(buffer), fp) == NULL)
break;
if (buffer[0] == '\n')
continue;
char alias[512], name[512], dummy;
if (sscanf(buffer, "%s %s%c", alias, name, &dummy) != 3 || dummy != '\n')
continue;
auto it = s_available_fonts.find(BAN::String(name));
if (it == s_available_fonts.end())
continue;
MUST(s_available_fonts.insert(BAN::String(alias), it->value));
}
fclose(fp);
} while(false);
printf("found %zu fonts and aliases\n", s_available_fonts.size());
}
static bool matches_pattern(BAN::StringView pattern, BAN::StringView string)
{
while (!pattern.empty())
{
switch (pattern.front())
{
case '*':
{
ssize_t len = string.size();
while (len >= 0)
if (matches_pattern(pattern.substring(1), string.substring(len--)))
return true;
return false;
}
case '?':
{
if (string.empty())
return false;
pattern = pattern.substring(1);
string = string.substring(1);
continue;
}
}
if (string.empty() || tolower(pattern.front()) != tolower(string.front()))
return false;
pattern = pattern.substring(1);
string = string.substring(1);
}
return string.empty();
}
static BAN::ErrorOr<BAN::RefPtr<PCFFont>> get_fontable(Client& client_info, CARD32 fid, BYTE opcode)
{
auto it = g_objects.find(fid);
if (it != g_objects.end() && it->value->type == Object::Type::GraphicsContext)
return get_fontable(client_info, it->value->object.get<Object::GraphicsContext>().font, opcode);
if (it == g_objects.end() || it->value->type != Object::Type::Font)
{
xError error {
.type = X_Error,
.errorCode = BadFont,
.sequenceNumber = client_info.sequence,
.resourceID = fid,
.minorCode = 0,
.majorCode = opcode,
};
TRY(encode(client_info.output_buffer, error));
return BAN::Error::from_errno(ENOENT);
}
return it->value->object.get<BAN::RefPtr<PCFFont>>();
}
BAN::ErrorOr<void> open_font(Client& client_info, BAN::ConstByteSpan packet)
{
auto request = decode<xOpenFontReq>(packet).value();
BAN::String pattern = BAN::StringView((char*)packet.data(), request.nbytes);
dprintln("OpenFont");
dprintln(" fid: {}", request.fid);
dprintln(" pattern: {}", pattern);
for (const auto& [name, path] : s_available_fonts)
{
if (!matches_pattern(pattern, name))
continue;
BAN::RefPtr<PCFFont> font;
if (auto it = s_loaded_fonts.find(path); it != s_loaded_fonts.end())
font = it->value.lock();
if (!font)
{
auto font_or_error = parse_font(path);
if (font_or_error.is_error())
continue;
font = font_or_error.release_value();
TRY(s_loaded_fonts.insert_or_assign(path, TRY((font->get_weak_ptr()))));
}
TRY(client_info.objects.insert(request.fid));
TRY(g_objects.insert(
request.fid,
TRY(BAN::UniqPtr<Object>::create(Object {
.type = Object::Type::Font,
.object = font,
}))
));
return {};
}
xError error {
.type = X_Error,
.errorCode = BadName,
.sequenceNumber = client_info.sequence,
.resourceID = 0,
.minorCode = 0,
.majorCode = X_OpenFont,
};
TRY(encode(client_info.output_buffer, error));
return {};
}
BAN::ErrorOr<void> close_font(Client& client_info, BAN::ConstByteSpan packet)
{
const CARD32 fid = packet.as_span<const CARD32>()[1];
dprintln("CloseFont");
dprintln(" fid: {}", fid);
(void)TRY(get_fontable(client_info, fid, X_CloseFont));
client_info.objects.remove(fid);
g_objects.remove(fid);
return {};
}
BAN::ErrorOr<void> query_font(Client& client_info, BAN::ConstByteSpan packet)
{
const CARD32 fid = packet.as_span<const CARD32>()[1];
dprintln("QueryFont");
dprintln(" fid: {}", fid);
auto font = TRY(get_fontable(client_info, fid, X_QueryFont));
xQueryFontReply reply {
.type = X_Reply,
.sequenceNumber = client_info.sequence,
.length = static_cast<CARD32>(7 + 3 * font->glyphs.size()),
.minBounds = font->min_bounds,
.maxBounds = font->max_bounds,
.minCharOrByte2 = font->min_char_or_byte2,
.maxCharOrByte2 = font->max_char_or_byte2,
.defaultChar = font->default_char,
.nFontProps = 0, // TODO
.drawDirection = FontLeftToRight,
.minByte1 = font->min_byte1,
.maxByte1 = font->max_byte1,
.allCharsExist = font->all_chars_exist,
.fontAscent = font->font_ascent,
.fontDescent = font->font_descent,
.nCharInfos = static_cast<CARD32>(font->glyphs.size()),
};
TRY(encode(client_info.output_buffer, reply));
for (const auto& glyph : font->glyphs)
TRY(encode(client_info.output_buffer, glyph.char_info));
return {};
}
BAN::ErrorOr<void> list_fonts(Client& client_info, BAN::ConstByteSpan packet)
{
auto request = decode<xListFontsReq>(packet).value();
BAN::String pattern = BAN::StringView((char*)packet.data(), request.nbytes);
dprintln("ListFonts");
dprintln(" maxNames: {}", request.maxNames);
dprintln(" pattern: {}", pattern);
CARD16 nfonts = 0;
BAN::Vector<uint8_t> result;
for (const auto& [name, _] : s_available_fonts)
{
if (nfonts == request.maxNames)
break;
if (!matches_pattern(pattern, name))
continue;
TRY(encode<BYTE>(result, name.size()));
TRY(encode(result, name));
nfonts++;
}
xListFontsReply reply {
.type = X_Reply,
.sequenceNumber = client_info.sequence,
.length = static_cast<CARD32>((result.size() + 3) / 4),
.nFonts = nfonts,
};
TRY(encode(client_info.output_buffer, reply));
TRY(encode(client_info.output_buffer, result));
for (size_t i = 0; (result.size() + i) % 4; i++)
TRY(encode(client_info.output_buffer, '\0'));
return {};
}
BAN::ErrorOr<void> list_fonts_with_info(Client& client_info, BAN::ConstByteSpan packet)
{
auto request = decode<xListFontsWithInfoReq>(packet).value();
BAN::String pattern = BAN::StringView((char*)packet.data(), request.nbytes);
dprintln("ListFontsWithInfo");
dprintln(" maxNames: {}", request.maxNames);
dprintln(" pattern: {}", pattern);
size_t nfonts = 0;
for (const auto& [name, path] : s_available_fonts)
{
if (nfonts == request.maxNames)
break;
if (!matches_pattern(pattern, name))
continue;
BAN::RefPtr<PCFFont> font;
if (auto it = s_loaded_fonts.find(path); it != s_loaded_fonts.end())
font = it->value.lock();
if (!font)
{
auto font_or_error = parse_font(path);
if (font_or_error.is_error())
continue;
font = font_or_error.release_value();
}
xListFontsWithInfoReply reply {
.type = X_Reply,
.nameLength = static_cast<BYTE>(name.size()),
.sequenceNumber = client_info.sequence,
.length = static_cast<CARD32>(7 + (name.size() + 3) / 4),
.minBounds = font->min_bounds,
.maxBounds = font->max_bounds,
.minCharOrByte2 = font->min_char_or_byte2,
.maxCharOrByte2 = font->max_char_or_byte2,
.defaultChar = font->default_char,
.nFontProps = 0, // TODO
.drawDirection = FontLeftToRight,
.minByte1 = font->min_byte1,
.maxByte1 = font->max_byte1,
.allCharsExist = font->all_chars_exist,
.fontAscent = font->font_ascent,
.fontDescent = font->font_descent,
.nReplies = 1,
};
TRY(encode(client_info.output_buffer, reply));
TRY(encode(client_info.output_buffer, name));
for (size_t i = 0; (name.size() + i) % 4; i++)
TRY(encode(client_info.output_buffer, '\0'));
nfonts++;
}
xListFontsWithInfoReply reply {
.type = X_Reply,
.nameLength = 0,
.sequenceNumber = client_info.sequence,
.length = 7,
};
TRY(encode(client_info.output_buffer, reply));
return {};
}
struct WriteTextInfo
{
uint32_t string_len;
const uint8_t* string;
bool wide;
int32_t x;
int32_t y;
uint32_t* out_data_u32;
uint32_t out_w, out_h;
int32_t min_x;
int32_t max_x;
int32_t min_y;
int32_t max_y;
const PCFFont* font;
const Object::GraphicsContext& gc;
};
static void write_text(WriteTextInfo& info)
{
for (size_t i = 0; i < info.string_len; i++)
{
const uint16_t codepoint = info.wide ? (info.string[i * 2] << 8) | info.string[i * 2 + 1] : info.string[i];
auto glyph_index = info.font->find_glyph(codepoint);
if (!glyph_index.has_value())
continue;
const auto& glyph = info.font->glyphs[glyph_index.value()];
const auto& ci = glyph.char_info;
const auto width = ci.rightSideBearing - ci.leftSideBearing;
const auto height = ci.ascent + ci.descent;
const uint8_t* glyph_bitmap_base = info.font->bitmap.data() + glyph.bitmap_offset;
for (auto rel_y = 0; rel_y < height; rel_y++)
{
const uint8_t* glyph_row_base = glyph_bitmap_base + (width + 7) / 8 * rel_y;
for (auto rel_x = 0; rel_x < width; rel_x++)
{
const auto out_x = info.x + rel_x + ci.leftSideBearing;
const auto out_y = info.y + rel_y - ci.ascent;
if (out_x < 0 || out_y < 0 || out_x >= info.out_w || out_y >= info.out_h)
continue;
if (info.gc.is_clipped(out_x, out_y))
continue;
const auto color = (glyph_row_base[rel_x / 8] & (1 << (rel_x % 8)))
? info.gc.foreground
: info.gc.background;
if (color != LibGUI::Texture::color_invisible)
info.out_data_u32[out_y * info.out_w + out_x] = color;
}
}
info.min_x = BAN::Math::min(info.min_x, info.x + ci.leftSideBearing);
info.max_x = BAN::Math::max(info.max_x, info.x + ci.rightSideBearing);
info.min_y = BAN::Math::min(info.min_y, info.y - ci.ascent);
info.max_y = BAN::Math::max(info.max_y, info.y + ci.descent);
info.x += ci.characterWidth;
}
}
BAN::ErrorOr<void> poly_text(Client& client_info, BAN::ConstByteSpan packet, bool wide)
{
const BYTE opcode = wide ? X_PolyText16 : X_PolyText8;
auto request = decode<xPolyTextReq>(packet).value();
dprintln("PolyText{}", wide ? "16" : "8");
dprintln(" drawable: {}", request.drawable);
dprintln(" gc: {}", request.gc);
dprintln(" x: {}", request.x);
dprintln(" y: {}", request.y);
auto [out_data_u32, out_w, out_h, _] = TRY(get_drawable_info(client_info, request.drawable, opcode));
const auto& gc = TRY_REF(get_gc(client_info, request.gc, opcode));
WriteTextInfo info {
.string_len = 0,
.string = nullptr,
.wide = wide,
.x = request.x,
.y = request.y,
.out_data_u32 = out_data_u32,
.out_w = out_w,
.out_h = out_h,
.min_x = INT32_MAX,
.max_x = INT32_MIN,
.min_y = INT32_MAX,
.max_y = INT32_MIN,
.font = nullptr,
.gc = gc,
};
while (!packet.empty())
{
if (packet[0] == 255)
{
if (packet.size() < 5)
break;
const CARD32 fid =
packet[1] << 24 |
packet[2] << 16 |
packet[3] << 8 |
packet[4] << 0;
info.font = TRY(get_fontable(client_info, fid, opcode)).ptr();
packet = packet.slice(5);
continue;
}
if (packet.size() < 2 + packet[0] * (1 + wide))
break;
if (info.font == nullptr)
info.font = TRY(get_fontable(client_info, gc.font, opcode)).ptr();
info.string_len = packet[0];
info.string = packet.data() + 2;
write_text(info);
info.x += packet[1];
packet = packet.slice(2 + packet[0] * (1 + wide));
}
if (g_objects[request.drawable]->type == Object::Type::Window)
invalidate_window(request.drawable, info.min_x, info.min_y, info.max_x - info.min_x + 1, info.max_y - info.min_y + 1);
return {};
}
BAN::ErrorOr<void> image_text(Client& client_info, BAN::ConstByteSpan packet, bool wide)
{
const BYTE opcode = wide ? X_ImageText16 : X_ImageText8;
auto request = decode<xImageTextReq>(packet).value();
dprintln("ImageText{}", wide ? "16" : "8");
dprintln(" nChars: {}", request.nChars);
dprintln(" drawable: {}", request.drawable);
dprintln(" gc: {}", request.gc);
dprintln(" x: {}", request.x);
dprintln(" y: {}", request.y);
auto [out_data_u32, out_w, out_h, _] = TRY(get_drawable_info(client_info, request.drawable, opcode));
const auto& gc = TRY_REF(get_gc(client_info, request.gc, opcode));
WriteTextInfo info {
.string_len = request.nChars,
.string = packet.data(),
.wide = wide,
.x = request.x,
.y = request.y,
.out_data_u32 = out_data_u32,
.out_w = out_w,
.out_h = out_h,
.min_x = INT32_MAX,
.max_x = INT32_MIN,
.min_y = INT32_MAX,
.max_y = INT32_MIN,
.font = TRY(get_fontable(client_info, gc.font, opcode)).ptr(),
.gc = gc,
};
write_text(info);
if (g_objects[request.drawable]->type == Object::Type::Window)
invalidate_window(request.drawable, info.min_x, info.min_y, info.max_x - info.min_x + 1, info.max_y - info.min_y + 1);
return {};
}
BAN::ErrorOr<void> create_glyph_cursor(Client& client_info, BAN::ConstByteSpan packet)
{
auto request = decode<xCreateGlyphCursorReq>(packet).value();
dprintln("CreateGlyphCursor");
dprintln(" cid: {}", request.cid);
dprintln(" source: {}", request.source);
dprintln(" mask: {}", request.mask);
dprintln(" sourceChar: {}", request.sourceChar);
dprintln(" maskChar: {}", request.maskChar);
dprintln(" foreRed: {}", request.foreRed);
dprintln(" foreGreen: {}", request.foreGreen);
dprintln(" foreBlue: {}", request.foreBlue);
dprintln(" backRed: {}", request.backRed);
dprintln(" backGreen: {}", request.backGreen);
dprintln(" backBlue: {}", request.backBlue);
const uint32_t foreground =
static_cast<uint32_t>(request.foreRed >> 8) << 16 |
static_cast<uint32_t>(request.foreBlue >> 8) << 8 |
static_cast<uint32_t>(request.foreGreen >> 8) << 0;
const uint32_t background =
static_cast<uint32_t>(request.backRed >> 8) << 16 |
static_cast<uint32_t>(request.backBlue >> 8) << 8 |
static_cast<uint32_t>(request.backGreen >> 8) << 0;
const auto& source_font = TRY(get_fontable(client_info, request.source, X_CreateGlyphCursor));
auto source_glyph_index = source_font->find_glyph(request.sourceChar);
if (!source_glyph_index.has_value())
{
xError error {
.type = X_Error,
.errorCode = BadValue,
.sequenceNumber = client_info.sequence,
.resourceID = request.source,
.minorCode = 0,
.majorCode = X_CreateGlyphCursor,
};
TRY(encode(client_info.output_buffer, error));
return {};
}
const auto& source_glyph = source_font->glyphs[source_glyph_index.value()];
const auto& source_ci = source_glyph.char_info;
const uint32_t source_width = source_ci.rightSideBearing - source_ci.leftSideBearing;
const uint32_t source_height = source_ci.ascent + source_ci.descent;
Object::Cursor cursor {
.width = source_width,
.height = source_height,
.origin_x = -source_ci.leftSideBearing,
.origin_y = source_ci.ascent,
};
TRY(cursor.pixels.resize(cursor.width * cursor.height));
for (uint32_t y = 0; y < source_height; y++)
{
const uint8_t* row_base = source_font->bitmap.data() + source_glyph.bitmap_offset + (source_width + 7) / 8 * y;
for (uint32_t x = 0; x < source_width; x++)
cursor.pixels[y * source_width + x] = 0xFF000000 | ((row_base[x / 8] & (1 << (x % 8))) ? foreground : background);
}
if (request.mask != None)
{
const auto& mask_font = TRY(get_fontable(client_info, request.mask, X_CreateGlyphCursor));
auto mask_glyph_index = mask_font->find_glyph(request.maskChar);
if (!mask_glyph_index.has_value())
{
xError error {
.type = X_Error,
.errorCode = BadValue,
.sequenceNumber = client_info.sequence,
.resourceID = request.mask,
.minorCode = 0,
.majorCode = X_CreateGlyphCursor,
};
TRY(encode(client_info.output_buffer, error));
return {};
}
const auto& mask_glyph = mask_font->glyphs[source_glyph_index.value()];
const auto& mask_ci = mask_glyph.char_info;
const int32_t mask_width = mask_ci.rightSideBearing - mask_ci.leftSideBearing;
const int32_t mask_height = mask_ci.ascent + mask_ci.descent;
for (int32_t src_y = 0; src_y < source_height; src_y++)
{
for (int32_t src_x = 0; src_x < source_width; src_x++)
{
const auto mask_x = src_x + source_ci.leftSideBearing - mask_ci.leftSideBearing;
const auto mask_y = src_y + source_ci.ascent - mask_ci.ascent;
if (mask_x < 0 || mask_y < 0 || mask_x >= mask_width || mask_y >= mask_height)
continue;
const uint8_t* row_base = mask_font->bitmap.data() + mask_glyph.bitmap_offset + (mask_width + 7) / 8 * mask_y;
if (!(row_base[mask_x / 8] & (1 << (mask_x % 8))))
cursor.pixels[src_y * source_width + src_x] = 0;
}
}
}
TRY(client_info.objects.insert(request.cid));
TRY(g_objects.insert(
request.cid,
TRY(BAN::UniqPtr<Object>::create(Object {
.type = Object::Type::Cursor,
.object = BAN::move(cursor),
}))
));
return {};
}