1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
name: CI
on:
pull_request:
branches:
push:
branches:
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
check:
name: Check & Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libfontconfig1-dev libfreetype-dev
- name: Check formatting
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --all-targets -- -D warnings
# Rustdoc warnings are silent locally unless you happen to run
# `cargo doc`, so broken intra-doc links accumulated unnoticed until a
# review counted 11 of them. `-D warnings` catches dangling `[`links`]`,
# links from public docs to private items, and prose that rustdoc parses
# as HTML (`Vec<Thing>` outside backticks).
- name: Doc links
run: cargo doc --no-deps
env:
RUSTDOCFLAGS: -D warnings
test:
name: Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libfontconfig1-dev libfreetype-dev
- name: Run tests
run: cargo test --all
# `subset-fonts` is default-on, so `cargo test --all` never compiles the
# crate without it. AGENTS.md documents `--no-default-features` as a
# supported build; nothing verified it, which is how a `not(feature)`
# arm survived in a module that only exists *with* the feature. This
# catches the real failure mode: a `fontcull` reference leaking outside
# the gated module.
- name: Build without default features
run: cargo build --no-default-features
build:
name: Build release
runs-on: ubuntu-latest
needs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libfontconfig1-dev libfreetype-dev
- name: Build release binary
run: cargo build --release
- name: Print binary size
run: ls -lh target/release/dxpdf
# Mirrors the build matrix in `python.yml` to validate every wheel target
# is buildable on every PR — not only at release time. Keep the matrix in
# sync with `python.yml`; a divergence between the two means the publish
# workflow can fail on a target that CI does not exercise.
build-wheels:
name: Build wheel (${{ matrix.target.label }})
runs-on: ${{ matrix.target.runner }}
strategy:
fail-fast: false
matrix:
target:
- label: linux-x86_64
os: linux
runner: ubuntu-latest
triple: x86_64-unknown-linux-gnu
- label: linux-aarch64
os: linux
runner: ubuntu-24.04-arm
triple: aarch64-unknown-linux-gnu
- label: macos-arm64
os: macos
runner: macos-latest
triple: aarch64-apple-darwin
- label: macos-x86_64
os: macos
runner: macos-15-intel
triple: x86_64-apple-darwin
- label: windows-x86_64
os: windows
runner: windows-latest
triple: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Build wheel (linux)
if: matrix.target.os == 'linux'
uses: PyO3/maturin-action@v1
env:
# skia-bindings only supports clang on Linux: it unconditionally
# injects clang-only `--target=<triple>` into Skia's GN cflags.
# AlmaLinux 8's default libstdc++ (GCC 8) lacks C++20 <compare>,
# so point clang at gcc-toolset-13's libstdc++ via --gcc-toolchain.
# Skia's GN build does NOT propagate CFLAGS/CXXFLAGS — we have to
# inject the toolchain flag through SKIA_GN_ARGS (`+=` to preserve
# skia-bindings' own extra_cflags like `-O3` and `--target=...`).
CC: clang
CXX: clang++
CFLAGS: --gcc-toolchain=/opt/rh/gcc-toolset-13/root/usr
CXXFLAGS: --gcc-toolchain=/opt/rh/gcc-toolset-13/root/usr
BINDGEN_EXTRA_CLANG_ARGS: --gcc-toolchain=/opt/rh/gcc-toolset-13/root/usr
# `+=` requires the variable to already exist in the GN scope.
# `extra_cflags` is auto-set by skia-bindings (so `+=` extends it);
# `extra_ldflags` is not, so we'd get "Undefined identifier" on `+=`.
# The Skia build only emits static archives (no link step), so we
# don't need extra_ldflags here — the cdylib link is rustc's job.
SKIA_GN_ARGS: 'extra_cflags+=["--gcc-toolchain=/opt/rh/gcc-toolset-13/root/usr"]'
with:
args: --release --out dist --features python
manylinux: 2_28
before-script-linux: dnf install -y fontconfig-devel clang ninja-build gcc-toolset-13
target: ${{ matrix.target.triple }}
# maturin-action filters env vars by prefix (CC/CXX/CFLAGS pass; SKIA_*
# and BINDGEN_* don't). Forward the two we set above explicitly.
docker-options: -e SKIA_GN_ARGS -e BINDGEN_EXTRA_CLANG_ARGS
- name: Build wheel (macos)
if: matrix.target.os == 'macos'
uses: PyO3/maturin-action@v1
with:
args: --release --out dist --features python
target: ${{ matrix.target.triple }}
- name: Build wheel (windows)
if: matrix.target.os == 'windows'
uses: PyO3/maturin-action@v1
with:
args: --release --out dist --features python
target: ${{ matrix.target.triple }}
- name: Verify FreeType is embedded (linux/macos)
if: matrix.target.os != 'windows'
shell: bash
run: python3 scripts/verify_wheel.py dist/*.whl