openscad-cpp-evaluator 1.28.0__tar.gz → 1.28.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 (222) hide show
  1. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/text_metrics.hpp +13 -3
  3. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/function_builtins.cpp +4 -4
  5. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/primitives_3d.cpp +15 -8
  6. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/text.cpp +3 -3
  7. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/text/freetype_font_provider.cpp +41 -6
  8. openscad_cpp_evaluator-1.28.2/src/text/text_metrics.cpp +80 -0
  9. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_primitives.cpp +29 -0
  10. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_text.cpp +36 -0
  11. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/uv.lock +1 -1
  12. openscad_cpp_evaluator-1.28.0/src/text/text_metrics.cpp +0 -70
  13. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/.github/workflows/ci.yml +0 -0
  14. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/.github/workflows/wheels.yml +0 -0
  15. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/.gitignore +0 -0
  16. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/.gitmodules +0 -0
  17. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/CLAUDE.md +0 -0
  18. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/CMakeLists.txt +0 -0
  19. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/LICENSE +0 -0
  20. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/README.md +0 -0
  21. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/bindings/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/bindings/ast_to_py.cpp +0 -0
  23. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/bindings/module.cpp +0 -0
  24. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/cmake/embed_font.cmake +0 -0
  25. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/examples/CMakeLists.txt +0 -0
  26. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/examples/eval_perf_benchmark.cpp +0 -0
  27. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/examples/manifold_cache_reuse.cpp +0 -0
  28. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/examples/minimal_debugger.cpp +0 -0
  29. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  30. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/.gitignore +0 -0
  31. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  32. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  33. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/README.md +0 -0
  34. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  35. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  36. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  37. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  38. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  39. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  40. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  41. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  42. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  43. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  44. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  45. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  46. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  47. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  48. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  49. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  50. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  51. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/api.cpp +0 -0
  52. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  53. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  54. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  55. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  56. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  57. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  58. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  59. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  60. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  61. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  62. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  63. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  64. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  65. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  66. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  67. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  68. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/position.cpp +0 -0
  69. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  70. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  71. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  72. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  73. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  74. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  75. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  76. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  77. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  78. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  79. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  80. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  81. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  82. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  83. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  84. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  85. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  86. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  87. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  88. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  89. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  90. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  91. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  92. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  93. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  94. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  95. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  96. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  97. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  98. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  99. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  100. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  101. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  102. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  103. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  104. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  105. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  106. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  107. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  108. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  109. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  110. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  111. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  112. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  113. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  114. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  115. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/export.hpp +0 -0
  116. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  117. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  118. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  119. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  120. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  121. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  122. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  123. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  124. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  125. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  126. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  127. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  128. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  129. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  130. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  131. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/value.hpp +0 -0
  132. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  133. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/python/openscad_cpp_evaluator/__init__.py +0 -0
  134. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/resources/fonts/LICENSE +0 -0
  135. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  136. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  137. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  138. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  139. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/CMakeLists.txt +0 -0
  140. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/booleans.cpp +0 -0
  141. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/builtins.hpp +0 -0
  142. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/call_args.cpp +0 -0
  143. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/color.cpp +0 -0
  144. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/control.cpp +0 -0
  145. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/dxf_dim.cpp +0 -0
  146. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/extrude.cpp +0 -0
  147. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/import.cpp +0 -0
  148. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/modifiers.cpp +0 -0
  149. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/primitives_2d.cpp +0 -0
  150. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/registry.cpp +0 -0
  151. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/roof.cpp +0 -0
  152. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/segments.cpp +0 -0
  153. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/surface.cpp +0 -0
  154. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/topology.cpp +0 -0
  155. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/builtins/transforms.cpp +0 -0
  156. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/bytecode_compiler.cpp +0 -0
  157. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/bytecode_vm.cpp +0 -0
  158. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/colored_body.cpp +0 -0
  159. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/coverage.cpp +0 -0
  160. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/csg_generate.cpp +0 -0
  161. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/csg_resolve.cpp +0 -0
  162. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/css_colors.cpp +0 -0
  163. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/debug/debug_repl.cpp +0 -0
  164. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/debug_profile.cpp +0 -0
  165. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/eval_context.cpp +0 -0
  166. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/eval_error.cpp +0 -0
  167. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/eval_use.cpp +0 -0
  168. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/export.cpp +0 -0
  169. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/expr_eval.cpp +0 -0
  170. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/format_closure.cpp +0 -0
  171. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/import/dxf_import.cpp +0 -0
  172. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/import/mesh_import.cpp +0 -0
  173. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/import/surface_load.cpp +0 -0
  174. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/import/svg_import.cpp +0 -0
  175. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/manifold_cache.cpp +0 -0
  176. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/measure_geometry.cpp +0 -0
  177. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/mesh_check.cpp +0 -0
  178. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/stmt_eval.cpp +0 -0
  179. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/text/font_match.cpp +0 -0
  180. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/user_calls.cpp +0 -0
  181. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/utf8.cpp +0 -0
  182. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/value.cpp +0 -0
  183. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/src/zip_stored.cpp +0 -0
  184. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/CMakeLists.txt +0 -0
  185. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_booleans.cpp +0 -0
  186. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_bytecode_compiler.cpp +0 -0
  187. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_children_separate.cpp +0 -0
  188. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_cli.cpp +0 -0
  189. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_control_flow.cpp +0 -0
  190. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_coverage.cpp +0 -0
  191. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_csg_tree.cpp +0 -0
  192. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_debug_hooks.cpp +0 -0
  193. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_dxf_svg_import.cpp +0 -0
  194. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_eval_context.cpp +0 -0
  195. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_export.cpp +0 -0
  196. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_expr_eval.cpp +0 -0
  197. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_extrude_roof.cpp +0 -0
  198. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_feature_detection.cpp +0 -0
  199. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_function_builtins.cpp +0 -0
  200. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_helpers.hpp +0 -0
  201. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_import_export.cpp +0 -0
  202. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_include_cache.cpp +0 -0
  203. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_manifold_cache.cpp +0 -0
  204. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_mesh_check.cpp +0 -0
  205. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_multi_color_merge.cpp +0 -0
  206. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_profiling.cpp +0 -0
  207. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_python_bindings.py +0 -0
  208. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_render_expr.cpp +0 -0
  209. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_scope_trail.cpp +0 -0
  210. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_scoping.cpp +0 -0
  211. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_surface.cpp +0 -0
  212. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_tail_calls.cpp +0 -0
  213. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_transforms.cpp +0 -0
  214. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_unexpected_args.cpp +0 -0
  215. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_use.cpp +0 -0
  216. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_value.cpp +0 -0
  217. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_viewport_params.cpp +0 -0
  218. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tests/test_zip_stored.cpp +0 -0
  219. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tools/cli/CMakeLists.txt +0 -0
  220. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tools/cli/cli_lib.cpp +0 -0
  221. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tools/cli/cli_lib.hpp +0 -0
  222. {openscad_cpp_evaluator-1.28.0 → openscad_cpp_evaluator-1.28.2}/tools/cli/main.cpp +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.28.0
3
+ Version: 1.28.2
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -12,7 +12,12 @@ namespace oscadeval {
12
12
  // for `size`. `glyphs` is what text() places: each shaped glyph with its
13
13
  // pen position, already scaled. An inkless glyph (a space) still advances
14
14
  // the pen and still gets a glyphs entry -- it just contributes nothing to
15
- // ascent/descent/inkMinX/inkMaxX.
15
+ // the ink box or ascent/descent.
16
+ //
17
+ // As in OpenSCAD's ShapeResults: left/right/bottom/top is the ink box of the
18
+ // glyphs where they are placed, while ascent/descent are the glyphs' own
19
+ // extents above and below THEIR baseline, ignoring where they sit. The two
20
+ // only differ for a vertical run or a glyph the shaper offsets (a mark).
16
21
  //
17
22
  // The run comes from the shaper, so kerning and ligatures are already
18
23
  // applied and `spacing` scales the shaped advances rather than raw
@@ -23,8 +28,11 @@ struct TextMeasurement {
23
28
  double x = 0, y = 0;
24
29
  };
25
30
 
26
- double ascent = 0, descent = 0, inkMinX = 0, inkMaxX = 0;
31
+ double ascent = 0, descent = 0;
32
+ double left = 0, right = 0, bottom = 0, top = 0;
27
33
  double advanceX = 0, advanceY = 0;
34
+ bool hasInk = false;
35
+ bool vertical = false; // shaped ttb/btt
28
36
  std::vector<Placed> glyphs;
29
37
  };
30
38
 
@@ -32,7 +40,9 @@ TextMeasurement measureText(FontProvider& fp, FontHandle handle, const std::stri
32
40
  double spacing, const ShapeOptions& opts = {});
33
41
 
34
42
  // (offsetX, offsetY) translation for halign/valign, given a
35
- // TextMeasurement. Shared by textmetrics() (reports it) and text()
43
+ // TextMeasurement. "default" is left/baseline for a horizontal run and
44
+ // center/top for a vertical one, as in OpenSCAD; text with no ink is not
45
+ // moved at all. Shared by textmetrics() (reports it) and text()
36
46
  // (applies it).
37
47
  std::pair<double, double> textAlignOffset(const std::string& halign, const std::string& valign,
38
48
  const TextMeasurement& m);
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.28.0"
7
+ version = "1.28.2"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -792,8 +792,8 @@ Value builtinTextmetrics(Evaluator& ev, const CallArgs& args) {
792
792
  // `font` being name-only here is what made
793
793
  // `textmetrics("Hi", 10, "Liberation Sans:style=Bold")` silently
794
794
  // measure the default face (BelfrySCAD #381).
795
- const std::string halign = asStringOr(getArg(args, 6, "halign", Value{std::string("left")}), "left");
796
- const std::string valign = asStringOr(getArg(args, 7, "valign", Value{std::string("baseline")}), "baseline");
795
+ const std::string halign = asStringOr(getArg(args, 6, "halign", Value{std::string("default")}), "default");
796
+ const std::string valign = asStringOr(getArg(args, 7, "valign", Value{std::string("default")}), "default");
797
797
  const double spacing = toDoubleLenient(getArg(args, 8, "spacing", Value{1.0}));
798
798
  const std::string fontSpec = asStringOr(getArg(args, 2, "font", Value{std::string("")}), "");
799
799
  ShapeOptions shape;
@@ -807,8 +807,8 @@ Value builtinTextmetrics(Evaluator& ev, const CallArgs& args) {
807
807
  const auto [offsetX, offsetY] = textAlignOffset(halign, valign, m);
808
808
 
809
809
  return objectOf({
810
- {"position", numList({offsetX + m.inkMinX, offsetY + m.descent})},
811
- {"size", numList({m.inkMaxX - m.inkMinX, m.ascent - m.descent})},
810
+ {"position", numList({offsetX + m.left, offsetY + m.bottom})},
811
+ {"size", numList({m.right - m.left, m.top - m.bottom})},
812
812
  {"ascent", Value{m.ascent}},
813
813
  {"descent", Value{m.descent}},
814
814
  {"offset", numList({offsetX, offsetY})},
@@ -102,18 +102,25 @@ std::string describeEdge(const M& mesh, std::pair<uint32_t, uint32_t> edge) {
102
102
 
103
103
  CSGParams resolveCube(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
104
104
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
105
- Value sizeArg = getArg(args, 0, "size", Value{1.0});
105
+ const Value sizeArg = getArg(args, 0, "size", Value{});
106
106
  const bool center = truthy(getArg(args, 1, "center", Value{false}));
107
107
 
108
- std::vector<Value> sizeVec;
108
+ // As OpenSCAD reads it: undef (absent or explicit) is the default of 1, a
109
+ // number or three numbers are the size, and anything else warns and
110
+ // falls back to 1. Explicit undef built a zero-size cube -- nothing --
111
+ // and a 2-vector or a string was converted leniently, without a word.
112
+ std::vector<Value> sizeVec(3, Value{1.0});
113
+ const ListPtr* l = std::get_if<ListPtr>(&sizeArg);
109
114
  if (const double* s = std::get_if<double>(&sizeArg)) {
110
115
  sizeVec = {Value{*s}, Value{*s}, Value{*s}};
111
- } else if (const ListPtr* l = std::get_if<ListPtr>(&sizeArg); l && *l) {
112
- for (size_t i = 0; i < 3; ++i) {
113
- sizeVec.push_back(Value{i < (*l)->items.size() ? toDoubleLenient((*l)->items[i]) : 0.0});
114
- }
115
- } else {
116
- sizeVec = {Value{0.0}, Value{0.0}, Value{0.0}};
116
+ } else if (l && *l && (*l)->items.size() == 3 &&
117
+ std::all_of((*l)->items.begin(), (*l)->items.end(),
118
+ [](const Value& v) { return std::holds_alternative<double>(v); })) {
119
+ sizeVec = (*l)->items;
120
+ } else if (!std::holds_alternative<std::monostate>(sizeArg)) {
121
+ ev.warn("Unable to convert cube(size=" + fmtValue(sizeArg) +
122
+ ", ...) parameter to a number or a vec3 of numbers",
123
+ &node.position());
117
124
  }
118
125
 
119
126
  CSGParams params;
@@ -7,7 +7,7 @@
7
7
 
8
8
  #include <algorithm>
9
9
 
10
- // text(text=, size=10, font=, halign="left", valign="baseline", spacing=1,
10
+ // text(text=, size=10, font=, halign="default", valign="default", spacing=1,
11
11
  // direction=, language=, script=) -- renders `text` as 2D glyph outlines
12
12
  // through the FontProvider (Evaluator::fontProvider(), lazily the built-in
13
13
  // FreetypeFontProvider if none was injected).
@@ -46,8 +46,8 @@ CSGParams resolveText(Evaluator& ev, const oscad::ModularCall& node, EvalContext
46
46
  // genuinely differ here -- textmetrics takes all nine positionally --
47
47
  // so neither can be assumed from the other.
48
48
  const std::string fontSpec = asStringOr(getArg(args, 2, "font", Value{std::string("")}), "");
49
- const std::string halign = asStringOr(getArg(args, std::nullopt, "halign", Value{std::string("left")}), "left");
50
- const std::string valign = asStringOr(getArg(args, std::nullopt, "valign", Value{std::string("baseline")}), "baseline");
49
+ const std::string halign = asStringOr(getArg(args, std::nullopt, "halign", Value{std::string("default")}), "default");
50
+ const std::string valign = asStringOr(getArg(args, std::nullopt, "valign", Value{std::string("default")}), "default");
51
51
  const double spacing = toDoubleLenient(getArg(args, std::nullopt, "spacing", Value{1.0}));
52
52
  ShapeOptions shape;
53
53
  shape.direction = asStringOr(getArg(args, std::nullopt, "direction", Value{std::string("")}), "");
@@ -31,6 +31,45 @@ void scaleHbFontToEm(hb_font_t* font, unsigned int unitsPerEm) {
31
31
  hb_font_set_scale(font, static_cast<int>(unitsPerEm), static_cast<int>(unitsPerEm));
32
32
  }
33
33
 
34
+ // Vertical metrics come from FreeType, as they do in OpenSCAD (which shapes
35
+ // through hb-ft). For a font with no vmtx -- nearly every Latin font,
36
+ // Liberation Sans included -- the two disagree: hb-ot falls back to hhea
37
+ // ascender-descender, FreeType to OS/2 typo ascender-descender, so a ttb
38
+ // run of "abc" was 46.5 tall here and 39.1 there. Mirrors hb-ft's own
39
+ // callbacks; font units in and out, since the hb font is scaled to the em.
40
+ hb_position_t ftVAdvance(hb_font_t*, void* data, hb_codepoint_t glyph, void*) {
41
+ FT_Face ft = static_cast<FT_Face>(data);
42
+ if (FT_Load_Glyph(ft, glyph, FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_VERTICAL_LAYOUT) != 0) return 0;
43
+ return static_cast<hb_position_t>(-ft->glyph->metrics.vertAdvance); // FreeType's vertical grows downward
44
+ }
45
+
46
+ hb_bool_t ftVOrigin(hb_font_t*, void* data, hb_codepoint_t glyph, hb_position_t* x, hb_position_t* y, void*) {
47
+ FT_Face ft = static_cast<FT_Face>(data);
48
+ if (FT_Load_Glyph(ft, glyph, FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_VERTICAL_LAYOUT) != 0) return false;
49
+ const FT_Glyph_Metrics& m = ft->glyph->metrics;
50
+ *x = static_cast<hb_position_t>(m.horiBearingX - m.vertBearingX);
51
+ *y = static_cast<hb_position_t>(m.horiBearingY + m.vertBearingY);
52
+ return true;
53
+ }
54
+
55
+ // hb-ot for everything, with the vertical metrics overridden by FreeType.
56
+ hb_font_t* makeHbFont(hb_face_t* face, FT_Face ft, unsigned int unitsPerEm) {
57
+ static hb_font_funcs_t* vfuncs = [] {
58
+ hb_font_funcs_t* f = hb_font_funcs_create();
59
+ hb_font_funcs_set_glyph_v_advance_func(f, ftVAdvance, nullptr, nullptr);
60
+ hb_font_funcs_set_glyph_v_origin_func(f, ftVOrigin, nullptr, nullptr);
61
+ hb_font_funcs_make_immutable(f);
62
+ return f;
63
+ }();
64
+ hb_font_t* parent = hb_font_create(face);
65
+ hb_ot_font_set_funcs(parent);
66
+ scaleHbFontToEm(parent, unitsPerEm);
67
+ hb_font_t* font = hb_font_create_sub_font(parent); // everything unset delegates to parent
68
+ hb_font_destroy(parent);
69
+ hb_font_set_funcs(font, vfuncs, ft, nullptr);
70
+ return font;
71
+ }
72
+
34
73
  struct OutlineSink {
35
74
  GlyphContours contours;
36
75
  std::vector<std::array<double, 2>> current;
@@ -159,10 +198,8 @@ struct FreetypeFontProvider::Impl {
159
198
  return std::nullopt;
160
199
  }
161
200
  f.hbFace = hb_face_create(f.blob, static_cast<unsigned>(index));
162
- f.hbFont = hb_font_create(f.hbFace);
163
- hb_ot_font_set_funcs(f.hbFont);
164
201
  f.metrics = readMetrics(f.ft);
165
- scaleHbFontToEm(f.hbFont, static_cast<unsigned>(f.metrics.unitsPerEm));
202
+ f.hbFont = makeHbFont(f.hbFace, f.ft, static_cast<unsigned>(f.metrics.unitsPerEm));
166
203
  faces.push_back(f);
167
204
  return faces.size() - 1;
168
205
  }
@@ -185,10 +222,8 @@ struct FreetypeFontProvider::Impl {
185
222
  f.blob = hb_blob_create(reinterpret_cast<const char*>(src.data),
186
223
  static_cast<unsigned>(src.size), HB_MEMORY_MODE_READONLY, nullptr, nullptr);
187
224
  f.hbFace = hb_face_create(f.blob, 0);
188
- f.hbFont = hb_font_create(f.hbFace);
189
- hb_ot_font_set_funcs(f.hbFont);
190
225
  f.metrics = readMetrics(f.ft);
191
- scaleHbFontToEm(f.hbFont, static_cast<unsigned>(f.metrics.unitsPerEm));
226
+ f.hbFont = makeHbFont(f.hbFace, f.ft, static_cast<unsigned>(f.metrics.unitsPerEm));
192
227
  faces.push_back(f);
193
228
  }
194
229
  }
@@ -0,0 +1,80 @@
1
+ #include "openscad_cpp_evaluator/text_metrics.hpp"
2
+
3
+ #include <algorithm>
4
+ #include <cctype>
5
+
6
+ namespace oscadeval {
7
+
8
+ TextMeasurement measureText(FontProvider& fp, FontHandle handle, const std::string& text, double size,
9
+ double spacing, const ShapeOptions& opts) {
10
+ const FontMetrics fm = fp.metrics(handle);
11
+ // The 100/72 factor is real OpenSCAD's long-standing text() size bug
12
+ // (its issue #4304: FT_Set_Char_Size is given 100 dpi where 72 is
13
+ // correct, making glyphs ~1.39x the nominal size). It is reproduced
14
+ // deliberately -- a decade of models are drawn to it, and OpenSCAD
15
+ // itself chose to keep the bug and add an `em=` parameter rather than
16
+ // fix it.
17
+ const double scale = size * (100.0 / 72.0) / fm.unitsPerEm;
18
+
19
+ TextMeasurement m;
20
+ // hb_direction_from_string reads only the first letter, so this is
21
+ // exactly the set of strings the shaper takes as vertical.
22
+ const char d = opts.direction.empty() ? 'l' : static_cast<char>(std::tolower(opts.direction[0]));
23
+ m.vertical = d == 't' || d == 'b';
24
+ double penX = 0.0, penY = 0.0;
25
+
26
+ for (const ShapedGlyph& g : fp.shapeText(handle, text, opts)) {
27
+ const double x = (penX + g.xOffset) * scale;
28
+ const double y = (penY + g.yOffset) * scale;
29
+
30
+ if (const std::optional<std::array<double, 4>> b = fp.glyphInkBounds(handle, g.glyph)) {
31
+ const double gl = x + (*b)[0] * scale, gr = x + (*b)[2] * scale;
32
+ const double gb = y + (*b)[1] * scale, gt = y + (*b)[3] * scale;
33
+ const double asc = (*b)[3] * scale, desc = (*b)[1] * scale;
34
+ if (!m.hasInk) {
35
+ m.left = gl, m.right = gr, m.bottom = gb, m.top = gt;
36
+ m.ascent = asc, m.descent = desc;
37
+ m.hasInk = true;
38
+ } else {
39
+ m.left = std::min(m.left, gl);
40
+ m.right = std::max(m.right, gr);
41
+ m.bottom = std::min(m.bottom, gb);
42
+ m.top = std::max(m.top, gt);
43
+ m.ascent = std::max(m.ascent, asc);
44
+ m.descent = std::min(m.descent, desc);
45
+ }
46
+ }
47
+
48
+ m.glyphs.push_back(TextMeasurement::Placed{g.glyph, x, y});
49
+ penX += g.xAdvance * spacing;
50
+ penY += g.yAdvance * spacing;
51
+ }
52
+
53
+ m.advanceX = penX * scale;
54
+ m.advanceY = penY * scale;
55
+ return m;
56
+ }
57
+
58
+ // OpenSCAD's ShapeResults::calc_offsets_horiz/_vert. An unknown value (and,
59
+ // for a vertical run, valign="baseline") does not move the text there
60
+ // either; OpenSCAD also warns, which this does not.
61
+ std::pair<double, double> textAlignOffset(const std::string& halign, const std::string& valign,
62
+ const TextMeasurement& m) {
63
+ if (!m.hasInk) return {0.0, 0.0};
64
+ double offsetX = 0.0, offsetY = 0.0;
65
+ if (m.vertical) {
66
+ if (halign == "right") offsetX = -m.right;
67
+ else if (halign == "left") offsetX = -m.left;
68
+ if (valign == "center") offsetY = -m.advanceY / 2;
69
+ else if (valign == "bottom") offsetY = -m.advanceY;
70
+ } else {
71
+ if (halign == "center") offsetX = -0.5 * m.advanceX;
72
+ else if (halign == "right") offsetX = -m.advanceX;
73
+ if (valign == "top") offsetY = -m.ascent;
74
+ else if (valign == "center") offsetY = -(m.ascent + m.descent) / 2;
75
+ else if (valign == "bottom") offsetY = -m.descent;
76
+ }
77
+ return {offsetX, offsetY};
78
+ }
79
+
80
+ } // namespace oscadeval
@@ -438,3 +438,32 @@ TEST(SphereStyle, StyleRespectsFnAndRadius) {
438
438
  const double v20 = sphereStats("sphere(r=20, style=\"aligned\", $fn=24);").volume;
439
439
  EXPECT_NEAR(v20 / v10, 8.0, 1e-6); // volume goes as r^3
440
440
  }
441
+
442
+ // cube()'s size as OpenSCAD 2026.02.01 reads it: undef, absent or explicit,
443
+ // is the default of 1; anything but a number or three numbers warns and
444
+ // falls back to 1. Explicit undef built a zero-size cube -- nothing at all.
445
+ TEST(Primitives, CubeBadSizeIsAUnitCube) {
446
+ const std::pair<const char*, const char*> cases[] = {
447
+ {"undef", ""},
448
+ {"\"a\"", "size=\"a\""},
449
+ {"[1,2]", "size=[1, 2]"},
450
+ {"[1,2,undef]", "size=[1, 2, undef]"},
451
+ {"true", "size=true"},
452
+ {"[1,\"a\",3]", "size=[1, \"a\", 3]"},
453
+ };
454
+ for (const auto& [arg, shown] : cases) {
455
+ std::vector<std::string> log;
456
+ Evaluated e = evalSrc(std::string("cube(") + arg + ");", [&](const std::string& m) { log.push_back(m); });
457
+ ASSERT_EQ(e.bodies.size(), 1u) << arg;
458
+ ASSERT_TRUE(e.bodies[0].body.has_value()) << arg;
459
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 1.0, 1e-9) << arg;
460
+ if (*shown == '\0') {
461
+ EXPECT_TRUE(log.empty()) << arg;
462
+ } else {
463
+ ASSERT_EQ(log.size(), 1u) << arg;
464
+ EXPECT_EQ(log[0], std::string("WARNING: Unable to convert cube(") + shown +
465
+ ", ...) parameter to a number or a vec3 of numbers in file <string>, line 1")
466
+ << arg;
467
+ }
468
+ }
469
+ }
@@ -97,6 +97,42 @@ TEST(Textmetrics, AdvanceXIsPositiveForNonEmptyText) {
97
97
  EXPECT_DOUBLE_EQ(asNum(items[1]), 0.0);
98
98
  }
99
99
 
100
+ // Vertical runs, against OpenSCAD 2026.02.01 (it hints, so ~0.01 apart).
101
+ // Its vertical metrics are FreeType's -- OS/2 typo ascender-descender for a
102
+ // font without vmtx -- not hb-ot's hhea fallback, which gave 46.5 here.
103
+ namespace {
104
+ double vec(const Value& obj, const std::string& key, int i) {
105
+ return asNum(std::get<ListPtr>(objGet(obj, key))->items[i]);
106
+ }
107
+ } // namespace
108
+
109
+ TEST(Textmetrics, VerticalRunMatchesOpenscad) {
110
+ Evaluator ev;
111
+ Value v = asExpr("textmetrics(\"abc\", size=10, direction=\"ttb\")", ev);
112
+ EXPECT_NEAR(vec(v, "advance", 1), -39.1032, 1e-3);
113
+ EXPECT_NEAR(vec(v, "position", 0), -3.2733, 0.01);
114
+ EXPECT_NEAR(vec(v, "position", 1), -36.3957, 0.01);
115
+ EXPECT_NEAR(vec(v, "size", 1), 33.6848, 0.01);
116
+ EXPECT_NEAR(asNum(objGet(v, "ascent")), 10.0672, 0.01); // the glyphs' own, not their placement
117
+ }
118
+
119
+ TEST(Textmetrics, VerticalRunAlignsByItsOwnRules) {
120
+ Evaluator ev;
121
+ Value lc = asExpr("textmetrics(\"abc\", size=10, direction=\"ttb\", halign=\"left\", valign=\"center\")", ev);
122
+ EXPECT_NEAR(vec(lc, "offset", 0), 3.2733, 0.01);
123
+ EXPECT_NEAR(vec(lc, "offset", 1), 19.5516, 1e-3);
124
+ Value rb = asExpr("textmetrics(\"abc\", size=10, direction=\"ttb\", halign=\"right\", valign=\"bottom\")", ev);
125
+ EXPECT_NEAR(vec(rb, "offset", 0), -3.8627, 0.01);
126
+ EXPECT_NEAR(vec(rb, "offset", 1), 39.1032, 1e-3);
127
+ }
128
+
129
+ TEST(Textmetrics, InklessTextIsNotAligned) {
130
+ Evaluator ev;
131
+ Value v = asExpr("textmetrics(\" \", size=10, halign=\"right\")", ev);
132
+ EXPECT_EQ(vec(v, "offset", 0), 0.0);
133
+ EXPECT_GT(vec(v, "advance", 0), 7.0);
134
+ }
135
+
100
136
  TEST(Textmetrics, AscentIsPositiveDescentIsNonPositive) {
101
137
  Evaluator ev;
102
138
  Value v = asExpr("textmetrics(text=\"Hi\", size=10)", ev);
@@ -55,7 +55,7 @@ wheels = [
55
55
 
56
56
  [[package]]
57
57
  name = "openscad-cpp-evaluator"
58
- version = "1.28.0"
58
+ version = "1.28.2"
59
59
  source = { editable = "." }
60
60
  dependencies = [
61
61
  { name = "numpy" },
@@ -1,70 +0,0 @@
1
- #include "openscad_cpp_evaluator/text_metrics.hpp"
2
-
3
- #include <algorithm>
4
-
5
- namespace oscadeval {
6
-
7
- TextMeasurement measureText(FontProvider& fp, FontHandle handle, const std::string& text, double size,
8
- double spacing, const ShapeOptions& opts) {
9
- const FontMetrics fm = fp.metrics(handle);
10
- // The 100/72 factor is real OpenSCAD's long-standing text() size bug
11
- // (its issue #4304: FT_Set_Char_Size is given 100 dpi where 72 is
12
- // correct, making glyphs ~1.39x the nominal size). It is reproduced
13
- // deliberately -- a decade of models are drawn to it, and OpenSCAD
14
- // itself chose to keep the bug and add an `em=` parameter rather than
15
- // fix it.
16
- const double scale = size * (100.0 / 72.0) / fm.unitsPerEm;
17
-
18
- TextMeasurement m;
19
- double penX = 0.0, penY = 0.0;
20
- bool hasInk = false;
21
-
22
- for (const ShapedGlyph& g : fp.shapeText(handle, text, opts)) {
23
- const double x = (penX + g.xOffset) * scale;
24
- const double y = (penY + g.yOffset) * scale;
25
-
26
- if (const std::optional<std::array<double, 4>> b = fp.glyphInkBounds(handle, g.glyph)) {
27
- const double left = x + (*b)[0] * scale;
28
- const double right = x + (*b)[2] * scale;
29
- const double bottom = y + (*b)[1] * scale;
30
- const double top = y + (*b)[3] * scale;
31
- if (!hasInk) {
32
- m.inkMinX = left;
33
- m.inkMaxX = right;
34
- m.ascent = top;
35
- m.descent = bottom;
36
- hasInk = true;
37
- } else {
38
- m.inkMinX = std::min(m.inkMinX, left);
39
- m.inkMaxX = std::max(m.inkMaxX, right);
40
- m.ascent = std::max(m.ascent, top);
41
- m.descent = std::min(m.descent, bottom);
42
- }
43
- }
44
-
45
- m.glyphs.push_back(TextMeasurement::Placed{g.glyph, x, y});
46
- penX += g.xAdvance * spacing;
47
- penY += g.yAdvance * spacing;
48
- }
49
-
50
- m.advanceX = penX * scale;
51
- m.advanceY = penY * scale;
52
- return m;
53
- }
54
-
55
- std::pair<double, double> textAlignOffset(const std::string& halign, const std::string& valign,
56
- const TextMeasurement& m) {
57
- double offsetX = 0.0;
58
- if (halign == "center") offsetX = -0.5 * m.advanceX;
59
- else if (halign == "right") offsetX = -1.0 * m.advanceX;
60
-
61
- double offsetY = 0.0;
62
- if (valign == "top") offsetY = -m.ascent;
63
- else if (valign == "center") offsetY = -(m.ascent + m.descent) / 2;
64
- else if (valign == "bottom") offsetY = -m.descent;
65
- // "baseline" (or anything else) -> 0
66
-
67
- return {offsetX, offsetY};
68
- }
69
-
70
- } // namespace oscadeval