mirror of
https://github.com/mirkobrombin/raven.git
synced 2026-11-01 21:45:06 +00:00
fix: enumerate font families per character set with their Win32 names
This commit is contained in:
1 parent
1a9f5c089d
commit
91eed7db6c
3 files changed
+182
-82
No files matched your search
@@ -1536,16 +1536,6 @@ void NativeDesktop::register_gdi_api(ApiRegistry ®istry)
|
||||
for (const auto *name : {"StartDocW", "StartDocA", "EndDoc", "StartPage", "EndPage", "AbortDoc"})
|
||||
registry.add(name, 2, [](ApiCall &call) { call.fail(120, 0); });
|
||||
registry.add("GdiGetCodePage", 1, [](ApiCall &call) { call.ret(1252); });
|
||||
registry.add("GetTextCharsetInfo", 3, [=, this](ApiCall &call) {
|
||||
guarded(call, 0, [&] {
|
||||
require_dc(call, 0);
|
||||
if (call[1]) {
|
||||
std::vector<uint8_t> signature(24, 0);
|
||||
call.write(call[1], signature);
|
||||
}
|
||||
call.ret(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
+131
-42
@@ -73,14 +73,20 @@ std::vector<InstalledFamily> enumerable_families()
|
||||
|
||||
// The LOGFONT of an enumerated family and the NEWTEXTMETRICEX that follows the ENUMLOGFONTEX at metrics: the ANSI forms keep characters
|
||||
// in bytes, so their fields after tmLastChar sit four bytes earlier. Sizes are those of a 12-pixel em.
|
||||
void describe_family(std::vector<uint8_t> &record, size_t metrics, const InstalledFamily &family, bool ansi)
|
||||
void describe_family(std::vector<uint8_t> &record, size_t metrics, const InstalledFamily &family, bool ansi, uint8_t charset)
|
||||
{
|
||||
const auto put = [&](size_t at, int32_t value) { std::memcpy(record.data() + at, &value, 4); };
|
||||
const int32_t weight = int32_t(family.weight);
|
||||
put(0, -12);
|
||||
// The LOGFONT of a TrueType face as the enumeration gives it: the cell height and average width of the metrics below, stroke
|
||||
// output and clip precision and proof quality.
|
||||
put(0, 16);
|
||||
put(4, 7);
|
||||
put(16, weight);
|
||||
record[20] = family.italic;
|
||||
record[23] = 0;
|
||||
record[23] = charset;
|
||||
record[24] = 3;
|
||||
record[25] = 2;
|
||||
record[26] = 1;
|
||||
record[27] = family.fixed ? 0x31 : 0x22;
|
||||
put(metrics, 16);
|
||||
put(metrics + 4, 13);
|
||||
@@ -106,7 +112,7 @@ void describe_family(std::vector<uint8_t> &record, size_t metrics, const Install
|
||||
record[at] = family.italic;
|
||||
// TMPF_TRUETYPE | TMPF_VECTOR, and the bit Windows sets for a variable pitch, under the family kind.
|
||||
record[at + 3] = uint8_t((family.fixed ? 0x06 : 0x07) | (family.fixed ? 0x30 : 0x20));
|
||||
record[at + 4] = 0;
|
||||
record[at + 4] = charset;
|
||||
at += 8;
|
||||
put(at, (family.weight >= 600 ? 0x20 : 0) | (family.italic ? 0x01 : 0) | (family.weight < 600 && !family.italic ? 0x40 : 0));
|
||||
put(at + 4, 2048);
|
||||
@@ -125,6 +131,46 @@ std::u16string_view style_of(const InstalledFamily &family)
|
||||
return family.italic ? u"Italic" : u"Regular";
|
||||
}
|
||||
|
||||
// The character sets a face covers, from the code page bits of its OS/2 table in the order TranslateCharsetInfo gives them, each with
|
||||
// the script name EnumFontFamiliesEx reports. A symbol font has the symbol set alone; a face that claims none is Western.
|
||||
struct FontScript {
|
||||
uint8_t charset;
|
||||
const char16_t *script;
|
||||
};
|
||||
|
||||
std::vector<FontScript> scripts_of(const InstalledFamily &family)
|
||||
{
|
||||
static const struct { int bit; FontScript script; } bits[] = {
|
||||
{0, {0, u"Western"}}, {1, {238, u"Central European"}}, {2, {204, u"Cyrillic"}}, {3, {161, u"Greek"}}, {4, {162, u"Turkish"}},
|
||||
{5, {177, u"Hebrew"}}, {6, {178, u"Arabic"}}, {7, {186, u"Baltic"}}, {8, {163, u"Vietnamese"}}, {16, {222, u"Thai"}},
|
||||
{17, {128, u"Japanese"}}, {18, {134, u"CHINESE_GB2312"}}, {19, {129, u"Hangul"}}, {20, {136, u"CHINESE_BIG5"}},
|
||||
{21, {130, u"Hangul(Johab)"}}};
|
||||
if (family.code_pages[0] & 0x80000000U)
|
||||
return {{2, u"Symbol"}};
|
||||
std::vector<FontScript> scripts;
|
||||
for (const auto &entry : bits)
|
||||
if (family.code_pages[0] & (1U << entry.bit))
|
||||
scripts.push_back(entry.script);
|
||||
if (scripts.empty())
|
||||
scripts.push_back({0, u"Western"});
|
||||
return scripts;
|
||||
}
|
||||
|
||||
// The reserved last argument of EnumFontFamiliesEx, which callers pass as zero, carries this when EnumFontFamilies forwards to it.
|
||||
constexpr uint64_t once_marker = 0x52564e31;
|
||||
|
||||
// The records EnumFontFamiliesEx hands out for a family: one per character set it covers when DEFAULT_CHARSET (or no LOGFONT) asks
|
||||
// for any, the one for the set asked for otherwise, and the first alone for the older EnumFontFamilies, which lists a family once.
|
||||
std::vector<FontScript> enumerated_scripts(const InstalledFamily &family, int charset, bool once)
|
||||
{
|
||||
auto scripts = scripts_of(family);
|
||||
if (once)
|
||||
scripts.resize(1);
|
||||
else if (charset != 1)
|
||||
std::erase_if(scripts, [&](const FontScript &script) { return script.charset != charset; });
|
||||
return scripts;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void NativeDesktop::register_text_api(ApiRegistry ®istry)
|
||||
@@ -401,7 +447,7 @@ void NativeDesktop::register_text_api(ApiRegistry ®istry)
|
||||
bytes[at + 1] = font.definition[21];
|
||||
bytes[at + 2] = font.definition[22];
|
||||
bytes[at + 3] = info.pitch;
|
||||
bytes[at + 4] = font.definition[23];
|
||||
bytes[at + 4] = font.definition[23] == 1 ? 0 : font.definition[23];
|
||||
call.write(call[1], bytes);
|
||||
call.ret(1);
|
||||
});
|
||||
@@ -451,6 +497,36 @@ void NativeDesktop::register_text_api(ApiRegistry ®istry)
|
||||
call.ret(face.size() + 1);
|
||||
});
|
||||
});
|
||||
// GetTextCharsetInfo(dc, FONTSIGNATURE, flags): the character set of the selected font, and the scripts and code pages its OS/2 table
|
||||
// claims; a face the system does not enumerate stands in for the default face, which covers Latin.
|
||||
registry.add("GetTextCharsetInfo", 3, [=, this](ApiCall &call) {
|
||||
guarded(call, 1, [&] {
|
||||
auto &dc = require_dc(call, 0);
|
||||
const auto &font = selected_font(dc);
|
||||
std::u16string face;
|
||||
for (size_t at = 28; at + 1 < font.definition.size() && at < 28 + 64; at += 2) {
|
||||
const char16_t character = char16_t(font.definition[at] | font.definition[at + 1] << 8);
|
||||
if (!character)
|
||||
break;
|
||||
face.push_back(character);
|
||||
}
|
||||
InstalledFamily family{face};
|
||||
family.unicode_ranges[0] = 3;
|
||||
family.code_pages[0] = 1;
|
||||
const auto families = enumerable_families();
|
||||
if (const auto found = std::find_if(families.begin(), families.end(), [&](const InstalledFamily &item) { return equal_fold(item.name, face); });
|
||||
found != families.end())
|
||||
family = *found;
|
||||
if (call[1]) {
|
||||
std::vector<uint8_t> signature(24, 0);
|
||||
std::memcpy(signature.data(), family.unicode_ranges, 16);
|
||||
std::memcpy(signature.data() + 16, family.code_pages, 8);
|
||||
call.write(call[1], signature);
|
||||
}
|
||||
// DEFAULT_CHARSET is realized as the ANSI set; any other request comes back as asked.
|
||||
call.ret(font.definition[23] == 1 ? 0 : font.definition[23]);
|
||||
});
|
||||
});
|
||||
// GetGlyphOutlineW(dc, character, format, GLYPHMETRICS, size, buffer, MAT2): the metrics of a character of the selected font and its
|
||||
// bitmap, rendered as text draws it: GGO_METRICS (0), GGO_BITMAP (1, a bit per pixel) and GGO_GRAY2, GRAY4 and GRAY8 (4, 5, 6: 4, 16
|
||||
// and 64 levels a byte), rows padded to four bytes. Glyph indices, outlines and transforms other than the identity are not drawn:
|
||||
@@ -579,7 +655,7 @@ void NativeDesktop::register_text_api(ApiRegistry ®istry)
|
||||
const uint16_t characters[4] = {info.first, info.last, info.default_character, info.break_character};
|
||||
std::memcpy(bytes.data() + 48, characters, 8);
|
||||
bytes[56] = font.definition[20]; bytes[57] = font.definition[21]; bytes[58] = font.definition[22]; bytes[59] = info.pitch;
|
||||
bytes[60] = font.definition[23];
|
||||
bytes[60] = font.definition[23] == 1 ? 0 : font.definition[23];
|
||||
const int32_t em = std::max<int32_t>(info.height - info.internal_leading, 1);
|
||||
put32(76, (weight >= 700 ? 0x20 : 0) | (font.definition[20] ? 1 : 0) | 0x40);
|
||||
const uint32_t design_em = objects.text().logical_units_per_em(font.definition);
|
||||
@@ -785,7 +861,9 @@ void NativeDesktop::register_text_api(ApiRegistry ®istry)
|
||||
require_dc(call, 0);
|
||||
const auto families = enumerable_families();
|
||||
std::u16string wanted;
|
||||
int charset = 1;
|
||||
if (call[1]) {
|
||||
charset = call.read(call[1] + 23, 1)[0];
|
||||
const auto bytes = call.read(call[1] + 28, 64);
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
const char16_t c = char16_t(bytes[i * 2] | (bytes[i * 2 + 1] << 8));
|
||||
@@ -816,26 +894,30 @@ void NativeDesktop::register_text_api(ApiRegistry ®istry)
|
||||
for (const auto &family : families) {
|
||||
if (!wanted.empty() && lower(family.name) != lower(wanted))
|
||||
continue;
|
||||
// ENUMLOGFONTEXW (LOGFONTW, full name, style, script) followed by NEWTEXTMETRICEXW.
|
||||
std::vector<uint8_t> record(512, 0);
|
||||
describe_family(record, 348, family, false);
|
||||
const auto put_text = [&](size_t at, std::u16string_view text, size_t limit) {
|
||||
for (size_t i = 0; i < std::min(text.size(), limit); ++i) {
|
||||
record[at + i * 2] = uint8_t(text[i]);
|
||||
record[at + i * 2 + 1] = uint8_t(text[i] >> 8);
|
||||
}
|
||||
};
|
||||
put_text(28, family.name, 31);
|
||||
put_text(92, family.name, 63);
|
||||
put_text(92 + 128, style_of(family), 31);
|
||||
put_text(92 + 192, u"Western", 31);
|
||||
call.write(scratch, record);
|
||||
const uint64_t arguments[] = {scratch, scratch + 348, 4, call[3]};
|
||||
const auto outcome = invoke(call[2], arguments);
|
||||
if (outcome.stop != NativeStop::returned)
|
||||
throw outcome;
|
||||
result = outcome.value;
|
||||
if (!uint32_t(outcome.value))
|
||||
for (const auto &script : enumerated_scripts(family, charset, call[4] == once_marker)) {
|
||||
// ENUMLOGFONTEXW (LOGFONTW, full name, style, script) followed by NEWTEXTMETRICEXW.
|
||||
std::vector<uint8_t> record(512, 0);
|
||||
describe_family(record, 348, family, false, script.charset);
|
||||
const auto put_text = [&](size_t at, std::u16string_view text, size_t limit) {
|
||||
for (size_t i = 0; i < std::min(text.size(), limit); ++i) {
|
||||
record[at + i * 2] = uint8_t(text[i]);
|
||||
record[at + i * 2 + 1] = uint8_t(text[i] >> 8);
|
||||
}
|
||||
};
|
||||
put_text(28, family.name, 31);
|
||||
put_text(92, family.name, 63);
|
||||
put_text(92 + 128, style_of(family), 31);
|
||||
put_text(92 + 192, script.script, 31);
|
||||
call.write(scratch, record);
|
||||
const uint64_t arguments[] = {scratch, scratch + 348, 4, call[3]};
|
||||
const auto outcome = invoke(call[2], arguments);
|
||||
if (outcome.stop != NativeStop::returned)
|
||||
throw outcome;
|
||||
result = outcome.value;
|
||||
if (!uint32_t(outcome.value))
|
||||
break;
|
||||
}
|
||||
if (!uint32_t(result))
|
||||
break;
|
||||
}
|
||||
} catch (...) {
|
||||
@@ -869,7 +951,7 @@ void NativeDesktop::register_text_api(ApiRegistry ®istry)
|
||||
const auto block = process.map_memory(4096, PROT_READ | PROT_WRITE);
|
||||
try {
|
||||
call.write(block, logical);
|
||||
ApiCall inner(call.process, call.environment, {call[0], block, call[2], call[3], 0});
|
||||
ApiCall inner(call.process, call.environment, {call[0], block, call[2], call[3], once_marker});
|
||||
extended->second(inner);
|
||||
process.unmap_memory(block);
|
||||
call.ret(inner.result);
|
||||
@@ -889,7 +971,9 @@ void NativeDesktop::register_text_api(ApiRegistry ®istry)
|
||||
require_dc(call, 0);
|
||||
const auto families = enumerable_families();
|
||||
std::string wanted;
|
||||
int charset = 1;
|
||||
if (call[1]) {
|
||||
charset = call.read(call[1] + 23, 1)[0];
|
||||
const auto bytes = call.read(call[1] + 28, 32);
|
||||
for (size_t i = 0; i < 32 && bytes[i]; ++i)
|
||||
wanted.push_back(char(bytes[i]));
|
||||
@@ -911,21 +995,26 @@ void NativeDesktop::register_text_api(ApiRegistry ®istry)
|
||||
const auto name = to_ansi(family.name, 1252);
|
||||
if (!wanted.empty() && lower(name) != lower(wanted))
|
||||
continue;
|
||||
// ENUMLOGFONTEXA (LOGFONTA, full name, style, script) followed by NEWTEXTMETRICEXA.
|
||||
std::vector<uint8_t> record(512, 0);
|
||||
describe_family(record, 256, family, true);
|
||||
const auto style = to_ansi(style_of(family), 1252);
|
||||
std::memcpy(record.data() + 28, name.data(), std::min<size_t>(name.size(), 31));
|
||||
std::memcpy(record.data() + 60, name.data(), std::min<size_t>(name.size(), 63));
|
||||
std::memcpy(record.data() + 124, style.data(), std::min<size_t>(style.size(), 31));
|
||||
std::memcpy(record.data() + 156, "Western", 7);
|
||||
call.write(scratch, record);
|
||||
const uint64_t arguments[] = {scratch, scratch + 256, 4, call[3]};
|
||||
const auto outcome = invoke(call[2], arguments);
|
||||
if (outcome.stop != NativeStop::returned)
|
||||
throw outcome;
|
||||
result = outcome.value;
|
||||
if (!uint32_t(outcome.value))
|
||||
for (const auto &script : enumerated_scripts(family, charset, call[4] == once_marker)) {
|
||||
// ENUMLOGFONTEXA (LOGFONTA, full name, style, script) followed by NEWTEXTMETRICEXA.
|
||||
std::vector<uint8_t> record(512, 0);
|
||||
describe_family(record, 256, family, true, script.charset);
|
||||
const auto style = to_ansi(style_of(family), 1252);
|
||||
const auto script_name = to_ansi(script.script, 1252);
|
||||
std::memcpy(record.data() + 28, name.data(), std::min<size_t>(name.size(), 31));
|
||||
std::memcpy(record.data() + 60, name.data(), std::min<size_t>(name.size(), 63));
|
||||
std::memcpy(record.data() + 124, style.data(), std::min<size_t>(style.size(), 31));
|
||||
std::memcpy(record.data() + 156, script_name.data(), std::min<size_t>(script_name.size(), 31));
|
||||
call.write(scratch, record);
|
||||
const uint64_t arguments[] = {scratch, scratch + 256, 4, call[3]};
|
||||
const auto outcome = invoke(call[2], arguments);
|
||||
if (outcome.stop != NativeStop::returned)
|
||||
throw outcome;
|
||||
result = outcome.value;
|
||||
if (!uint32_t(outcome.value))
|
||||
break;
|
||||
}
|
||||
if (!uint32_t(result))
|
||||
break;
|
||||
}
|
||||
} catch (...) {
|
||||
|
||||
+51
-30
@@ -62,7 +62,40 @@ void put_text(std::vector<uint8_t> &bytes, const char *text)
|
||||
bytes.push_back(0);
|
||||
}
|
||||
|
||||
// The system fonts, as records the guest reads: sizes, weight, style, width, flags, then path, family, face, full and PostScript names.
|
||||
// The English name a face's name table gives for an id, in UTF-8: Windows names are UTF-16 big endian, Macintosh names single byte.
|
||||
std::string sfnt_name(FT_Face face, uint32_t id)
|
||||
{
|
||||
std::string result;
|
||||
const FT_UInt count = FT_Get_Sfnt_Name_Count(face);
|
||||
for (FT_UInt index = 0; index < count && result.empty(); ++index) {
|
||||
FT_SfntName name;
|
||||
if (FT_Get_Sfnt_Name(face, index, &name) || name.name_id != id)
|
||||
continue;
|
||||
if (name.platform_id == 3 && (name.language_id & 0x3ff) == 9) {
|
||||
for (FT_UInt at = 0; at + 1 < name.string_len; at += 2) {
|
||||
const uint32_t unit = uint32_t(name.string[at]) << 8 | name.string[at + 1];
|
||||
if (unit < 0x80)
|
||||
result.push_back(char(unit));
|
||||
else if (unit < 0x800) {
|
||||
result.push_back(char(0xc0 | unit >> 6));
|
||||
result.push_back(char(0x80 | (unit & 0x3f)));
|
||||
} else {
|
||||
result.push_back(char(0xe0 | unit >> 12));
|
||||
result.push_back(char(0x80 | ((unit >> 6) & 0x3f)));
|
||||
result.push_back(char(0x80 | (unit & 0x3f)));
|
||||
}
|
||||
}
|
||||
} else if (name.platform_id == 1 && name.language_id == 0) {
|
||||
result.assign(reinterpret_cast<const char *>(name.string), name.string_len);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// The system fonts, as records the guest reads: sizes, weight, style, width, flags, then path, family, face, full and PostScript names,
|
||||
// then the Win32 family and the typographic family and face. The family and face are the weight-stretch-style ones DirectWrite groups
|
||||
// by (name ids 21 and 22, else 1 and 2); GDI names a family by its Win32 name (id 1), which keeps Calibri Light or Arial Narrow apart
|
||||
// from Calibri and Arial, where FreeType's family name is the typographic one (id 16).
|
||||
// They are the fonts installed in the Windows Fonts folder of the prefix, which links the host's fonts and keeps the ones programs
|
||||
// install; without a prefix, the host's own font folders.
|
||||
std::vector<uint8_t> scan_fonts()
|
||||
@@ -112,7 +145,16 @@ std::vector<uint8_t> scan_fonts()
|
||||
}
|
||||
const uint32_t style = (face->style_flags & FT_STYLE_FLAG_ITALIC) ? 2 : 0;
|
||||
const uint32_t flags = (FT_IS_FIXED_WIDTH(face) ? 2U : 0U);
|
||||
InstalledFamily found{from_utf8(face->family_name), weight, style != 0, (flags & 2) != 0};
|
||||
const auto named = [&](uint32_t id, uint32_t fallback, const char *last) {
|
||||
auto text = sfnt_name(face, id);
|
||||
if (text.empty())
|
||||
text = sfnt_name(face, fallback);
|
||||
return text.empty() ? std::string(last ? last : "") : text;
|
||||
};
|
||||
const auto win32_family = named(1, 1, face->family_name), win32_face = named(2, 2, face->style_name);
|
||||
const auto family = named(21, 1, face->family_name), style_name = named(22, 2, face->style_name);
|
||||
const auto typographic_family = named(16, 1, face->family_name), typographic_face = named(17, 2, face->style_name);
|
||||
InstalledFamily found{from_utf8(win32_family), weight, style != 0, (flags & 2) != 0};
|
||||
if (const auto *os2 = static_cast<TT_OS2 *>(FT_Get_Sfnt_Table(face, FT_SFNT_OS2))) {
|
||||
found.unicode_ranges[0] = uint32_t(os2->ulUnicodeRange1);
|
||||
found.unicode_ranges[1] = uint32_t(os2->ulUnicodeRange2);
|
||||
@@ -135,11 +177,14 @@ std::vector<uint8_t> scan_fonts()
|
||||
put32(record, stretch);
|
||||
put32(record, flags);
|
||||
put_text(record, walk->path().c_str());
|
||||
put_text(record, face->family_name);
|
||||
put_text(record, face->style_name);
|
||||
std::string full = std::string(face->family_name) + (face->style_name ? std::string(" ") + face->style_name : std::string());
|
||||
put_text(record, family.c_str());
|
||||
put_text(record, style_name.c_str());
|
||||
const auto full = named(4, 4, (win32_family + (win32_face.empty() ? "" : " " + win32_face)).c_str());
|
||||
put_text(record, full.c_str());
|
||||
put_text(record, FT_Get_Postscript_Name(face));
|
||||
put_text(record, win32_family.c_str());
|
||||
put_text(record, typographic_family.c_str());
|
||||
put_text(record, typographic_face.c_str());
|
||||
const uint32_t size = uint32_t(record.size());
|
||||
std::memcpy(record.data(), &size, 4);
|
||||
cache.insert(cache.end(), record.begin(), record.end());
|
||||
@@ -448,31 +493,7 @@ void register_font_service(ApiRegistry ®istry, KernelContext &)
|
||||
call.ret(0);
|
||||
return;
|
||||
}
|
||||
std::string result;
|
||||
const FT_UInt count = FT_Get_Sfnt_Name_Count(font->face);
|
||||
for (FT_UInt index = 0; index < count && result.empty(); ++index) {
|
||||
FT_SfntName name;
|
||||
if (FT_Get_Sfnt_Name(font->face, index, &name) || name.name_id != call.u32(1))
|
||||
continue;
|
||||
// Windows names are UTF-16 big endian; Macintosh names are single byte.
|
||||
if (name.platform_id == 3 && (name.language_id & 0x3ff) == 9) {
|
||||
for (FT_UInt at = 0; at + 1 < name.string_len; at += 2) {
|
||||
const uint32_t unit = uint32_t(name.string[at]) << 8 | name.string[at + 1];
|
||||
if (unit < 0x80)
|
||||
result.push_back(char(unit));
|
||||
else if (unit < 0x800) {
|
||||
result.push_back(char(0xc0 | unit >> 6));
|
||||
result.push_back(char(0x80 | (unit & 0x3f)));
|
||||
} else {
|
||||
result.push_back(char(0xe0 | unit >> 12));
|
||||
result.push_back(char(0x80 | ((unit >> 6) & 0x3f)));
|
||||
result.push_back(char(0x80 | (unit & 0x3f)));
|
||||
}
|
||||
}
|
||||
} else if (name.platform_id == 1 && name.language_id == 0) {
|
||||
result.assign(reinterpret_cast<const char *>(name.string), name.string_len);
|
||||
}
|
||||
}
|
||||
std::string result = sfnt_name(font->face, call.u32(1));
|
||||
if (call[2] && call.u32(3) > result.size()) {
|
||||
result.push_back('\0');
|
||||
call.write(call[2], std::span<const uint8_t>(reinterpret_cast<const uint8_t *>(result.data()), result.size()));
|
||||
|
||||
Reference in new issue
Block a user