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
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Preserve upstream document bytes and original whitespace on every platform.
upstream/** -text -whitespace
84 changes: 84 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
mcpp:
name: mcpp (${{ matrix.os }}, ${{ matrix.toolchain }})
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-24.04, toolchain: 'gcc@16.1.0' }
- { os: ubuntu-24.04, toolchain: 'llvm@23.1.3' }
- { os: macos-15, toolchain: 'llvm@23.1.3' }
- { os: windows-2025, toolchain: 'llvm@23.1.3' }
runs-on: ${{ matrix.os }}
timeout-minutes: 60
defaults:
run:
shell: bash
env:
XLINGS_NON_INTERACTIVE: 1
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Mwrap identity and bilingual README contract
run: python3 tools/check_mwrap_release.py

- name: Install Xlings (Linux/macOS)
if: runner.os != 'Windows'
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/refs/heads/main/tools/other/quick_install.sh | bash
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"

- name: Install Xlings (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
irm https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.ps1 | iex
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

- name: Install project tools (.xlings.json)
run: |
for attempt in 1 2 3; do
xlings update > /dev/null 2>&1 || true
if xlings install; then break; fi
[ "$attempt" = 3 ] && exit 1
sleep 30
done
mcpp --version

- name: Select the toolchain
run: |
name="${{ matrix.toolchain }}"
mcpp toolchain install "${name%@*}" "${name#*@}"
mcpp toolchain default "$name"

- name: Build the modules
run: mcpp build

- name: Tests (the API, every macro mapping against the header, header mixing)
run: mcpp test

- name: Export audit (the generated sources are fresh; every declaration and macro of the public headers)
run: python3 tools/check_exports.py

- name: FriBidi's own tests (charset tests, BidiTest.txt, BidiCharacterTest.txt) on programs written against the module
run: python3 tools/run_conformance.py
49 changes: 49 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Release
on:
push:
tags: ["mwrap-*"]
permissions:
contents: read
jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
actions: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Check tag, upstream version and bilingual entry pages
run: python3 tools/check_mwrap_release.py --tag "$GITHUB_REF_NAME"
- name: Require successful source CI at this exact commit
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
result=$(gh run list --repo "$GITHUB_REPOSITORY" --workflow ci.yml --commit "$GITHUB_SHA" --limit 1 --json status,conclusion --jq '.[0] | .status == "completed" and .conclusion == "success"')
test "$result" = true || { echo 'No successful source CI at the tag commit'; exit 1; }
- name: Build immutable source asset and publish
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#mwrap-}"
repo="${GITHUB_REPOSITORY#*/}"
asset="${repo}-${version}.tar.gz"
git archive --format=tar.gz --prefix="${repo}-${version}/" "$GITHUB_SHA" > "$RUNNER_TEMP/$asset"
(cd "$RUNNER_TEMP" && sha256sum "$asset" > "$asset.sha256")
if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo 'Release already exists; refusing to change immutable assets'; exit 1
fi
extra=()
if [[ "$version" == *-rc* ]]; then extra+=(--prerelease); fi
gh release create "$GITHUB_REF_NAME" "$RUNNER_TEMP/$asset" "$RUNNER_TEMP/$asset.sha256" \
--repo "$GITHUB_REPOSITORY" --verify-tag --title "$repo $version" \
--notes "Module wrapper for upstream $version. New mwrap package identities become installable after mcpp-index publication; see the bilingual README for the tested support scope." "${extra[@]}"
28 changes: 28 additions & 0 deletions .github/workflows/templates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Actual project templates
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: templates-${{ github.ref }}
cancel-in-progress: true
env:
MCPP_VERSION: "2026.10.26.1"
jobs:
templates:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Install the pinned mcpp
shell: bash
run: |
archive="mcpp-${MCPP_VERSION}-linux-x86_64.tar.gz"
curl -L -fsS -o "$archive" "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/$archive"
tar -xzf "$archive"
echo "$PWD/mcpp-${MCPP_VERSION}-linux-x86_64/bin" >> "$GITHUB_PATH"
- name: Actual scaffold, strict build and run of the default template
run: python3 tools/test_template.py --toolchain gcc@16.1.0
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
target
compile_commands.json
mcpp.lock
.mcpp/
.xlings/
doctor.log
__pycache__/
5 changes: 5 additions & 0 deletions .xlings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"workspace": {
"xim:mcpp": "2026.10.26.1"
}
}
27 changes: 27 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
fribidi-m: C++ module wrapper
Copyright (c) 2026 mcpp-community contributors

The wrapper contributions are licensed under the Apache License, Version 2.0.
Upstream code, copied interfaces and dependencies retain their original
licenses and copyright notices. GNU FriBidi is distributed under the GNU
Lesser General Public License, version 2.1 or (at your option) any later
version:

Copyright (C) 2004 Sharif FarsiWeb, Inc
Copyright (C) 2001, 2002, 2004, 2005 Behdad Esfahbod
Copyright (C) 1999, 2000, 2017, 2018, 2019 Dov Grobgeld
and the authors listed in upstream/fribidi/AUTHORS

Its original README, license (COPYING) and authors are retained in
upstream/fribidi/. Its character tables are generated from the Unicode
Character Database 16.0.0, (c) 2024 Unicode, Inc., whose notice is retained
in upstream/fribidi/gen.tab/unidata/ReadMe.txt. The library itself is built
by compat.fribidi from the same release; this wrapper links it, it does not
modify it. A program that is distributed with FriBidi linked into it must
meet the LGPL-2.1 terms for FriBidi (see docs/usage.md, "Licensing").

examples/fribidi holds ports of FriBidi's fribidi program and of its
Unicode conformance runners to the module; as works derived from them they
remain under LGPL-2.1-or-later, with their original copyright notices.

Provenance and checksums are recorded in upstream/documents.json.
110 changes: 109 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,109 @@
# fribidi-m — GNU FriBidi as C++23 modules (`import mcpplibs.mwrap.fribidi;`)
# fribidi

> GNU FriBidi, the Unicode Bidirectional Algorithm, through C++23 modules.

**English** · [简体中文](README.zh.md) · [Upstream](https://github.com/fribidi/fribidi) · [Releases](https://github.com/mcpplibs/fribidi-m/releases) · [mcpp](https://github.com/mcpp-community/mcpp) · [Index](https://github.com/mcpplibs/mcpp-index)

[![C++23](https://img.shields.io/badge/C%2B%2B-23-blue.svg)](https://en.cppreference.com/w/cpp/23)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![FriBidi](https://img.shields.io/badge/FriBidi-LGPL--2.1--or--later-blue.svg)](upstream/fribidi/COPYING)
[![mcpp-community](https://img.shields.io/badge/mcpp--community-mwrap-blue)](https://github.com/mcpp-community)
[![CI](https://github.com/mcpplibs/fribidi-m/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mcpplibs/fribidi-m/actions/workflows/ci.yml)

FriBidi turns text stored in logical order into the visual order a display
needs for Arabic, Hebrew and mixed-direction text (UAX #9, Unicode 16.0.0).
The upstream C API is unchanged: `FriBidiCharType`,
`fribidi_get_par_embedding_levels_ex`, `fribidi_reorder_line` and
`fribidi_log2vis` are the upstream globals of `<fribidi.h>`. The library is
`compat.fribidi` 1.0.16. This package maps FriBidi's macros to constants and
constexpr functions, each checked against the header. FriBidi's own test
suite passes on every CI run: the charset tests and all 861,948 Unicode
conformance cases, on programs written against the module.

## Modules

- `import mcpplibs.mwrap.fribidi;`: the whole API of `<fribidi.h>`, re-exporting the module below
- `import mcpplibs.mwrap.fribidi.macros;`: `FRIBIDI_PAR_ON`, `FRIBIDI_TYPE_RTL`, `FRIBIDI_FLAGS_DEFAULT`, `FRIBIDI_IS_RTL` … in `mcpplibs::mwrap::fribidi::macros`

> More Modules API -> [Usage and API](docs/usage.md)

## Getting started

```sh
mcpp new bidi --template mcpplibs.mwrap.fribidi@1.0.16
cd bidi
mcpp run
```

The default template lays out UTF-8 lines as a renderer does: it computes
bidi types, bracket pairs and embedding levels, then reorders each line to
visual order with mirrored brackets. Run `mcpp run -- "your text"` for your
own lines.

```toml
[dependencies]
"mcpplibs.mwrap.fribidi" = "1.0.16"
```

```cpp
import std;
import mcpplibs.mwrap.fribidi;
using namespace mcpplibs::mwrap::fribidi::macros; // FRIBIDI_PAR_ON, FRIBIDI_LEVEL_IS_RTL …

int main() {
std::u32string_view text = U"abc אבג (123)";
std::vector<FriBidiChar> str(text.begin(), text.end());
auto n = static_cast<FriBidiStrIndex>(str.size());
std::vector<FriBidiCharType> types(str.size());
std::vector<FriBidiBracketType> brackets(str.size());
std::vector<FriBidiLevel> levels(str.size());
fribidi_get_bidi_types(str.data(), n, types.data());
fribidi_get_bracket_types(str.data(), n, types.data(), brackets.data());
FriBidiParType base = FRIBIDI_PAR_ON;
if (!fribidi_get_par_embedding_levels_ex(types.data(), brackets.data(), n, &base, levels.data())) return 1;
for (FriBidiLevel level : levels) std::print("{}", FRIBIDI_LEVEL_IS_RTL(level) ? 'R' : 'L');
std::println();
}
```

## Macros and two adapted functions

Constants such as `FRIBIDI_TYPE_LTR` keep the type their macro has in C++
(`long`). Computations such as `FRIBIDI_IS_RTL` and `FRIBIDI_SET_BITS` are
constexpr functions whose body is the macro itself. They match the macro's
value, type and value category for every argument type. Attributes,
`extern "C"` brackets, pragmas and the X-macro tables stay macros in
`<mwrap/fribidi/macros.hpp>`, included before the import.

`fribidi-common.h` declares `fribidi_debug_status` and `fribidi_set_debug`
outside its `extern "C"` block, so C++ calls to them do not link. The module
adapts both in `mcpplibs::mwrap::fribidi`. Details:
[docs/usage.md](docs/usage.md).

## Examples

- [examples/fribidi](examples/fribidi): upstream's `fribidi` command line program (logical to visual, in every
character set it supports) and its Unicode conformance runners, written against the module.
[tools/run_conformance.py](tools/run_conformance.py) runs FriBidi's tests on them.

## Build and testing

Linux, macOS and Windows CI build the modules, then run the tests and the
export audit ([tools/check_exports.py](tools/check_exports.py)). The audit
checks that the generated sources are fresh, that every declaration is
exported and that every macro is mapped or listed with its reason. CI also
runs FriBidi's test suite: the six charset tests, BidiTest.txt (770,241
cases) and BidiCharacterTest.txt (91,707 cases). A separate job scaffolds the
default template as an installed package and runs it.

## License

[Apache-2.0](LICENSE) for the wrapper, by mcpp-community ([NOTICE](NOTICE)).
GNU FriBidi remains LGPL-2.1-or-later
([upstream/fribidi/COPYING](upstream/fribidi/COPYING),
[authors](upstream/fribidi/AUTHORS)). It is linked statically, so a program
distributed in binary form must meet the LGPL's terms for FriBidi, including
relinking ([docs/usage.md](docs/usage.md#licensing)). Its tables come from
the Unicode Character Database
([notice](upstream/fribidi/gen.tab/unidata/ReadMe.txt)). The ports in
[examples/fribidi](examples/fribidi) remain LGPL-2.1-or-later.
Loading
Loading