1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 23:33:05 +04:00
Files
Abhishek Gola aa1b8a2a21 Merge pull request #29288 from abhishek-gola:doc_v3
Documentation fixes, Added How to use pre-built opencv doc #29288

closes: https://github.com/opencv/opencv/issues/29263

co-authored by: @kirtijindal14 @Akansha-977 
### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
2026-07-09 17:31:43 +03:00

294 lines
10 KiB
Python

# This file is part of OpenCV project.
# It is subject to the license terms in the LICENSE file found in the top-level directory
# of this distribution and at http://opencv.org/license.html.
# Copyright (C) 2026, BigVision LLC, all rights reserved.
# Third party copyrights are property of their respective owners.
"""Runtime patches for Sphinx C++ domain and breathe; applied at import."""
from __future__ import annotations
import re
def _patch_cpp_xref_resolver():
"""Work around Sphinx 8.1.x parentSymbol assert in _resolve_xref_inner."""
try:
from sphinx.domains.cpp import CPPDomain
except ImportError:
return
original = CPPDomain._resolve_xref_inner
def guarded(self, env, fromdocname, builder, typ, target, node, contnode):
try:
return original(self, env, fromdocname, builder, typ, target,
node, contnode)
except AssertionError:
return None, None
CPPDomain._resolve_xref_inner = guarded
# Drop breathe unresolvable-xref log noise; text still renders.
import logging
_UNRESOLVED_XREF_PATTERNS = (
"Unable to resolve function",
"Unable to resolve class",
"Cannot find function",
"Cannot find class",
"Cannot find variable",
"Cannot find typedef",
"Cannot find enum",
"Cannot find enumerator",
"Cannot find define",
"Duplicate C++ declaration",
)
class _UnresolvedXrefFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
msg = record.getMessage()
return not any(p in msg for p in _UNRESOLVED_XREF_PATTERNS)
_filt = _UnresolvedXrefFilter()
for _logger_name in ("sphinx", "docutils"):
logging.getLogger(_logger_name).addFilter(_filt)
_patch_cpp_xref_resolver()
def _silence_breathe_anon_enum_warning():
"""Mute Sphinx parser warning on Doxygen's anonymous nested enums."""
import logging
class _AnonEnumFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
msg = record.getMessage()
return not (
"Invalid C++ declaration" in msg
and "Expected identifier in nested name" in msg
)
for _name in ("sphinx", "docutils"):
logging.getLogger(_name).addFilter(_AnonEnumFilter())
_silence_breathe_anon_enum_warning()
def _patch_breathe_operator_signatures():
"""Fix breathe {doxygenfunction} mis-splitting operator overloads."""
try:
import breathe.directives.function as _bf
except ImportError:
return
def _split_operator(s: str):
rp = s.rfind(")")
if rp == -1:
return None
depth, j = 0, rp
while j >= 0:
if s[j] == ")":
depth += 1
elif s[j] == "(":
depth -= 1
if depth == 0:
break
j -= 1
if j < 0:
return None
func_part, args_part = s[:j].strip(), s[j:]
k = func_part.find("::operator")
if k != -1:
return func_part[:k], func_part[k + 2:], args_part
if "::" in func_part:
ns, fn = func_part.rsplit("::", 1)
return ns, fn, args_part
return "", func_part, args_part
class _Shim:
__slots__ = ("_g",)
def __init__(self, g1, g2, g3):
self._g = (None, g1, g2, g3)
def group(self, i=0):
return self._g[i]
class _OperatorAwareRe:
def __init__(self, real):
object.__setattr__(self, "_real", real)
def __getattr__(self, name):
return getattr(self._real, name)
def match(self, pattern, string, *args, **kwargs):
m = self._real.match(pattern, string, *args, **kwargs)
if (m is not None and getattr(m.re, "groups", 0) >= 3
and "::operator" in string):
res = _split_operator(string)
if res is not None:
ns, fn, ar = res
return _Shim(ns or None, fn, ar)
return m
if not isinstance(_bf.re, _OperatorAwareRe):
_bf.re = _OperatorAwareRe(_bf.re)
_patch_breathe_operator_signatures()
def _patch_breathe_docsect():
"""Render title-less docSectN nodes breathe 4.36 drops."""
try:
from breathe.renderer import sphinxrenderer as _bsr
except ImportError:
return
_methods = _bsr.SphinxRenderer.methods
if getattr(_methods.get("docsect1"), "_opencv_docsect_patch", False):
return
_orig_visit = _methods["docsect1"]
def _visit_docsectN(self, node):
if not getattr(node, "title", None):
return self.render_iterable(node.content_)
return _orig_visit(self, node)
_visit_docsectN._opencv_docsect_patch = True
for _kind in ("docsect1", "docsect2", "docsect3"):
_methods[_kind] = _visit_docsectN
_patch_breathe_docsect()
def _silence_orphan_toctree_warning():
"""Mute toctree-orphan warning for intentionally unlinked external pages."""
import logging
class _OrphanFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
return "included in any toctree" not in record.getMessage()
for _name in ("sphinx", "docutils"):
logging.getLogger(_name).addFilter(_OrphanFilter())
_silence_orphan_toctree_warning()
def _patch_sidebar_section_root():
"""Root the left sidebar at a page's own top-level section.
A page in two toctrees (e.g. a `cuda*` extra module also grouped under main
`cuda`) gets a last-wins parent from `_get_toctree_ancestors`, rooting its
sidebar at the foreign section. Re-pick the parent sharing the longest path
prefix (same section) so the full sibling list shows."""
try:
import pydata_sphinx_theme.toctree as _pt
from sphinx.environment.adapters.toctree import TocTree
except ImportError:
return
def _section_aware_ancestor(app, pagename, startdepth):
ti = app.env.toctree_includes
cand: dict[str, list[str]] = {}
for _p, _children in ti.items():
for _c in _children:
cand.setdefault(_c, []).append(_p)
def _shared(parent: str, child: str) -> int:
a, b, i = parent.split("/"), child.split("/"), 0
while i < len(a) and i < len(b) and a[i] == b[i]:
i += 1
return i
ancestors: list[str] = []
d = pagename
while d not in ancestors:
ps = cand.get(d)
if not ps:
break
ancestors.append(d)
d = max(ps, key=lambda p: _shared(p, d))
try:
out = ancestors[-startdepth]
except IndexError:
out = None
# Childless root => empty sidebar. Fall back to the dead-end ancestor `d`
# when it's a same-section page with children, else the section api_root.
if out is None or not ti.get(out):
_sec = pagename.split("/", 1)[0]
_base = pagename.rsplit("/", 1)[-1]
# Doxygen file/dir-reference pages are orphan utilities, not module
# content: leave None to suppress the sidebar rather than root at api_root.
if re.search(r"_8\w+$", _base) or _base.startswith("dir_"):
out = None
elif d != pagename and ti.get(d) and d.split("/", 1)[0] == _sec:
out = d
elif ti.get(_sec + "/api_root"):
out = _sec + "/api_root"
return out, TocTree(app.env)
_pt._get_ancestor_pagename = _section_aware_ancestor
_patch_sidebar_section_root()
def _patch_sphinx_toctree_ancestors():
"""Fix which branch the collapsed startdepth=0 sidebar auto-expands.
Sphinx picks it via `_get_toctree_ancestors`, whose last-wins parent map
mis-picks for a page in two toctrees (e.g. a `cuda*` extra module also under
main `cuda`), so its section won't expand. Prefer the parent sharing the
longest path prefix (same section)."""
try:
import sphinx.environment.adapters.toctree as _st
except ImportError:
return
def _section_aware(toctree_includes, docname):
cand: dict[str, list[str]] = {}
for _p, _children in toctree_includes.items():
for _c in _children:
cand.setdefault(_c, []).append(_p)
def _shared(parent: str, child: str) -> int:
a, b, i = parent.split("/"), child.split("/"), 0
while i < len(a) and i < len(b) and a[i] == b[i]:
i += 1
return i
ancestors: list[str] = []
d = docname
while d not in ancestors:
ps = cand.get(d)
if not ps:
break
ancestors.append(d)
d = max(ps, key=lambda p: _shared(p, d))
return dict.fromkeys(ancestors).keys()
_st._get_toctree_ancestors = _section_aware
_patch_sphinx_toctree_ancestors()
def register_global_sidebar(app):
"""Make the sidebar list ALL top-level sections (startdepth=0), current one
auto-expanded, instead of only the active section's subtree.
Wrap the theme's `generate_toctree_html` (its sole sidebar nav generator) to
force startdepth=0, connecting after the theme's html-page-context handler so
it keeps its own template and we flip only this arg. This makes
`_patch_sidebar_section_root` a no-op (its lookup only runs when startdepth != 0)."""
def _globalize(app, pagename, templatename, context, doctree):
gen = context.get("generate_toctree_html")
if not callable(gen):
return
def wrapped(kind, startdepth=0, show_nav_level=0, **kwargs):
# collapse=True: expand only the current branch. Without it,
# startdepth=0 renders the whole tree on every page (slow + bloated).
kwargs["collapse"] = True
return gen(kind, startdepth=0, show_nav_level=0, **kwargs)
context["generate_toctree_html"] = wrapped
app.connect("html-page-context", _globalize, priority=900)