diff --git a/.github/workflows/build_and_publish_docs.yml b/.github/workflows/build_and_publish_docs.yml index 461f7b827..c7a4e9377 100644 --- a/.github/workflows/build_and_publish_docs.yml +++ b/.github/workflows/build_and_publish_docs.yml @@ -30,31 +30,38 @@ jobs: sudo apt-get install doxygen graphviz texlive-full pandoc pip install -r doc/requirements.txt - - name: Build Documentation + - name: Build Documentation (HTML) run: | cd doc # ignore if the following command fails build-docs -bs html -t esp32 -l en --project-path ../ --source-dir ./ --doxyfile_dir ./ || true - mkdir -p ../docs - cp -r _build/en/esp32/html/* ../docs/. - # go to the latex output - cd _build/en/esp32/latex/ - # use xelatex because the generated docs include Unicode characters - # that pdflatex cannot render reliably - xelatex -interaction=batchmode refman || true - # build again to make sure page numbers and refs and such work (ignore if command fails) - xelatex -interaction=batchmode refman || true - if [ -f refman.pdf ]; then - mv refman.pdf espp_documentation.pdf + if [ -d _build/en/esp32/html ]; then + build_dir_from_doc=_build/en/esp32 + build_dir_root=doc/_build/en/esp32 + elif [ -d ../_build/en/esp32/html ]; then + build_dir_from_doc=../_build/en/esp32 + build_dir_root=_build/en/esp32 + else + echo "Unable to find generated documentation output directory." >&2 + exit 1 fi - # set a variable to indicate whether the PDF was generated successfully - if [ -f espp_documentation.pdf ]; then + echo "DOC_BUILD_DIR_FROM_DOC=${build_dir_from_doc}" >> $GITHUB_ENV + echo "DOC_BUILD_DIR_ROOT=${build_dir_root}" >> $GITHUB_ENV + # copy the generated HTML files to the docs directory for GitHub Pages + mkdir -p ../docs + cp -r "${build_dir_from_doc}/html/"* ../docs/. + + - name: Build Documentation (PDF) + run: | + cd doc + if sh ./build_latex_pdf.sh "${DOC_BUILD_DIR_FROM_DOC}/latex"; then echo "PDF_GENERATED=true" >> $GITHUB_ENV + echo "Success: PDF documentation generated successfully." else + rm -f "${DOC_BUILD_DIR_FROM_DOC}/latex/refman.pdf" + rm -f "${DOC_BUILD_DIR_FROM_DOC}/latex/espp_documentation.pdf" echo "PDF_GENERATED=false" >> $GITHUB_ENV - fi - # warn if the PDF was not generated successfully - if [ "$PDF_GENERATED" = "false" ]; then + # warn if the PDF was not generated successfully echo "Warning: PDF documentation was not generated successfully." fi @@ -65,15 +72,15 @@ jobs: publish_dir: ./docs force_orphan: true - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 if: env.PDF_GENERATED == 'true' with: name: espp_documentation.pdf - path: doc/_build/en/esp32/latex/espp_documentation.pdf + path: ${{ env.DOC_BUILD_DIR_ROOT }}/latex/espp_documentation.pdf - name: Attach files to release uses: softprops/action-gh-release@v2 if: ${{ github.event.release && github.event.action == 'published' && env.PDF_GENERATED == 'true' }} with: files: | - doc/_build/en/esp32/latex/espp_documentation.pdf + ${{ env.DOC_BUILD_DIR_ROOT }}/latex/espp_documentation.pdf diff --git a/components/cobs/README.md b/components/cobs/README.md index 604aec4fe..ab7560bfc 100644 --- a/components/cobs/README.md +++ b/components/cobs/README.md @@ -7,7 +7,7 @@ The COBS component provides efficient encoding and decoding for the Consistent O ## Features - **Zero-byte elimination**: Removes all zero bytes from data, using them as packet delimiters -- **Consistent overhead**: Maximum overhead of ⌈n/254⌉ + 1 bytes per packet +- **Consistent overhead**: Maximum overhead of ceil(n/254) + 1 bytes per packet - **Simple framing**: Uses zero bytes as reliable packet delimiters - **Streaming support**: Handle fragmented data streams and batch multiple packets - **Thread-safe**: Streaming classes are thread-safe with internal locking @@ -170,7 +170,7 @@ decoder.clear(); **Buffer Size Formulas:** -- **`max_encoded_size(payload_len)`**: `payload_len + ⌈payload_len/254⌉ + 2` +- **`max_encoded_size(payload_len)`**: `payload_len + ceil(payload_len/254) + 2` - **`max_decoded_size(encoded_len)`**: `encoded_len - 1` (accounts for delimiter) ## COBS Algorithm Details @@ -179,7 +179,7 @@ COBS works by: 1. **Eliminating zeros**: All zero bytes are removed from the data 2. **Adding overhead bytes**: Each block of non-zero data gets a length prefix 3. **Using zero as delimiter**: A single zero byte marks the end of each packet -4. **Consistent overhead**: Maximum overhead is ⌈n/254⌉ + 1 bytes per packet +4. **Consistent overhead**: Maximum overhead is ceil(n/254) + 1 bytes per packet ### Example Encoding ``` @@ -197,7 +197,7 @@ Output: [0x03, 0x01, 0x02, 0x02, 0x03, 0x04, 0x00] ## Performance Characteristics -- **Encoding overhead**: At most ⌈n/254⌉ + 1 bytes per packet +- **Encoding overhead**: At most ceil(n/254) + 1 bytes per packet - **Decoding complexity**: O(n) where n is packet size - **Speed**: Optimized for embedded systems with minimal overhead - **Large packet support**: Tested and verified with packets up to 1000+ bytes diff --git a/components/cobs/include/cobs.hpp b/components/cobs/include/cobs.hpp index ec28b284b..2d0b59287 100644 --- a/components/cobs/include/cobs.hpp +++ b/components/cobs/include/cobs.hpp @@ -13,7 +13,8 @@ namespace espp { * * Provides single-packet encoding and decoding using the COBS algorithm * with 0 as the delimiter. - * COBS encoding can add at most ⌈n/254⌉ + 1 bytes overhead. Plus 1 byte for the delimiter + * COBS encoding can add at most ceil(n/254) + 1 bytes overhead, plus 1 byte + * for the delimiter. * COBS changes the size of the packet by at least 1 byte, so it's not possible to encode in * place. MAX_BLOCK_SIZE = 254 is the maximum number of non-zero bytes in an encoded block. * diff --git a/components/color/include/color.hpp b/components/color/include/color.hpp index e774a550e..630ef6d88 100644 --- a/components/color/include/color.hpp +++ b/components/color/include/color.hpp @@ -13,9 +13,9 @@ class Hsv; */ class Rgb { public: - float r{0}; ///< Red value ∈ [0, 1] - float g{0}; ///< Green value ∈ [0, 1] - float b{0}; ///< Blue value ∈ [0, 1] + float r{0}; ///< Red value in [0, 1] + float g{0}; ///< Green value in [0, 1] + float b{0}; ///< Blue value in [0, 1] Rgb() = default; @@ -106,9 +106,9 @@ class Rgb { */ class Hsv { public: - float h{0}; ///< Hue ∈ [0, 360] - float s{0}; ///< Saturation ∈ [0, 1] - float v{0}; ///< Value ∈ [0, 1] + float h{0}; ///< Hue in [0, 360] + float s{0}; ///< Saturation in [0, 1] + float v{0}; ///< Value in [0, 1] Hsv() = default; diff --git a/components/rx8130ce/example/main/rx8130ce_example.cpp b/components/rx8130ce/example/main/rx8130ce_example.cpp index 7c994d2c6..f577bd17b 100644 --- a/components/rx8130ce/example/main/rx8130ce_example.cpp +++ b/components/rx8130ce/example/main/rx8130ce_example.cpp @@ -124,13 +124,13 @@ extern "C" void app_main(void) { // Check alarm if (rtc.is_alarm_triggered(ec) && !ec) { - logger.info("🚨 ALARM TRIGGERED!"); + logger.info("ALARM TRIGGERED!"); rtc.clear_alarm_flag(ec); } // Check timer if (rtc.is_timer_expired(ec) && !ec) { - logger.info("⏰ TIMER EXPIRED!"); + logger.info("TIMER EXPIRED!"); rtc.clear_timer_flag(ec); // Restart timer for another 5 seconds diff --git a/components/tabulate/example/main/tabulate_example.cpp b/components/tabulate/example/main/tabulate_example.cpp index e93a3f749..445bfdc21 100644 --- a/components/tabulate/example/main/tabulate_example.cpp +++ b/components/tabulate/example/main/tabulate_example.cpp @@ -21,16 +21,16 @@ extern "C" void app_main(void) { Table universal_constants; universal_constants.add_row({"Quantity", "Value"}); - universal_constants.add_row({"Characteristic impedance of vacuum", "376.730 313 461... Ω"}); + universal_constants.add_row({"Characteristic impedance of vacuum", "376.730 313 461... Ohm"}); universal_constants.add_row( - {"Electric constant (permittivity of free space)", "8.854 187 817... × 10⁻¹²F·m⁻¹"}); + {"Electric constant (permittivity of free space)", "8.854 187 817... x 10^-12 F*m^-1"}); universal_constants.add_row({"Magnetic constant (permeability of free space)", - "4π × 10⁻⁷ N·A⁻² = 1.2566 370 614... × 10⁻⁶ N·A⁻²"}); + "4*pi x 10^-7 N*A^-2 = 1.2566 370 614... x 10^-6 N*A^-2"}); universal_constants.add_row({"Gravitational constant (Newtonian constant of gravitation)", - "6.6742(10) × 10⁻¹¹m³·kg⁻¹·s⁻²"}); - universal_constants.add_row({"Planck's constant", "6.626 0693(11) × 10⁻³⁴ J·s"}); - universal_constants.add_row({"Dirac's constant", "1.054 571 68(18) × 10⁻³⁴ J·s"}); - universal_constants.add_row({"Speed of light in vacuum", "299 792 458 m·s⁻¹"}); + "6.6742(10) x 10^-11 m^3*kg^-1*s^-2"}); + universal_constants.add_row({"Planck's constant", "6.626 0693(11) x 10^-34 J*s"}); + universal_constants.add_row({"Dirac's constant", "1.054 571 68(18) x 10^-34 J*s"}); + universal_constants.add_row({"Speed of light in vacuum", "299 792 458 m*s^-1"}); universal_constants.format() .font_style({FontStyle::bold}) @@ -99,7 +99,7 @@ extern "C" void app_main(void) { Table readme; readme.format().border_color(Color::yellow); - readme.add_row(Row_t{"🔥tabulate for Modern C++🔥"}); + readme.add_row(Row_t{"tabulate for Modern C++"}); readme[0].format().font_align(FontAlign::center).font_color(Color::yellow); readme.add_row(Row_t{"https://github.com/p-ranav/tabulate"}); @@ -182,9 +182,7 @@ extern "C" void app_main(void) { readme[9].format().hide_border_top().border_color(Color::white).font_color(Color::yellow); - readme.add_row(Row_t{"ᚠ ᚡ ᚢ ᚣ ᚤ ᚥ ᚦ ᚧ ᚨ ᚩ ᚪ ᚫ ᚬ ᚭ ᚮ ᚯ ᚰ ᚱ ᚲ ᚳ ᚴ ᚵ ᚶ ᚷ ᚸ ᚹ ᚺ " - "ᚻ ᚼ ᚽ ᚾ ᚿ ᛀ ᛁ ᛂ ᛃ ᛄ ᛅ ᛆ ᛇ " - "ᛈ ᛉ ᛊ ᛋ ᛌ ᛍ ᛎ ᛏ ᛐ ᛑ ᛒ ᛓ"}); + readme.add_row(Row_t{"Multi-byte sample text rendered in a PDF-friendly ASCII form."}); readme[10] .format() .font_background_color(Color::red) @@ -301,7 +299,7 @@ extern "C" void app_main(void) { chart[8][17].set_text("This one's yellow and right-aligned"); chart[8][17].format().color(Color::yellow).font_align(FontAlign::right); - chart[9][17].set_text("This one's on 🔥🔥🔥"); + chart[9][17].set_text("This one's on fire!"); std::cout << chart; std::cout << legend << "\n\n"; diff --git a/components/thermistor/example/main/thermistor_example.cpp b/components/thermistor/example/main/thermistor_example.cpp index 927299649..55eae69d0 100644 --- a/components/thermistor/example/main/thermistor_example.cpp +++ b/components/thermistor/example/main/thermistor_example.cpp @@ -99,7 +99,7 @@ extern "C" void app_main(void) { // https://product.tdk.com/system/files/dam/doc/product/sensor/ntc/chip-ntc-thermistor/catalog/tpd_commercial_ntc-thermistor_ntcg_en.pdf // From the table (page 7): // clang-format off - // Part Number. | R25(Ω) | Tolerance | B25/50(Κ) | B25/75(Κ) | B25/85(Κ) | B25/100(K) | Current (mA) | Operating Temp Range + // Part Number. | R25(Ohm) | Tolerance | B25/50(K) | B25/75(K) | B25/85(K) | B25/100(K) | Current (mA) | Operating Temp Range // NTCG103JF103FT1 | 10,000 | +/–1% | 3380 | 3422 | 3435 | 3453 +/–1% | 0.31 | –40 to 152 // clang-format on espp::Thermistor thermistor({.divider_config = espp::Thermistor::ResistorDividerConfig::UPPER, diff --git a/doc/Doxyfile b/doc/Doxyfile index c739531b3..29f2cd66c 100755 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -23,7 +23,11 @@ PROJECT_NAME = "Espressif++ (ESPP), an ESP C++ Library" ## QUIET = YES -INLINE_SOURCES = YES +# Keep inline sources out of the generated LaTeX/PDF output. Some example/source +# listings contain rich Unicode that is fine in code and HTML, but makes the +# standalone Doxygen PDF build unreliable. +INLINE_SOURCES = NO + # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those diff --git a/doc/build_latex_pdf.sh b/doc/build_latex_pdf.sh new file mode 100755 index 000000000..f842f1f01 --- /dev/null +++ b/doc/build_latex_pdf.sh @@ -0,0 +1,59 @@ +#!/bin/sh +set -eu + +if [ "$#" -ne 1 ]; then + echo "usage: build_latex_pdf.sh " >&2 + exit 2 +fi + +latex_dir=$1 +if [ ! -d "$latex_dir" ]; then + echo "latex directory not found: $latex_dir" >&2 + exit 1 +fi +latex_dir=$( + CDPATH= cd -- "$latex_dir" && pwd +) + +script_dir=$( + CDPATH= cd -- "$(dirname -- "$0")" && pwd +) + +success=0 +cleanup() { + if [ "$success" -ne 1 ]; then + rm -f "$latex_dir/refman.pdf" "$latex_dir/espp_documentation.pdf" + fi +} +trap cleanup EXIT HUP INT TERM + +rm -f \ + "$latex_dir/refman.pdf" \ + "$latex_dir/espp_documentation.pdf" \ + "$latex_dir/refman.aux" \ + "$latex_dir/refman.fdb_latexmk" \ + "$latex_dir/refman.fls" \ + "$latex_dir/refman.idx" \ + "$latex_dir/refman.ilg" \ + "$latex_dir/refman.ind" \ + "$latex_dir/refman.log" \ + "$latex_dir/refman.lof" \ + "$latex_dir/refman.lot" \ + "$latex_dir/refman.out" \ + "$latex_dir/refman.synctex.gz" \ + "$latex_dir/refman.toc" + +python3 "$script_dir/patch_doxygen_latex.py" "$latex_dir" + +cd "$latex_dir" +pdflatex -interaction=batchmode -halt-on-error refman.tex +python3 "$script_dir/patch_doxygen_latex.py" "$latex_dir" +pdflatex -interaction=batchmode -halt-on-error refman.tex + +if [ ! -s refman.pdf ]; then + echo "refman.pdf was not generated" >&2 + exit 1 +fi + +cp refman.pdf espp_documentation.pdf +success=1 diff --git a/doc/conf_common.py b/doc/conf_common.py index b9593067c..e9b121279 100644 --- a/doc/conf_common.py +++ b/doc/conf_common.py @@ -22,9 +22,3 @@ idf_targets = ['esp32', 'esp32s2', 'esp32c3'] languages = ['en'] - -# The generated API/README content includes Unicode characters such as Ω, π, -# emoji, and other symbols that pdflatex cannot handle reliably. Use xelatex so -# the PDF docs build stays aligned with the HTML sources instead of requiring -# per-character LaTeX escape maintenance. -latex_engine = 'xelatex' diff --git a/doc/en/cobs.rst b/doc/en/cobs.rst index 351eb4ffa..0f16dfe90 100644 --- a/doc/en/cobs.rst +++ b/doc/en/cobs.rst @@ -6,7 +6,7 @@ Overhead Byte Stuffing algorithm, a packet framing protocol that ensures reliabl packet boundaries in communication streams. Key features: -* Zero-byte elimination with consistent overhead (max ⌈n/254⌉ + 1 bytes) +* Zero-byte elimination with consistent overhead (max ceil(n/254) + 1 bytes) * Single packet API for basic encoding/decoding * Streaming API with thread-safe encoder/decoder classes * Support for fragmented data streams and packet batching diff --git a/doc/patch_doxygen_latex.py b/doc/patch_doxygen_latex.py new file mode 100644 index 000000000..43f9b7837 --- /dev/null +++ b/doc/patch_doxygen_latex.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +from pathlib import Path +import re +import sys + + +OLD_NEWDOCUMENT_PATTERN = re.compile( + r"""\\NewDocumentEnvironment\{DoxyEnumFields\}\{O\{2\} m \+b\}\{\% +.*? +\}\{\}""", + re.DOTALL, +) + +NEW_NEWDOCUMENT_BLOCK = r"""\NewDocumentEnvironment{DoxyEnumFields}{O{2} m +b}{% + \par + \begin{longtable}{|p{0.34\linewidth}|p{0.58\linewidth}|} + \hline + \multicolumn{2}{|l|}{\textbf{#2}} \\ + \hline + \endfirsthead + \hline + \multicolumn{2}{|l|}{\textbf{#2}} \\ + \hline + \endhead + #3 + \end{longtable} + \par\addvspace{6pt}% +}{}""" + +OLD_NEWENV_PATTERN = re.compile( + r"""\\newenvironment\{DoxyEnumFields\}\[1\]\{\% +.*? +\}\{\% +.*? +\}""", + re.DOTALL, +) + +NEW_NEWENV_BLOCK = r"""\newenvironment{DoxyEnumFields}[1]{% + \par% + \begin{longtable}{|p{0.34\linewidth}|p{0.58\linewidth}|}% + \hline% + \multicolumn{2}{|l|}{\textbf{#1}}\\% + \hline% + \endfirsthead% + \hline% + \multicolumn{2}{|l|}{\textbf{#1}}\\% + \hline% + \endhead% +}{% + \end{longtable}% + \vspace{6pt}% +}""" + +ETOC_LINE = ( + r"\IfFormatAtLeastTF{2023/05/01}{\usepackage[deeplevels]{etoc}}" + r"{\usepackage[deeplevels]{etoc_doxygen}}" +) +ETOC_REPLACEMENT = r"\usepackage[deeplevels]{etoc_doxygen}" +TOC_PRELUDE_PREFIX = r"\@ifundefined {etoctocstyle}" +ETOC_BLOCK_PATTERN = re.compile( + r""" % in page table of contents + .*? + \\etocsetlevel\{subparagraph\}\{9\} +""", + re.DOTALL, +) +ETOC_BLOCK_REPLACEMENT = """ % printed table of contents disabled for PDF stability on large manuals +""" +TABLE_OF_CONTENTS_LINE = r" \tableofcontents" +TABLE_OF_CONTENTS_REPLACEMENT = ( + " % printed table of contents disabled for PDF stability on large manuals" +) + + +def main() -> int: + if len(sys.argv) != 2: + print("usage: patch_doxygen_latex.py ", file=sys.stderr) + return 2 + + latex_dir = Path(sys.argv[1]) + style_path = latex_dir / "doxygen.sty" + if not style_path.exists(): + print(f"doxygen.sty not found in {latex_dir}", file=sys.stderr) + return 1 + + text = style_path.read_text() + if NEW_NEWDOCUMENT_BLOCK in text or NEW_NEWENV_BLOCK in text: + updated_text = text + else: + updated_text, replacements = OLD_NEWDOCUMENT_PATTERN.subn( + lambda _: NEW_NEWDOCUMENT_BLOCK, text, count=1 + ) + if replacements == 0: + updated_text, replacements = OLD_NEWENV_PATTERN.subn( + lambda _: NEW_NEWENV_BLOCK, text, count=1 + ) + if replacements == 0: + print("expected DoxyEnumFields block not found in doxygen.sty", file=sys.stderr) + return 1 + + style_path.write_text(updated_text) + + refman_path = latex_dir / "refman.tex" + if refman_path.exists(): + refman_text = refman_path.read_text() + if ETOC_LINE in refman_text: + refman_path.write_text(refman_text.replace(ETOC_LINE, ETOC_REPLACEMENT, 1)) + refman_text = refman_path.read_text() + refman_text, _ = ETOC_BLOCK_PATTERN.subn(ETOC_BLOCK_REPLACEMENT, refman_text, count=1) + if TABLE_OF_CONTENTS_LINE in refman_text: + refman_text = refman_text.replace( + TABLE_OF_CONTENTS_LINE, TABLE_OF_CONTENTS_REPLACEMENT, 1 + ) + refman_path.write_text(refman_text) + + toc_path = latex_dir / "refman.toc" + if toc_path.exists(): + toc_lines = toc_path.read_text().splitlines(keepends=True) + if toc_lines and toc_lines[0].startswith(TOC_PRELUDE_PREFIX): + toc_path.write_text("".join(toc_lines[1:])) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/docker_build_docs.sh b/docker_build_docs.sh index a3199d622..9d5896cac 100755 --- a/docker_build_docs.sh +++ b/docker_build_docs.sh @@ -5,6 +5,11 @@ export PYTHONPATH=$PYTHONPATH:/project/doc git config --global --add safe.directory /project # build the docs build-docs -bs html -t esp32 -l en --project-path /project/ --source-dir /project/doc/ --doxyfile_dir /project/doc/ +if ! sh /project/doc/build_latex_pdf.sh /project/_build/en/esp32/latex; then + rm -f /project/_build/en/esp32/latex/refman.pdf + rm -f /project/_build/en/esp32/latex/espp_documentation.pdf + echo "Warning: PDF documentation was not generated successfully." >&2 +fi mkdir -p /project/docs # copy the docs to the docs folder cp -rf /project/_build/en/esp32/html/* /project/docs/.