openscad-cpp-evaluator 0.31.2__tar.gz → 0.32.2__tar.gz

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 (207) hide show
  1. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/CLAUDE.md +87 -52
  2. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/CMakeLists.txt +58 -5
  3. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/PKG-INFO +1 -1
  4. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/evaluator.hpp +1 -1
  5. openscad_cpp_evaluator-0.32.2/include/openscad_cpp_evaluator/font_match.hpp +62 -0
  6. openscad_cpp_evaluator-0.32.2/include/openscad_cpp_evaluator/font_provider.hpp +98 -0
  7. openscad_cpp_evaluator-0.32.2/include/openscad_cpp_evaluator/freetype_font_provider.hpp +38 -0
  8. openscad_cpp_evaluator-0.32.2/include/openscad_cpp_evaluator/text_metrics.hpp +40 -0
  9. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/pyproject.toml +1 -1
  10. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/CMakeLists.txt +3 -2
  11. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/function_builtins.cpp +36 -8
  12. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/text.cpp +31 -17
  13. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/expr_eval.cpp +2 -2
  14. openscad_cpp_evaluator-0.32.2/src/text/font_match.cpp +190 -0
  15. openscad_cpp_evaluator-0.32.2/src/text/freetype_font_provider.cpp +310 -0
  16. openscad_cpp_evaluator-0.32.2/src/text/text_metrics.cpp +70 -0
  17. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_bytecode_compiler.cpp +29 -0
  18. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_control_flow.cpp +56 -0
  19. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_function_builtins.cpp +28 -1
  20. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_text.cpp +119 -0
  21. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/uv.lock +1 -1
  22. openscad_cpp_evaluator-0.31.2/include/openscad_cpp_evaluator/font_provider.hpp +0 -73
  23. openscad_cpp_evaluator-0.31.2/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -31
  24. openscad_cpp_evaluator-0.31.2/include/openscad_cpp_evaluator/text_metrics.hpp +0 -36
  25. openscad_cpp_evaluator-0.31.2/src/text/stb_font_provider.cpp +0 -171
  26. openscad_cpp_evaluator-0.31.2/src/text/text_metrics.cpp +0 -87
  27. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/.github/workflows/ci.yml +0 -0
  28. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/.github/workflows/wheels.yml +0 -0
  29. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/.gitignore +0 -0
  30. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/.gitmodules +0 -0
  31. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/LICENSE +0 -0
  32. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/README.md +0 -0
  33. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/bindings/CMakeLists.txt +0 -0
  34. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/bindings/ast_to_py.cpp +0 -0
  35. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/bindings/module.cpp +0 -0
  36. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/cmake/embed_font.cmake +0 -0
  37. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/examples/CMakeLists.txt +0 -0
  38. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/examples/eval_perf_benchmark.cpp +0 -0
  39. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/examples/manifold_cache_reuse.cpp +0 -0
  40. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/examples/minimal_debugger.cpp +0 -0
  41. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  42. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/.gitignore +0 -0
  43. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  44. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  45. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/README.md +0 -0
  46. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  47. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  48. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  49. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  50. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  51. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  52. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  53. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  54. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  55. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  56. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  57. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  58. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  59. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  60. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  61. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  62. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/api.cpp +0 -0
  63. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  64. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  65. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  66. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  67. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  68. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  69. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  70. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  71. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  72. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  73. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  74. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  75. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  76. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  77. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  78. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  79. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/position.cpp +0 -0
  80. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  81. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  82. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  83. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  84. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  85. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  86. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  87. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  88. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  89. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  90. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  91. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  92. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  93. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  94. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  95. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  96. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  97. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  98. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  99. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  100. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  101. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  102. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  103. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  104. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  105. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  106. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  107. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  108. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  109. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  110. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  111. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  112. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  113. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  114. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  115. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  116. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  117. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  118. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  119. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  120. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  121. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  122. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/export.hpp +0 -0
  123. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  124. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  125. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  126. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  127. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  128. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  129. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  130. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  131. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  132. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  133. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/value.hpp +0 -0
  134. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  135. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/python/openscad_cpp_evaluator/__init__.py +0 -0
  136. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/resources/fonts/LICENSE +0 -0
  137. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  138. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/booleans.cpp +0 -0
  139. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/builtins.hpp +0 -0
  140. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/call_args.cpp +0 -0
  141. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/color.cpp +0 -0
  142. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/control.cpp +0 -0
  143. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/extrude.cpp +0 -0
  144. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/import.cpp +0 -0
  145. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/modifiers.cpp +0 -0
  146. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/primitives_2d.cpp +0 -0
  147. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/primitives_3d.cpp +0 -0
  148. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/registry.cpp +0 -0
  149. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/roof.cpp +0 -0
  150. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/segments.cpp +0 -0
  151. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/surface.cpp +0 -0
  152. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/topology.cpp +0 -0
  153. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/builtins/transforms.cpp +0 -0
  154. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/bytecode_compiler.cpp +0 -0
  155. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/bytecode_vm.cpp +0 -0
  156. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/colored_body.cpp +0 -0
  157. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/csg_generate.cpp +0 -0
  158. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/csg_resolve.cpp +0 -0
  159. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/css_colors.cpp +0 -0
  160. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/debug/debug_repl.cpp +0 -0
  161. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/debug_profile.cpp +0 -0
  162. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/eval_context.cpp +0 -0
  163. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/eval_error.cpp +0 -0
  164. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/eval_use.cpp +0 -0
  165. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/export.cpp +0 -0
  166. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/import/dxf_import.cpp +0 -0
  167. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/import/mesh_import.cpp +0 -0
  168. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/import/surface_load.cpp +0 -0
  169. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/import/svg_import.cpp +0 -0
  170. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/manifold_cache.cpp +0 -0
  171. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/mesh_check.cpp +0 -0
  172. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/stmt_eval.cpp +0 -0
  173. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/user_calls.cpp +0 -0
  174. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/value.cpp +0 -0
  175. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/src/zip_stored.cpp +0 -0
  176. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/CMakeLists.txt +0 -0
  177. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_booleans.cpp +0 -0
  178. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_cli.cpp +0 -0
  179. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_csg_tree.cpp +0 -0
  180. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_debug_hooks.cpp +0 -0
  181. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_dxf_svg_import.cpp +0 -0
  182. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_eval_context.cpp +0 -0
  183. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_export.cpp +0 -0
  184. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_expr_eval.cpp +0 -0
  185. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_extrude_roof.cpp +0 -0
  186. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_helpers.hpp +0 -0
  187. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_import_export.cpp +0 -0
  188. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_manifold_cache.cpp +0 -0
  189. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_mesh_check.cpp +0 -0
  190. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_multi_color_merge.cpp +0 -0
  191. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_primitives.cpp +0 -0
  192. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_profiling.cpp +0 -0
  193. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_python_bindings.py +0 -0
  194. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_scope_trail.cpp +0 -0
  195. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_scoping.cpp +0 -0
  196. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_surface.cpp +0 -0
  197. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_tail_calls.cpp +0 -0
  198. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_transforms.cpp +0 -0
  199. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_unexpected_args.cpp +0 -0
  200. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_use.cpp +0 -0
  201. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_value.cpp +0 -0
  202. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_viewport_params.cpp +0 -0
  203. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tests/test_zip_stored.cpp +0 -0
  204. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tools/cli/CMakeLists.txt +0 -0
  205. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tools/cli/cli_lib.cpp +0 -0
  206. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tools/cli/cli_lib.hpp +0 -0
  207. {openscad_cpp_evaluator-0.31.2 → openscad_cpp_evaluator-0.32.2}/tools/cli/main.cpp +0 -0
@@ -12,14 +12,26 @@ OpenSCAD AST and produces Manifold CSG geometry. This port builds on two depende
12
12
  tag, not vendored) — wired in as of Phase 2
13
13
  - Boost.Polygon (`boostorg/polygon` + `boostorg/config`, fetched individually — not the full Boost
14
14
  superproject) for `roof()`'s Voronoi-diagram construction — wired in as of Phase 6, see below
15
- - `nothings/stb` (`stb_image.h`, `stb_truetype.h`; header-only, no build step) for `surface()`'s
16
- PNG loader and the built-in `FontProvider` default's TTF parsing/outline extraction — wired in as
17
- of Phase 7
15
+ - `nothings/stb` (`stb_image.h`; header-only, no build step) for `surface()`'s PNG loader — wired
16
+ in as of Phase 7
17
+ - FreeType (`VER-2-14-3`) and HarfBuzz (`14.3.1`), both via `FetchContent` — the font backend:
18
+ HarfBuzz shapes, FreeType supplies outlines and face metrics. Deliberately NOT fontconfig, the
19
+ third leg of real OpenSCAD's font stack: it is meson-only (no CMake build at all), needs expat,
20
+ and wants a runtime `fonts.conf` plus a cache directory, none of which survives contact with six
21
+ cibuildwheel targets. `src/text/font_match.cpp` reimplements the part of it that `font=` actually
22
+ uses — spec syntax and directory scanning — so name matching behaves identically everywhere
23
+ rather than only where a fontconfig build exists. The two libraries are also kept independent of
24
+ *each other* (`HB_HAVE_FREETYPE=OFF`): HarfBuzz reads the font through its own hb-ot tables
25
+ reader, so no `find_package(Freetype)` has to resolve against a FetchContent target that is
26
+ installed nowhere. Both address glyphs by the same font-internal index and both work in font
27
+ units (`hb_font_set_scale` to unitsPerEm on one side, `FT_LOAD_NO_SCALE` on the other), so the
28
+ split costs nothing to bridge.
18
29
 
19
30
  The full design and phase-by-phase build order live in the plan this repo is being built from:
20
31
  `/Users/gminette/.claude/plans/please-plan-out-a-twinkly-anchor.md`. Read it before starting a new
21
32
  phase — it has the rationale for every non-obvious choice (value model, `EvalContext` lifetime,
22
- the `FontProvider` callback abstraction for Qt-hosted cross-platform font support, why Manifold's
33
+ the `FontProvider` abstraction (whose built-in backend has since become
34
+ FreeType/HarfBuzz rather than the stb_truetype single-font default the plan describes), why Manifold's
23
35
  own vendored `linalg.h` types and not GLM/Eigen, etc.) and the exit criterion each phase needs to
24
36
  hit before the next one starts. §6a was corrected during Phase 2 after checking Manifold's real
25
37
  source (it no longer uses GLM) — a live example of why the plan says to verify API assumptions
@@ -43,7 +55,7 @@ CSG-tree shape like union/difference/intersection, role-split via the shared `sp
43
55
  `POLYLINE`) and SVG (`path`/`polygon`/`polyline`/`rect`/`circle`/`ellipse`, nested
44
56
  `transform=`, all path commands including elliptical arcs) 2D contour import in both module
45
57
  (Region geometry) and expression (Region value) contexts, and the `FontProvider` abstraction
46
- (`font_provider.hpp`) + built-in `StbFontProvider` default + `text()`/`textmetrics()`/
58
+ (`font_provider.hpp`) + built-in FreeType/HarfBuzz backend + `text()`/`textmetrics()`/
47
59
  `fontmetrics()`; and (Phase 8) `ManifoldCache` — an opt-in, thread-safe content-hash cache of
48
60
  already-generated CSGNode subtrees (`manifold_cache.hpp`/`.cpp`), plus `rands()`-taint tracking
49
61
  (`Evaluator::noteRandsCall()`/`CSGNode::uncacheable`) so a subtree whose resolved content isn't a
@@ -215,31 +227,45 @@ top-level 2D primitive/projection() script had been silently unexportable via th
215
227
  Fixed by porting `toRenderableBodies()` (`colored_body.hpp`/`.cpp`) and calling it from the CLI right
216
228
  before export, matching the reference's own call site exactly.
217
229
 
218
- **Font support, Phase 7**: mirrors the plan's §6 design exactly — a `FontProvider` abstract
219
- interface (`font_provider.hpp`: `resolveFont`/`metrics`/`glyphMetrics`/`glyphOutline`) rather than
220
- hard-depending on the Python reference's `fc-match` shell-out (Linux-only, unusable inside a
221
- cross-platform Qt host app). `Evaluator`'s constructor takes an optional
222
- `shared_ptr<FontProvider>`; if unset, `Evaluator::fontProvider()` lazily constructs the built-in
223
- `StbFontProvider` default on first use (not eagerly in the constructor, so a script that never
224
- calls `text()`/`textmetrics()`/`fontmetrics()` never pays for font-file parsing).
225
- `StbFontProvider::resolveFont()` always returns the one bundled font (`resources/fonts/
226
- LiberationSans-Regular.ttf`, the *exact same* font file as the Python reference's own bundled
227
- default, copied over so cross-checks are apples-to-apples) regardless of the requested spec — no
228
- system font matching, matching the plan's documented limitation for the built-in default; a real Qt
229
- host app supplies its own `FontProvider` (`QFontDatabase`/`QRawFont`-backed) instead of this class
230
- for actual system-font resolution. `text_metrics.hpp`/`.cpp` holds the shared, backend-agnostic
231
- layout math (`measureText`/`textAlignOffset`, mirroring `_measure_text`/`_text_align_offset`
232
- exactly) used by both `text()` (`builtins/text.cpp`) and `textmetrics()`/`fontmetrics()`
233
- (`function_builtins.cpp`) — cross-checked bit-for-bit against the Python reference's fonttools-based
234
- metrics on the bundled font (identical `advance`/`ascent`/`descent`/family name values) and against
235
- its glyph-outline mesh output (identical triangle count and bounding box for `text()`).
236
-
237
- **Known gap carried from Phase 7**: `StbFontProvider::resolveFont()` ignores its `spec` argument
238
- entirely (always the one bundled font) this is the documented, intentional shape of the *built-in
239
- default* (see above), not a bug; a script that requests a specific system font (`font="Times New
240
- Roman:style=Bold"`) silently gets Liberation Sans instead under this default provider. No upgrade
241
- path needed in this repo: the fix is a Qt host application supplying its own `FontProvider`, which
242
- is exactly what the injection seam exists for.
230
+ **Font support**: a `FontProvider` abstract interface (`font_provider.hpp`: `resolveFont`/
231
+ `metrics`/`shapeText`/`glyphInkBounds`/`glyphOutline`) with `FreetypeFontProvider` as the built-in
232
+ implementation everything here actually uses. `Evaluator`'s constructor takes an optional
233
+ `shared_ptr<FontProvider>`; if unset, `Evaluator::fontProvider()` constructs the built-in one
234
+ lazily on first use (not eagerly in the constructor, so a script that never calls `text()`/
235
+ `textmetrics()`/`fontmetrics()` never pays for font parsing). Laziness runs one level deeper too:
236
+ the system font directories are only scanned once a `font=` spec actually names a family, so the
237
+ common case the default font, or a family already resolved once — never walks the filesystem.
238
+
239
+ The interface is shaped around shaping: `shapeText()` takes a whole string, because kerning,
240
+ ligatures, mark positioning and bidi reordering are properties of the run and not of any character
241
+ in it, and returns positioned *glyph indices* rather than codepoints (the mapping is neither 1:1
242
+ nor stable across fonts). `direction`/`language`/`script` are honoured rather than merely accepted;
243
+ whichever is left empty is filled in by `hb_buffer_guess_segment_properties`, the same job real
244
+ OpenSCAD's `detect_properties()`/`detect_script()` does by hand.
245
+
246
+ `resolveFont()` never fails. An unmatched family falls back to the bundled Liberation Sans
247
+ (`resources/fonts/`, embedded into the binary by `cmake/embed_font.cmake`, byte-identical to the
248
+ Python reference's own bundled font), which is both the default and the last-resort fallback — so
249
+ `text()` draws something even on a machine with no fonts installed at all. That mirrors
250
+ fontconfig's own never-fails matching, and so real OpenSCAD's: a missing font silently becomes the
251
+ nearest available one rather than an error.
252
+
253
+ `font_match.cpp` is the part of fontconfig that `font=` actually needs, reimplemented: comma-
254
+ separated family lists, `:style=Bold Italic` (and bare `:Bold`), other properties ignored rather
255
+ than rejected, generic `sans-serif`/`serif`/`monospace` aliases expanded to concrete families, and
256
+ a recursive scan of the platform font directories plus `OPENSCAD_FONT_PATH` (the same environment
257
+ variable real OpenSCAD reads). Family match is case-insensitive and required; style is a
258
+ *preference* — exact, then substring, then Regular, then the family's first face — because
259
+ dropping to Regular beats falling through to an unrelated family, which is what fontconfig does
260
+ too.
261
+
262
+ `text_metrics.hpp`/`.cpp` holds the shared layout math (`measureText`/`textAlignOffset`) used by
263
+ both `text()` (`builtins/text.cpp`) and `textmetrics()`/`fontmetrics()` (`function_builtins.cpp`).
264
+ Sharing it is the point: the numbers a script positions against cannot disagree with what it
265
+ draws. The `size * (100/72) / unitsPerEm` scale factor reproduces real OpenSCAD's own long-standing
266
+ `text()` size bug (its issue #4304 — `FT_Set_Char_Size` given 100 dpi where 72 is correct, making
267
+ glyphs ~1.39x nominal) deliberately: a decade of models are drawn to it, and OpenSCAD itself chose
268
+ to keep it and add an `em=` parameter rather than fix it.
243
269
 
244
270
  **Debugger/profiler, Phase 9 (the last phase)**: `checkDebug()` fires at **full parity with the
245
271
  reference's `_check_debug` call sites** — the statement checkpoint in `evalChildren`'s `runAll`,
@@ -633,30 +659,39 @@ grep for `ponytail:`.
633
659
  OpenSCAD up) at the point-transform step, `applyMat()` (named to avoid colliding with
634
660
  `std::apply` in an ADL lookup that broke the build once — see its own comment).
635
661
  - `include/openscad_cpp_evaluator/font_provider.hpp` — the `FontProvider` abstract interface
636
- (`resolveFont`/`metrics`/`glyphMetrics`/`glyphOutline`), the injection seam that keeps
637
- `text()`/`textmetrics()`/`fontmetrics()` decoupled from any specific font backend (the plan's §6
638
- design avoids hard-depending on the Python reference's Linux-only `fc-match` shell-out, since
639
- this library is meant to be embedded in a cross-platform Qt GUI app). `Evaluator`'s constructor
640
- takes an optional injected `shared_ptr<FontProvider>`; `Evaluator::fontProvider()` lazily
641
- constructs the built-in default (below) on first use otherwise.
642
- - `include/openscad_cpp_evaluator/stb_font_provider.hpp`, `src/text/stb_font_provider.cpp` — the
643
- built-in `FontProvider` default: `stb_truetype.h` over the one bundled font
644
- (`resources/fonts/LiberationSans-Regular.ttf`, byte-identical to the Python reference's own
645
- bundled font). `resolveFont()` ignores its `spec` argument entirely and always returns that one
646
- font (see the "known gap" note above) — a real Qt host app supplies its own `QFontDatabase`/
647
- `QRawFont`-backed `FontProvider` instead of this class for real system-font matching.
662
+ (`resolveFont`/`metrics`/`shapeText`/`glyphInkBounds`/`glyphOutline`), the seam that keeps
663
+ `text()`/`textmetrics()`/`fontmetrics()` decoupled from any specific font backend. `Evaluator`'s
664
+ constructor takes an optional injected `shared_ptr<FontProvider>`; `Evaluator::fontProvider()`
665
+ lazily constructs the built-in one (below) on first use otherwise. The seam stays because a host
666
+ that already owns a font stack (a Qt app with `QRawFont`) may prefer to answer these five
667
+ questions itself rather than run a second font engine in the same process.
668
+ - `include/openscad_cpp_evaluator/freetype_font_provider.hpp`,
669
+ `src/text/freetype_font_provider.cpp` the built-in implementation. HarfBuzz shapes
670
+ (`hb_blob_create_from_file_or_fail` `hb_face_create` `hb_font_create` +
671
+ `hb_ot_font_set_funcs`, scaled to unitsPerEm so positions come back in font units); FreeType
672
+ supplies outlines (`FT_LOAD_NO_SCALE` + `FT_Outline_Decompose`, curves flattened uniformly in
673
+ `t` the same approximation real OpenSCAD's `DrawingCallback` makes) and face metrics
674
+ (`lineGap` derived as `height - (ascender - descender)`, since FreeType reports the summed line
675
+ height). Faces are cached per resolved spec; handle 0 is always the bundled font.
676
+ - `include/openscad_cpp_evaluator/font_match.hpp`, `src/text/font_match.cpp` — the fontconfig
677
+ replacement: `parseFontSpec()` (family list + `:style=`), `expandGenericFamily()`
678
+ (sans-serif/serif/monospace → concrete families), `systemFontDirs()`/`findFontFiles()` (platform
679
+ font directories + `OPENSCAD_FONT_PATH`, `.ttf`/`.otf`/`.ttc`/`.otc`, permission errors skipped
680
+ rather than thrown — a font directory with odd permissions must not take down a render), and
681
+ `matchFace()` (case-insensitive family match required, style preferred). All pure functions over
682
+ plain data, so their tests need no system fonts.
648
683
  - `include/openscad_cpp_evaluator/text_metrics.hpp`, `src/text/text_metrics.cpp` — the
649
684
  backend-agnostic text-layout math shared by `text()` and `textmetrics()`/`fontmetrics()`:
650
- `measureText()` (left-to-right glyph layout, ink-bbox/advance aggregation mirrors
651
- `_measure_text` exactly, including the "unmapped codepoint skipped entirely, mapped-but-inkless
652
- codepoint still advances" distinction) and `textAlignOffset()` (halign/valign →
653
- translation offset, mirrors `_text_align_offset`). `utf8DecodeAll()` here decodes a UTF-8 byte
654
- string to full Unicode scalar values for per-codepoint `FontProvider` calls.
655
- - `src/builtins/text.cpp` `text()` itself: resolves layout via `text_metrics.hpp` at resolve
656
- time (so `resolveText`'s params carry only plain data per-glyph codepoint+pen-x, scale, segment
657
- count, alignment offset no `FontProvider`/`EvalContext` reference crossing into generate),
658
- then at generate time calls `FontProvider::glyphOutline()` per glyph, unions the resulting
659
- `CrossSection`s, and applies the alignment offset.
685
+ `measureText()` (shapes the run, then aggregates ink-bbox/advance over the positioned glyphs
686
+ an inkless glyph like a space still advances the pen while contributing nothing to the ink box)
687
+ and `textAlignOffset()` (halign/valign → translation offset).
688
+ - `src/builtins/text.cpp` `text()` itself: resolves the font and lays the run out via
689
+ `text_metrics.hpp` at resolve time (so `resolveText`'s params carry only plain data — per-glyph
690
+ index + pen x/y, scale, segment count, alignment offset no `FontProvider`/`EvalContext`
691
+ reference crossing into generate), then at generate time calls `FontProvider::glyphOutline()`
692
+ per glyph, builds each `CrossSection` with the `NonZero` fill rule (what turns a counter wound
693
+ against its outer contour into a hole rather than a second filled blob), unions them, and
694
+ applies the alignment offset.
660
695
  - `include/openscad_cpp_evaluator/debug_hooks.hpp`, `include/openscad_cpp_evaluator/profile.hpp`,
661
696
  `src/debug_profile.cpp` — the debugging/profiling injection seam. `DebugHookFn`/`ErrorBreakFn`/
662
697
  `ReturnHookFn` (bundled into one `DebugHooks` constructor parameter) mirror the reference's
@@ -129,13 +129,66 @@ FetchContent_Declare(boost_config GIT_REPOSITORY https://github.com/boostorg/con
129
129
  FetchContent_Declare(boost_polygon GIT_REPOSITORY https://github.com/boostorg/polygon.git GIT_TAG boost-1.86.0 GIT_SHALLOW TRUE)
130
130
  FetchContent_MakeAvailable(boost_config boost_polygon)
131
131
 
132
- # nothings/stb: single-header stb_image.h (surface()'s PNG loader) and
133
- # stb_truetype.h (the built-in FontProvider's TTF parser/rasterizer, Phase
134
- # 7) -- no build system of its own, just headers, so this only needs an
135
- # include path, not add_subdirectory/MakeAvailable's target machinery.
132
+ # nothings/stb: single-header stb_image.h, surface()'s PNG loader -- no
133
+ # build system of its own, just headers, so this only needs an include
134
+ # path, not add_subdirectory/MakeAvailable's target machinery. (It used to
135
+ # supply stb_truetype.h for the font backend too; FreeType/HarfBuzz below
136
+ # replaced that.)
136
137
  FetchContent_Declare(stb GIT_REPOSITORY https://github.com/nothings/stb.git GIT_TAG master GIT_SHALLOW TRUE)
137
138
  FetchContent_MakeAvailable(stb)
138
139
 
140
+ # FreeType (glyph outlines + face metrics) and HarfBuzz (shaping), the two
141
+ # halves of the font backend. Both build with plain CMake, which is why
142
+ # they can be FetchContent deps like everything else here -- fontconfig,
143
+ # the third leg of real OpenSCAD's font stack, is meson-only and is
144
+ # deliberately NOT used; font_match.cpp implements its spec syntax and
145
+ # directory scanning directly so that name matching behaves identically on
146
+ # all six wheel targets rather than only where a fontconfig build exists.
147
+ #
148
+ # The two are kept independent of each other on purpose: HB_HAVE_FREETYPE
149
+ # is OFF, so HarfBuzz reads the font through its own hb-ot tables reader
150
+ # rather than through FreeType, and no find_package(Freetype) has to
151
+ # resolve against a FetchContent target that isn't installed anywhere.
152
+ # Both sides address glyphs by the same font-internal glyph index, so the
153
+ # split costs nothing: HarfBuzz shapes text into positioned glyph indices,
154
+ # FreeType turns each index into an outline. Everything crosses that
155
+ # boundary in font units (hb_font_set_scale to unitsPerEm on one side,
156
+ # FT_LOAD_NO_SCALE on the other), so there is no scale mismatch to
157
+ # reconcile either.
158
+ #
159
+ # FreeType's optional dependencies are all disabled: zlib/bzip2/brotli
160
+ # decompress rare embedded-table formats, PNG is for colour bitmap emoji
161
+ # strikes, and HarfBuzz there is only for autohinting quality -- none of
162
+ # which matter when the only thing being asked for is a scalable outline.
163
+ # Leaving them on would make the wheel build depend on whatever those
164
+ # libraries happen to be on six different build images.
165
+ set(FT_DISABLE_ZLIB ON CACHE BOOL "" FORCE)
166
+ set(FT_DISABLE_BZIP2 ON CACHE BOOL "" FORCE)
167
+ set(FT_DISABLE_PNG ON CACHE BOOL "" FORCE)
168
+ set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "" FORCE)
169
+ set(FT_DISABLE_BROTLI ON CACHE BOOL "" FORCE)
170
+ FetchContent_Declare(
171
+ freetype
172
+ GIT_REPOSITORY https://github.com/freetype/freetype.git
173
+ GIT_TAG VER-2-14-3
174
+ GIT_SHALLOW TRUE
175
+ )
176
+ FetchContent_MakeAvailable(freetype)
177
+
178
+ set(HB_HAVE_FREETYPE OFF CACHE BOOL "" FORCE)
179
+ set(HB_HAVE_GLIB OFF CACHE BOOL "" FORCE)
180
+ set(HB_HAVE_ICU OFF CACHE BOOL "" FORCE)
181
+ set(HB_BUILD_SUBSET OFF CACHE BOOL "" FORCE)
182
+ set(HB_BUILD_UTILS OFF CACHE BOOL "" FORCE)
183
+ set(HB_BUILD_TESTS OFF CACHE BOOL "" FORCE)
184
+ FetchContent_Declare(
185
+ harfbuzz
186
+ GIT_REPOSITORY https://github.com/harfbuzz/harfbuzz.git
187
+ GIT_TAG 14.3.1
188
+ GIT_SHALLOW TRUE
189
+ )
190
+ FetchContent_MakeAvailable(harfbuzz)
191
+
139
192
  # yhirose/cpp-linenoise: header-only, BSD-2-Clause C++ port of antirez/
140
193
  # linenoise with native Windows console support (no ANSI emulation layer,
141
194
  # unlike GNU readline's Windows story) -- gives the --debug REPL's real
@@ -149,7 +202,7 @@ FetchContent_MakeAvailable(cpp_linenoise)
149
202
 
150
203
  # Embeds the bundled default font's (resources/fonts/) raw bytes into a
151
204
  # generated header (kBundledFontData/kBundledFontDataSize), so
152
- # StbFontProvider's default constructor needs no runtime file path at all --
205
+ # FreetypeFontProvider needs no runtime file path for its default font --
153
206
  # a real installed/packaged build (e.g. the PyPI wheel) has no guarantee the
154
207
  # source tree's resources/fonts/ is present or at any predictable path
155
208
  # alongside the installed shared library.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.31.2
3
+ Version: 0.32.2
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -63,7 +63,7 @@ public:
63
63
  // `fontProvider`: injection seam for text()/textmetrics()/fontmetrics()
64
64
  // (see font_provider.hpp). Left null (the common case for this repo's
65
65
  // own tests/CLI, and for any host that hasn't wired in its own) lazily
66
- // constructs the built-in StbFontProvider default on first use, not in
66
+ // constructs the built-in FreetypeFontProvider on first use, not in
67
67
  // the constructor -- avoids paying for font-file parsing on every
68
68
  // Evaluator even when a script never calls text()/textmetrics().
69
69
  // `manifoldCache`: opt-in content-hash cache (see manifold_cache.hpp)
@@ -0,0 +1,62 @@
1
+ #pragma once
2
+
3
+ #include <optional>
4
+ #include <string>
5
+ #include <vector>
6
+
7
+ namespace oscadeval {
8
+
9
+ // A parsed fontconfig-style font spec, which is what OpenSCAD's `font=`
10
+ // takes: a comma-separated family list, then colon-separated properties.
11
+ //
12
+ // "Liberation Sans"
13
+ // "Liberation Sans:style=Bold Italic"
14
+ // "Helvetica,Arial,sans-serif:style=Bold"
15
+ //
16
+ // Only `style=` is meaningful here; fontconfig accepts a long tail of
17
+ // other properties (:size=, :weight=, :lang=) that OpenSCAD models never
18
+ // use, and quietly ignoring them beats rejecting a spec that fontconfig
19
+ // itself would have accepted. A bare `:Bold` (no `=`) is read as a style
20
+ // too, since fontconfig's own parser allows it.
21
+ struct FontSpec {
22
+ std::vector<std::string> families;
23
+ std::string style;
24
+ };
25
+
26
+ FontSpec parseFontSpec(const std::string& spec);
27
+
28
+ // Generic family names, expanded to the concrete families likely to be
29
+ // installed. fontconfig resolves these through its own alias config
30
+ // (OpenSCAD ships 10-liberation.conf pointing them at the Liberation
31
+ // faces); with no fontconfig, the alias list lives here instead.
32
+ // Returns an empty vector for a family that isn't generic.
33
+ std::vector<std::string> expandGenericFamily(const std::string& family);
34
+
35
+ // Every directory the platform keeps fonts in, plus anything on
36
+ // OPENSCAD_FONT_PATH (same environment variable real OpenSCAD reads, same
37
+ // platform path separator). Directories that don't exist are dropped, so
38
+ // the result is safe to walk blindly.
39
+ std::vector<std::string> systemFontDirs();
40
+
41
+ // Every font file under those directories: .ttf/.otf/.ttc/.otc, recursive.
42
+ // A directory that can't be read is skipped rather than throwing -- a font
43
+ // directory with odd permissions must not take down a render.
44
+ std::vector<std::string> findFontFiles();
45
+
46
+ // One face inside one font file. A .ttc collection holds several, hence
47
+ // the index; a plain .ttf is always index 0.
48
+ struct FontFace {
49
+ std::string path;
50
+ int faceIndex = 0;
51
+ std::string family;
52
+ std::string style;
53
+ };
54
+
55
+ // Best match for `spec` among `faces`, or nullopt if no requested family
56
+ // (after generic expansion) is present at all. Family match is
57
+ // case-insensitive and required; style is a preference, not a filter,
58
+ // resolved in the order: exact style, then substring, then "Regular",
59
+ // then the first face of that family.
60
+ std::optional<FontFace> matchFace(const FontSpec& spec, const std::vector<FontFace>& faces);
61
+
62
+ } // namespace oscadeval
@@ -0,0 +1,98 @@
1
+ #pragma once
2
+
3
+ #include <array>
4
+ #include <cstdint>
5
+ #include <optional>
6
+ #include <string>
7
+ #include <vector>
8
+
9
+ namespace oscadeval {
10
+
11
+ using FontHandle = std::uint64_t;
12
+
13
+ // A font-internal glyph index, NOT a Unicode codepoint. Shaping is what
14
+ // turns one into the other, and the mapping is neither 1:1 nor stable
15
+ // across fonts: one codepoint can shape to several glyphs, several
16
+ // codepoints can shape to one ligature glyph, and the same character has a
17
+ // different index in every font.
18
+ using GlyphId = std::uint32_t;
19
+
20
+ // Global (whole-font) metrics, all in font units (not yet scaled for a
21
+ // particular `size=`) -- mirrors fontmetrics()'s "nominal"/"max" split:
22
+ // ascent/descent/lineGap come from the font's hhea table (or equivalent),
23
+ // yMax/yMin from its head table (or equivalent) -- these commonly differ
24
+ // (yMax/yMin bound the tallest/deepest glyph actually in the font; ascent/
25
+ // descent are the font designer's nominal line-layout values).
26
+ struct FontMetrics {
27
+ double unitsPerEm = 1000.0;
28
+ double ascent = 0, descent = 0, lineGap = 0;
29
+ double yMax = 0, yMin = 0;
30
+ std::string family, style;
31
+ };
32
+
33
+ // One glyph as placed by the shaper, in font units. `xOffset`/`yOffset`
34
+ // adjust this glyph's own position without moving the pen (mark
35
+ // attachment, cursive joins); `xAdvance`/`yAdvance` move the pen. Both are
36
+ // shaping output, not raw hmtx values -- kerning and ligature
37
+ // substitutions are already applied by the time this exists.
38
+ struct ShapedGlyph {
39
+ GlyphId glyph = 0;
40
+ double xOffset = 0, yOffset = 0;
41
+ double xAdvance = 0, yAdvance = 0;
42
+ };
43
+
44
+ // What OpenSCAD's text()/textmetrics() call direction/language/script.
45
+ // Any field left empty is guessed from the text itself by the shaper,
46
+ // which is what real OpenSCAD's detect_properties() does by hand.
47
+ struct ShapeOptions {
48
+ std::string direction; // "ltr", "rtl", "ttb", "btt"
49
+ std::string language; // BCP 47, e.g. "en"
50
+ std::string script; // ISO 15924, e.g. "Latn", "Arab"
51
+ };
52
+
53
+ // Flattened polyline contours (curves already split into straight
54
+ // segments), in font units.
55
+ using GlyphContours = std::vector<std::vector<std::array<double, 2>>>;
56
+
57
+ // Abstraction over font resolution/shaping/outline extraction, so
58
+ // text()/textmetrics()/fontmetrics() don't hard-depend on a specific font
59
+ // backend. The library ships FreetypeFontProvider (HarfBuzz for shaping,
60
+ // FreeType for outlines and face metrics, and its own fontconfig-syntax
61
+ // matcher for system fonts) as the built-in default, which is what every
62
+ // build here actually uses; the interface stays because a host that
63
+ // already owns a font stack (a Qt app with QRawFont, say) may prefer to
64
+ // answer these five questions itself rather than have a second font
65
+ // engine resolving names in the same process.
66
+ class FontProvider {
67
+ public:
68
+ virtual ~FontProvider() = default;
69
+
70
+ // Resolves an OpenSCAD/fontconfig-style spec (e.g. "Times New
71
+ // Roman:style=Bold", or "" for the default) to an opaque handle.
72
+ // Never fails: an unmatched spec falls back to the default font,
73
+ // matching fontconfig's own never-fails matching (and so real
74
+ // OpenSCAD's), where a missing font silently becomes the nearest
75
+ // available one rather than an error.
76
+ virtual FontHandle resolveFont(const std::string& spec) = 0;
77
+
78
+ virtual FontMetrics metrics(FontHandle handle) = 0;
79
+
80
+ // Lays out a whole string at once -- not per codepoint, because
81
+ // kerning, ligatures, mark positioning and bidi reordering are all
82
+ // properties of the run rather than of any character in it.
83
+ virtual std::vector<ShapedGlyph> shapeText(FontHandle handle, const std::string& utf8,
84
+ const ShapeOptions& opts) = 0;
85
+
86
+ // The glyph's ink bounding box (xMin, yMin, xMax, yMax) in font units,
87
+ // or nullopt for a glyph that draws nothing (a space, or a .notdef
88
+ // with an empty outline) -- such a glyph still advances the pen, so it
89
+ // affects `advance` while contributing nothing to the ink box that
90
+ // textmetrics() reports as position/size.
91
+ virtual std::optional<std::array<double, 4>> glyphInkBounds(FontHandle handle, GlyphId glyph) = 0;
92
+
93
+ // Only called by text() itself (rendering) -- textmetrics()/
94
+ // fontmetrics() need only the two calls above.
95
+ virtual GlyphContours glyphOutline(FontHandle handle, GlyphId glyph, int segsPerCurve) = 0;
96
+ };
97
+
98
+ } // namespace oscadeval
@@ -0,0 +1,38 @@
1
+ #pragma once
2
+
3
+ #include "openscad_cpp_evaluator/font_provider.hpp"
4
+
5
+ #include <memory>
6
+
7
+ namespace oscadeval {
8
+
9
+ // The built-in FontProvider: HarfBuzz shapes, FreeType supplies outlines
10
+ // and face metrics, and font_match.cpp resolves `font=` specs against the
11
+ // platform's font directories. No fontconfig -- see the CMakeLists note on
12
+ // why, and font_match.hpp for what of its behaviour is reimplemented.
13
+ //
14
+ // The bundled Liberation Sans (resources/fonts/, embedded into the binary
15
+ // by cmake/embed_font.cmake) is the default font and the last-resort
16
+ // fallback, so `text()` always draws something even on a machine with no
17
+ // fonts installed at all -- the same font real OpenSCAD falls back to.
18
+ //
19
+ // Font files are opened lazily and cached per resolved spec: a script that
20
+ // never calls text()/textmetrics()/fontmetrics() opens nothing, and one
21
+ // that only uses the default font never scans a system directory.
22
+ class FreetypeFontProvider : public FontProvider {
23
+ public:
24
+ FreetypeFontProvider();
25
+ ~FreetypeFontProvider() override;
26
+
27
+ FontHandle resolveFont(const std::string& spec) override;
28
+ FontMetrics metrics(FontHandle handle) override;
29
+ std::vector<ShapedGlyph> shapeText(FontHandle handle, const std::string& utf8, const ShapeOptions& opts) override;
30
+ std::optional<std::array<double, 4>> glyphInkBounds(FontHandle handle, GlyphId glyph) override;
31
+ GlyphContours glyphOutline(FontHandle handle, GlyphId glyph, int segsPerCurve) override;
32
+
33
+ private:
34
+ struct Impl;
35
+ std::unique_ptr<Impl> impl_;
36
+ };
37
+
38
+ } // namespace oscadeval
@@ -0,0 +1,40 @@
1
+ #pragma once
2
+
3
+ #include "openscad_cpp_evaluator/font_provider.hpp"
4
+
5
+ #include <string>
6
+ #include <utility>
7
+ #include <vector>
8
+
9
+ namespace oscadeval {
10
+
11
+ // Layout of `text`'s ink-bbox/advance metrics, in OpenSCAD units scaled
12
+ // for `size`. `glyphs` is what text() places: each shaped glyph with its
13
+ // pen position, already scaled. An inkless glyph (a space) still advances
14
+ // the pen and still gets a glyphs entry -- it just contributes nothing to
15
+ // ascent/descent/inkMinX/inkMaxX.
16
+ //
17
+ // The run comes from the shaper, so kerning and ligatures are already
18
+ // applied and `spacing` scales the shaped advances rather than raw
19
+ // per-character widths.
20
+ struct TextMeasurement {
21
+ struct Placed {
22
+ GlyphId glyph = 0;
23
+ double x = 0, y = 0;
24
+ };
25
+
26
+ double ascent = 0, descent = 0, inkMinX = 0, inkMaxX = 0;
27
+ double advanceX = 0, advanceY = 0;
28
+ std::vector<Placed> glyphs;
29
+ };
30
+
31
+ TextMeasurement measureText(FontProvider& fp, FontHandle handle, const std::string& text, double size,
32
+ double spacing, const ShapeOptions& opts = {});
33
+
34
+ // (offsetX, offsetY) translation for halign/valign, given a
35
+ // TextMeasurement. Shared by textmetrics() (reports it) and text()
36
+ // (applies it).
37
+ std::pair<double, double> textAlignOffset(const std::string& halign, const std::string& valign,
38
+ const TextMeasurement& m);
39
+
40
+ } // namespace oscadeval
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.31.2"
7
+ version = "0.32.2"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -21,7 +21,8 @@ add_library(openscad_cpp_evaluator STATIC
21
21
  import/surface_load.cpp
22
22
  import/dxf_import.cpp
23
23
  import/svg_import.cpp
24
- text/stb_font_provider.cpp
24
+ text/freetype_font_provider.cpp
25
+ text/font_match.cpp
25
26
  text/text_metrics.cpp
26
27
  debug/debug_repl.cpp
27
28
  builtins/text.cpp
@@ -51,7 +52,7 @@ target_include_directories(openscad_cpp_evaluator PRIVATE ${stb_SOURCE_DIR} ${CM
51
52
  add_dependencies(openscad_cpp_evaluator generate_bundled_font)
52
53
 
53
54
  target_link_libraries(openscad_cpp_evaluator PUBLIC openscad_cpp_parser manifold Boost::polygon)
54
- target_link_libraries(openscad_cpp_evaluator PRIVATE linenoise)
55
+ target_link_libraries(openscad_cpp_evaluator PRIVATE linenoise freetype harfbuzz)
55
56
  target_compile_options(openscad_cpp_evaluator PRIVATE
56
57
  $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall;-Wextra>
57
58
  )
@@ -320,10 +320,13 @@ Value objectOf(std::vector<std::pair<std::string, Value>> items) {
320
320
  return Value{std::make_shared<const ValueObject>(ValueObject{std::move(items)})};
321
321
  }
322
322
 
323
- // textmetrics(text=, size=10, halign=, valign=, spacing=, font=) -- measures
324
- // `text` against the FontProvider-resolved font (same resolution text()
325
- // uses) and returns an OscObject with position/size/ascent/descent/offset/
326
- // advance, matching real OpenSCAD's key order. Mirrors _builtin_textmetrics.
323
+ // textmetrics(text=, size=10, font=, direction=, language=, script=,
324
+ // halign=, valign=, spacing=) -- measures `text` against the
325
+ // FontProvider-resolved font, laid out by exactly the same shaping call
326
+ // text() makes, and returns an OscObject with position/size/ascent/
327
+ // descent/offset/advance in real OpenSCAD's key order. Going through the
328
+ // same measureText() the geometry goes through is the point: the numbers
329
+ // a script positions against cannot disagree with what it draws.
327
330
  Value builtinTextmetrics(Evaluator& ev, const CallArgs& args) {
328
331
  const std::string text = asStringOr(getArg(args, 0, "text", Value{std::string("")}), "");
329
332
  const double size = toDoubleLenient(getArg(args, 1, "size", Value{10.0}));
@@ -331,10 +334,14 @@ Value builtinTextmetrics(Evaluator& ev, const CallArgs& args) {
331
334
  const std::string valign = asStringOr(getArg(args, std::nullopt, "valign", Value{std::string("baseline")}), "baseline");
332
335
  const double spacing = toDoubleLenient(getArg(args, std::nullopt, "spacing", Value{1.0}));
333
336
  const std::string fontSpec = asStringOr(getArg(args, std::nullopt, "font", Value{std::string("")}), "");
337
+ ShapeOptions shape;
338
+ shape.direction = asStringOr(getArg(args, std::nullopt, "direction", Value{std::string("")}), "");
339
+ shape.language = asStringOr(getArg(args, std::nullopt, "language", Value{std::string("")}), "");
340
+ shape.script = asStringOr(getArg(args, std::nullopt, "script", Value{std::string("")}), "");
334
341
 
335
342
  FontProvider& fp = ev.fontProvider();
336
343
  const FontHandle handle = fp.resolveFont(fontSpec);
337
- const TextMeasurement m = measureText(fp, handle, text, size, spacing);
344
+ const TextMeasurement m = measureText(fp, handle, text, size, spacing, shape);
338
345
  const auto [offsetX, offsetY] = textAlignOffset(halign, valign, m);
339
346
 
340
347
  return objectOf({
@@ -343,7 +350,7 @@ Value builtinTextmetrics(Evaluator& ev, const CallArgs& args) {
343
350
  {"ascent", Value{m.ascent}},
344
351
  {"descent", Value{m.descent}},
345
352
  {"offset", numList({offsetX, offsetY})},
346
- {"advance", numList({m.advanceX, 0.0})},
353
+ {"advance", numList({m.advanceX, m.advanceY})},
347
354
  });
348
355
  }
349
356
 
@@ -684,8 +691,29 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
684
691
  }
685
692
  return Value{false};
686
693
  }
687
- case BuiltinFnId::Version: return numList({2025.0, 1.0, 1.0});
688
- case BuiltinFnId::VersionNum: return Value{20250101.0};
694
+ // The OpenSCAD release we track. version_num() is that same
695
+ // year/month/day folded as y * 10000 + m * 100 + d, exactly like the
696
+ // reference's own builtin_version_num (builtin_functions.cc).
697
+ case BuiltinFnId::Version: return numList({2026.0, 1.0, 1.0});
698
+ case BuiltinFnId::VersionNum: {
699
+ // The optional vector argument the reference also accepts: with
700
+ // no argument this is our own version() folded; with one, it is
701
+ // whatever [y, m] / [y, m, d] the caller passed, so
702
+ // version_num([2019, 5, 0]) == 20190500 regardless of what
703
+ // release we report. A 2-element vector defaults the day to 0
704
+ // (getVec3's own defaultval), and anything else -- a non-list, a
705
+ // wrong length, a non-numeric element -- is undef.
706
+ //
707
+ // One deliberate divergence: the reference's size-2 path ignores
708
+ // getVec2's own failure and folds uninitialized doubles for e.g.
709
+ // version_num(["a", "b"]). That is undef here rather than
710
+ // whatever happened to be on the stack.
711
+ const Value* arg = args.findPositional(0);
712
+ if (!arg) return Value{20260101.0};
713
+ const auto v = allNumericList(*arg);
714
+ if (!v || (v->size() != 2 && v->size() != 3)) return Value{};
715
+ return Value{(*v)[0] * 10000.0 + (*v)[1] * 100.0 + (v->size() == 3 ? (*v)[2] : 0.0)};
716
+ }
689
717
  case BuiltinFnId::ParentModule:
690
718
  return ev.parentModuleName(static_cast<int>(toDoubleLenient(getArg(args, 0, "index", Value{0.0}))));
691
719
  }