openscad-cpp-evaluator 1.28.1__tar.gz → 1.28.3__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.3}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/text_metrics.hpp +18 -4
  3. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/function_builtins.cpp +8 -7
  5. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/text.cpp +5 -4
  6. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/text/freetype_font_provider.cpp +41 -6
  7. openscad_cpp_evaluator-1.28.3/src/text/text_metrics.cpp +95 -0
  8. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_text.cpp +63 -0
  9. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/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.3}/.github/workflows/ci.yml +0 -0
  12. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/.github/workflows/wheels.yml +0 -0
  13. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/.gitignore +0 -0
  14. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/.gitmodules +0 -0
  15. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/CLAUDE.md +0 -0
  16. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/CMakeLists.txt +0 -0
  17. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/LICENSE +0 -0
  18. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/README.md +0 -0
  19. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/bindings/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/bindings/ast_to_py.cpp +0 -0
  21. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/bindings/module.cpp +0 -0
  22. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/cmake/embed_font.cmake +0 -0
  23. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/examples/CMakeLists.txt +0 -0
  24. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/examples/eval_perf_benchmark.cpp +0 -0
  25. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/examples/manifold_cache_reuse.cpp +0 -0
  26. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/examples/minimal_debugger.cpp +0 -0
  27. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  28. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/.gitignore +0 -0
  29. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  30. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  31. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/README.md +0 -0
  32. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  33. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/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.3}/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.3}/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.3}/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.3}/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.3}/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.3}/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.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  41. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  42. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  43. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/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.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  45. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/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.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  47. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/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.3}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  49. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/api.cpp +0 -0
  50. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  51. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  52. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  53. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  54. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  55. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  56. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  57. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  58. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  59. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  60. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  61. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  62. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  63. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  64. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  65. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  66. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/position.cpp +0 -0
  67. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  68. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  69. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  70. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  71. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  72. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  73. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  74. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  75. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  76. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  77. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  78. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  79. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  80. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  81. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  82. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  83. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  84. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  85. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  86. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  87. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  88. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  89. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  90. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  91. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  92. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  93. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  94. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  95. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  96. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  97. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  98. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  99. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  100. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  101. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  102. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  103. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  104. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  105. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  106. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  107. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  108. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  109. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  110. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  111. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  112. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  113. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/export.hpp +0 -0
  114. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  115. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  116. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  117. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  118. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  119. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  120. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  121. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  122. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  123. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  124. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  125. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  126. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  127. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  128. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  129. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/value.hpp +0 -0
  130. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  131. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/python/openscad_cpp_evaluator/__init__.py +0 -0
  132. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/resources/fonts/LICENSE +0 -0
  133. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  134. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  135. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  136. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  137. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/CMakeLists.txt +0 -0
  138. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/booleans.cpp +0 -0
  139. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/builtins.hpp +0 -0
  140. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/call_args.cpp +0 -0
  141. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/color.cpp +0 -0
  142. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/control.cpp +0 -0
  143. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/dxf_dim.cpp +0 -0
  144. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/extrude.cpp +0 -0
  145. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/import.cpp +0 -0
  146. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/modifiers.cpp +0 -0
  147. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/primitives_2d.cpp +0 -0
  148. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/primitives_3d.cpp +0 -0
  149. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/registry.cpp +0 -0
  150. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/roof.cpp +0 -0
  151. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/segments.cpp +0 -0
  152. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/surface.cpp +0 -0
  153. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/topology.cpp +0 -0
  154. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/builtins/transforms.cpp +0 -0
  155. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/bytecode_compiler.cpp +0 -0
  156. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/bytecode_vm.cpp +0 -0
  157. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/colored_body.cpp +0 -0
  158. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/coverage.cpp +0 -0
  159. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/csg_generate.cpp +0 -0
  160. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/csg_resolve.cpp +0 -0
  161. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/css_colors.cpp +0 -0
  162. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/debug/debug_repl.cpp +0 -0
  163. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/debug_profile.cpp +0 -0
  164. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/eval_context.cpp +0 -0
  165. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/eval_error.cpp +0 -0
  166. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/eval_use.cpp +0 -0
  167. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/export.cpp +0 -0
  168. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/expr_eval.cpp +0 -0
  169. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/format_closure.cpp +0 -0
  170. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/import/dxf_import.cpp +0 -0
  171. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/import/mesh_import.cpp +0 -0
  172. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/import/surface_load.cpp +0 -0
  173. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/import/svg_import.cpp +0 -0
  174. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/manifold_cache.cpp +0 -0
  175. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/measure_geometry.cpp +0 -0
  176. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/mesh_check.cpp +0 -0
  177. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/stmt_eval.cpp +0 -0
  178. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/text/font_match.cpp +0 -0
  179. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/user_calls.cpp +0 -0
  180. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/utf8.cpp +0 -0
  181. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/value.cpp +0 -0
  182. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/src/zip_stored.cpp +0 -0
  183. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/CMakeLists.txt +0 -0
  184. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_booleans.cpp +0 -0
  185. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_bytecode_compiler.cpp +0 -0
  186. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_children_separate.cpp +0 -0
  187. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_cli.cpp +0 -0
  188. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_control_flow.cpp +0 -0
  189. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_coverage.cpp +0 -0
  190. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_csg_tree.cpp +0 -0
  191. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_debug_hooks.cpp +0 -0
  192. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_dxf_svg_import.cpp +0 -0
  193. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_eval_context.cpp +0 -0
  194. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_export.cpp +0 -0
  195. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_expr_eval.cpp +0 -0
  196. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_extrude_roof.cpp +0 -0
  197. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_feature_detection.cpp +0 -0
  198. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_function_builtins.cpp +0 -0
  199. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_helpers.hpp +0 -0
  200. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_import_export.cpp +0 -0
  201. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_include_cache.cpp +0 -0
  202. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_manifold_cache.cpp +0 -0
  203. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_mesh_check.cpp +0 -0
  204. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_multi_color_merge.cpp +0 -0
  205. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_primitives.cpp +0 -0
  206. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_profiling.cpp +0 -0
  207. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_python_bindings.py +0 -0
  208. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_render_expr.cpp +0 -0
  209. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_scope_trail.cpp +0 -0
  210. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_scoping.cpp +0 -0
  211. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_surface.cpp +0 -0
  212. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_tail_calls.cpp +0 -0
  213. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_transforms.cpp +0 -0
  214. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_unexpected_args.cpp +0 -0
  215. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_use.cpp +0 -0
  216. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_value.cpp +0 -0
  217. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_viewport_params.cpp +0 -0
  218. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tests/test_zip_stored.cpp +0 -0
  219. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tools/cli/CMakeLists.txt +0 -0
  220. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tools/cli/cli_lib.cpp +0 -0
  221. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/tools/cli/cli_lib.hpp +0 -0
  222. {openscad_cpp_evaluator-1.28.1 → openscad_cpp_evaluator-1.28.3}/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.3
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include "openscad_cpp_evaluator/font_provider.hpp"
4
4
 
5
+ #include <functional>
5
6
  #include <string>
6
7
  #include <utility>
7
8
  #include <vector>
@@ -12,7 +13,12 @@ namespace oscadeval {
12
13
  // for `size`. `glyphs` is what text() places: each shaped glyph with its
13
14
  // pen position, already scaled. An inkless glyph (a space) still advances
14
15
  // the pen and still gets a glyphs entry -- it just contributes nothing to
15
- // ascent/descent/inkMinX/inkMaxX.
16
+ // the ink box or ascent/descent.
17
+ //
18
+ // As in OpenSCAD's ShapeResults: left/right/bottom/top is the ink box of the
19
+ // glyphs where they are placed, while ascent/descent are the glyphs' own
20
+ // extents above and below THEIR baseline, ignoring where they sit. The two
21
+ // only differ for a vertical run or a glyph the shaper offsets (a mark).
16
22
  //
17
23
  // The run comes from the shaper, so kerning and ligatures are already
18
24
  // applied and `spacing` scales the shaped advances rather than raw
@@ -23,8 +29,11 @@ struct TextMeasurement {
23
29
  double x = 0, y = 0;
24
30
  };
25
31
 
26
- double ascent = 0, descent = 0, inkMinX = 0, inkMaxX = 0;
32
+ double ascent = 0, descent = 0;
33
+ double left = 0, right = 0, bottom = 0, top = 0;
27
34
  double advanceX = 0, advanceY = 0;
35
+ bool hasInk = false;
36
+ bool vertical = false; // shaped ttb/btt
28
37
  std::vector<Placed> glyphs;
29
38
  };
30
39
 
@@ -32,9 +41,14 @@ TextMeasurement measureText(FontProvider& fp, FontHandle handle, const std::stri
32
41
  double spacing, const ShapeOptions& opts = {});
33
42
 
34
43
  // (offsetX, offsetY) translation for halign/valign, given a
35
- // TextMeasurement. Shared by textmetrics() (reports it) and text()
44
+ // TextMeasurement. "default" is left/baseline for a horizontal run and
45
+ // center/top for a vertical one, as in OpenSCAD; text with no ink is not
46
+ // moved at all. Shared by textmetrics() (reports it) and text()
36
47
  // (applies it).
48
+ // `warn`, when given, receives OpenSCAD's own warning for an unknown
49
+ // halign/valign, and for valign="baseline" on a vertical run.
37
50
  std::pair<double, double> textAlignOffset(const std::string& halign, const std::string& valign,
38
- const TextMeasurement& m);
51
+ const TextMeasurement& m,
52
+ const std::function<void(const std::string&)>& warn = {});
39
53
 
40
54
  } // 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 = "1.28.1"
7
+ version = "1.28.3"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -782,7 +782,7 @@ Value objectOf(std::vector<std::pair<std::string, Value>> items) {
782
782
  // descent/offset/advance in real OpenSCAD's key order. Going through the
783
783
  // same measureText() the geometry goes through is the point: the numbers
784
784
  // a script positions against cannot disagree with what it draws.
785
- Value builtinTextmetrics(Evaluator& ev, const CallArgs& args) {
785
+ Value builtinTextmetrics(Evaluator& ev, const CallArgs& args, const oscad::Position* pos) {
786
786
  const std::string text = asStringOr(getArg(args, 0, "text", Value{std::string("")}), "");
787
787
  const double size = toDoubleLenient(getArg(args, 1, "size", Value{10.0}));
788
788
  // Positional indices, all of them: OpenSCAD's own signature is
@@ -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;
@@ -804,11 +804,12 @@ Value builtinTextmetrics(Evaluator& ev, const CallArgs& args) {
804
804
  FontProvider& fp = ev.fontProvider();
805
805
  const FontHandle handle = fp.resolveFont(fontSpec);
806
806
  const TextMeasurement m = measureText(fp, handle, text, size, spacing, shape);
807
- const auto [offsetX, offsetY] = textAlignOffset(halign, valign, m);
807
+ const auto [offsetX, offsetY] = textAlignOffset(
808
+ halign, valign, m, [&](const std::string& w) { ev.warn(w, pos); });
808
809
 
809
810
  return objectOf({
810
- {"position", numList({offsetX + m.inkMinX, offsetY + m.descent})},
811
- {"size", numList({m.inkMaxX - m.inkMinX, m.ascent - m.descent})},
811
+ {"position", numList({offsetX + m.left, offsetY + m.bottom})},
812
+ {"size", numList({m.right - m.left, m.top - m.bottom})},
812
813
  {"ascent", Value{m.ascent}},
813
814
  {"descent", Value{m.descent}},
814
815
  {"offset", numList({offsetX, offsetY})},
@@ -1251,7 +1252,7 @@ Value evalBuiltinFunctionResolved(Evaluator& ev, BuiltinFnId id, const std::vect
1251
1252
 
1252
1253
  switch (id) {
1253
1254
  case BuiltinFnId::None: return Value{}; // unreachable: handled above
1254
- case BuiltinFnId::TextMetrics: return builtinTextmetrics(ev, args);
1255
+ case BuiltinFnId::TextMetrics: return builtinTextmetrics(ev, args, &node.position());
1255
1256
  case BuiltinFnId::FontMetrics: return builtinFontmetrics(ev, args);
1256
1257
  case BuiltinFnId::Abs: return Value{std::fabs(toDoubleLenient(getArg(args, 0, "x", Value{})))};
1257
1258
  case BuiltinFnId::Sign: {
@@ -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("")}), "");
@@ -60,7 +60,8 @@ CSGParams resolveText(Evaluator& ev, const oscad::ModularCall& node, EvalContext
60
60
  const double scale = size * (100.0 / 72.0) / fm.unitsPerEm;
61
61
  const int segs = std::max(2, fnSegmentsFromCtx(effCtx) / 2);
62
62
  const TextMeasurement m = measureText(fp, handle, text, size, spacing, shape);
63
- const auto [offsetX, offsetY] = textAlignOffset(halign, valign, m);
63
+ const auto [offsetX, offsetY] = textAlignOffset(
64
+ halign, valign, m, [&](const std::string& w) { ev.warn(w, &node.position()); });
64
65
 
65
66
  std::vector<Value> glyphs;
66
67
  glyphs.reserve(m.glyphs.size());
@@ -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,95 @@
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, warnings included. An
59
+ // unknown value, or valign="baseline" on a vertical run, leaves that axis
60
+ // where it is. Text with no ink never gets this far there, so it never
61
+ // warns either.
62
+ std::pair<double, double> textAlignOffset(const std::string& halign, const std::string& valign,
63
+ const TextMeasurement& m,
64
+ const std::function<void(const std::string&)>& warn) {
65
+ if (!m.hasInk) return {0.0, 0.0};
66
+ auto unknown = [&](const char* param, const char* choices, const std::string& value) {
67
+ if (warn) {
68
+ warn(std::string("Unknown value for the ") + param + " parameter (use " + choices + "): '" + value + "'");
69
+ }
70
+ };
71
+ const char* hChoices = "\"left\", \"right\" or \"center\"";
72
+ const char* vChoices = "\"baseline\", \"bottom\", \"top\" or \"center\"";
73
+ double offsetX = 0.0, offsetY = 0.0;
74
+ if (m.vertical) {
75
+ if (halign == "right") offsetX = -m.right;
76
+ else if (halign == "left") offsetX = -m.left;
77
+ else if (halign != "center" && halign != "default") unknown("halign", hChoices, halign);
78
+ if (valign == "center") offsetY = -m.advanceY / 2;
79
+ else if (valign == "bottom") offsetY = -m.advanceY;
80
+ else if (valign == "baseline") {
81
+ if (warn) warn("Don't use valign=\"baseline\" with vertical layouts");
82
+ } else if (valign != "top" && valign != "default") unknown("valign", vChoices, valign);
83
+ } else {
84
+ if (halign == "center") offsetX = -0.5 * m.advanceX;
85
+ else if (halign == "right") offsetX = -m.advanceX;
86
+ else if (halign != "left" && halign != "default") unknown("halign", hChoices, halign);
87
+ if (valign == "top") offsetY = -m.ascent;
88
+ else if (valign == "center") offsetY = -(m.ascent + m.descent) / 2;
89
+ else if (valign == "bottom") offsetY = -m.descent;
90
+ else if (valign != "baseline" && valign != "default") unknown("valign", vChoices, valign);
91
+ }
92
+ return {offsetX, offsetY};
93
+ }
94
+
95
+ } // namespace oscadeval
@@ -97,6 +97,69 @@ 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
+ // OpenSCAD 2026.02.01's warnings, verbatim. Inkless text never reaches
130
+ // its alignment code there, so it does not warn.
131
+ TEST(Text, UnknownAlignmentWarnsAsOpenscadDoes) {
132
+ const std::pair<const char*, std::vector<std::string>> cases[] = {
133
+ {"m = textmetrics(\"ab\", halign=\"middle\");",
134
+ {"Unknown value for the halign parameter (use \"left\", \"right\" or \"center\"): 'middle'"}},
135
+ {"m = textmetrics(\"ab\", valign=\"mid\");",
136
+ {"Unknown value for the valign parameter (use \"baseline\", \"bottom\", \"top\" or \"center\"): 'mid'"}},
137
+ {"m = textmetrics(\"ab\", direction=\"ttb\", valign=\"baseline\");",
138
+ {"Don't use valign=\"baseline\" with vertical layouts"}},
139
+ {"text(\"ab\", direction=\"ttb\", halign=\"x\", valign=\"y\");",
140
+ {"Unknown value for the halign parameter (use \"left\", \"right\" or \"center\"): 'x'",
141
+ "Unknown value for the valign parameter (use \"baseline\", \"bottom\", \"top\" or \"center\"): 'y'"}},
142
+ {"m = textmetrics(\" \", halign=\"bogus\");", {}},
143
+ {"m = textmetrics(\"ab\", halign=\"default\", valign=\"default\");", {}},
144
+ {"m = textmetrics(\"ab\", direction=\"ttb\", valign=\"top\");", {}},
145
+ };
146
+ for (const auto& [src, want] : cases) {
147
+ std::vector<std::string> log;
148
+ evalSrc(src, [&](const std::string& m) { log.push_back(m); });
149
+ ASSERT_EQ(log.size(), want.size()) << src;
150
+ for (size_t i = 0; i < want.size(); ++i) {
151
+ EXPECT_EQ(log[i], "WARNING: " + want[i] + " in file <string>, line 1") << src;
152
+ }
153
+ }
154
+ }
155
+
156
+ TEST(Textmetrics, InklessTextIsNotAligned) {
157
+ Evaluator ev;
158
+ Value v = asExpr("textmetrics(\" \", size=10, halign=\"right\")", ev);
159
+ EXPECT_EQ(vec(v, "offset", 0), 0.0);
160
+ EXPECT_GT(vec(v, "advance", 0), 7.0);
161
+ }
162
+
100
163
  TEST(Textmetrics, AscentIsPositiveDescentIsNonPositive) {
101
164
  Evaluator ev;
102
165
  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.3"
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