Skip to content

[rapidocr] Add new port - #53217

Draft
sparcyu (sparcyu) wants to merge 6 commits into
microsoft:masterfrom
sparcyu:rapidocr-port
Draft

[rapidocr] Add new port#53217
sparcyu (sparcyu) wants to merge 6 commits into
microsoft:masterfrom
sparcyu:rapidocr-port

Conversation

@sparcyu

@sparcyu sparcyu (sparcyu) commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a new port rapidocr — a C++ OCR pipeline built on ONNX Runtime,
packaging RapidAI/RapidOcrOnnx
v1.2.3 (PaddleOCR-style text detection, angle classification, and
recognition).

The upstream CMake expects pre-vendored static OpenCV and ONNX Runtime trees,
so the port supplies a small CMake build that uses vcpkg packages and exports
unofficial::rapidocr::rapidocr.

Review fixes, first round

  • Rewrites upstream ONNX Runtime includes to vcpkg's installed public layout.
  • Uses transitive OpenCV imported targets so public opencv2/... includes work.
  • Replaces vendored Clipper with the polyclipping port.
  • Removes the optional CLI/getopt surface; the port now packages only the library.
  • Keeps model weights external and documents the required files in usage.

Review fixes, second round

  • C API linkage. OcrLiteCApi.h selected dllexport/dllimport from
    __CLIB__, which a consumer of the installed headers cannot define, so it saw
    dllimport and required __imp_Ocr*. The portfile now strips the storage
    class, which is correct for a static-only port. __CLIB__ remains defined for
    the build because it also gates the whole body of src/OcrLiteCApi.cpp.
  • Uninitialised ONNX sessions. DbNet, CrnnNet and AngleNet left
    Ort::Session* indeterminate until initModel() and deleted it
    unconditionally in their destructors, so destroying a default-constructed
    OcrLite faulted. Fixed by fix-uninitialized-session.patch.
  • usage. Now separates the dictionary the port installs from the three
    ONNX weights that are not redistributed, and documents which of the two
    installed interfaces suits which integration.
  • Stale comments in the port CMake removed.

Direct root-include consumption of the C++ API is not achievable while it
exposes OpenCV types: include/opencv4/opencv2/core.hpp includes
opencv2/core/cvdef.h, which exists only under include/opencv4, so any
consumer of an OpenCV header needs that directory regardless of this port. The
C API has no #include directives at all and does compile from the triplet
include root alone; usage states this.

New port checklist

  • Changes comply with the maintainer guide.
  • The packaged project is mature and ready for broad sharing with vcpkg users
    • Has a release at least 6 months old or 6 months of demonstrated public development
    • Is an official component of something else meeting that criteria
    • Some other reason (please explain)
  • The packaged project shows strong association with the chosen port name. Check this box if at least one of the following criteria is met:
    • The project is in Repology: https://repology.org/project/rapidocr/versions
    • The project is amongst the first web search results for "rapidocr" or "rapidocr C++". Include a screenshot of the search engine results in the PR.
    • The port name follows the 'GitHubOrg-GitHubRepo' form or equivalent Owner-Project form.
  • Optional dependencies of the build are all controlled by the port. A dependency is controlled if it is declared an unconditional dependency in vcpkg.json, or explicitly disabled through patches or build system arguments such as CMAKE_DISABLE_FIND_PACKAGE_Xxx or VCPKG_LOCK_FIND_PACKAGE
  • The versioning scheme in vcpkg.json matches what upstream says.
  • The license declaration in vcpkg.json matches what upstream says.
  • The installed as the "copyright" file matches what upstream says.
  • The source code of the component installed comes from an authoritative source.
  • The generated "usage text" is brief and accurate. See adding-usage for context. Don't add a usage file if the automatically generated usage is correct.
  • The version database is fixed by rerunning ./vcpkg x-add-version --all and committing the result.
  • Exactly one version is added in each modified versions file.

Screenshot for the web-search criterion, as supplied in review:

rapidocr web search results

@BillyONeal
Billy O'Neal (BillyONeal) marked this pull request as draft August 6, 2026 01:42
@BillyONeal

Copy link
Copy Markdown
Member

Drafted due to build failures. GPT 5.6 Sol says:

  • Windows builds cannot resolve ONNX Runtime because OcrLite.h includes onnxruntime/core/..., while CMakeLists.txt receives an include directory already ending in onnxruntime. The build must derive a compatible include root or patch the include.
  • The tools installation writes directly to tools/rapidocr, but vcpkg_copy_tools() searches bin, causing feature packaging to fail.
  • The Apache-2.0 declaration omits vendored Clipper's BSL-1.0 and the tools feature's getopt MIT licensing and notices.
  • Public headers such as OcrLite.h include opencv2/..., but OpenCV is installed beneath include/opencv4. Direct integration using only the triplet root include directory therefore fails; the installed interface needs a root-resolvable layout or forwarding mechanism.

There are also a lot of vendored dependencies here that likely need to be extracted.

Please also make sure you have filled out the "New Port Checklist" we have because it helps people resolve things we're looking for

- [ ] Changes comply with the [maintainer guide](https://github.com/microsoft/vcpkg-docs/blob/main/vcpkg/contributing/maintainer-guide.md).
- [ ] The packaged project is [mature and ready for broad sharing with vcpkg users](https://learn.microsoft.com/vcpkg/contributing/maintainer-guide#packaged-projects-should-be-mature)
- [ ] Has a release at least 6 months old or 6 months of demonstrated public development
- [ ] Is an official component of something else meeting that criteria
- [ ] Some other reason (please explain)
- [ ] The packaged project shows strong association with the chosen port name. Check this box if at least one of the following criteria is met:
- [ ] The project is in Repology: https://repology.org/project/<PORT NAME>/versions
- [ ] The project is amongst the first web search results for "<PORT NAME>" or "<PORT NAME> C++". Include a screenshot of the search engine results in the PR.
- [ ] The port name follows the 'GitHubOrg-GitHubRepo' form or equivalent `Owner-Project` form.
- [ ] Optional dependencies of the build are all controlled by the port. A dependency is controlled if it is declared an unconditional dependency in `vcpkg.json`, or explicitly disabled through patches or build system arguments such as [CMAKE_DISABLE_FIND_PACKAGE_Xxx](https://cmake.org/cmake/help/latest/variable/CMAKE_DISABLE_FIND_PACKAGE_PackageName.html) or [VCPKG_LOCK_FIND_PACKAGE](https://learn.microsoft.com/vcpkg/users/buildsystems/cmake-integration#vcpkg_lock_find_package_pkg)
- [ ] The versioning scheme in `vcpkg.json` matches what upstream says.
- [ ] The license declaration in `vcpkg.json` matches what upstream says.
- [ ] The installed as the "copyright" file matches what upstream says.
- [ ] The source code of the component installed comes from an authoritative source.
- [ ] The generated "usage text" is brief and accurate. See [adding-usage](https://github.com/microsoft/vcpkg-docs/blob/main/vcpkg/examples/adding-usage.md) for context. Don't add a usage file if the automatically generated usage is correct.
- [ ] The version database is fixed by rerunning `./vcpkg x-add-version --all` and committing the result.
- [ ] Exactly one version is added in each modified versions file.

@sparcyu

Copy link
Copy Markdown
Contributor Author

Thanks for the review — all four points are addressed in d0fba47, and CI is now green on every leg.

ONNX Runtime include root. The portfile now rewrites the upstream include to vcpkg's installed public layout rather than trying to reconstruct a compatible root:

string(REPLACE "onnxruntime/core/session/onnxruntime_cxx_api.h"
               "onnxruntime/onnxruntime_cxx_api.h" contents "${contents}")

tools installation vs vcpkg_copy_tools(). The CLI is no longer packaged at all, so the mismatch is gone rather than papered over: the tools feature, the RapidOcrOnnx executable, the install(TARGETS ... DESTINATION tools/rapidocr) and the vcpkg_copy_tools() call are all removed. The port installs only the library.

Licensing. With the CLI gone, src/getopt.cpp (MIT) is neither built nor packaged. Vendored Clipper (BSL-1.0) is deleted from the extracted tree and replaced by the polyclipping port:

file(REMOVE "${SOURCE_PATH}/include/clipper.hpp" "${SOURCE_PATH}/src/clipper.cpp")
string(REPLACE "\"clipper.hpp\"" "<polyclipping/clipper.hpp>" contents "${contents}")

clipper.hpp is also excluded from the installed headers. The remaining packaged code is upstream's own, so the Apache-2.0 declaration now matches what is shipped.

OpenCV under include/opencv4. Handled through imported targets rather than a raw include directory: OpenCV is linked PUBLIC, so consumers inherit its interface include directories and #include <opencv2/...> resolves without depending on the triplet root layout. unofficial-rapidocr-config.cmake carries the matching find_dependency(OpenCV), find_dependency(onnxruntime CONFIG) and find_dependency(polyclipping CONFIG).

On the vendored dependencies more generally: Clipper was the only vendored library still compiled into the target, and it is now an external port dependency. The remaining vendored pieces (main.cpp, getopt.*, the JNI bindings) belong to the CLI/Android surfaces and are excluded from both the build and the installed headers.

The New Port Checklist in the description is filled out, including the licensing rationale above.

@sparcyu
sparcyu (sparcyu) marked this pull request as ready for review August 6, 2026 19:17
@BillyONeal

Billy O'Neal (BillyONeal) commented Aug 8, 2026

Copy link
Copy Markdown
Member

I normally try to avoid posting verbatim what the LLM says but in this case I don't have anything to add:

  • The installed C++ headers do not support direct integration from the triplet include root. <rapidocr/OcrLite.h> includes <opencv2/core.hpp>, but vcpkg's OpenCV headers are under include/opencv4; both Release and Debug compilation failed with C1083. The port must provide a root-resolvable installed header interface for the required direct-consumer path rather than relying only on CMake target include propagation.
  • The installed C API cannot be linked through unofficial::rapidocr::rapidocr on Windows. The port is static-only, but OcrLiteCApi.h declares dllimport unless __CLIB__ is defined, while the target defines __CLIB__ only privately. Release and Debug both failed on unresolved __imp_OcrInit and __imp_OcrDestroy. Patch the header for static consumption, propagate the correct consumer definition, or omit the unsupported C API.
  • The usage text says ppocr_keys_v1.txt must be downloaded even though the port installs it under share/rapidocr/models.
  • Several comments are stale or redundant, including “fixed by patch” when the port performs source rewrites and the “flat include convenience” comment where no flat headers are installed.
  • A default-constructed OcrLite crashed on destruction in both configurations because upstream network-session pointers are uninitialized until model initialization. This exists in upstream 1.2.3; any vcpkg patch should also be

Suggested fixes

For direct header consumption, install or generate forwarding headers/layout so opencv2/... resolves with only <triplet>/include, then re-run direct Release and Debug examples without adding include/opencv4.

For the C API, make the static-library contract explicit. A static-only build should not expose dllimport declarations to consumers; alternatively, exclude OcrLiteCApi.h and OcrLiteCApi.cpp if only the C++ interface is intended.

Update usage to distinguish the three external ONNX weights from the already installed dictionary and remove comments that do not describe actual behavior.

It might be a good idea to add a test port.

@BillyONeal

Copy link
Copy Markdown
Member

Sorry more useful content:

Integration Release Debug Notes
CMake C++ target compile/link pass compile/link pass find_package(unofficial-rapidocr CONFIG REQUIRED) and unofficial::rapidocr::rapidocr
CMake C++ execution fail fail 0xC0000005; pre-existing uninitialized upstream session pointers
CMake C API target fail fail unresolved __imp_OcrInit / __imp_OcrDestroy
Direct C++ root include fail fail cannot resolve opencv2/core.hpp
Direct C API, root include plus every triplet .lib pass pass Linker pulled the exported C API implementation when all libraries were supplied
pkg-config N/A N/A The port does not provide pkg-config metadata

All example sources, response files, binaries, and build directories are retained under D:\vcpkg-prs\batch-20260807-1212\pr-53217\examples.

examples.zip

@BillyONeal
Billy O'Neal (BillyONeal) marked this pull request as draft August 8, 2026 04:35
@BillyONeal

Billy O'Neal (BillyONeal) commented Aug 8, 2026

Copy link
Copy Markdown
Member

[x] The project shows strong association with the chosen port name.

  • The upstream project and documentation consistently identify the library as RapidOCR / RapidOcrOnnx.

That is not what the checklist says.

  • The packaged project shows strong association with the chosen port name. Check this box if at least one of the following criteria is met:
    • The project is in Repology: https://repology.org/project/\<PORT NAME>/versions
    • The project is amongst the first web search results for "<PORT NAME>" or "<PORT NAME> C++". Include a screenshot of the search engine results in the PR.
    • The port name follows the 'GitHubOrg-GitHubRepo' form or equivalent Owner-Project form.
image

- OcrLiteCApi.h selected dllexport/dllimport from __CLIB__, which a consumer
  of the installed headers cannot define, so it saw dllimport and required
  __imp_Ocr*. Strip the storage class; a static-only port should not present
  __declspec to consumers. __CLIB__ stays defined for the build because it
  also gates the whole body of src/OcrLiteCApi.cpp.
- DbNet/CrnnNet/AngleNet left Ort::Session* indeterminate until initModel()
  and deleted it unconditionally in their destructors, so destroying a
  default-constructed OcrLite faulted. Add fix-uninitialized-session.patch.
- usage now separates the dictionary the port installs from the ONNX weights
  that are not redistributed, and documents that the C API is consumable from
  the triplet include root while the C++ API needs the CMake target because it
  exposes OpenCV types.
- Drop stale comments in the port CMake.
@sparcyu

Copy link
Copy Markdown
Contributor Author

Thanks for testing this properly — the artifacts made the failures easy to reproduce.

C API linkage — fixed

The header keys its storage class off __CLIB__, which a consumer compiling against the installed headers has no way to define, so it saw dllimport and asked for __imp_Ocr*. The portfile now strips the storage class outright, which is the right answer for a static-only port:

string(REPLACE "#define _QM_OCR_API __declspec(dllexport)" "#define _QM_OCR_API" contents "${contents}")
string(REPLACE "#define _QM_OCR_API __declspec(dllimport)" "#define _QM_OCR_API" contents "${contents}")

__CLIB__ stays defined for the build, because it also gates the whole body of src/OcrLiteCApi.cpp — dropping it removes the C API from the library entirely.

Verified on x64-windows-static-md, compiling with WIN32 defined and no __CLIB__:

Symbols required by the consumer
before __imp_OcrInit, __imp_OcrDestroy
after OcrInit, OcrDestroy

dumpbin /linkermember on the installed rapidocr.lib now lists OcrInit, OcrDetect, OcrGetLen, OcrGetResult, OcrDestroy, and a consumer linking the port's libraries resolves them with nothing unresolved from rapidocr.lib.

usage — fixed

It now separates the dictionary the port installs (share/rapidocr/models/ppocr_keys_v1.txt) from the three ONNX weights that are not redistributed.

Stale comments — removed

Both the "fixed by patch" comment and the "flat include convenience" block are gone.

Direct header consumption

Here I think the requirement cannot be met for the C++ API, and I would like to check the expectation before doing anything further.

OpenCV's own headers are not resolvable from the triplet include root. include/opencv4/opencv2/core.hpp line 52 is:

#include "opencv2/core/cvdef.h"

and cvdef.h exists only at include/opencv4/opencv2/core/cvdef.h. So any translation unit that reaches an OpenCV header needs include/opencv4 on its search path, regardless of what this port does to its own includes. I tried rewriting rapidocr's includes to <opencv4/opencv2/...>; that resolves rapidocr's own directives and then fails inside OpenCV at the line above. Adding include/opencv4 makes it compile. I reverted the rewrite since it only made the headers less faithful without fixing anything.

That constraint applies to any port exposing OpenCV types in its public API, not to this one specifically.

What the port can do, and now does explicitly, is offer an OpenCV-free interface for the direct path. OcrLiteCApi.h has no #include directives at all and compiles from the triplet include root alone; OcrLite.h does not. usage now states which interface is for which integration:

  * The C++ API (#include <rapidocr/OcrLite.h>) exposes cv::Mat and other
    OpenCV types, so it must be consumed through the CMake target above ...

  * The C API (#include <rapidocr/OcrLiteCApi.h>) is self-contained and has no
    OpenCV in its surface, so it can be consumed directly with only the triplet
    include root on the search path.

If direct root-include consumption of the C++ API is a hard requirement, the only route I can see is hiding OpenCV behind an opaque C++ interface, which is an upstream API redesign rather than packaging work. Happy to go that way if that is the expectation — I would rather ask than guess.

Destructor crash — fixed

DbNet, CrnnNet and AngleNet declare Ort::Session *session; with no
initialiser, assign it only in initModel(), and their destructors run
delete session unconditionally, so destroying a default-constructed OcrLite
deletes an indeterminate pointer. Added fix-uninitialized-session.patch
(generated against the 1.2.3 tag), which gives the three members
= nullptr. Building a consumer that constructs and destroys an OcrLite
without calling initModel() now exits 0 where it previously faulted.

I will raise this upstream as well.

Checklist

Corrected — I had paraphrased the association item instead of selecting one of
its criteria. It now uses the template wording with the web search criterion
checked, and the screenshot is included in the description.

Note on the version database

vcpkg x-add-version rapidocr --overwrite-version fails here with an internal
error, unrelated to the safe.directory hint it prints (git operations in this
clone work normally):

note: while getting local treeish objects for ports
internal error: src\vcpkg\commands.add-version.cpp(433): Value was null

So versions/r-/rapidocr.json was updated by hand to the tree of the commit
that changes the port, and verified to equal
git rev-parse HEAD:ports/rapidocr (925186f7b0e7e8dc3cd3009a5c9aa23fc0a3be8a).
Flagging it in case the same failure shows up in CI.

Test port

Happy to add one — let me know whether you would prefer a vcpkg-ci-rapidocr
style consumer exercising both the CMake target and the direct C API path, or
something narrower.

Exercise the CMake C++ target (default-construct/destroy OcrLite),
the C API through the same target, and compile OcrLiteCApi.h with
only the triplet include root.
@sparcyu

Copy link
Copy Markdown
Contributor Author

Two follow-ups from the last review:

Direct C++ include root / OpenCV

I still do not think this port can make <rapidocr/OcrLite.h> compile from the triplet include root alone. That header includes OpenCV types, and OpenCV's own headers need include/opencv4 on the search path (opencv2/core.hpp then includes "opencv2/core/cvdef.h", which lives only under include/opencv4). That is OpenCV's installed layout, not something RapidOCR can paper over without hiding cv::Mat behind an opaque C++ API (an upstream redesign).

What the port does instead, and what usage documents:

  • C++ API → unofficial::rapidocr::rapidocr (propagates OpenCV includes)
  • C API → <rapidocr/OcrLiteCApi.h> has no includes and compiles from the triplet include root

If C++ root-include is a hard requirement, please say so and I will go the opaque-interface route. Otherwise I would like to leave the C++ surface as upstream defined it.

Test port

Added scripts/test_ports/vcpkg-ci-rapidocr. It:

  1. Builds and runs a CMake C++ consumer that default-constructs and destroys OcrLite (the uninitialised-session crash).
  2. Builds and runs a CMake C API consumer (OcrInit on missing model paths, then OcrDestroy if a handle is returned).
  3. try_compiles OcrLiteCApi.h with only the include-root directory on the search path.

Cross-builds compile only; native triplets also execute the two binaries in rel and dbg.

x64-windows-static Debug failed LNK2019 on cudaMalloc because CI opencv4[cuda] leaves CUDA objects inside static opencv_core without INTERFACE-linking cudart. Link CUDA::cudart from the test port when CUDAToolkit is present.

Stop calling OcrInit on missing model paths; that abort is not a supported entry and crashed the native run step on arm64-osx. Taking addresses is enough to prove the C API is in the static library.
@sparcyu

Copy link
Copy Markdown
Contributor Author

The microsoft.vcpkg.pr (x64_windows_static) check (build 136094) is a test-port failure, not a rapidocr build failure. rapidocr:x64-windows-static itself succeeded in 1.1 min.

x64-windows-static Debug died at vcpkg_cmake_build linking the two consumer exes:

opencv_core4d.lib(gpu_mat.cu.obj) : error LNK2019: unresolved external symbol cudaMalloc
...
cmake_capi.exe : fatal error LNK1120: 48 unresolved externals

CI's overlay opencv4 is the kitchen-sink build (cuda, qt, vtk, …). Static opencv_core then contains CUDA objects, but the imported target does not INTERFACE-link the CUDA runtime, so target_link_libraries(... unofficial::rapidocr::rapidocr) is not enough on that triplet. x64-windows-release passed because that job does not hit this Debug static combination; arm64_* has no CUDA OpenCV.

arm64-osx compiled, then portfile.cmake ran cmake_capi, which called OcrInit on missing ONNX paths and aborted (Subprocess aborted). That is not a supported entry.

Pushed 1ae023bf7a:

  1. find_package(CUDAToolkit QUIET) and CUDA::cudart on the test executables when the toolkit is present.
  2. cmake_capi only takes addresses of OcrInit / OcrDestroy (still forces a link of the C API) and no longer calls them.
  3. CMAKE_AUTOMOC OFF so CI opencv4[qt] does not inject Qt autogen into the smoke binaries.

The C++ run step still default-constructs/destroys OcrLite. Direct C-header try_compile from the include root is unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants