Skip to content

Commit 9158c45

Browse files
authored
mcpplibs.mwrap.fribidi 1.0.16: fribidi-m (GNU FriBidi as C++23 modules) (#593)
1 parent 08b109c commit 9158c45

8 files changed

Lines changed: 216 additions & 0 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Add mcpplibs.mwrap.fribidi (fribidi-m 1.0.16)
2+
3+
## Form
4+
5+
- Third-party upstream [fribidi/fribidi](https://github.com/fribidi/fribidi), release 1.0.16, LGPL-2.1-or-later.
6+
The tarball (`1b1cde5b…a9ea05c`) is the one compat.fribidi 1.0.16 already pins.
7+
- Form A external mwrap module repository [mcpplibs/fribidi-m](https://github.com/mcpplibs/fribidi-m), package
8+
`mcpplibs.mwrap.fribidi`, version 1.0.16 (first release of the identity, no revision suffix), tag series
9+
`mwrap-${version}`.
10+
- Implementation ownership: the library is compat.fribidi's (a static library; its configuration and ABI are
11+
sufficient). The wrapper vendors no upstream code. It retains upstream's README.md, COPYING, AUTHORS and the
12+
Unicode Character Database notice of the data its tables come from under `upstream/`, with checksums in
13+
`upstream/documents.json`, and deploys them with its own LICENSE/NOTICE (`runtime.deploy`): the LGPL travels
14+
with every program that links FriBidi statically.
15+
16+
## Modules
17+
18+
- `mcpplibs.mwrap.fribidi`: every type, enumerator (FriBidiCharSet, the joining types), FRIBIDI_ENTRY function
19+
(32, deprecated included) and variable of the headers upstream installs, by `export using`, generated from the
20+
pinned headers as a C++ translation unit sees them (`FriBidiCharType`/`FriBidiParType` are `uint32_t` in C++).
21+
- `fribidi_debug_status` / `fribidi_set_debug` are declared by `fribidi-common.h` outside its `extern "C"` block,
22+
so a C++ call through the header does not link. They are adapted under their names in `mcpplibs::mwrap::fribidi`,
23+
calling the library's C functions.
24+
- `mcpplibs.mwrap.fribidi.macros`: 171 object-like macros as `inline constexpr` of the type of their C++ expansion,
25+
55 function-like macros as constexpr function templates whose body is the macro itself (value, type and value
26+
category of the macro for every argument type; assigning macros take their argument by reference).
27+
- Stay macros, in `<mwrap/fribidi/macros.hpp>` (= `<fribidi.h>`, with `FRIBIDI_LIB_STATIC` on Windows as the
28+
module has it): declaration attributes, the `extern "C"` brackets, the deprecation pragmas and the X-macro
29+
protocol of the `*-list.h` tables. Not mapped: the 16 include guards.
30+
31+
`tools/check_exports.py` fails when the generated sources are stale, a declaration is neither exported nor
32+
adapted, or a macro is neither mapped nor listed; every mapping is checked against the raw header in a
33+
header-mixing test.
34+
35+
## Verification
36+
37+
Wrapper CI (Linux GCC 16.1 and LLVM 23.1.3, macOS LLVM, Windows LLVM, all required): build, the four test
38+
programs, the export audit, and FriBidi's whole test suite on `examples/fribidi` (upstream's `fribidi` program
39+
and its two conformance runners, ported to the module): the six charset tests byte for byte, BidiTest.txt
40+
(770,241 cases) and BidiCharacterTest.txt (91,707 cases), nothing excluded. The templates job scaffolds the
41+
default `bidi` template as an installed package and runs it.
42+
43+
Here, with the CI-pinned mcpp 2026.10.26.1 and `MCPP_INDEX_MIRROR=GLOBAL`:
44+
45+
- `bash tests/run_members.sh fribidi-bidi`: the real release archive through the candidate index; paragraph
46+
levels with bracket pairs, log2vis with mirroring, Arabic shaping, the macros, and the side header beside the
47+
import with an X-macro table: `test result ok`.
48+
- `python3 tests/mwrap/check_installed_template.py --package mcpplibs.mwrap.fribidi@1.0.16 --deploy`: the
49+
unmodified template from the real archive, then a relocated self-contained bundle whose `licenses/fribidi/`
50+
holds the wrapper's LICENSE/NOTICE and FriBidi's COPYING, AUTHORS, README and UCD notice byte for byte.
51+
52+
## Mirror and updates
53+
54+
No `mcpp-res` access in this change, so the descriptor uses the GitHub release URL as a plain string (CN users
55+
fall back to it); a maintainer can add the GitCode mirror with identical bytes. `extensions.autoupdate`
56+
follows the repository's own `mwrap-${version}` tags.
57+
58+
## Motivation
59+
60+
openmui's text engine (openmui.text) computes bidi levels with FriBidi; through this module its native unit
61+
imports the library instead of including C headers.

‎.github/workflows/validate.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1623,6 +1623,11 @@ jobs:
16231623
shell: bash
16241624
run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.libpng@1.6.43
16251625

1626+
- name: Installed FriBidi wrapper archive, actual default bidi template and its relocated LGPL notices
1627+
if: contains(format(' {0} ', env.MEMBERS), ' fribidi-bidi ') && matrix.toolchain == 'default'
1628+
shell: bash
1629+
run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.fribidi@1.0.16 --deploy
1630+
16261631
- name: Installed Godot wrapper archive and actual standalone math template
16271632
if: contains(format(' {0} ', env.MEMBERS), ' godot-math ') && matrix.toolchain == 'default'
16281633
shell: bash

‎mcpp.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ members = [
2020
"tests/mwrap/lua-embed",
2121
"tests/mwrap/quickjs-embed",
2222
"tests/mwrap/libpng-image",
23+
"tests/mwrap/fribidi-bidi",
2324
"tests/mwrap/grpc-cpu",
2425
"tests/mwrap/grpc-tools",
2526
"tests/mwrap/godot-math",

‎pkgs/mwrap/fribidi.lua‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
-- Immutable upstream-aligned mwrap source release (fribidi-m mwrap-1.0.16).
2+
-- The CN mirror is added by a maintainer with mcpp-res access; until then CN
3+
-- users fall back to the GitHub release (docs/cn-mirror.md).
4+
--
5+
-- The wrapper is Apache-2.0; the library it links, compat.fribidi, stays
6+
-- LGPL-2.1-or-later and is linked statically: the package deploys FriBidi's
7+
-- COPYING, AUTHORS and README beside programs (runtime.deploy).
8+
package = {
9+
spec = "1",
10+
namespace = "mcpplibs.mwrap",
11+
name = "fribidi",
12+
description = "fribidi-m: GNU FriBidi (the Unicode Bidirectional Algorithm) as C++23 modules",
13+
licenses = { "Apache-2.0" },
14+
repo = "https://github.com/mcpplibs/fribidi-m",
15+
type = "package",
16+
extensions = {
17+
autoupdate = {
18+
tag = "mwrap-${version}",
19+
url = "https://github.com/mcpplibs/fribidi-m/releases/download/${tag}/fribidi-m-${version}.tar.gz",
20+
},
21+
},
22+
xpm = {
23+
linux = {
24+
["1.0.16"] = {
25+
url = "https://github.com/mcpplibs/fribidi-m/releases/download/mwrap-1.0.16/fribidi-m-1.0.16.tar.gz",
26+
sha256 = "e05d6f3407689e8372a9876c1a0089df38cfbe33a7df9174ef80c134b7b198f4",
27+
},
28+
},
29+
macosx = {
30+
["1.0.16"] = {
31+
url = "https://github.com/mcpplibs/fribidi-m/releases/download/mwrap-1.0.16/fribidi-m-1.0.16.tar.gz",
32+
sha256 = "e05d6f3407689e8372a9876c1a0089df38cfbe33a7df9174ef80c134b7b198f4",
33+
},
34+
},
35+
windows = {
36+
["1.0.16"] = {
37+
url = "https://github.com/mcpplibs/fribidi-m/releases/download/mwrap-1.0.16/fribidi-m-1.0.16.tar.gz",
38+
sha256 = "e05d6f3407689e8372a9876c1a0089df38cfbe33a7df9174ef80c134b7b198f4",
39+
},
40+
},
41+
},
42+
}

‎tests/mwrap/check_installed_template.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,25 @@ def main():
143143
assert copied.is_file(), f"Missing retained OpenCV notice: {copied}"
144144
assert copied.read_bytes() == notice.read_bytes(), copied
145145
print(f"Verified {len(notices)} byte-identical wrapper/upstream license files", flush=True)
146+
elif identity == "mcpplibs.mwrap.fribidi":
147+
# FriBidi is LGPL-2.1-or-later and linked statically: its license,
148+
# authors and README travel with the program, beside the wrapper's.
149+
# The installed archive in the test home's package store.
150+
sources = [path for store in (home / "xpkgs", home / "registry/data/xpkgs")
151+
for path in store.glob(f"mcpplibs.mwrap-x-fribidi/{version}/*/src/fribidi.cppm")]
152+
assert len(sources) == 1, sources
153+
source = sources[0].parent.parent
154+
declared = tomllib.loads((source / "mcpp.toml").read_text(encoding="utf-8"))["runtime"]["deploy"]
155+
roots = list(relocated.rglob("licenses/fribidi"))
156+
assert len(roots) == 1, f"Missing FriBidi runtime license tree: {roots}"
157+
for entry in declared:
158+
notice = source / entry["from"]
159+
copied = roots[0].parent.parent / entry["to"] / notice.name
160+
assert copied.is_file() and copied.read_bytes() == notice.read_bytes(), copied
161+
assert b"GNU LESSER GENERAL PUBLIC LICENSE" in (roots[0] / "upstream/fribidi/COPYING").read_bytes()
162+
assert b"Apache License" in (roots[0] / "LICENSE").read_bytes()
163+
assert b"mcpp-community" in (roots[0] / "NOTICE").read_bytes()
164+
print(f"Verified {len(declared)} byte-identical wrapper/FriBidi license files", flush=True)
146165
else:
147166
raise ValueError(f"No deployment acceptance contract for {identity}")
148167
runtime_env = {key: value for key, value in os.environ.items()

‎tests/mwrap/fribidi-bidi/mcpp.toml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "fribidi-bidi"
3+
version = "1.0.16"
4+
description = "Consume the immutable FriBidi wrapper: paragraph levels with bracket pairs, line reordering, log2vis, Arabic shaping, the mapped macros, the side header beside the import"
5+
6+
[indices]
7+
# Use the complete candidate built-in index prepared by tests/run_members.sh.
8+
# An explicit empty table overrides the legacy workspace redirect.
9+
10+
[dependencies]
11+
"mcpplibs.mwrap.fribidi" = "1.0.16"
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import std;
2+
import mcpplibs.mwrap.fribidi;
3+
4+
using namespace mcpplibs::mwrap::fribidi::macros;
5+
6+
namespace {
7+
std::vector<FriBidiChar> text(std::u32string_view s) { return {s.begin(), s.end()}; }
8+
std::u32string chars(const std::vector<FriBidiChar>& v) {
9+
std::u32string out;
10+
for (auto c : v) out.push_back(static_cast<char32_t>(c));
11+
return out;
12+
}
13+
} // namespace
14+
15+
int main() {
16+
// UAX #9 N0's example in an RTL paragraph: a bracket pair resolves together.
17+
auto s = text(U"אב(גד[&ef]!)gh");
18+
const auto n = static_cast<FriBidiStrIndex>(s.size());
19+
std::vector<FriBidiCharType> types(s.size());
20+
std::vector<FriBidiBracketType> brackets(s.size());
21+
std::vector<FriBidiLevel> levels(s.size());
22+
fribidi_get_bidi_types(s.data(), n, types.data());
23+
fribidi_get_bracket_types(s.data(), n, types.data(), brackets.data());
24+
FriBidiParType base = FRIBIDI_PAR_RTL;
25+
if (fribidi_get_par_embedding_levels_ex(types.data(), brackets.data(), n, &base, levels.data()) != 3) return 1;
26+
if (std::vector<int>(levels.begin(), levels.end()) != std::vector<int>{1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 2}) return 2;
27+
28+
// A line to visual order, with mirrored brackets.
29+
auto logical = text(U"abc אב(ג) def");
30+
const auto len = static_cast<FriBidiStrIndex>(logical.size());
31+
std::vector<FriBidiChar> visual(logical.size());
32+
FriBidiParType dir = FRIBIDI_PAR_ON;
33+
if (fribidi_log2vis(logical.data(), len, &dir, visual.data(), nullptr, nullptr, nullptr) == 0) return 3;
34+
if (dir != FRIBIDI_PAR_LTR || chars(visual) != U"abc (ג)בא def") return 4;
35+
36+
// Arabic joining and shaping to presentation forms.
37+
auto arabic = text(U"بب");
38+
std::vector<FriBidiCharType> atypes(2);
39+
std::vector<FriBidiLevel> alevels(2);
40+
std::vector<FriBidiArabicProp> props(2);
41+
fribidi_get_bidi_types(arabic.data(), 2, atypes.data());
42+
FriBidiParType abase = FRIBIDI_PAR_ON;
43+
if (!fribidi_get_par_embedding_levels_ex(atypes.data(), nullptr, 2, &abase, alevels.data())) return 5;
44+
fribidi_get_joining_types(arabic.data(), 2, props.data());
45+
fribidi_join_arabic(atypes.data(), 2, alevels.data(), props.data());
46+
fribidi_shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC, alevels.data(), 2, props.data(), arabic.data());
47+
if (chars(arabic) != U"\uFE91\uFE90") return 6;
48+
49+
// Macros as constants and constexpr functions; the adapted debug function.
50+
static_assert(FRIBIDI_IS_RTL(FRIBIDI_TYPE_AL) && FRIBIDI_DIR_TO_LEVEL(FRIBIDI_PAR_RTL) == 1);
51+
static_assert(std::string_view(FRIBIDI_VERSION) == "1.0.16");
52+
if (mcpplibs::mwrap::fribidi::fribidi_set_debug(0) != 0) return 7;
53+
if (std::string_view(fribidi_unicode_version) != "16.0.0") return 8;
54+
if (fribidi_parse_charset("UTF-8") != FRIBIDI_CHAR_SET_UTF8) return 9;
55+
std::println("fribidi-bidi: levels, reordering, shaping and macros ok");
56+
return 0;
57+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// The side header from the installed package, beside the import: the raw
2+
// macros and the X-macro tables with the module's entities.
3+
#include <mwrap/fribidi/macros.hpp>
4+
5+
import std;
6+
import mcpplibs.mwrap.fribidi;
7+
8+
constexpr std::pair<FriBidiCharSet, const char*> sets[] = {
9+
#define _FRIBIDI_ADD_CHAR_SET(CHAR_SET, char_set) {FRIBIDI_CHAR_SET_##CHAR_SET, #char_set},
10+
#include <fribidi-char-sets-list.h>
11+
#undef _FRIBIDI_ADD_CHAR_SET
12+
};
13+
14+
int main() {
15+
if (std::size(sets) != static_cast<std::size_t>(FRIBIDI_CHAR_SETS_NUM)) return 1;
16+
for (auto [set, name] : sets)
17+
if (fribidi_parse_charset(fribidi_char_set_name(set)) != set) return 2;
18+
if (FRIBIDI_IS_RTL(::fribidi_get_bidi_type(0x05D0)) != (mcpplibs::mwrap::fribidi::macros::FRIBIDI_IS_RTL)(FRIBIDI_TYPE_RTL)) return 3;
19+
return 0;
20+
}

0 commit comments

Comments
 (0)