libpng 1.6.58.4 → 1.6.58.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +92 -27
  3. data/.github/workflows/release.yml +74 -21
  4. data/.rubocop.yml +9 -1
  5. data/CHANGELOG.md +86 -0
  6. data/CLAUDE.md +45 -11
  7. data/README.adoc +31 -6
  8. data/TODO.ohos/01-architecture.md +105 -0
  9. data/TODO.ohos/02-close-pr-12-and-branch.md +30 -0
  10. data/TODO.ohos/03-setup-ndk-sh.md +54 -0
  11. data/TODO.ohos/04-ohos-module-and-ndk.md +65 -0
  12. data/TODO.ohos/05-zlib-builder.md +65 -0
  13. data/TODO.ohos/06-recipe-factory-and-ohos-recipe.md +126 -0
  14. data/TODO.ohos/07-code-signer.md +53 -0
  15. data/TODO.ohos/08-smoke-test-and-verify-prepare.md +79 -0
  16. data/TODO.ohos/09-ci-workflow.md +101 -0
  17. data/TODO.ohos/10-specs.md +170 -0
  18. data/TODO.ohos/11-docs.md +91 -0
  19. data/TODO.ohos/12-pr.md +152 -0
  20. data/ext/extconf.rb +1 -1
  21. data/ext/ohos/setup-ndk.sh +140 -0
  22. data/ext/ohos/smoke-test.c +73 -0
  23. data/ext/ohos/verify-prepare.sh +56 -0
  24. data/lib/libpng/binding.rb +77 -1
  25. data/lib/libpng/chunk_walker.rb +35 -0
  26. data/lib/libpng/decoded_image.rb +4 -1
  27. data/lib/libpng/metadata_writer.rb +139 -0
  28. data/lib/libpng/ohos/code_signer.rb +38 -0
  29. data/lib/libpng/ohos/ndk.rb +83 -0
  30. data/lib/libpng/ohos/recipe.rb +76 -0
  31. data/lib/libpng/ohos/zlib_builder.rb +57 -0
  32. data/lib/libpng/ohos.rb +28 -0
  33. data/lib/libpng/recipe.rb +19 -0
  34. data/lib/libpng/simplified_decoder.rb +8 -1
  35. data/lib/libpng/standard_decoder.rb +234 -0
  36. data/lib/libpng/standard_encoder.rb +23 -1
  37. data/lib/libpng/text_writer.rb +105 -0
  38. data/lib/libpng/version.rb +1 -1
  39. data/lib/libpng.rb +96 -3
  40. metadata +24 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3764dc7e75c29be74bf732ba3d3ac4a66b17c4d20f5e1404f7d1d4fb4a7bff5d
4
- data.tar.gz: 81d239f7c17eef66172e93543c949d1cee3a5d5bd281f997c3e26153e7beafe5
3
+ metadata.gz: fda67e6ac3b6280ed407565fc08a8166668093554afd534e33cf041d47900ecd
4
+ data.tar.gz: 060045bc45e2dc049d5957063a2dd045283b01f821666247b6a789638d35b277
5
5
  SHA512:
6
- metadata.gz: 4d7d3caf69638aaf251a34631346007aed404d662a5d238dadf628b0f202ff2db51a751c6de310e76358c974c9b8d12207cb9f89f0f7c8b6d45fd47ae8d0a5be
7
- data.tar.gz: aedc2495d938619919ba47c65fb1f54f3b38af2cddcf39d38fef0eb9464031d5dcdc3922f3e6230d2bf05c612e271c28e0878ef263b3200c5a15c14336453c97
6
+ metadata.gz: 253211e3494d116f6cf13e78b5688cf171b0c4f85817b2f818fb3d69cffaa4e025687c7e086885b27ea57c2083555097a99732f188464f786676224987e4a08c
7
+ data.tar.gz: adc29322d89eb94d2f978f57e82be6f59d2a9691c233cefc09552d0c021e11f3f438fe1a8aebb6da2df56db5acbc9b7e8c0a1ff9697815489505f80b2ddc592b
@@ -143,12 +143,6 @@ jobs:
143
143
  platform: x86_64-linux-musl
144
144
  - os: ubuntu-24.04-arm
145
145
  platform: aarch64-linux-musl
146
- # OHOS (OpenHarmony / Huawei HarmonyOS PC) is musl-based arm64.
147
- # Built inside the same Alpine container as aarch64-linux-musl;
148
- # the resulting binary is byte-compatible. Only the gem's
149
- # platform label differs so RubyGems on OHOS selects it.
150
- - os: ubuntu-24.04-arm
151
- platform: aarch64-linux-ohos
152
146
 
153
147
  steps:
154
148
  - uses: actions/checkout@v4
@@ -174,27 +168,12 @@ jobs:
174
168
  git config --global --add safe.directory /work
175
169
  bundle install --jobs 4
176
170
  bundle exec rake "gem:native:${PLATFORM}"
177
- case "${PLATFORM}" in
178
- *-ohos)
179
- # OHOS gem is labeled aarch64-linux-ohos so RubyGems on OHOS
180
- # selects it, but Alpine Ruby reports *-linux-musl -- a
181
- # platform mismatch. The .so inside is musl-compatible, so
182
- # unpack the gem and load it via -Ilib directly to verify
183
- # the binary works without going through RubyGems install.
184
- mkdir -p /tmp/ohos-smoke
185
- gem unpack pkg/libpng-*.gem --target /tmp/ohos-smoke
186
- (cd /tmp/ohos-smoke/libpng-* && \
187
- ruby -Ilib -e 'require "libpng"; png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); puts Libpng.decode(png, pixel_format: "RGBA").inspect')
188
- ;;
189
- *)
190
- gem install -b pkg/libpng-*.gem
191
- # Smoke test from /tmp so bundler's source-tree LOAD_PATH doesn't
192
- # shadow the installed gem. Activate via `gem` so RubyGems resolves
193
- # the platform-specific binary gem (Alpine Ruby's platform string
194
- # is *-linux-musl, which must match the gem's platform suffix).
195
- (cd /tmp && ruby -e 'gem "libpng"; require "libpng"; png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); File.binwrite("test.png", png); puts Libpng.decode(png, pixel_format: "RGBA").inspect')
196
- ;;
197
- esac
171
+ gem install -b pkg/libpng-*.gem
172
+ # Smoke test from /tmp so bundler's source-tree LOAD_PATH doesn't
173
+ # shadow the installed gem. Activate via `gem` so RubyGems resolves
174
+ # the platform-specific binary gem (Alpine Ruby's platform string
175
+ # is *-linux-musl, which must match the gem's platform suffix).
176
+ (cd /tmp && ruby -e 'gem "libpng"; require "libpng"; png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); File.binwrite("test.png", png); puts Libpng.decode(png, pixel_format: "RGBA").inspect')
198
177
  BUILD_EOF
199
178
  chmod +x /tmp/alpine-build.sh
200
179
  docker run --rm \
@@ -208,4 +187,90 @@ jobs:
208
187
  - uses: actions/upload-artifact@v4
209
188
  with:
210
189
  name: pkg-${{ matrix.platform }}
190
+ path: pkg/*.gem
191
+
192
+ # OHOS (OpenHarmony / Huawei HarmonyOS PC) requires the official OHOS
193
+ # NDK to cross-compile. The NDK's clang and binary-sign-tool are x86_64
194
+ # ELF binaries; on this arm64 runner they run transparently via
195
+ # binfmt_misc + qemu-user-static. Verification (dockerharmony) runs
196
+ # natively because the arm64 rootfs matches the host architecture.
197
+ build_ohos:
198
+ name: build aarch64-linux-ohos (NDK)
199
+ runs-on: ubuntu-24.04-arm
200
+ needs: prepare
201
+ steps:
202
+ - uses: actions/checkout@v4
203
+
204
+ - name: Install host-side tools
205
+ # These are used for: NDK setup (curl/jq/unzip), artifact prep
206
+ # (file), and dockerharmony verification (docker is preinstalled).
207
+ run: |
208
+ sudo apt-get update
209
+ sudo apt-get install -y curl jq unzip file
210
+
211
+ - name: Register binfmt for amd64 docker emulation
212
+ # The OHOS NDK is x86_64-only. We run the entire build inside an
213
+ # amd64 docker container on this arm64 host -- docker's binfmt_misc
214
+ # registration handles the emulation transparently inside the
215
+ # container, with no library juggling on the host side.
216
+ run: docker run --privileged --rm tonistiigi/binfmt --install amd64
217
+
218
+ - name: Cache OHOS NDK
219
+ id: cache-ndk
220
+ uses: actions/cache@v4
221
+ with:
222
+ path: ext/ohos/ndk
223
+ key: ohos-ndk-${{ hashFiles('ext/ohos/setup-ndk.sh') }}
224
+
225
+ - name: Setup OHOS NDK (host-side; only curl/jq/unzip needed)
226
+ if: steps.cache-ndk.outputs.cache-hit != 'true'
227
+ run: sh ext/ohos/setup-ndk.sh --prefix ext/ohos/ndk
228
+
229
+ - name: Build OHOS gem in amd64 container
230
+ # The entire build (libpng + static zlib + signing) runs inside an
231
+ # amd64 docker container so the NDK's x86_64 clang + binary-sign-tool
232
+ # run natively (no qemu at the process level; docker's binfmt handles
233
+ # the container emulation as a whole).
234
+ run: |
235
+ docker run --rm --platform linux/amd64 \
236
+ -v "$PWD:/work" -w /work \
237
+ -e target_platform=aarch64-linux-ohos \
238
+ ruby:3.3 \
239
+ sh -c '
240
+ set -e
241
+ apt-get update
242
+ apt-get install -y --no-install-recommends cmake ninja-build zlib1g-dev
243
+ bundle install --jobs 4
244
+ bundle exec rake gem:native:aarch64-linux-ohos
245
+ # Confirm the .so was produced and is arm64 ELF (sanity).
246
+ file lib/libpng/libpng16.so
247
+ '
248
+
249
+ - name: Prepare dockerharmony artifacts (host-side)
250
+ # verify-prepare.sh cross-compiles smoke-test.c using the NDK clang.
251
+ # We run it inside the same amd64 container for the same reason.
252
+ run: |
253
+ docker run --rm --platform linux/amd64 \
254
+ -v "$PWD:/work" -w /work \
255
+ -e NDK_ROOT=/work/ext/ohos/ndk \
256
+ ruby:3.3 \
257
+ sh -c '
258
+ set -e
259
+ apt-get update
260
+ apt-get install -y --no-install-recommends file
261
+ sh ext/ohos/verify-prepare.sh
262
+ '
263
+
264
+ - name: Verify in dockerharmony (real OHOS userland, arm64-native)
265
+ run: |
266
+ docker pull ghcr.io/hqzing/dockerharmony:latest
267
+ docker run --rm \
268
+ -v "$PWD/ohos-verify:/work" \
269
+ -w /work \
270
+ ghcr.io/hqzing/dockerharmony:latest \
271
+ sh -c 'LD_LIBRARY_PATH=. ./smoke-test'
272
+
273
+ - uses: actions/upload-artifact@v4
274
+ with:
275
+ name: pkg-aarch64-linux-ohos
211
276
  path: pkg/*.gem
@@ -201,10 +201,6 @@ jobs:
201
201
  platform: x86_64-linux-musl
202
202
  - os: ubuntu-24.04-arm
203
203
  platform: aarch64-linux-musl
204
- # OHOS: same Alpine-built musl arm64 binary as aarch64-linux-musl,
205
- # packaged under a distinct platform label.
206
- - os: ubuntu-24.04-arm
207
- platform: aarch64-linux-ohos
208
204
  steps:
209
205
  - uses: actions/checkout@v4
210
206
  with:
@@ -222,22 +218,8 @@ jobs:
222
218
  git config --global --add safe.directory /work
223
219
  bundle install --jobs 4
224
220
  bundle exec rake "gem:native:${PLATFORM}"
225
- case "${PLATFORM}" in
226
- *-ohos)
227
- # OHOS gem is labeled aarch64-linux-ohos so RubyGems on OHOS
228
- # selects it, but Alpine Ruby reports *-linux-musl. The .so
229
- # inside is musl-compatible -- unpack and load via -Ilib to
230
- # verify the binary without going through RubyGems install.
231
- mkdir -p /tmp/ohos-smoke
232
- gem unpack pkg/libpng-*.gem --target /tmp/ohos-smoke
233
- (cd /tmp/ohos-smoke/libpng-* && \
234
- ruby -Ilib -e 'require "libpng"; png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); puts Libpng.decode(png, pixel_format: "RGBA").inspect')
235
- ;;
236
- *)
237
- gem install -b pkg/libpng-*.gem
238
- (cd /tmp && ruby -e 'gem "libpng"; require "libpng"; png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); puts Libpng.decode(png, pixel_format: "RGBA").inspect')
239
- ;;
240
- esac
221
+ gem install -b pkg/libpng-*.gem
222
+ (cd /tmp && ruby -e 'gem "libpng"; require "libpng"; png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); puts Libpng.decode(png, pixel_format: "RGBA").inspect')
241
223
  BUILD_EOF
242
224
  chmod +x /tmp/alpine-build.sh
243
225
  docker run --rm \
@@ -253,8 +235,79 @@ jobs:
253
235
  name: pkg-${{ matrix.platform }}
254
236
  path: pkg/*.gem
255
237
 
238
+ # OHOS NDK cross-compile. See build.yml's build_ohos job for full notes.
239
+ build_ohos:
240
+ needs: bump
241
+ if: always() && !cancelled() && !failure() && (needs.bump.result == 'success' || needs.bump.result == 'skipped')
242
+ runs-on: ubuntu-24.04-arm
243
+ steps:
244
+ - uses: actions/checkout@v4
245
+ with:
246
+ ref: ${{ needs.bump.outputs.sha || github.ref }}
247
+
248
+ - name: Install host-side tools
249
+ run: |
250
+ sudo apt-get update
251
+ sudo apt-get install -y curl jq unzip file
252
+
253
+ - name: Register binfmt for amd64 docker emulation
254
+ run: docker run --privileged --rm tonistiigi/binfmt --install amd64
255
+
256
+ - name: Cache OHOS NDK
257
+ id: cache-ndk
258
+ uses: actions/cache@v4
259
+ with:
260
+ path: ext/ohos/ndk
261
+ key: ohos-ndk-${{ hashFiles('ext/ohos/setup-ndk.sh') }}
262
+
263
+ - name: Setup OHOS NDK
264
+ if: steps.cache-ndk.outputs.cache-hit != 'true'
265
+ run: sh ext/ohos/setup-ndk.sh --prefix ext/ohos/ndk
266
+
267
+ - name: Build OHOS gem in amd64 container
268
+ run: |
269
+ docker run --rm --platform linux/amd64 \
270
+ -v "$PWD:/work" -w /work \
271
+ -e target_platform=aarch64-linux-ohos \
272
+ ruby:3.3 \
273
+ sh -c '
274
+ set -e
275
+ apt-get update
276
+ apt-get install -y --no-install-recommends cmake ninja-build zlib1g-dev
277
+ bundle install --jobs 4
278
+ bundle exec rake gem:native:aarch64-linux-ohos
279
+ file lib/libpng/libpng16.so
280
+ '
281
+
282
+ - name: Prepare dockerharmony artifacts
283
+ run: |
284
+ docker run --rm --platform linux/amd64 \
285
+ -v "$PWD:/work" -w /work \
286
+ -e NDK_ROOT=/work/ext/ohos/ndk \
287
+ ruby:3.3 \
288
+ sh -c '
289
+ set -e
290
+ apt-get update
291
+ apt-get install -y --no-install-recommends file
292
+ sh ext/ohos/verify-prepare.sh
293
+ '
294
+
295
+ - name: Verify in dockerharmony (real OHOS userland)
296
+ run: |
297
+ docker pull ghcr.io/hqzing/dockerharmony:latest
298
+ docker run --rm \
299
+ -v "$PWD/ohos-verify:/work" \
300
+ -w /work \
301
+ ghcr.io/hqzing/dockerharmony:latest \
302
+ sh -c 'LD_LIBRARY_PATH=. ./smoke-test'
303
+
304
+ - uses: actions/upload-artifact@v4
305
+ with:
306
+ name: pkg-aarch64-linux-ohos
307
+ path: pkg/*.gem
308
+
256
309
  publish:
257
- needs: [ bump, build, build_musl ]
310
+ needs: [ bump, build, build_musl, build_ohos ]
258
311
  if: always() && !cancelled() && !failure()
259
312
  runs-on: ubuntu-latest
260
313
  steps:
data/.rubocop.yml CHANGED
@@ -22,7 +22,7 @@ Metrics/ModuleLength:
22
22
  Metrics/PerceivedComplexity:
23
23
  Max: 20
24
24
  Metrics/ParameterLists:
25
- Max: 9
25
+ Max: 10
26
26
 
27
27
  Metrics/BlockLength:
28
28
  Exclude:
@@ -37,3 +37,11 @@ Style/FrozenStringLiteralComment:
37
37
  Exclude:
38
38
  - ext/extconf.rb
39
39
  - lib/libpng/recipe.rb
40
+
41
+ # libpng's C function names use mixed case + digits (e.g.
42
+ # png_set_strip_16, png_set_expand_gray_1_2_4_to_8). We attach them
43
+ # via FFI with their canonical names so docs and headers cross-
44
+ # reference cleanly.
45
+ Naming/VariableNumber:
46
+ Exclude:
47
+ - lib/libpng/binding.rb
data/CHANGELOG.md CHANGED
@@ -7,6 +7,92 @@ This gem follows a `{LIBPNG_VERSION}.{LIBPNG_RUBY_ITERATION}` version
7
7
  scheme. `LIBPNG_VERSION` is the upstream libpng release; `ITERATION`
8
8
  bumps for Ruby-side changes and resets to 0 when LIBPNG_VERSION bumps.
9
9
 
10
+ ## [1.6.58.6] - 2026-07-26
11
+
12
+ ### Changed
13
+ - **OHOS (`aarch64-linux-ohos`) is now cross-compiled with the official OHOS
14
+ NDK** (Huawei's `ohos.toolchain.cmake` + LLVM-19 clang), replacing the
15
+ 1.6.58.4/.5 approach of shipping Alpine-built musl bytes labeled as OHOS.
16
+
17
+ The build runs on `ubuntu-24.04-arm`. The OHOS NDK clang is an x86_64
18
+ binary and runs transparently via `binfmt_misc` + `qemu-user-static`.
19
+ zlib is built statically with the same toolchain and linked into
20
+ `libpng16.so` (avoids the OHOS non-standard zlib SONAME
21
+ `libshared_libz.z.so`). The freshly built `.so` is code-signed with
22
+ `binary-sign-tool sign -selfSign 1` (mandatory for runtime loading on
23
+ production OHOS devices).
24
+
25
+ Verification: the signed `.so` is loaded by a smoke-test binary inside
26
+ real OHOS userland (the `dockerharmony` container running natively on
27
+ the same arm64 host -- no qemu). Build fails if the smoke test fails.
28
+
29
+ ### Added
30
+ - `lib/libpng/ohos.rb`, `lib/libpng/ohos/{ndk,zlib_builder,code_signer,recipe}.rb`
31
+ -- OHOS cross-compile support. Lazy-loaded only when building for OHOS.
32
+ - `Libpng::OHOS::NDK` -- pure-data class for NDK path discovery.
33
+ - `Libpng::OHOS::ZlibBuilder < MiniPortileCMake` -- static zlib build.
34
+ - `Libpng::OHOS::CodeSigner` -- wraps `binary-sign-tool`.
35
+ - `Libpng::OHOS::Recipe < Libpng::Recipe` -- orchestrates the OHOS build.
36
+ - `lib/libpng/recipe.rb`: `Recipe.for_target(platform)` factory -- OCP seam
37
+ that returns `OHOS::Recipe` for `*-ohos` targets, base `Recipe` otherwise.
38
+ - `ext/ohos/setup-ndk.sh` -- downloads OHOS SDK + LLVM-19 via daily_build API
39
+ (adapted from ohos-node's `build.sh`).
40
+ - `ext/ohos/smoke-test.c` -- minimal libpng round-trip test for dockerharmony.
41
+ - `ext/ohos/verify-prepare.sh` -- cross-compiles smoke-test with NDK clang,
42
+ bundles signed `.so` + SONAME symlinks (fixes PR #12's symlink bug).
43
+ - 49 new specs under `spec/ohos/` + `spec/recipe_factory_spec.rb`.
44
+
45
+ ### Caveats
46
+ - The 1.6.58.4/.5 OHOS gems shipped Alpine-built bytes without NDK
47
+ verification. This version is the first with proper OHOS NDK
48
+ cross-compilation + signing.
49
+ - Code signing uses `-selfSign 1` (self-signed). Sufficient for OHOS
50
+ userland load. Production deployment on Huawei-managed hardware may
51
+ require re-signing with an enrolled cert.
52
+
53
+ ### Superseded
54
+ - PR #11 (closed) -- hand-written `toolchain.cmake` + system CMake. Replaced
55
+ by NDK's bundled `ohos.toolchain.cmake`.
56
+ - PR #12 (closed) -- dockerharmony verification of Alpine bytes. The
57
+ verification step is reused here; the Alpine-built `.so` is replaced
58
+ with the NDK-built, signed one.
59
+
60
+ ## [1.6.58.5] - 2026-07-26
61
+
62
+ ### Added
63
+ - **Write-side metadata on `encode_standard`**. Six new keyword options:
64
+ - `text:` -- `Hash<String,String>` of tEXt/iTXt entries. Non-ASCII
65
+ values automatically use iTXt (UTF-8) instead of tEXt (Latin-1).
66
+ - `gamma:` -- `Float` file gamma (e.g. `0.45455` for sRGB).
67
+ - `srgb_intent:` -- `Integer 0..3` rendering intent.
68
+ - `chromaticities:` -- `Hash` with the 8 cHRM primaries.
69
+ - `icc_profile:` -- `Hash` with `:name` and `:data` for an ICC
70
+ color profile (libpng compresses internally).
71
+ - `phys:` -- `Hash` with `:pixels_per_unit_x/y` and `:unit`
72
+ (`0` unknown, `1` meters).
73
+ - **`pHYs` chunk read support**. `Libpng::ChunkWalker#phys_chunk`
74
+ returns a Hash with `:pixels_per_unit_x/y`, `:unit`, and (when
75
+ `unit==1`) computed `:dpi_x/y`. `DecodedImage#phys` is populated
76
+ by both decode paths.
77
+ - **`Libpng.decode_standard`** -- standard libpng read API
78
+ (`png_create_read_struct` -> `png_read_info` -> transforms ->
79
+ `png_read_image`). Counterpart to `encode_standard`; gives
80
+ callers explicit control over which transforms apply
81
+ (palette-to-RGB, gray-to-RGB, alpha add/strip, 16-to-8 demotion,
82
+ interlace handling). Returns the same `DecodedImage` shape as
83
+ `decode`, so metadata access is uniform across both decode paths.
84
+ - New classes/modules: `Libpng::StandardDecoder`,
85
+ `Libpng::MetadataWriter`, `Libpng::TextWriter`, `Libpng::TextEntry`.
86
+ - 27 new specs across `spec/write_metadata_spec.rb` (round-trips for
87
+ every metadata type + validation errors) and
88
+ `spec/standard_decoder_spec.rb` (transform coverage + Ractor safety).
89
+
90
+ ### Fixed
91
+ - Internal: refactored `StandardEncoder` to delegate metadata writing
92
+ to `MetadataWriter`. Class length back under rubocop limits.
93
+ - Corrected `FILLER_BEFORE`/`FILLER_AFTER` constant values (the code
94
+ had them swapped -- libpng uses `BEFORE=0, AFTER=1`).
95
+
10
96
  ## [1.6.58.4] - 2026-07-26
11
97
 
12
98
  ### Added
data/CLAUDE.md CHANGED
@@ -20,20 +20,33 @@ loaded via `autoload` from `lib/libpng.rb`. **Never use `require_relative`
20
20
  | `lib/libpng.rb` | Module + FFI setup + constants + public dispatch (`encode`/`decode`/`encode_standard`) + autoloads |
21
21
  | `lib/libpng/version.rb` | `LIBPNG_VERSION`, `LIBPNG_RUBY_ITERATION`, `VERSION` |
22
22
  | `lib/libpng/error.rb` | `Libpng::Error` |
23
- | `lib/libpng/decoded_image.rb` | `Libpng::DecodedImage` (Struct returned by `decode`) |
24
- | `lib/libpng/chunk_walker.rb` | `Libpng::ChunkWalker` (walk/strip/extract metadata: `#each_chunk`, `#ihdr_fields`, `#text_chunks` for tEXt/zTXt/iTXt, `#color_chunks` for gAMA/cHRM/sRGB/iCCP, `#strip_ancillary`) |
23
+ | `lib/libpng/decoded_image.rb` | `Libpng::DecodedImage` (Struct returned by `decode` / `decode_standard`) |
24
+ | `lib/libpng/chunk_walker.rb` | `Libpng::ChunkWalker` (walk/strip/extract metadata: `#each_chunk`, `#ihdr_fields`, `#text_chunks` for tEXt/zTXt/iTXt, `#color_chunks` for gAMA/cHRM/sRGB/iCCP, `#phys_chunk` for pHYs, `#strip_ancillary`) |
25
25
  | `lib/libpng/bytes_per_pixel.rb` | `Libpng::BytesPerPixel` (pure-data lookup) |
26
26
  | `lib/libpng/simplified_encoder.rb` | `Libpng::SimplifiedEncoder` (libpng simplified write API) |
27
- | `lib/libpng/simplified_decoder.rb` | `Libpng::SimplifiedDecoder` (libpng simplified read API + IHDR metadata) |
28
- | `lib/libpng/standard_encoder.rb` | `Libpng::StandardEncoder` (libpng standard write API with filter/compression/interlace/bit_depth/palette options) |
29
- | `lib/libpng/recipe.rb` | `Libpng::Recipe < MiniPortileCMake` (builds libpng from source for the source gem) |
30
- | `ext/extconf.rb` | Gem extension entry. Triggers `Libpng::Recipe` autoload via `require 'libpng'`, then emits a dummy Makefile |
27
+ | `lib/libpng/simplified_decoder.rb` | `Libpng::SimplifiedDecoder` (libpng simplified read API + metadata via ChunkWalker) |
28
+ | `lib/libpng/standard_encoder.rb` | `Libpng::StandardEncoder` (libpng standard write API; filter/compression/interlace/bit_depth/palette + metadata via MetadataWriter) |
29
+ | `lib/libpng/standard_decoder.rb` | `Libpng::StandardDecoder` (libpng standard read API; explicit transform control) |
30
+ | `lib/libpng/metadata_writer.rb` | `Libpng::MetadataWriter` (validates + writes text/gAMA/sRGB/cHRM/iCCP/pHYs onto a png_ptr/info_ptr pair) |
31
+ | `lib/libpng/text_writer.rb` | `Libpng::TextWriter` + `Libpng::TextEntry` (builds png_text struct array, calls `png_set_text`) |
32
+ | `lib/libpng/recipe.rb` | `Libpng::Recipe < MiniPortileCMake` (builds libpng from source for the source gem). Has `.for_target(platform)` factory -- OCP seam that returns `OHOS::Recipe` for `*-ohos`, base `Recipe` otherwise |
33
+ | `lib/libpng/ohos.rb` | `Libpng::OHOS` namespace + autoloads. Lazy-loaded only when `Recipe.for_target` is called with an OHOS target |
34
+ | `lib/libpng/ohos/ndk.rb` | `OHOS::NDK` -- pure-data class for NDK path discovery (toolchain/sysroot/clang/sign-tool). Idempotent `#download` via `setup-ndk.sh` |
35
+ | `lib/libpng/ohos/zlib_builder.rb` | `OHOS::ZlibBuilder < MiniPortileCMake` -- builds `libz.a` statically with the OHOS toolchain |
36
+ | `lib/libpng/ohos/code_signer.rb` | `OHOS::CodeSigner` -- wraps `binary-sign-tool sign -selfSign 1`. Pure-data `#sign_command` is testable without invoking the tool |
37
+ | `lib/libpng/ohos/recipe.rb` | `OHOS::Recipe < Libpng::Recipe` -- orchestrates NDK setup, zlib build, libpng cross-compile with `ohos.toolchain.cmake`, and post-install signing |
38
+ | `ext/extconf.rb` | Gem extension entry. Calls `Libpng::Recipe.for_target(ENV['target_platform']).new`, then emits a dummy Makefile |
39
+ | `ext/ohos/setup-ndk.sh` | Downloads OHOS SDK + LLVM-19 via OpenHarmony daily_build API (adapted from ohos-node/build.sh). Idempotent |
40
+ | `ext/ohos/smoke-test.c` | Minimal libpng round-trip test, cross-compiled with NDK clang and run inside dockerharmony |
41
+ | `ext/ohos/verify-prepare.sh` | Cross-compiles smoke-test, bundles signed `.so` + SONAME symlinks for dockerharmony |
31
42
 
32
43
  ### Public API
33
44
 
34
45
  ```ruby
35
46
  Libpng.encode(width, height, pixels, pixel_format:, convert_to_8bit:, strip_colorspace:)
36
- Libpng.encode_standard(width, height, pixels, pixel_format:, filter:, compression_level:, interlace:, bit_depth:, palette:)
47
+ Libpng.encode_standard(width, height, pixels, pixel_format:, filter:, compression_level:, interlace:, bit_depth:, palette:, **metadata)
48
+ Libpng.decode(png, pixel_format:)
49
+ Libpng.decode_standard(png, pixel_format:, bit_depth:)
37
50
  Libpng.decode(png, pixel_format:)
38
51
  ```
39
52
 
@@ -58,7 +71,7 @@ Ruby 4.0 removed `Ractor#take`; use the helper `ractor_result(r)` which prefers
58
71
  ```sh
59
72
  bundle install
60
73
  bundle exec rake compile # build libpng via MiniPortile (needs cmake + zlib)
61
- bundle exec rake spec # all specs (~105 examples)
74
+ bundle exec rake spec # all specs (~132 examples)
62
75
  bundle exec rspec spec/libpng_spec.rb:17 # single spec by line
63
76
  bundle exec rake rubocop
64
77
  bundle exec rake # default: spec + rubocop
@@ -69,6 +82,25 @@ bundle exec rake gem:native:any # source gem (compiles on install)
69
82
 
70
83
  Platform gem tasks: `x64-mingw32`, `x64-mingw-ucrt`, `aarch64-mingw-ucrt`, `x86_64-linux`, `x86_64-linux-musl`, `aarch64-linux`, `aarch64-linux-musl`, `aarch64-linux-ohos`, `x86_64-darwin`, `arm64-darwin`.
71
84
 
85
+ ### OHOS (`aarch64-linux-ohos`) build notes
86
+
87
+ OHOS uses a dedicated `build_ohos` CI job (see `.github/workflows/build.yml`)
88
+ that runs on `ubuntu-24.04-arm`. The NDK's clang and `binary-sign-tool` are
89
+ x86_64 ELF and run via `binfmt_misc` + `qemu-user-static`. The job:
90
+
91
+ 1. Downloads OHOS SDK + LLVM-19 via `ext/ohos/setup-ndk.sh` (~1.5 GB, cached
92
+ across runs by `actions/cache@v4`).
93
+ 2. Runs `rake gem:native:aarch64-linux-ohos`, which delegates to
94
+ `Libpng::OHOS::Recipe` (sets up NDK, builds static zlib, cross-compiles
95
+ libpng with `ohos.toolchain.cmake`, signs the `.so`).
96
+ 3. Cross-compiles `ext/ohos/smoke-test.c` with NDK clang via
97
+ `verify-prepare.sh`, then runs it inside the `dockerharmony` container
98
+ (real OHOS userland) — build fails if smoke-test doesn't output `OK`.
99
+
100
+ Local dev: `target_platform=aarch64-linux-ohos bundle exec rake compile`
101
+ will only succeed on an arm64 host with `qemu-user-static` registered,
102
+ because the NDK clang is x86_64 ELF. CI handles this automatically.
103
+
72
104
  ## Release process
73
105
 
74
106
  Releases are tag-triggered via `.github/workflows/release.yml`. Trigger with:
@@ -96,12 +128,14 @@ The workflow bumps `lib/libpng/version.rb`, pushes a `v*` tag, builds all 11 pla
96
128
  - `spec/libpng_spec.rb` — `encode`/`decode` simplified API
97
129
  - `spec/libpng_standard_spec.rb` — `encode_standard` core
98
130
  - `spec/standard_encoder_options_spec.rb` — `interlace:`/`bit_depth:`/`palette:` options
131
+ - `spec/write_metadata_spec.rb` — text/gAMA/sRGB/cHRM/pHYs write round-trips + validation
99
132
  - `spec/decoded_image_metadata_spec.rb` — IHDR metadata + `ChunkWalker`
100
- - `spec/text_chunk_spec.rb` — tEXt/zTXt/iTXt parsing
101
- - `spec/color_metadata_spec.rb` — gAMA/cHRM/sRGB/iCCP parsing
133
+ - `spec/text_chunk_spec.rb` — tEXt/zTXt/iTXt parsing (read side)
134
+ - `spec/color_metadata_spec.rb` — gAMA/cHRM/sRGB/iCCP parsing (read side)
135
+ - `spec/standard_decoder_spec.rb` — `decode_standard` transforms + Ractor safety
102
136
  - `spec/malformed_input_spec.rb` — corrupt PNG input handling
103
137
  - `spec/ractor_spec.rb` — Ractor safety for simplified API
104
- - `spec/ractor_standard_spec.rb` — Ractor safety for standard API
138
+ - `spec/ractor_standard_spec.rb` — Ractor safety for standard write API
105
139
  - `spec/benchmark_spec.rb` — encode/decode timing comparison
106
140
 
107
141
  ## See also
data/README.adoc CHANGED
@@ -44,7 +44,7 @@ for the current set):
44
44
  | `x86_64-linux-musl` | x86_64 Linux (musl, e.g. Alpine) | `ruby:<ver>-alpine` on x86_64
45
45
  | `aarch64-linux` | ARM64 Linux (glibc) | `ubuntu-24.04-arm` (native)
46
46
  | `aarch64-linux-musl` | ARM64 Linux (musl) | `ruby:<ver>-alpine` on arm64
47
- | `aarch64-linux-ohos` | ARM64 OpenHarmony / Huawei HarmonyOS PC | `ruby:<ver>-alpine` on arm64 (byte-compatible with `aarch64-linux-musl`)
47
+ | `aarch64-linux-ohos` | ARM64 OpenHarmony / Huawei HarmonyOS PC | `ubuntu-24.04-arm` + OHOS NDK (cross-compiled, signed, **verified in dockerharmony**)
48
48
  | `x64-mingw32` | x64 Windows, RubyInstaller < 3.0 (MSVCRT) | `windows-latest`
49
49
  | `x64-mingw-ucrt` | x64 Windows, RubyInstaller >= 3.0 (UCRT) | `windows-latest`
50
50
  | `aarch64-mingw-ucrt` | ARM64 Windows (Ruby >= 3.4) | `windows-11-arm` (native)
@@ -71,6 +71,7 @@ for the current set):
71
71
  decoded.interlace # => 0 (PNG_INTERLACE_NONE, from IHDR)
72
72
  decoded.text # => {"Title" => "Sunset", "Author" => "Jane"} (tEXt/zTXt/iTXt)
73
73
  decoded.color # => {:gamma => 0.45455, :srgb_intent => 0, ...} (gAMA/cHRM/sRGB/iCCP)
74
+ decoded.phys # => {:pixels_per_unit_x => 3779, :unit => 1, :dpi_x => 95.99, ...} (pHYs)
74
75
 
75
76
  === Supported pixel formats
76
77
 
@@ -109,7 +110,14 @@ by `libemf2svg`'s `rgb2png` -- use `encode_standard` instead of
109
110
  compression_level: 6,
110
111
  interlace: :none,
111
112
  bit_depth: 8,
112
- palette: nil)
113
+ palette: nil,
114
+ # Metadata writers (each optional):
115
+ text: { "Title" => "Sunset", "Author" => "Jane" },
116
+ gamma: 0.45455,
117
+ srgb_intent: 0,
118
+ chromaticities: { white_point_x: 0.3127, ... },
119
+ icc_profile: { name: "sRGB IEC61966-2.1", data: bytes },
120
+ phys: { pixels_per_unit_x: 3779, pixels_per_unit_y: 3779, unit: 1 })
113
121
 
114
122
  `pixel_format`:: `:gray`, `:ga`, `:rgb`, `:rgba`, or `:palette`. The
115
123
  byte-order variants (`BGR`, `ARGB`, `BGRA`, `ABGR`) are simplified-API
@@ -148,12 +156,29 @@ Differences from `encode`:
148
156
  `ensure` block calls `png_destroy_write_struct` to release the C
149
157
  state.
150
158
 
159
+ === Standard API (decode_standard)
160
+
161
+ `Libpng.decode_standard` is the read-side counterpart to
162
+ `encode_standard`. It calls `png_create_read_struct` +
163
+ `png_set_read_fn` (memory reader) + `png_read_info` + transforms +
164
+ `png_read_image`, giving the caller explicit control over which
165
+ transforms apply (palette-to-RGB, gray-to-RGB, alpha add/strip,
166
+ 16-to-8 demotion, interlace handling).
167
+
168
+ decoded = Libpng.decode_standard(png_bytes, pixel_format: "RGBA", bit_depth: 8)
169
+
170
+ Returns the same `Libpng::DecodedImage` shape as `decode`, so metadata
171
+ fields (`text`, `color`, `phys`) work uniformly across both decode
172
+ paths. Use `decode` for the common case (auto-transforms); use
173
+ `decode_standard` when you need to know exactly which transforms
174
+ applied (e.g. for parity with `libemf2svg`'s `png2rgb`).
175
+
151
176
  == Ractor safety
152
177
 
153
- `Libpng.encode` and `Libpng.decode` are Ractor-safe. Every call
154
- allocates and frees its own libpng `png_image`; no shared mutable state
155
- exists on the Ruby side. Calls from multiple Ractors run concurrently
156
- without locking.
178
+ `Libpng.encode`, `Libpng.encode_standard`, `Libpng.decode`, and
179
+ `Libpng.decode_standard` are all Ractor-safe. Every call allocates and
180
+ frees its own libpng state; no shared mutable state exists on the Ruby
181
+ side. Calls from multiple Ractors run concurrently without locking.
157
182
 
158
183
  Example (Ruby 3.x uses `Ractor#take`; Ruby 4.0+ uses `Ractor#value`):
159
184
 
@@ -0,0 +1,105 @@
1
+ # 01 - Architecture
2
+
3
+ ## Goal
4
+
5
+ Cross-compile `libpng16.so` for `aarch64-linux-ohos` using the official OHOS
6
+ NDK (Huawei-blessed toolchain), code-sign it, and empirically verify it in
7
+ real OHOS userland (dockerharmony). Replace the 1.6.58.4/.5/.6 approach of
8
+ "Alpine-built bytes labeled as OHOS".
9
+
10
+ ## Why this design (vs PR #11 / PR #12)
11
+
12
+ | Approach | Status | Why rejected |
13
+ |---|---|---|
14
+ | Alpine bytes labeled OHOS (1.6.58.4/.5) | shipped | unverified assumption; user flagged musl symbol visibility, TLS, pthread layout risks |
15
+ | PR #11: hand-written `toolchain.cmake` + system CMake | closed | 5 CI iterations failed (sysroot path, missing zlib in sysroot, try_compile couldn't find crt objects) — root cause: NOT using NDK's bundled `ohos.toolchain.cmake` |
16
+ | PR #12: Alpine bytes + dockerharmony verification | closed | still ships Alpine bytes labeled OHOS; user wants proper NDK bytes |
17
+ | **PR #13 (this plan)** | current | NDK-bundled `ohos.toolchain.cmake` + static zlib + `binary-sign-tool` + dockerharmony verify |
18
+
19
+ ## Key insights from references
20
+
21
+ - **OHOS CMake doc** (`openharmony-6.0-app-dev-docs/napi/build-with-ndk-cmake.md`):
22
+ - NDK ships own CMake at `${SDK_PATH}/native/build-tools/cmake/bin/cmake`
23
+ - Toolchain file at `${SDK_PATH}/native/build/cmake/ohos.toolchain.cmake`
24
+ - Invoke: `cmake -DOHOS_ARCH=arm64-v8a -DOHOS_PLATFORM=OHOS -DCMAKE_TOOLCHAIN_FILE=...`
25
+ - Toolchain handles sysroot + `--target=arm-linux-ohos` + try_compile compatibility
26
+
27
+ - **ohos-node** (`~/src/external/ohos-node/build.sh`):
28
+ - NDK download via `https://dcp.openharmony.cn/api/daily_build/build/list/component`
29
+ - Two components needed: `ohos-sdk-public` (SDK + `binary-sign-tool`) + `LLVM-19` (clang + sysroot)
30
+ - `binary-sign-tool sign -selfSign 1` is mandatory post-build
31
+ - Output runs on OHOS via dockerharmony (real OHOS rootfs)
32
+
33
+ - **dockerharmony** (`~/src/external/dockerharmony/`):
34
+ - OHOS rootfs (musl + toybox + mksh) in Docker, runs aarch64-linux-musl binaries natively on arm64 hosts
35
+ - OHOS ships zlib as `libshared_libz.z.so` (NOT `libz.so`) — patchelf needed if linking dynamically
36
+
37
+ - **NDK architecture reality** (verified):
38
+ - `LLVM-19` tarball contains `llvm-linux-x86_64.tar.gz` (x86_64-build clang) + `ohos-sysroot.tar.gz` (architecture-independent sysroot files)
39
+ - There is **no arm64 build of the NDK clang** in the daily_build output
40
+ - `ohos.toolchain.cmake` is a CMake script (architecture-independent)
41
+ - `binary-sign-tool` is in the x86_64-build ohos-sdk
42
+
43
+ ## CI topology (corrected: native arm64 + transparent qemu for NDK binaries)
44
+
45
+ Single job on `ubuntu-24.04-arm` (native arm64 runner):
46
+
47
+ 1. Install `qemu-user-static` + `binfmt-support` — registers binfmt_misc so
48
+ x86_64 ELF binaries run transparently via `qemu-x86_64`.
49
+ 2. Run `ext/ohos/setup-ndk.sh` → NDK + LLVM-19 in `ext/ohos/ndk/`
50
+ 3. `bundle exec rake gem:native:aarch64-linux-ohos` →
51
+ - `OHOS::Recipe#cook` runs:
52
+ - Downloads libpng source (MiniPortile)
53
+ - Builds static zlib via `OHOS::ZlibBuilder` (NDK clang under binfmt)
54
+ - CMake-configures libpng with `ohos.toolchain.cmake` + static zlib (NDK cmake/clang under binfmt)
55
+ - Builds + installs `libpng16.so`
56
+ - Signs `.so` via `OHOS::CodeSigner` (`binary-sign-tool` under binfmt)
57
+ 4. Run `ext/ohos/verify-prepare.sh` → `ohos-verify/` artifacts (cross-compile smoke-test with NDK clang)
58
+ 5. Run dockerharmony natively: `docker run --rm -v ...:/work ghcr.io/hqzing/dockerharmony:latest sh -c 'LD_LIBRARY_PATH=. ./smoke-test'`
59
+
60
+ ### Why this is the right topology
61
+
62
+ | Aspect | x86_64 runner (original plan) | arm64 runner + qemu (revised) |
63
+ |---|---|---|
64
+ | NDK clang / binary-sign-tool | native speed | ~5x slower (qemu emulation) |
65
+ | dockerharmony verification | ~5x slower (qemu binfmt for arm64) | **native speed** |
66
+ | CI complexity | needs binfmt setup, multi-arch docker pull | standard docker pull |
67
+ | Total CI time | dominated by slow dockerharmony emulation | dominated by slow NDK compile (smaller) |
68
+
69
+ Compilation is ~1 min native → ~5 min under qemu (acceptable).
70
+ dockerharmony smoke-test is ~50ms native → ~250ms under qemu — small absolute number,
71
+ but the binfmt setup for arm64 emulation on x86_64 is finicky and has been flaky
72
+ in CI historically. Native arm64 docker is rock-solid.
73
+
74
+ **Net**: arm64 + qemu is simpler AND faster overall.
75
+
76
+ ## MECE file layout
77
+
78
+ ```
79
+ ext/ohos/
80
+ ├── setup-ndk.sh # Downloads SDK + LLVM-19 (idempotent, shell)
81
+ ├── smoke-test.c # Round-trip test (kept from PR #12)
82
+ ├── verify-prepare.sh # Cross-compiles smoke-test, bundles for dockerharmony
83
+ └── (no toolchain.cmake — we use NDK's bundled ohos.toolchain.cmake)
84
+
85
+ lib/libpng/
86
+ ├── recipe.rb # Add Recipe.for_target factory (OCP seam)
87
+ ├── ohos.rb # Module Libpng::OHOS + autoloads
88
+ └── ohos/
89
+ ├── ndk.rb # OHOS::NDK: pure-data path discovery + download
90
+ ├── zlib_builder.rb # OHOS::ZlibBuilder < MiniPortileCMake (static zlib)
91
+ ├── code_signer.rb # OHOS::CodeSigner: wraps binary-sign-tool
92
+ └── recipe.rb # OHOS::Recipe < Libpng::Recipe
93
+
94
+ lib/libpng.rb # Add autoload :OHOS, 'libpng/ohos'
95
+ ext/extconf.rb # Use Recipe.for_target(...) factory
96
+ ```
97
+
98
+ ## OCP compliance
99
+
100
+ Adding a new cross-compile platform = adding `lib/libpng/<platform>/` with a
101
+ `<Platform>::Recipe < Libpng::Recipe` subclass and registering it in
102
+ `Recipe.for_target`. No modification of `Libpng::Recipe` needed.
103
+
104
+ The existing `Libpng::Recipe` is unchanged (except adding the factory class
105
+ method). All OHOS logic is in `lib/libpng/ohos/`.