Skip to content

Commit 44de010

Browse files
committed
feat(gnome.pango*): 文本排版那条线接上了 —— 三个成员,各带模块
§19.7 说 pango 被 gio 挡住。gio 到位(#322),闸门就开了:PangoFontMap 声明 G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL, …),没有 gio 它连注册都做不到。测试在 **运行期**断言这一点,而不是拿构建通过当证据。 gnome.pango 分项、bidi、断行、markup 851 个模块导出 gnome.pangoft2 fontconfig 选文件,FreeType 出字形 88 gnome.pangocairo cairo 后端 284 判据仍是生成器不是行数。pango 有三个,fork 再加一个(模块包装体),树里没有 sh 也没有 python。 ⚠️ config.h 是特殊的:上游 configure_file 没有 input 模板,meson 按 key 逐条发 #define,树里没有可替换的东西。文件必须写出来,每个值都是决定。两个是算术 —— PANGO_BINARY_AGE = minor*100+micro、PANGO_INTERFACE_AGE = minor 奇数 ? 0 : micro —— 而 pango_version_check 读第一个,写错不会构建失败,只会让一次正确的版本比较 给出错误答案。 ⭐ 唯一一个产出像素的测试:pangocairo 把 "Hello 世界" 画到 ARGB32 surface,数非 透明像素 —— 1,492 个。走到那一步要七个包,空白图片意味着其中之一没干活。它诚实 地降级:runner 没有字体时那条断言不跑,而且**说出来**,因为「0 个字体族所以没跑」 和「文字画出来了」不能长得一样。 ⚠️ 本 fork 踩到的一次静默降级:HAVE_CAIRO_FREETYPE 被编辑失误弄丢,什么都没有构建 失败,pangocairo 只是一个后端都没注册,运行期死于空的 "Available backends are:" 然后段错误。测试现在读 font map 的字体类型并要求 CAIRO_FONT_TYPE_FT。 ⚠️ 待办:freedesktop.cairo 的模块自己不够用 —— 1.18.2 实测 470 个名字、零个枚举 量、没有 cairo_t。索引自己的 cairo 示例没发现,因为它同时写了 #include <cairo.h> 和 import,头文件补上了模块缺的那半;也就是说那个 import 从没被单独检验过。 pangocairo 不能混用两条路,所以自己扫了 cairo.h;cairo 修好后可以去掉。 消费者点名 gnome.pangocairo 一个就够:另外两个是它的 workspace path 依赖。 本地:三个示例 × 两条路线 × 两条工具链全绿;fork CI 三个 job 全绿。
1 parent 65a1603 commit 44de010

14 files changed

Lines changed: 1148 additions & 1 deletion

File tree

‎.agents/docs/2026-08-30-graphics-stack-coverage-design.md‎

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# mcpp 图形栈:从「能跑通」到「能开发」的覆盖面设计
22

3-
Date: 2026-08-30 · 前置:[`2026-08-30-gbm-cross-repo-closed-loop-plan.md`](2026-08-30-gbm-cross-repo-closed-loop-plan.md) §19/§20 · **状态:已实现并闭环验证(v1.8,§11 总账 / §14 fork 规范 / §18 八角度复核 / §19 合成器闸门与 GObject 栈,含沙箱实测 · §20 gio 与一次被推翻的判断 · §21 namespace 是契约:四个成员补上 import)**
3+
Date: 2026-08-30 · 前置:[`2026-08-30-gbm-cross-repo-closed-loop-plan.md`](2026-08-30-gbm-cross-repo-closed-loop-plan.md) §19/§20 · **状态:已实现并闭环验证(v1.9,§11 总账 / §14 fork 规范 / §18 八角度复核 / §19 合成器闸门与 GObject 栈,含沙箱实测 · §20 gio 与一次被推翻的判断 · §21 namespace 是契约 · §22 pango:文本排版接上了)**
44

55
## 0. 这份文档解决什么
66

@@ -2047,3 +2047,112 @@ error: use of undeclared identifier 'G_UNICODE_OTHER_LETTER'
20472047

20482048
CI 加了导出数下限(glib ≥1900、gio ≥2400)、pango 那三个符号按名断言、一个
20492049
枚举量、以及转出链。**塌陷是静默的,所以必须有东西去数。**
2050+
2051+
---
2052+
2053+
## 22. pango:文本排版那条线接上了(2026-08-31)
2054+
2055+
§19.7 说 pango 被 gio 挡住。gio 到位(§20),闸门就开了。
2056+
2057+
### 22.1 三个成员,和 fork 的判据
2058+
2059+
| 成员 | 是什么 | 模块导出 |
2060+
|---|---|---|
2061+
| `gnome.pango` | 分项、bidi、断行、markup | 851 |
2062+
| `gnome.pangoft2` | fontconfig 选文件,FreeType 出字形 | 88 |
2063+
| `gnome.pangocairo` | cairo 后端 —— `pango_cairo_show_layout` | 284 |
2064+
2065+
判据仍是**生成器,不是行数**。pango 有三个,本 fork 再加一个:
2066+
2067+
```
2068+
configure_file pango-features.h.meson -> pango-features.h
2069+
configure_file (没有输入模板) -> config.h
2070+
gobject/glib-mkenums pango_headers -> pango-enum-types.{h,c} 27 个
2071+
(fork 自己的) gen_module() -> 三个 .cppm
2072+
```
2073+
2074+
⚠️ **`config.h` 是特殊的一个**:上游写的是
2075+
`configure_file(output: 'config.h', configuration: pango_conf)`,**没有
2076+
`input:`** —— meson 按 key 逐条发 `#define`,树里根本没有可替换的模板。所以这个
2077+
文件必须**写出来**,里面每个值都是本包做的**决定**。其中两个是算术而非选择:
2078+
2079+
```
2080+
PANGO_BINARY_AGE = minor * 100 + micro # 5601
2081+
PANGO_INTERFACE_AGE = minor 为奇数 ? 0 : micro # 1
2082+
```
2083+
2084+
`pango_version_check()` 读第一个,所以写错不会构建失败,而是让一次**正确的版本
2085+
比较给出错误答案**。测试两个方向都断言了。
2086+
2087+
### 22.2 ⭐ 唯一一个产出像素的测试
2088+
2089+
`pangocairo` 把 `"Hello 世界"` 画到 ARGB32 surface 上,数非透明像素:**1,492**。
2090+
走到那一步要七个包:
2091+
2092+
```
2093+
gnome.pango 分项、bidi、断行
2094+
gnome.pangoft2 fontconfig 选文件,FreeType 光栅化
2095+
gnome.gio PangoFontMap 是 GListModel
2096+
compat.harfbuzz 整形
2097+
compat.fribidi bidi 算法
2098+
freedesktop.cairo 字形落下去的那张 surface
2099+
```
2100+
2101+
**空白图片意味着其中之一没干活。**
2102+
2103+
⚠️ 而且它**诚实地降级**:`freedesktop.fontconfig` 故意把运行期路径编译成空,
2104+
所以没有 `FONTCONFIG_FILE` 的 runner 合法地找到零个字体族 —— 那就没有东西可画、
2105+
也没有东西可断言。**那种情况被报告出来,而不是被静默略过**,因为「0 个字体族,
2106+
所以唯一的真检查没跑」和「文字画出来了」不能长得一样。
2107+
2108+
### 22.3 ⚠️ 本 fork 踩到的一次静默降级
2109+
2110+
`HAVE_CAIRO_FREETYPE` 被一次编辑失误从 config.h 里弄丢了。**什么都没有构建失败。**
2111+
`pangocairo-fontmap.c` 只是**一个后端都没注册**,程序在运行期死于
2112+
2113+
```
2114+
Pango-CRITICAL: Unknown $PANGOCAIRO_BACKEND value.
2115+
Available backends are: <- 空列表
2116+
```
2117+
2118+
随后段错误。测试现在读 font map 的**字体类型**并要求 `CAIRO_FONT_TYPE_FT` ——
2119+
这条断言会当场抓住它。同一类:**"能编译" 对配置宏没有任何证明力。**
2120+
2121+
### 22.4 ⚠️ `freedesktop.cairo` 的模块自己就不够用(待办)
2122+
2123+
1.18.2 实测:导出 470 个名字、**零个枚举量**(没有 `CAIRO_FORMAT_ARGB32`、
2124+
没有 `CAIRO_FONT_TYPE_FT`)、**没有 `cairo_t`**。
2125+
2126+
索引自己的 cairo 示例没发现,因为它**同时**写了 `#include <cairo.h>` 和
2127+
`import freedesktop.cairo;` —— 头文件补上了模块缺的那半。**这意味着
2128+
`import freedesktop.cairo;` 从来没有被单独检验过。**
2129+
2130+
pangocairo 不能混用两条路(pangocairo.h 经 glib 到 `<stdio.h>`,混用会让
2131+
`struct _IO_FILE` 变成两个实体),所以 `gnome.pangocairo` 自己扫 `cairo.h`。
2132+
从两个模块导出同一个实体是无害的(是同一批 global-module 实体),所以这是**追加**
2133+
而不是冲突。**cairo 的包装体修好后那行可以去掉。**
2134+
2135+
同类的漏名坑见 §21.4;`freedesktop.cairo` 是同一个生成器家族里还没修的一个。
2136+
2137+
### 22.5 依赖形态:点名 pangocairo 一个就够
2138+
2139+
`gnome.pango` 和 `gnome.pangoft2` 是 `gnome.pangocairo` 的 workspace **path**
2140+
依赖,`gnome.gio` 又是 `gnome.pango` 的。消费者再点名任何一个都会得到
2141+
2142+
```
2143+
error: dependency 'gnome.gobject' is requested as both a version dep
2144+
(by 'pango') and a path dep (by 'gnome.gio@2.82.5'). Pick one.
2145+
```
2146+
2147+
这条错误就是本 fork 第一次构建时学到的。
2148+
2149+
### 22.6 结果
2150+
2151+
| | |
2152+
|---|---|
2153+
| fork CI | 三个 job 全绿(两条工具链 + `upstream/` 未改) |
2154+
| 测试 | 六个:三个成员 × 两条消费路线 |
2155+
| 索引 | 三个示例,gcc 16.1 与 llvm 22.1 双绿 |
2156+
| 端到端 | 1,492 个像素 |
2157+
2158+
**文本排版那条线接上了。**

‎mcpp.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ members = [
108108
"tests/examples/gobject",
109109
"tests/examples/gmodule",
110110
"tests/examples/gio",
111+
"tests/examples/pango",
112+
"tests/examples/pangoft2",
113+
"tests/examples/pangocairo",
111114
"tests/examples/libgbm",
112115
"tests/examples/libpng",
113116
"tests/examples/libwebp",

‎pkgs/p/gnome.pango.lua‎

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
-- gnome.pango — international text layout.
2+
--
3+
-- Itemisation, the bidirectional algorithm, line breaking, markup and layout.
4+
-- It knows about scripts and fonts and NOTHING about rasterising: that is
5+
-- `gnome.pangoft2`, and drawing is `gnome.pangocairo`. Upstream ships them as
6+
-- three shared libraries and this index follows that split, because it is what
7+
-- a consumer links.
8+
--
9+
-- ─────────────────────────────────────────────────────────────────────────
10+
-- ⭐ THIS IS THE PACKAGE THAT WAS BLOCKED ON gio
11+
--
12+
-- `PangoFontMap` declares
13+
--
14+
-- G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, pango_font_map_list_model_init)
15+
--
16+
-- so the type cannot even REGISTER without gio's interface. The index recorded
17+
-- that gap for a while under a reason that turned out to be wrong — see
18+
-- `gnome.gio` for what the reason was and why it did not follow.--
19+
-- ─────────────────────────────────────────────────────────────────────────
20+
-- ⭐ TWO WAYS TO CONSUME IT, AND YOU PICK ONE
21+
--
22+
-- import gnome.pango;
23+
-- #include <pango/pango.h>
24+
--
25+
-- The namespace is the contract in this index: `compat.xxx` means headers, an
26+
-- owner namespace means the package exposes `import`. This module exports
27+
-- 851 names and re-exports `gnome.gio`, because pango.h
28+
-- includes glib-object.h and a consumer cannot name gio itself — it is a
29+
-- workspace path dependency.
30+
--
31+
-- ⚠️ THE TWO ROUTES DO NOT COMPOSE. A TU that does both reaches <stdio.h>
32+
-- twice — once through the module's global fragment, once directly — and the
33+
-- same `struct _IO_FILE` becomes two entities. Which route to take is decided
34+
-- by macros, which a module cannot carry: code using `PANGO_TYPE_*` or
35+
-- `G_OBJECT` takes the header route, code using the function API imports and
36+
-- includes nothing. Each member ships a test for each route.
37+
--
38+
-- ─────────────────────────────────────────────────────────────────────────
39+
-- WHY A FORK: GENERATORS, NOT LINE COUNT
40+
--
41+
-- The criterion that made cairo (104k lines) a plain descriptor and
42+
-- libdisplay-info (2k) a fork. Pango has three, and the fork adds a fourth:
43+
--
44+
-- configure_file pango-features.h.meson -> pango-features.h
45+
-- configure_file (NO input template) -> config.h
46+
-- gobject/glib-mkenums pango_headers -> pango-enum-types.{h,c}
47+
-- (the fork's own) gen_module() -> the .cppm wrapper
48+
--
49+
-- ⚠️ config.h is the odd one: upstream writes
50+
-- `configure_file(output: 'config.h', configuration: pango_conf)` with NO
51+
-- `input:`, so meson emits a `#define` per key and there is nothing in the
52+
-- tree to substitute into. The file is WRITTEN, and every value in it is a
53+
-- decision. Two are arithmetic — `PANGO_BINARY_AGE = minor*100 + micro` and
54+
-- `PANGO_INTERFACE_AGE = minor odd ? 0 : micro` — and `pango_version_check()`
55+
-- reads the first, so a wrong value makes a correct version comparison answer
56+
-- wrongly AT RUN TIME rather than failing to build.
57+
--
58+
-- https://github.com/mcpplibs/pango
59+
--
60+
-- ─────────────────────────────────────────────────────────────────────────
61+
-- LINUX ONLY. The generated config.h fixes the visibility attribute and the
62+
-- font backend, and pangoxft (the X11 backend) is deliberately not built —
63+
-- Xft is not in this index and a Wayland stack does not want it.
64+
--
65+
-- ⚠️ NAME gio ALONE if you also want it. glib, gobject and gmodule are
66+
-- workspace PATH dependencies of `gnome.gio`, and mcpp rejects a package
67+
-- requested both ways:
68+
--
69+
-- error: dependency 'gnome.gobject' is requested as both a version dep
70+
-- (by 'pango') and a path dep (by 'gnome.gio@2.82.5'). Pick one.
71+
--
72+
-- That error is how this fork learned it, on the first build.
73+
package = {
74+
spec = "1",
75+
namespace = "gnome",
76+
name = "pango",
77+
description = "Pango 1.56.1 — international text layout: itemisation, shaping, bidi and line breaking",
78+
licenses = {"LGPL-2.1-or-later"},
79+
repo = "https://github.com/mcpplibs/pango",
80+
type = "package",
81+
82+
xpm = {
83+
linux = {
84+
["1.56.1"] = {
85+
url = {
86+
GLOBAL = "https://github.com/mcpplibs/pango/archive/refs/tags/1.56.1.tar.gz",
87+
-- Verified byte-identical to the GLOBAL tag archive, not
88+
-- merely "same version": the asset IS that archive.
89+
CN = "https://gitcode.com/mcpp-res/pango/releases/download/1.56.1/pango-1.56.1.tar.gz",
90+
},
91+
sha256 = "52b32cd2b30326459950ad9a6ecb24ab4909dbe3f631a2657e3973d78015ed01",
92+
},
93+
},
94+
},
95+
96+
mcpp = "*/mcpp/pango/mcpp.toml",
97+
}

‎pkgs/p/gnome.pangocairo.lua‎

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
-- gnome.pangocairo — the cairo backend, and the usual entry point.
2+
--
3+
-- Almost every consumer reaches pango through this member:
4+
-- `pango_cairo_create_layout(cr)` then `pango_cairo_show_layout(cr, layout)`
5+
-- is the whole API most programs need.
6+
--
7+
-- ⚠️ IT DEPENDS ON pangoft2, NOT JUST cairo. Upstream compiles
8+
-- `pangocairo-fcfont.c` and `pangocairo-fcfontmap.c` whenever the freetype
9+
-- backend is enabled (meson.build:489) — they are what let a cairo surface
10+
-- draw a fontconfig-selected font, which on Linux is every font.
11+
--
12+
-- ─────────────────────────────────────────────────────────────────────────
13+
-- ⚠️ A SILENT DEGRADATION WORTH KNOWING ABOUT
14+
--
15+
-- `HAVE_CAIRO_FREETYPE` was dropped from config.h once by an editing slip.
16+
-- NOTHING FAILED TO BUILD. `pangocairo-fontmap.c` simply registered no
17+
-- backends, and the program died at run time with
18+
--
19+
-- Pango-CRITICAL: Unknown $PANGOCAIRO_BACKEND value.
20+
-- Available backends are: <- an empty list
21+
--
22+
-- then segfaulted. The fork's test now reads the font map's FONT TYPE and
23+
-- requires `CAIRO_FONT_TYPE_FT`, which catches it immediately.
24+
--
25+
-- ⚠️ AND freedesktop.cairo's MODULE IS NOT SUFFICIENT ON ITS OWN. Measured on
26+
-- 1.18.2: 470 names, ZERO enumerators (no `CAIRO_FORMAT_ARGB32`, no
27+
-- `CAIRO_FONT_TYPE_FT`) and no `cairo_t`. The index's own cairo example does
28+
-- not notice, because it writes BOTH `#include <cairo.h>` and
29+
-- `import freedesktop.cairo;`. pangocairo cannot mix the routes, so its module
30+
-- scans cairo.h itself; that can go when cairo's wrapper is fixed.--
31+
-- ─────────────────────────────────────────────────────────────────────────
32+
-- ⭐ TWO WAYS TO CONSUME IT, AND YOU PICK ONE
33+
--
34+
-- import gnome.pangocairo;
35+
-- #include <pango/pangocairo.h>
36+
--
37+
-- The namespace is the contract in this index: `compat.xxx` means headers, an
38+
-- owner namespace means the package exposes `import`. This module exports
39+
-- 284 names and re-exports `gnome.pango`,
40+
-- `gnome.pangoft2` and `freedesktop.cairo`.
41+
--
42+
-- ⚠️ THE TWO ROUTES DO NOT COMPOSE. A TU that does both reaches <stdio.h>
43+
-- twice — once through the module's global fragment, once directly — and the
44+
-- same `struct _IO_FILE` becomes two entities. Which route to take is decided
45+
-- by macros, which a module cannot carry: code using `PANGO_TYPE_*` or
46+
-- `G_OBJECT` takes the header route, code using the function API imports and
47+
-- includes nothing. Each member ships a test for each route.
48+
--
49+
-- ─────────────────────────────────────────────────────────────────────────
50+
-- WHY A FORK: GENERATORS, NOT LINE COUNT
51+
--
52+
-- The criterion that made cairo (104k lines) a plain descriptor and
53+
-- libdisplay-info (2k) a fork. Pango has three, and the fork adds a fourth:
54+
--
55+
-- configure_file pango-features.h.meson -> pango-features.h
56+
-- configure_file (NO input template) -> config.h
57+
-- gobject/glib-mkenums pango_headers -> pango-enum-types.{h,c}
58+
-- (the fork's own) gen_module() -> the .cppm wrapper
59+
--
60+
-- ⚠️ config.h is the odd one: upstream writes
61+
-- `configure_file(output: 'config.h', configuration: pango_conf)` with NO
62+
-- `input:`, so meson emits a `#define` per key and there is nothing in the
63+
-- tree to substitute into. The file is WRITTEN, and every value in it is a
64+
-- decision. Two are arithmetic — `PANGO_BINARY_AGE = minor*100 + micro` and
65+
-- `PANGO_INTERFACE_AGE = minor odd ? 0 : micro` — and `pango_version_check()`
66+
-- reads the first, so a wrong value makes a correct version comparison answer
67+
-- wrongly AT RUN TIME rather than failing to build.
68+
--
69+
-- https://github.com/mcpplibs/pango
70+
--
71+
-- ─────────────────────────────────────────────────────────────────────────
72+
-- LINUX ONLY. The generated config.h fixes the visibility attribute and the
73+
-- font backend, and pangoxft (the X11 backend) is deliberately not built —
74+
-- Xft is not in this index and a Wayland stack does not want it.
75+
--
76+
-- ⚠️ `gnome.pango` and `gnome.pangoft2` are workspace PATH dependencies of
77+
-- this package, so a consumer must NOT name them as well. Naming
78+
-- `gnome.pangocairo` alone gets the whole stack.
79+
package = {
80+
spec = "1",
81+
namespace = "gnome",
82+
name = "pangocairo",
83+
description = "PangoCairo 1.56.1 — pango's cairo rendering backend, the usual entry point",
84+
licenses = {"LGPL-2.1-or-later"},
85+
repo = "https://github.com/mcpplibs/pango",
86+
type = "package",
87+
88+
xpm = {
89+
linux = {
90+
["1.56.1"] = {
91+
url = {
92+
GLOBAL = "https://github.com/mcpplibs/pango/archive/refs/tags/1.56.1.tar.gz",
93+
-- Verified byte-identical to the GLOBAL tag archive, not
94+
-- merely "same version": the asset IS that archive.
95+
CN = "https://gitcode.com/mcpp-res/pango/releases/download/1.56.1/pango-1.56.1.tar.gz",
96+
},
97+
sha256 = "52b32cd2b30326459950ad9a6ecb24ab4909dbe3f631a2657e3973d78015ed01",
98+
},
99+
},
100+
},
101+
102+
mcpp = "*/mcpp/pangocairo/mcpp.toml",
103+
}

0 commit comments

Comments
 (0)