Skip to content

Commit afde8dd

Browse files
authored
feat(cairo): 2D 矢量绘制——后端做成 feature,X11 默认关 (#312)
路径、描边、填充、文字排布,以及承载它们的表面。只做合成的合成器用不到它; 桌面**外壳**——面板、启动器、通知——是靠它画的,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/。
1 parent f25384a commit afde8dd

4 files changed

Lines changed: 251 additions & 0 deletions

File tree

mcpp.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ members = [
2020
"tests/examples/brotli",
2121
"tests/examples/build-mcpp",
2222
"tests/examples/c-ares",
23+
"tests/examples/cairo",
2324
"tests/examples/catch2",
2425
"tests/examples/catch2-main",
2526
"tests/examples/catch2-v2",

pkgs/f/freedesktop.cairo.lua

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
-- freedesktop.cairo — 2D vector drawing.
2+
--
3+
-- Paths, strokes, fills, text runs, and surfaces to put them on. A compositor
4+
-- that only composites does not need it; a desktop SHELL — panel, launcher,
5+
-- notification — draws through it, and pango renders text through it.
6+
--
7+
-- ─────────────────────────────────────────────────────────────────────────
8+
-- EASIER THAN ITS SIZE SUGGESTS, AND THAT CORRECTS A JUDGEMENT
9+
--
10+
-- 104k lines of C and NO CODE GENERATION AT ALL: upstream's meson emits exactly
11+
-- two artifacts, `config.h` and `cairo-features.h`, and both are
12+
-- `configure_file` — probe answers, not generated code. fontconfig is a
13+
-- quarter the size and needed seven generators.
14+
--
15+
-- The design doc had cairo listed as the larger job on line count alone. It was
16+
-- wrong: FORK DIFFICULTY IS DECIDED BY GENERATORS, NOT BY LINES.
17+
--
18+
-- ─────────────────────────────────────────────────────────────────────────
19+
-- THE BACKENDS ARE FEATURES, AND X11 IS OFF BY DEFAULT
20+
--
21+
-- default = ["ft", "fc", "png"]
22+
--
23+
-- Upstream makes each backend a `get_option()`, which lets a distribution
24+
-- decide once for everyone. An index cannot: a Wayland compositor and an X11
25+
-- application want different cairos out of the same package. So someone drawing
26+
-- a circle in a Wayland program does not acquire libX11 to do it, and someone
27+
-- who wants X11 writes
28+
--
29+
-- cairo = { version = "1.18.2", features = ["xlib"] }
30+
--
31+
-- The fork's CI checks that on the ARTIFACT, not on the manifest: no
32+
-- `cairo-xlib-*.o`, and no `XOpenDisplay` in any object.
33+
--
34+
-- ─────────────────────────────────────────────────────────────────────────
35+
-- THE ARCHIVE IS THE SOURCE, NOT THE TEST SUITE
36+
--
37+
-- cairo's release carries 61 MB of reference images under `test/`, none of
38+
-- which this package compiles. Published whole it was a 47 MB download for
39+
-- 6 MB of source; `test/` and `perf/` are removed and the fork's
40+
-- "upstream is unmodified" check compares the remaining tree, so every file a
41+
-- build can reach is still diffed. 47.8 MB -> 1.8 MB.
42+
--
43+
-- ⚠ ONE PROBE ANSWER IS WORTH KNOWING BEFORE SOMEONE "FIXES" IT.
44+
-- `WORDS_BIGENDIAN` and `FLOAT_WORDS_BIGENDIAN` are ABSENT from config.h, not
45+
-- defined to 0 — cairo tests them with `#ifdef` (cairoint.h:196), so a 0 says
46+
-- BIG-ENDIAN. On x86-64 that compiles, links, reports success, keeps
47+
-- `cairo_paint` working, and gives every PATH garbage fixed-point coordinates:
48+
-- `cairo_rectangle(4,4,16,16)` came out with extents -8.03e+06 … 4.37e+06 and
49+
-- `cairo_fill` changed zero pixels. Measured.
50+
package = {
51+
spec = "1",
52+
namespace = "freedesktop",
53+
name = "cairo",
54+
description = "cairo 1.18.2 — 2D vector drawing, backends as features with X11 off by default",
55+
licenses = {"LGPL-2.1-or-later", "MPL-1.1"},
56+
repo = "https://github.com/mcpplibs/cairo",
57+
type = "package",
58+
59+
xpm = {
60+
linux = {
61+
["1.18.2"] = {
62+
url = {
63+
GLOBAL = "https://github.com/mcpplibs/cairo/releases/download/v1.18.2/cairo-1.18.2-mcpp2.tar.gz",
64+
CN = "https://gitcode.com/mcpp-res/cairo/releases/download/1.18.2/cairo-1.18.2-mcpp2.tar.gz",
65+
},
66+
sha256 = "202352a38d0847628c55cd0f9c67e85ac78667ad76bc88691ab47606d1104ef7",
67+
},
68+
},
69+
},
70+
71+
mcpp = "*/mcpp/cairo/mcpp.toml",
72+
}

tests/examples/cairo/mcpp.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# cairo test member — draws, reads the pixels back, and checks the feature split.
2+
[indices]
3+
freedesktop = { path = "../../.." }
4+
5+
[package]
6+
name = "cairo-tests"
7+
version = "0.1.0"
8+
standard = "c++23"
9+
10+
# No `features` named, so the package's own default set applies — which is the
11+
# thing under test: ft/fc/png on, xlib/xcb off.
12+
[target.'cfg(linux)'.dependencies.freedesktop]
13+
cairo = "1.18.2"
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// freedesktop.cairo — draw something, read the pixels back, and check that the
2+
// feature split actually took effect.
3+
//
4+
// Cairo needs no display and no fonts on disk to draw: an image surface is
5+
// memory, and that is what a compositor's software fallback and every
6+
// screenshot path use. So this test draws for real rather than only checking
7+
// that symbols resolve.
8+
//
9+
// THE FEATURE ASSERTIONS ARE THE POINT OF THE PACKAGE. `xlib` and `xcb` are off
10+
// by default, and "off" has to mean the macro is undefined — otherwise a
11+
// consumer could `#include <cairo-xlib.h>` and fail at link instead of at
12+
// compile, which is the wrong place to find out.
13+
14+
#ifdef __linux__
15+
16+
#include <cairo.h>
17+
18+
#include <cstdio>
19+
#include <cstring>
20+
21+
import freedesktop.cairo;
22+
23+
namespace {
24+
25+
int failures = 0;
26+
27+
void check(bool ok, const char *what)
28+
{
29+
std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED");
30+
if (!ok) {
31+
++failures;
32+
}
33+
}
34+
35+
} // namespace
36+
37+
int main()
38+
{
39+
std::printf(" cairo %s\n", cairo_version_string());
40+
check(cairo_version() >= 11802, "cairo_version reports 1.18.2 or newer");
41+
42+
// ── 1. Draw, then read the pixel back ────────────────────────────────
43+
// A 64x64 ARGB32 surface, filled with an exact colour, then one pixel
44+
// sampled. Exact because cairo_set_source_rgb takes doubles and
45+
// 0.25/0.5/0.75 land on 64/128/191 without rounding ambiguity — the same
46+
// values the GBM/EGL closed-loop test uses, for the same reason.
47+
cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 64, 64);
48+
check(cairo_surface_status(s) == CAIRO_STATUS_SUCCESS,
49+
"cairo_image_surface_create(ARGB32, 64, 64)");
50+
51+
cairo_t *cr = cairo_create(s);
52+
check(cairo_status(cr) == CAIRO_STATUS_SUCCESS, "cairo_create on it");
53+
54+
// ONE drawing pass, THEN one flush, THEN read. The first version of this
55+
// test flushed and read between the paint and the stroke, and the stroke
56+
// then changed zero pixels with `cairo_status` still SUCCESS — cairo had
57+
// handed the buffer out and did not take it back. `cairo_surface_flush` is
58+
// the end of a drawing sequence, not a checkpoint inside one.
59+
cairo_set_source_rgb(cr, 0.25, 0.5, 0.75);
60+
cairo_paint(cr);
61+
62+
cairo_set_source_rgb(cr, 1, 1, 1);
63+
cairo_set_line_width(cr, 8);
64+
cairo_move_to(cr, 8, 8);
65+
cairo_line_to(cr, 56, 56);
66+
cairo_stroke(cr);
67+
68+
check(cairo_status(cr) == CAIRO_STATUS_SUCCESS, "…and the context is still clean");
69+
cairo_surface_flush(s);
70+
71+
const unsigned char *d = cairo_image_surface_get_data(s);
72+
const int stride = cairo_image_surface_get_stride(s);
73+
74+
// ARGB32 is little-endian: B G R A.
75+
// (56,8) is far from the diagonal, so it still carries the paint —
76+
// 0.25/0.5/0.75 land on 64/128/191 with no rounding ambiguity, the same
77+
// values the GBM/EGL closed-loop test uses and for the same reason.
78+
const unsigned char *bg = d + 8 * stride + 56 * 4;
79+
std::printf(" (56,8) = %u %u %u %u (B G R A)\n", bg[0], bg[1], bg[2], bg[3]);
80+
check(bg[2] == 64 && bg[1] == 128 && bg[0] == 191 && bg[3] == 255,
81+
"the painted background is exactly what was asked for");
82+
83+
// (32,32) is on the diagonal, under an 8px white stroke.
84+
const unsigned char *on = d + 32 * stride + 32 * 4;
85+
std::printf(" (32,32) = %u %u %u %u\n", on[0], on[1], on[2], on[3]);
86+
// ── 3. The path machinery, checked directly ─────────────────────────
87+
//
88+
// These three are here because of what they caught. With
89+
// `WORDS_BIGENDIAN` defined to 0 — which cairo tests with `#ifdef`, so 0
90+
// means BIG-ENDIAN — every path got garbage fixed-point coordinates while
91+
// `cairo_status` reported success and `cairo_paint` kept working:
92+
//
93+
// path_extents -8.03e+06 -8.03e+06 4.37e+06 4.37e+06
94+
// in_fill(40, 40) 1 (the point is outside the rectangle)
95+
//
96+
// The pixel check above catches it too, but only as "the stroke drew
97+
// nothing", which sends you looking for a missing source file. These say
98+
// where the arithmetic went wrong.
99+
{
100+
cairo_t *c3 = cairo_create(s);
101+
cairo_rectangle(c3, 4, 4, 16, 16);
102+
double x1, y1, x2, y2;
103+
cairo_path_extents(c3, &x1, &y1, &x2, &y2);
104+
std::printf(" path_extents = %g %g %g %g\n", x1, y1, x2, y2);
105+
check(x1 == 4 && y1 == 4 && x2 == 20 && y2 == 20,
106+
"a rectangle path has the extents it was given");
107+
check(cairo_in_fill(c3, 10, 10) == 1, "…and a point inside it reads as inside");
108+
check(cairo_in_fill(c3, 40, 40) == 0, "…and a point outside reads as outside");
109+
cairo_destroy(c3);
110+
}
111+
112+
check(on[0] == 255 && on[1] == 255 && on[2] == 255,
113+
"…and the stroked diagonal covers the centre in white");
114+
115+
116+
cairo_destroy(cr);
117+
cairo_surface_destroy(s);
118+
119+
// ── 3. The default feature set ───────────────────────────────────────
120+
#ifdef CAIRO_HAS_FT_FONT
121+
check(true, "feature ft is ON by default (CAIRO_HAS_FT_FONT)");
122+
#else
123+
check(false, "feature ft should be ON by default");
124+
#endif
125+
#ifdef CAIRO_HAS_FC_FONT
126+
check(true, "feature fc is ON by default (CAIRO_HAS_FC_FONT)");
127+
#else
128+
check(false, "feature fc should be ON by default");
129+
#endif
130+
#ifdef CAIRO_HAS_PNG_FUNCTIONS
131+
check(true, "feature png is ON by default");
132+
#else
133+
check(false, "feature png should be ON by default");
134+
#endif
135+
136+
// ── 4. X11 is OFF, and that is the whole point ───────────────────────
137+
// A Wayland program must not acquire libX11 by depending on cairo. If this
138+
// ever fails, someone made a backend unconditional.
139+
#ifdef CAIRO_HAS_XLIB_SURFACE
140+
check(false, "xlib must be OFF unless the consumer asks for it");
141+
#else
142+
check(true, "feature xlib is OFF by default — no libX11 pulled in");
143+
#endif
144+
#ifdef CAIRO_HAS_XCB_SURFACE
145+
check(false, "xcb must be OFF unless the consumer asks for it");
146+
#else
147+
check(true, "feature xcb is OFF by default");
148+
#endif
149+
150+
// ── 5. The module carries what the features enabled ──────────────────
151+
// `cairo_image_surface_create_from_png` lives behind CAIRO_HAS_PNG_FUNCTIONS
152+
// inside cairo.h itself, not in a feature header — the module generator has
153+
// to notice that or a png-enabled consumer cannot reach it through `import`.
154+
check(&cairo_surface_write_to_png != nullptr,
155+
"module exports a name guarded inside cairo.h (write_to_png)");
156+
check(&cairo_ft_font_face_create_for_ft_face != nullptr,
157+
"…and one from a feature header (cairo_ft_font_face_create…)");
158+
159+
std::printf("\n%d check(s) failed\n", failures);
160+
return failures == 0 ? 0 : 1;
161+
}
162+
163+
#else
164+
int main() { return 0; }
165+
#endif

0 commit comments

Comments
 (0)