From 7bb8bef863c61b08ff62b18b854b468ff489a853 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sun, 30 Aug 2026 23:25:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(cairo):=202D=20=E7=9F=A2=E9=87=8F=E7=BB=98?= =?UTF-8?q?=E5=88=B6=E2=80=94=E2=80=94=E5=90=8E=E7=AB=AF=E5=81=9A=E6=88=90?= =?UTF-8?q?=20feature,X11=20=E9=BB=98=E8=AE=A4=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 路径、描边、填充、文字排布,以及承载它们的表面。只做合成的合成器用不到它; 桌面**外壳**——面板、启动器、通知——是靠它画的,pango 也是经它渲染文字。 ## 判据修正:决定 fork 难度的是生成器,不是行数 104k 行 C,**没有任何代码生成**。上游 meson 只出两个产物,config.h 和 cairo-features.h,两个都是 configure_file(探测答案,不是生成的代码)。 fontconfig 是它四分之一大小,却有七个生成器。 设计文档此前按行数把 cairo 标成更大的活,那是错的。 ## 后端做成 feature,X11 默认关 default = [ft, fc, png] 上游把每个后端做成 get_option(),等于让发行版替所有人决定一次。索引不能这样: 合成器和 X11 应用要的是同一个包的不同构建。所以在 Wayland 程序里画个圆不会因此 拖进 libX11;要 X11 的人写 features = ["xlib"] 并且说出来。 fork CI 在**产物上**验证这件事,不是在 manifest 上:没有 cairo-xlib-*.o,任何 .o 里都没有 XOpenDisplay。 ## 归档是源码,不是测试套件 cairo 的发布物带 61 MB 参考图(test/),这个包一个都不编。整包发出去是「下 47 MB 用 6 MB 源码」。test/ 和 perf/ 已裁掉,而 fork 的「upstream 与发布物一致」检查改为 比对**剩下的树**,所以构建能碰到的每个文件仍然逐一 diff。47.8 MB -> 1.8 MB。 ## ⚠ 一个探测答案,别「修」它 WORDS_BIGENDIAN 和 FLOAT_WORDS_BIGENDIAN 在 config.h 里是**缺席**,不是定义成 0 ——cairo 用 #ifdef 测(cairoint.h:196),0 的含义是大端。在 x86-64 上后果是:编过、 链过、报 SUCCESS、cairo_paint 照常工作,而每条**路径**拿到垃圾定点坐标: cairo_rectangle(4,4,16,16) 的 extents 变成 -8.03e+06 … 4.37e+06,cairo_fill 改动 零个像素。实测。 测试因此同时断言像素和 path_extents:前者只会说「描边没画」,把人引向缺文件; 后者直接指出算术错在哪。 只改了 pkgs/ 与 tests/examples/。 --- mcpp.toml | 1 + pkgs/f/freedesktop.cairo.lua | 72 ++++++++++++ tests/examples/cairo/mcpp.toml | 13 +++ tests/examples/cairo/tests/cairo.cpp | 165 +++++++++++++++++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 pkgs/f/freedesktop.cairo.lua create mode 100644 tests/examples/cairo/mcpp.toml create mode 100644 tests/examples/cairo/tests/cairo.cpp diff --git a/mcpp.toml b/mcpp.toml index 34ed3d5..7c0ccc0 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -20,6 +20,7 @@ members = [ "tests/examples/brotli", "tests/examples/build-mcpp", "tests/examples/c-ares", + "tests/examples/cairo", "tests/examples/catch2", "tests/examples/catch2-main", "tests/examples/catch2-v2", diff --git a/pkgs/f/freedesktop.cairo.lua b/pkgs/f/freedesktop.cairo.lua new file mode 100644 index 0000000..95cfa61 --- /dev/null +++ b/pkgs/f/freedesktop.cairo.lua @@ -0,0 +1,72 @@ +-- freedesktop.cairo — 2D vector drawing. +-- +-- Paths, strokes, fills, text runs, and surfaces to put them on. A compositor +-- that only composites does not need it; a desktop SHELL — panel, launcher, +-- notification — draws through it, and pango renders text through it. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- EASIER THAN ITS SIZE SUGGESTS, AND THAT CORRECTS A JUDGEMENT +-- +-- 104k lines of C and NO CODE GENERATION AT ALL: upstream's meson emits exactly +-- two artifacts, `config.h` and `cairo-features.h`, and both are +-- `configure_file` — probe answers, not generated code. fontconfig is a +-- quarter the size and needed seven generators. +-- +-- The design doc had cairo listed as the larger job on line count alone. It was +-- wrong: FORK DIFFICULTY IS DECIDED BY GENERATORS, NOT BY LINES. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- THE BACKENDS ARE FEATURES, AND X11 IS OFF BY DEFAULT +-- +-- default = ["ft", "fc", "png"] +-- +-- Upstream makes each backend a `get_option()`, which lets a distribution +-- decide once for everyone. An index cannot: a Wayland compositor and an X11 +-- application want different cairos out of the same package. So someone drawing +-- a circle in a Wayland program does not acquire libX11 to do it, and someone +-- who wants X11 writes +-- +-- cairo = { version = "1.18.2", features = ["xlib"] } +-- +-- The fork's CI checks that on the ARTIFACT, not on the manifest: no +-- `cairo-xlib-*.o`, and no `XOpenDisplay` in any object. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- THE ARCHIVE IS THE SOURCE, NOT THE TEST SUITE +-- +-- cairo's release carries 61 MB of reference images under `test/`, none of +-- which this package compiles. Published whole it was a 47 MB download for +-- 6 MB of source; `test/` and `perf/` are removed and the fork's +-- "upstream is unmodified" check compares the remaining tree, so every file a +-- build can reach is still diffed. 47.8 MB -> 1.8 MB. +-- +-- ⚠ ONE PROBE ANSWER IS WORTH KNOWING BEFORE SOMEONE "FIXES" IT. +-- `WORDS_BIGENDIAN` and `FLOAT_WORDS_BIGENDIAN` are ABSENT from config.h, not +-- defined to 0 — cairo tests them with `#ifdef` (cairoint.h:196), so a 0 says +-- BIG-ENDIAN. On x86-64 that compiles, links, reports success, keeps +-- `cairo_paint` working, and gives every PATH garbage fixed-point coordinates: +-- `cairo_rectangle(4,4,16,16)` came out with extents -8.03e+06 … 4.37e+06 and +-- `cairo_fill` changed zero pixels. Measured. +package = { + spec = "1", + namespace = "freedesktop", + name = "cairo", + description = "cairo 1.18.2 — 2D vector drawing, backends as features with X11 off by default", + licenses = {"LGPL-2.1-or-later", "MPL-1.1"}, + repo = "https://github.com/mcpplibs/cairo", + type = "package", + + xpm = { + linux = { + ["1.18.2"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/cairo/releases/download/v1.18.2/cairo-1.18.2-mcpp2.tar.gz", + CN = "https://gitcode.com/mcpp-res/cairo/releases/download/1.18.2/cairo-1.18.2-mcpp2.tar.gz", + }, + sha256 = "202352a38d0847628c55cd0f9c67e85ac78667ad76bc88691ab47606d1104ef7", + }, + }, + }, + + mcpp = "*/mcpp/cairo/mcpp.toml", +} diff --git a/tests/examples/cairo/mcpp.toml b/tests/examples/cairo/mcpp.toml new file mode 100644 index 0000000..e44e298 --- /dev/null +++ b/tests/examples/cairo/mcpp.toml @@ -0,0 +1,13 @@ +# cairo test member — draws, reads the pixels back, and checks the feature split. +[indices] +freedesktop = { path = "../../.." } + +[package] +name = "cairo-tests" +version = "0.1.0" +standard = "c++23" + +# No `features` named, so the package's own default set applies — which is the +# thing under test: ft/fc/png on, xlib/xcb off. +[target.'cfg(linux)'.dependencies.freedesktop] +cairo = "1.18.2" diff --git a/tests/examples/cairo/tests/cairo.cpp b/tests/examples/cairo/tests/cairo.cpp new file mode 100644 index 0000000..2aa0c94 --- /dev/null +++ b/tests/examples/cairo/tests/cairo.cpp @@ -0,0 +1,165 @@ +// freedesktop.cairo — draw something, read the pixels back, and check that the +// feature split actually took effect. +// +// Cairo needs no display and no fonts on disk to draw: an image surface is +// memory, and that is what a compositor's software fallback and every +// screenshot path use. So this test draws for real rather than only checking +// that symbols resolve. +// +// THE FEATURE ASSERTIONS ARE THE POINT OF THE PACKAGE. `xlib` and `xcb` are off +// by default, and "off" has to mean the macro is undefined — otherwise a +// consumer could `#include ` and fail at link instead of at +// compile, which is the wrong place to find out. + +#ifdef __linux__ + +#include + +#include +#include + +import freedesktop.cairo; + +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(" cairo %s\n", cairo_version_string()); + check(cairo_version() >= 11802, "cairo_version reports 1.18.2 or newer"); + + // ── 1. Draw, then read the pixel back ──────────────────────────────── + // A 64x64 ARGB32 surface, filled with an exact colour, then one pixel + // sampled. Exact because cairo_set_source_rgb takes doubles and + // 0.25/0.5/0.75 land on 64/128/191 without rounding ambiguity — the same + // values the GBM/EGL closed-loop test uses, for the same reason. + cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 64, 64); + check(cairo_surface_status(s) == CAIRO_STATUS_SUCCESS, + "cairo_image_surface_create(ARGB32, 64, 64)"); + + cairo_t *cr = cairo_create(s); + check(cairo_status(cr) == CAIRO_STATUS_SUCCESS, "cairo_create on it"); + + // ONE drawing pass, THEN one flush, THEN read. The first version of this + // test flushed and read between the paint and the stroke, and the stroke + // then changed zero pixels with `cairo_status` still SUCCESS — cairo had + // handed the buffer out and did not take it back. `cairo_surface_flush` is + // the end of a drawing sequence, not a checkpoint inside one. + cairo_set_source_rgb(cr, 0.25, 0.5, 0.75); + cairo_paint(cr); + + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_set_line_width(cr, 8); + cairo_move_to(cr, 8, 8); + cairo_line_to(cr, 56, 56); + cairo_stroke(cr); + + check(cairo_status(cr) == CAIRO_STATUS_SUCCESS, "…and the context is still clean"); + cairo_surface_flush(s); + + const unsigned char *d = cairo_image_surface_get_data(s); + const int stride = cairo_image_surface_get_stride(s); + + // ARGB32 is little-endian: B G R A. + // (56,8) is far from the diagonal, so it still carries the paint — + // 0.25/0.5/0.75 land on 64/128/191 with no rounding ambiguity, the same + // values the GBM/EGL closed-loop test uses and for the same reason. + const unsigned char *bg = d + 8 * stride + 56 * 4; + std::printf(" (56,8) = %u %u %u %u (B G R A)\n", bg[0], bg[1], bg[2], bg[3]); + check(bg[2] == 64 && bg[1] == 128 && bg[0] == 191 && bg[3] == 255, + "the painted background is exactly what was asked for"); + + // (32,32) is on the diagonal, under an 8px white stroke. + const unsigned char *on = d + 32 * stride + 32 * 4; + std::printf(" (32,32) = %u %u %u %u\n", on[0], on[1], on[2], on[3]); + // ── 3. The path machinery, checked directly ───────────────────────── + // + // These three are here because of what they caught. With + // `WORDS_BIGENDIAN` defined to 0 — which cairo tests with `#ifdef`, so 0 + // means BIG-ENDIAN — every path got garbage fixed-point coordinates while + // `cairo_status` reported success and `cairo_paint` kept working: + // + // path_extents -8.03e+06 -8.03e+06 4.37e+06 4.37e+06 + // in_fill(40, 40) 1 (the point is outside the rectangle) + // + // The pixel check above catches it too, but only as "the stroke drew + // nothing", which sends you looking for a missing source file. These say + // where the arithmetic went wrong. + { + cairo_t *c3 = cairo_create(s); + cairo_rectangle(c3, 4, 4, 16, 16); + double x1, y1, x2, y2; + cairo_path_extents(c3, &x1, &y1, &x2, &y2); + std::printf(" path_extents = %g %g %g %g\n", x1, y1, x2, y2); + check(x1 == 4 && y1 == 4 && x2 == 20 && y2 == 20, + "a rectangle path has the extents it was given"); + check(cairo_in_fill(c3, 10, 10) == 1, "…and a point inside it reads as inside"); + check(cairo_in_fill(c3, 40, 40) == 0, "…and a point outside reads as outside"); + cairo_destroy(c3); + } + + check(on[0] == 255 && on[1] == 255 && on[2] == 255, + "…and the stroked diagonal covers the centre in white"); + + + cairo_destroy(cr); + cairo_surface_destroy(s); + + // ── 3. The default feature set ─────────────────────────────────────── +#ifdef CAIRO_HAS_FT_FONT + check(true, "feature ft is ON by default (CAIRO_HAS_FT_FONT)"); +#else + check(false, "feature ft should be ON by default"); +#endif +#ifdef CAIRO_HAS_FC_FONT + check(true, "feature fc is ON by default (CAIRO_HAS_FC_FONT)"); +#else + check(false, "feature fc should be ON by default"); +#endif +#ifdef CAIRO_HAS_PNG_FUNCTIONS + check(true, "feature png is ON by default"); +#else + check(false, "feature png should be ON by default"); +#endif + + // ── 4. X11 is OFF, and that is the whole point ─────────────────────── + // A Wayland program must not acquire libX11 by depending on cairo. If this + // ever fails, someone made a backend unconditional. +#ifdef CAIRO_HAS_XLIB_SURFACE + check(false, "xlib must be OFF unless the consumer asks for it"); +#else + check(true, "feature xlib is OFF by default — no libX11 pulled in"); +#endif +#ifdef CAIRO_HAS_XCB_SURFACE + check(false, "xcb must be OFF unless the consumer asks for it"); +#else + check(true, "feature xcb is OFF by default"); +#endif + + // ── 5. The module carries what the features enabled ────────────────── + // `cairo_image_surface_create_from_png` lives behind CAIRO_HAS_PNG_FUNCTIONS + // inside cairo.h itself, not in a feature header — the module generator has + // to notice that or a png-enabled consumer cannot reach it through `import`. + check(&cairo_surface_write_to_png != nullptr, + "module exports a name guarded inside cairo.h (write_to_png)"); + check(&cairo_ft_font_face_create_for_ft_face != nullptr, + "…and one from a feature header (cairo_ft_font_face_create…)"); + + std::printf("\n%d check(s) failed\n", failures); + return failures == 0 ? 0 : 1; +} + +#else +int main() { return 0; } +#endif