openscad-cpp-evaluator 1.28.1__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.1 → openscad_cpp_evaluator-1.28.2}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/text_metrics.hpp +13 -3
  3. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/function_builtins.cpp +4 -4
  5. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/text.cpp +3 -3
  6. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/text/freetype_font_provider.cpp +41 -6
  7. openscad_cpp_evaluator-1.28.2/src/text/text_metrics.cpp +80 -0
  8. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_text.cpp +36 -0
  9. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/uv.lock +1 -1
  10. openscad_cpp_evaluator-1.28.1/src/text/text_metrics.cpp +0 -70
  11. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/.github/workflows/ci.yml +0 -0
  12. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/.github/workflows/wheels.yml +0 -0
  13. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/.gitignore +0 -0
  14. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/.gitmodules +0 -0
  15. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/CLAUDE.md +0 -0
  16. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/CMakeLists.txt +0 -0
  17. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/LICENSE +0 -0
  18. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/README.md +0 -0
  19. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/bindings/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/bindings/ast_to_py.cpp +0 -0
  21. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/bindings/module.cpp +0 -0
  22. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/cmake/embed_font.cmake +0 -0
  23. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/examples/CMakeLists.txt +0 -0
  24. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/examples/eval_perf_benchmark.cpp +0 -0
  25. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/examples/manifold_cache_reuse.cpp +0 -0
  26. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/examples/minimal_debugger.cpp +0 -0
  27. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  28. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/.gitignore +0 -0
  29. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  30. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  31. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/README.md +0 -0
  32. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  33. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  34. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  35. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  36. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  37. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  38. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  39. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  40. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  41. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  42. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  43. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  44. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  45. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  46. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  47. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  48. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  49. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/api.cpp +0 -0
  50. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  51. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  52. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  53. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  54. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  55. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  56. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  57. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  58. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  59. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  60. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  61. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  62. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  63. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  64. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  65. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  66. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/position.cpp +0 -0
  67. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  68. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  69. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  70. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  71. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  72. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  73. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  74. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  75. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  76. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  77. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  78. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  79. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  80. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  81. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  82. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  83. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  84. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  85. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  86. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  87. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  88. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  89. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  90. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  91. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  92. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  93. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  94. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  95. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  96. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  97. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  98. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  99. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  100. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  101. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  102. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  103. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  104. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  105. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  106. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  107. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  108. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  109. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  110. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  111. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  112. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  113. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/export.hpp +0 -0
  114. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  115. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  116. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  117. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  118. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  119. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  120. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  121. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  122. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  123. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  124. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  125. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  126. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  127. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  128. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  129. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/value.hpp +0 -0
  130. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  131. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/python/openscad_cpp_evaluator/__init__.py +0 -0
  132. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/resources/fonts/LICENSE +0 -0
  133. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  134. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  135. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  136. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  137. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/CMakeLists.txt +0 -0
  138. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/booleans.cpp +0 -0
  139. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/builtins.hpp +0 -0
  140. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/call_args.cpp +0 -0
  141. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/color.cpp +0 -0
  142. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/control.cpp +0 -0
  143. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/dxf_dim.cpp +0 -0
  144. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/extrude.cpp +0 -0
  145. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/import.cpp +0 -0
  146. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/modifiers.cpp +0 -0
  147. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/primitives_2d.cpp +0 -0
  148. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/primitives_3d.cpp +0 -0
  149. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/registry.cpp +0 -0
  150. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/roof.cpp +0 -0
  151. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/segments.cpp +0 -0
  152. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/surface.cpp +0 -0
  153. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/topology.cpp +0 -0
  154. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/builtins/transforms.cpp +0 -0
  155. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/bytecode_compiler.cpp +0 -0
  156. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/bytecode_vm.cpp +0 -0
  157. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/colored_body.cpp +0 -0
  158. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/coverage.cpp +0 -0
  159. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/csg_generate.cpp +0 -0
  160. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/csg_resolve.cpp +0 -0
  161. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/css_colors.cpp +0 -0
  162. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/debug/debug_repl.cpp +0 -0
  163. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/debug_profile.cpp +0 -0
  164. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/eval_context.cpp +0 -0
  165. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/eval_error.cpp +0 -0
  166. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/eval_use.cpp +0 -0
  167. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/export.cpp +0 -0
  168. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/expr_eval.cpp +0 -0
  169. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/format_closure.cpp +0 -0
  170. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/import/dxf_import.cpp +0 -0
  171. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/import/mesh_import.cpp +0 -0
  172. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/import/surface_load.cpp +0 -0
  173. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/import/svg_import.cpp +0 -0
  174. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/manifold_cache.cpp +0 -0
  175. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/measure_geometry.cpp +0 -0
  176. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/mesh_check.cpp +0 -0
  177. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/stmt_eval.cpp +0 -0
  178. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/text/font_match.cpp +0 -0
  179. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/user_calls.cpp +0 -0
  180. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/utf8.cpp +0 -0
  181. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/value.cpp +0 -0
  182. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/src/zip_stored.cpp +0 -0
  183. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/CMakeLists.txt +0 -0
  184. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_booleans.cpp +0 -0
  185. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_bytecode_compiler.cpp +0 -0
  186. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_children_separate.cpp +0 -0
  187. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_cli.cpp +0 -0
  188. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_control_flow.cpp +0 -0
  189. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_coverage.cpp +0 -0
  190. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_csg_tree.cpp +0 -0
  191. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_debug_hooks.cpp +0 -0
  192. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_dxf_svg_import.cpp +0 -0
  193. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_eval_context.cpp +0 -0
  194. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_export.cpp +0 -0
  195. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_expr_eval.cpp +0 -0
  196. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_extrude_roof.cpp +0 -0
  197. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_feature_detection.cpp +0 -0
  198. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_function_builtins.cpp +0 -0
  199. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_helpers.hpp +0 -0
  200. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_import_export.cpp +0 -0
  201. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_include_cache.cpp +0 -0
  202. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_manifold_cache.cpp +0 -0
  203. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_mesh_check.cpp +0 -0
  204. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_multi_color_merge.cpp +0 -0
  205. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_primitives.cpp +0 -0
  206. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_profiling.cpp +0 -0
  207. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_python_bindings.py +0 -0
  208. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_render_expr.cpp +0 -0
  209. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_scope_trail.cpp +0 -0
  210. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_scoping.cpp +0 -0
  211. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_surface.cpp +0 -0
  212. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_tail_calls.cpp +0 -0
  213. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_transforms.cpp +0 -0
  214. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_unexpected_args.cpp +0 -0
  215. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_use.cpp +0 -0
  216. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_value.cpp +0 -0
  217. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_viewport_params.cpp +0 -0
  218. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tests/test_zip_stored.cpp +0 -0
  219. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tools/cli/CMakeLists.txt +0 -0
  220. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tools/cli/cli_lib.cpp +0 -0
  221. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.2}/tools/cli/cli_lib.hpp +0 -0
  222. {openscad_cpp_evaluator-1.28.1 → 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.1
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.1"
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})},
@@ -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
@@ -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.1"
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