Skip to content
Open
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
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions c2pa_c_ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ c2pa = { path = "../sdk", version = "0.78.4", default-features = false, features
"file_io",
"pdf",
] }
libc = "0.2"
scopeguard = "1.2.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion c2pa_c_ffi/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ endif

TARGET_DIR ?= ../target

CARGO_BUILD_FLAGS_EMSCRIPTEN = --release -p c2pa-c-ffi --no-default-features --features "rust_native_crypto"
CARGO_BUILD_FLAGS_EMSCRIPTEN = --release -p c2pa-c-ffi --no-default-features --features "rust_native_crypto,file_io"
CARGO_BUILD_FLAGS = --release -p c2pa-c-ffi --no-default-features --features "rust_native_crypto, add_thumbnails, http, file_io"

IPHONEOS_DEPLOYMENT_TARGET ?= 15.0
Expand Down
71 changes: 71 additions & 0 deletions c2pa_c_ffi/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
cmake_minimum_required(VERSION 3.13)
project(C2PAEmscriptenExample)

if(NOT EMSCRIPTEN)
message(FATAL_ERROR "This project requires Emscripten. Use: emcmake cmake")
endif()

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# C2PA_ROOT must point to the cargo workspace root (where target/ lives).
set(C2PA_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to c2pa-rs workspace root")

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(C2PA_LIB_DIR "${C2PA_ROOT}/target/wasm32-unknown-emscripten/debug")
else()
set(C2PA_LIB_DIR "${C2PA_ROOT}/target/wasm32-unknown-emscripten/release")
endif()

set(C2PA_LIBRARY "${C2PA_LIB_DIR}/libc2pa_c.a")

if(NOT EXISTS "${C2PA_LIBRARY}")
message(FATAL_ERROR
"c2pa library not found: ${C2PA_LIBRARY}\n"
"Build it first:\n"
" cargo +nightly build -Z build-std=std,panic_unwind \\\n"
" -p c2pa-c-ffi --target wasm32-unknown-emscripten --release \\\n"
" --no-default-features --features rust_native_crypto,file_io"
)
endif()

add_executable(c2pa_example emscripten_example.cpp)
target_include_directories(c2pa_example PRIVATE ${C2PA_LIB_DIR})
target_link_libraries(c2pa_example ${C2PA_LIBRARY})

# -pthread and -fwasm-exceptions must match the Rust library's compile flags
# (set in .cargo/config.toml for the wasm32-unknown-emscripten target).
set(EMSCRIPTEN_LINK_FLAGS
"-pthread"
"-fwasm-exceptions"
"-s WASM=1"
"-s USE_PTHREADS=1"
"-s ALLOW_MEMORY_GROWTH=1"
"-s INITIAL_MEMORY=256MB"
"-s MAXIMUM_MEMORY=2GB"
"-s EXPORTED_FUNCTIONS=['_main']"
"-s EXPORTED_RUNTIME_METHODS=['ccall','cwrap','FS']"
"-s FETCH=1"
"-s ENVIRONMENT=node,worker"
"-s NODERAWFS=1"
)

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
list(APPEND EMSCRIPTEN_LINK_FLAGS "-g" "-s ASSERTIONS=1" "-s SAFE_HEAP=1")
else()
list(APPEND EMSCRIPTEN_LINK_FLAGS "-O3" "-s ASSERTIONS=0")
endif()

string(REPLACE ";" " " EMSCRIPTEN_LINK_FLAGS_STR "${EMSCRIPTEN_LINK_FLAGS}")
set_target_properties(c2pa_example PROPERTIES
LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS_STR}"
SUFFIX ".js"
)

install(TARGETS c2pa_example RUNTIME DESTINATION bin)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/c2pa_example.js"
"${CMAKE_CURRENT_BINARY_DIR}/c2pa_example.wasm"
"${CMAKE_CURRENT_BINARY_DIR}/c2pa_example.worker.js"
DESTINATION bin
)
59 changes: 59 additions & 0 deletions c2pa_c_ffi/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# C2PA Emscripten Example

Demonstrates using the c2pa C library from C++ compiled with Emscripten.

## Prerequisites

**Rust nightly** (required to rebuild stdlib with `+atomics,+bulk-memory`):
```bash
rustup toolchain install nightly
rustup target add --toolchain nightly wasm32-unknown-emscripten
```

**Emscripten SDK**:
```bash
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk && ./emsdk install latest && ./emsdk activate latest
source ./emsdk_env.sh
```

## Build and Run

```bash
# Build (compiles Rust library + C++ example)
./build_emscripten_example.sh release

# Run with Node.js
node target/emscripten-example/c2pa_example.js path/to/image.jpg
```

CMake alternative: `./build_cmake_example.sh Release`

## Browser

Output is a `.js` module. Browser use requires:
- Running in a **Web Worker** (`EMSCRIPTEN_FETCH_SYNCHRONOUS` is not available on the main thread)
- COOP/COEP headers for `SharedArrayBuffer`:
```
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
```

## What the Example Covers

- **File read** — `c2pa_read_file`
- **Stream API** — `c2pa_reader_from_stream` with in-memory read/seek callbacks
- **Custom HTTP resolver** — `c2pa_http_resolver_create` + `c2pa_context_builder_set_http_resolver`, backed by `emscripten_fetch` for remote manifest fetching

## Common Build Errors

| Error | Fix |
|-------|-----|
| `--shared-memory is disallowed` | Use `cargo +nightly build -Z build-std=std,panic_unwind` |
| `__cpp_exception` undefined | Add `-fwasm-exceptions` to your emcc command |
| File not found at runtime | Add `-s NODERAWFS=1` for host filesystem access under Node.js |

## Further Reading

- [`EMSCRIPTEN_USAGE.md`](../EMSCRIPTEN_USAGE.md) — full integration guide
- [`c2pa.h`](../../target/wasm32-unknown-emscripten/release/c2pa.h) — generated C API header
53 changes: 53 additions & 0 deletions c2pa_c_ffi/examples/build_cmake_example.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/bash
# CMake-based build for the Emscripten C++ example.
#
# Usage: ./build_cmake_example.sh [Release|Debug] (default: Release)

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
WORKSPACE_ROOT="$(dirname "$PROJECT_ROOT")"
BUILD_TYPE="${1:-Release}"

echo "Build type: $BUILD_TYPE"

# Step 1: Build the Rust library (same as build_emscripten_example.sh).
cd "$WORKSPACE_ROOT"

if ! rustup toolchain list | grep -q '^nightly'; then
echo "Error: nightly toolchain not found. Run: rustup toolchain install nightly"
exit 1
fi
rustup target add --toolchain nightly wasm32-unknown-emscripten 2>/dev/null || true

CARGO_FLAGS="-p c2pa-c-ffi --target wasm32-unknown-emscripten --no-default-features --features rust_native_crypto,file_io"
if [ "$BUILD_TYPE" = "Debug" ]; then
cargo +nightly build -Z build-std=std,panic_unwind $CARGO_FLAGS
else
cargo +nightly build -Z build-std=std,panic_unwind $CARGO_FLAGS --release
fi
echo "✓ Rust library built"

# Step 2: Build with CMake + emcmake.
if ! command -v emcc &>/dev/null; then
echo "Error: emcc not found. Install the Emscripten SDK and source emsdk_env.sh."
exit 1
fi

BUILD_DIR="$SCRIPT_DIR/build-emscripten-$BUILD_TYPE"
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"

emcmake cmake .. \
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
-DC2PA_ROOT="$WORKSPACE_ROOT"

emmake make -j"$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"

echo ""
echo "✓ Build complete: $BUILD_DIR"
echo ""
echo "Run with Node.js:"
echo " node $BUILD_DIR/c2pa_example.js path/to/image.jpg"
85 changes: 85 additions & 0 deletions c2pa_c_ffi/examples/build_emscripten_example.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/bin/bash
# Builds the c2pa Rust library for wasm32-unknown-emscripten, then compiles
# the C++ example with emcc.
#
# Usage: ./build_emscripten_example.sh [release|debug] (default: release)

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
WORKSPACE_ROOT="$(dirname "$PROJECT_ROOT")"
BUILD_TYPE="${1:-release}"

echo "Build type: $BUILD_TYPE"

# Step 1: Build the Rust library.
# Nightly + -Z build-std is required so stdlib is rebuilt with
# +atomics,+bulk-memory, matching USE_PTHREADS=1.
cd "$WORKSPACE_ROOT"

if ! rustup toolchain list | grep -q '^nightly'; then
echo "Error: nightly toolchain not found. Run: rustup toolchain install nightly"
exit 1
fi
rustup target add --toolchain nightly wasm32-unknown-emscripten 2>/dev/null || true

CARGO_FLAGS="-p c2pa-c-ffi --target wasm32-unknown-emscripten --no-default-features --features rust_native_crypto,file_io"
if [ "$BUILD_TYPE" = "debug" ]; then
cargo +nightly build -Z build-std=std,panic_unwind $CARGO_FLAGS
LIB_DIR="$WORKSPACE_ROOT/target/wasm32-unknown-emscripten/debug"
else
cargo +nightly build -Z build-std=std,panic_unwind $CARGO_FLAGS --release
LIB_DIR="$WORKSPACE_ROOT/target/wasm32-unknown-emscripten/release"
fi

C2PA_LIB="$LIB_DIR/libc2pa_c.a"
C2PA_HEADER="$LIB_DIR/c2pa.h"

[ -f "$C2PA_LIB" ] || { echo "Error: library not found: $C2PA_LIB"; exit 1; }
[ -f "$C2PA_HEADER" ] || { echo "Error: header not found: $C2PA_HEADER"; exit 1; }
echo "✓ Rust library: $C2PA_LIB"

# Step 2: Compile the C++ example.
if ! command -v emcc &>/dev/null; then
echo "Error: emcc not found. Install the Emscripten SDK and source emsdk_env.sh."
exit 1
fi

OUTPUT_DIR="$WORKSPACE_ROOT/target/emscripten-example"
mkdir -p "$OUTPUT_DIR"

if [ "$BUILD_TYPE" = "debug" ]; then
OPT_FLAGS="-g -s ASSERTIONS=1 -s SAFE_HEAP=1"
else
OPT_FLAGS="-O3 -s ASSERTIONS=0"
fi

# -pthread and -fwasm-exceptions must match how the Rust library was compiled
# (see .cargo/config.toml for the wasm32-unknown-emscripten target).
emcc "$SCRIPT_DIR/emscripten_example.cpp" \
-I"$LIB_DIR" \
"$C2PA_LIB" \
-o "$OUTPUT_DIR/c2pa_example.js" \
-pthread \
-fwasm-exceptions \
-s WASM=1 \
-s USE_PTHREADS=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s INITIAL_MEMORY=256MB \
-s MAXIMUM_MEMORY=2GB \
-s EXPORTED_FUNCTIONS='["_main"]' \
-s EXPORTED_RUNTIME_METHODS='["ccall","cwrap","FS"]' \
-s FETCH=1 \
-s ENVIRONMENT='node,worker' \
-s NODERAWFS=1 \
-std=c++17 \
$OPT_FLAGS

echo ""
echo "✓ Build complete: $OUTPUT_DIR/c2pa_example.js"
echo ""
echo "Run with Node.js:"
echo " node $OUTPUT_DIR/c2pa_example.js path/to/image.jpg"
echo ""
echo "Note: the HTTP resolver example requires a Web Worker in the browser."
Loading
Loading