Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ members = [
"tests/examples/redis-plus-plus-async",
"tests/examples/sdl2",
"tests/examples/spdlog",
"tests/examples/fontconfig",
"tests/examples/freetype",
"tests/examples/glad",
"tests/examples/libaio",
Expand Down
78 changes: 78 additions & 0 deletions pkgs/f/freedesktop.fontconfig.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
-- freedesktop.fontconfig — font discovery and matching.
--
-- `compat.freetype` rasterizes a glyph and `compat.harfbuzz` shapes a run, but
-- neither can answer "give me a sans-serif that has CJK". That is fontconfig,
-- and it is what every toolkit above this layer links: cairo and pango both
-- require it, and any program that names a font rather than a font FILE needs
-- it.
--
-- ─────────────────────────────────────────────────────────────────────────
-- EVERY GENERATED FILE IS PRODUCED BY build.mcpp — NO python, sh OR gperf
--
-- Upstream's meson emits SEVEN artifacts through python and gperf. All seven
-- are text transforms over files already in the tarball, so the fork's
-- `build.mcpp` — C++ that mcpp compiles and runs — does them:
--
-- fcstdint.h a copy
-- fcalias.h/fcaliastail.h internal-alias headers (makealias.py)
-- fcftalias.h/… the same over fcfreetype.h
-- fcobjshash.h object-name lookup (cutout.py + gperf)
-- fclang.h 281 .orth files -> charset bitmaps
-- fccase.h Unicode case folding
-- src/fontconfig.cppm the C++23 module wrapper
--
-- THE TWO BIG ONES ARE VERIFIED BYTE-FOR-BYTE against upstream's own Python,
-- and the fork's CI re-runs those scripts and diffs on every push: `fclang.h`
-- is 4,897 lines, `fccase.h` is 368. A transliteration is only worth having if
-- something checks it against the original.
--
-- `fcobjshash.h` is a DECISION rather than a transliteration: upstream runs the
-- C preprocessor and gperf to build a perfect hash over 72 entries, and the one
-- consumer — `fcobjs.c` — calls `FcObjectTypeLookup(str, strlen(str))` and
-- reads a single field. A sorted table with a binary search has identical
-- semantics and removes both tools.
--
-- ─────────────────────────────────────────────────────────────────────────
-- THE RUNTIME PATHS ARE EMPTY, AND THAT IS THE USUAL DECISION
--
-- `FC_DEFAULT_FONTS`, `FC_FONTPATH`, `FC_CACHEDIR`, `CONFIGDIR` and
-- `FONTCONFIG_PATH` point into the build prefix upstream, which after
-- relocation is the HOST's fonts and the HOST's configuration — the same silent
-- host edge that gave Vulkan an llvmpipe device instead of the GPU.
--
-- fontconfig has the escape hatches that make empty workable: `FONTCONFIG_FILE`
-- (which config to read), `FONTCONFIG_PATH` (where configs live) and
-- `FONTCONFIG_SYSROOT` (a prefix over both), all read in fccfg.c. An ecosystem
-- that ships fonts declares them, exactly as xim:xkeyboard-config declares
-- XKB_CONFIG_ROOT — and until something does, `FcInit()` finds no fonts and
-- says so rather than rendering with the developer's.
--
-- ⚠ TWO FREETYPE PROBES ARE OFF, and this is worth knowing before someone
-- "fixes" them: `HAVE_FT_GET_BDF_PROPERTY` and `HAVE_FT_GET_PS_FONT_INFO` are
-- absent because `compat.freetype` does not build the BDF and Type1 modules.
-- Claiming them compiles cleanly and fails at LINK with eight undefined
-- references from fcfreetype.c. A probe answer has to describe THIS freetype,
-- not upstream freetype's default build.
package = {
spec = "1",
namespace = "freedesktop",
name = "fontconfig",
description = "fontconfig 2.15.0 — font discovery and matching, every generated file produced by build.mcpp",
licenses = {"MIT"},
repo = "https://github.com/mcpplibs/fontconfig",
type = "package",

xpm = {
linux = {
["2.15.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/fontconfig/releases/download/v2.15.0/fontconfig-2.15.0-mcpp3.tar.gz",
CN = "https://gitcode.com/mcpp-res/fontconfig/releases/download/2.15.0/fontconfig-2.15.0-mcpp3.tar.gz",
},
sha256 = "505938aeeafe92257ea2c08d0af0e091d25af3e8d872b0dc82478605cbf99233",
},
},
},

mcpp = "*/mcpp/fontconfig/mcpp.toml",
}
14 changes: 14 additions & 0 deletions tests/examples/fontconfig/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# fontconfig test member — the three generated tables.
[indices]
freedesktop = { path = "../../.." }

[package]
name = "fontconfig-tests"
version = "0.1.0"
standard = "c++23"

# `compat.expat` and `compat.freetype` arrive transitively; naming them here as
# version deps as well is fine because the package declares them the same way
# (not as path deps), unlike the wayland members.
[target.'cfg(linux)'.dependencies.freedesktop]
fontconfig = "2.15.0"
135 changes: 135 additions & 0 deletions tests/examples/fontconfig/tests/fontconfig.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// freedesktop.fontconfig — the three generated tables, exercised through the
// public API.
//
// WHAT CAN BE ASSERTED WITHOUT FONTS
//
// This package compiles the four runtime paths EMPTY on purpose (see the
// manifest), so `FcInit()` on a runner with no `FONTCONFIG_FILE` finds no
// configuration and no fonts. That is the intended behaviour, not a defect,
// and it means a font-matching test would only be asserting that the runner
// has a desktop.
//
// The three GENERATED tables need none of that, and they are what this fork
// exists to produce:
//
// fclang.h 281 .orth files compiled into charset bitmaps
// fccase.h Unicode case folding
// fcobjshash.h object-name → id, the gperf replacement
//
// Each is reachable through a public entry point that takes no config.

#ifdef __linux__

#include <fontconfig/fontconfig.h>

#include <cstdio>
#include <cstring>
#include <initializer_list>

namespace {

int failures = 0;

void check(bool ok, const char *what)
{
std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED");
if (!ok) {
++failures;
}
}

} // namespace

int main()
{
std::printf(" FcGetVersion() = %d\n", FcGetVersion());
check(FcGetVersion() >= 21500, "FcGetVersion reports 2.15.0 or newer");

// ── fcobjshash.h — the gperf replacement ─────────────────────────────
//
// Exercised through `FcNameParse`, which is the public route into the
// object table: the parser maps every "name=value" key through
// `FcObjectLookupIdByName`, which IS the generated lookup. `FcObject` and
// `FcObjectFromName` are internal (fcint.h), so reaching for them would
// test the table without testing that a consumer can get to it.
{
FcPattern *p = FcNameParse((const FcChar8 *) "Arial:pixelsize=12:slant=100");
check(p != nullptr, "fcobjshash: FcNameParse accepted three object names");
if (p != nullptr) {
FcChar8 *fam = nullptr;
double px = 0;
int slant = 0;
const bool gf = FcPatternGetString(p, FC_FAMILY, 0, &fam) == FcResultMatch;
const bool gp = FcPatternGetDouble(p, FC_PIXEL_SIZE, 0, &px) == FcResultMatch;
const bool gs = FcPatternGetInteger(p, FC_SLANT, 0, &slant) == FcResultMatch;
std::printf(" family=%s pixelsize=%g slant=%d\n",
gf && fam ? (const char *) fam : "(none)", px, slant);
check(gf && fam && std::strcmp((const char *) fam, "Arial") == 0,
"…and \"family\" round-tripped to the right object");
check(gp && px == 12.0, "…and \"pixelsize\" did too");
check(gs && slant == 100, "…and \"slant\" did too");
FcPatternDestroy(p);
}

// A name the table does not contain must not resolve to a builtin —
// fontconfig assigns it a dynamic id instead. This is the branch a
// binary search gets wrong when its bounds are off by one.
FcPattern *q = FcNameParse((const FcChar8 *) ":nosuchproperty=1");
check(q != nullptr, "…and an unknown property does not crash the parser");
if (q) FcPatternDestroy(q);
}

// ── fclang.h — the language charsets ─────────────────────────────────
// `FcLangGetCharSet` indexes straight into fcLangCharSets. Three languages
// with very different coverage, so a table that was truncated or misordered
// fails on at least one.
for (const char *l : {"en", "ru", "zh-cn"}) {
const FcCharSet *cs = FcLangGetCharSet((const FcChar8 *) l);
const FcChar32 n = cs ? FcCharSetCount(cs) : 0;
std::printf(" lang %-6s -> %u codepoints\n", l, n);
check(cs != nullptr && n > 0, "fclang: the language has a charset");
}
// Latin and Cyrillic must not be the same set — they are separate entries
// whose leaves were de-duplicated, and a dedup bug shows up here.
{
const FcCharSet *en = FcLangGetCharSet((const FcChar8 *) "en");
const FcCharSet *ru = FcLangGetCharSet((const FcChar8 *) "ru");
check(en && ru && !FcCharSetEqual(en, ru),
"…and English and Russian are different sets");
check(en && FcCharSetHasChar(en, 'A'), "…and English covers 'A'");
check(ru && FcCharSetHasChar(ru, 0x0416), "…and Russian covers U+0416");
check(en && !FcCharSetHasChar(en, 0x0416), "…and English does not");
}

// A language that does not exist must return null rather than index out of
// range — the fastpath table `fcLangCharSetRanges` is what decides that.
check(FcLangGetCharSet((const FcChar8 *) "zz-nowhere") == nullptr,
"fclang: an unknown language returns null");

// ── fccase.h — Unicode case folding ──────────────────────────────────
// `FcStrDowncase` walks fcCaseFold. ASCII exercises the EVEN_ODD/RANGE
// methods; U+0130 (Latin capital I with dot) is a FULL fold — one char to
// several — which is the method with its own byte pool.
{
FcChar8 *d = FcStrDowncase((const FcChar8 *) "ABC");
std::printf(" downcase(\"ABC\") = %s\n", d ? (const char *) d : "(null)");
check(d && std::strcmp((const char *) d, "abc") == 0,
"fccase: ASCII folds through the range methods");
if (d) FcStrFree(d);
}
{
// U+0410 CYRILLIC CAPITAL A -> U+0430, a two-byte fold.
FcChar8 *d = FcStrDowncase((const FcChar8 *) "\xd0\x90");
const bool ok = d && std::strcmp((const char *) d, "\xd0\xb0") == 0;
std::printf(" downcase(U+0410) = %s\n", ok ? "U+0430" : "(wrong)");
check(ok, "…and a Cyrillic capital folds to its lowercase");
if (d) FcStrFree(d);
}

std::printf("\n%d check(s) failed\n", failures);
return failures == 0 ? 0 : 1;
}

#else
int main() { return 0; }
#endif
Loading