openscad-cpp-evaluator 1.27.0__tar.gz → 1.28.0__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.
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/CLAUDE.md +13 -7
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +6 -1
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/dxf_dim.cpp +29 -6
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/import.cpp +37 -7
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/coverage.cpp +15 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/export.cpp +5 -1
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/import/svg_import.cpp +103 -2
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_coverage.cpp +36 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_dxf_svg_import.cpp +98 -1
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_export.cpp +47 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_import_export.cpp +33 -2
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/uv.lock +1 -1
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/README.md +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/bindings/module.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/topology.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/bytecode_vm.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/format_closure.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/utf8.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_children_separate.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_feature_detection.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_include_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tools/cli/main.cpp +0 -0
|
@@ -984,15 +984,16 @@ grep for `ponytail:`.
|
|
|
984
984
|
rather than out of the model. Ported from the reference's `io/dxfdim.cc` plus the DIMENSION/LINE
|
|
985
985
|
half of `io/DxfData.cc`: `dxf_dim()` returns the first matching DIMENSION's value by its type
|
|
986
986
|
(`70 & 7` — 0 rotated, 1 aligned, 2 angular, 3/4 diameter/radius, 6 ordinate; type 5 is
|
|
987
|
-
unsupported there too), `dxf_cross()` intersects the first two
|
|
987
|
+
unsupported there too), `dxf_cross()` intersects the first two 2-point paths. Note the reference's
|
|
988
988
|
own coordinate quirk, reproduced here: groups 11/12/16 (and 21/22/26) are scaled but NOT
|
|
989
989
|
origin-shifted, being extents rather than positions.
|
|
990
990
|
|
|
991
991
|
Deliberately its own group-code walk rather than sharing `src/import/dxf_import.cpp`'s: that one
|
|
992
992
|
produces closed contours, and these need the DIMENSION entities' seven coordinate slots and the
|
|
993
|
-
raw LINE endpoints, neither of which a contour list keeps.
|
|
994
|
-
|
|
995
|
-
|
|
993
|
+
raw LINE endpoints, neither of which a contour list keeps. A 2-point path is a LINE sharing
|
|
994
|
+
neither end with another LINE or an ARC (an ARC's ends read from its centre, radius and 50/51
|
|
995
|
+
angles): one that does belongs to an outline. Taking the first two LINEs outright found a "cross"
|
|
996
|
+
in example009.dxf's `fan_top` outline where the reference warns `Can't find cross`.
|
|
996
997
|
|
|
997
998
|
- `src/csg_generate.cpp` — `Evaluator::applyDimensionRules`, run centrally just before every node's
|
|
998
999
|
`GenerateFn` (and once more over the top-level list, which is an implicit union). Mirrors the
|
|
@@ -1210,9 +1211,14 @@ grep for `ponytail:`.
|
|
|
1210
1211
|
path-command tokenizer/flattener (`M`/`L`/`H`/`V`/`C`/`S`/`Q`/`T`/`A`, both absolute and relative,
|
|
1211
1212
|
cubic/quadratic Bezier flattening, full elliptical-arc endpoint-to-center parameterization) plus
|
|
1212
1213
|
`rect`/`circle`/`ellipse`/`polygon`/`polyline` and nested `transform="matrix()/translate()/
|
|
1213
|
-
scale()/rotate()"` composition (including through `<g>` groups)
|
|
1214
|
-
|
|
1215
|
-
|
|
1214
|
+
scale()/rotate()"` composition (including through `<g>` groups), in SVG user units at the
|
|
1215
|
+
point-transform step, `applyMat()` (named to avoid colliding with `std::apply` in an ADL lookup
|
|
1216
|
+
that broke the build once — see its own comment). **Placement is `pageMap()`, a port of the
|
|
1217
|
+
reference's `import_svg.cc`**: the page's `width`/`height` to mm (a unitless length at `dpi=`,
|
|
1218
|
+
72 by default; `px` at 96), the `viewBox` scaled onto the page under `preserveAspectRatio`
|
|
1219
|
+
(default `xMidYMid meet`), and Y flipped about the page HEIGHT -- or about the drawing's centre
|
|
1220
|
+
with `center=true`. Negating Y alone, as this did before, put a unitless 100-unit drawing in 2.8x
|
|
1221
|
+
too large and below the X axis. Bounds match OpenSCAD 2026.02.01 (`SvgPlacement` tests).
|
|
1216
1222
|
- `include/openscad_cpp_evaluator/font_provider.hpp` — the `FontProvider` abstract interface
|
|
1217
1223
|
(`resolveFont`/`metrics`/`shapeText`/`glyphInkBounds`/`glyphOutline`), the seam that keeps
|
|
1218
1224
|
`text()`/`textmetrics()`/`fontmetrics()` decoupled from any specific font backend. `Evaluator`'s
|
|
@@ -34,8 +34,13 @@ struct SvgFilter {
|
|
|
34
34
|
// `matched`, when given, reports whether the filter found anything. A miss
|
|
35
35
|
// imports nothing rather than falling back to the whole drawing; the caller
|
|
36
36
|
// warns. Always true when no filter was set.
|
|
37
|
+
// Placed as OpenSCAD places them: page units to mm (a unitless length at
|
|
38
|
+
// `dpi`), the viewBox under preserveAspectRatio, Y flipped about the page
|
|
39
|
+
// height -- or about the drawing's centre with `center`.
|
|
37
40
|
std::vector<Contour2d> loadSvgContours(const std::string& path,
|
|
38
41
|
const SvgFilter& filter = {},
|
|
39
|
-
bool* matched = nullptr
|
|
42
|
+
bool* matched = nullptr,
|
|
43
|
+
double dpi = 72.0,
|
|
44
|
+
bool center = false);
|
|
40
45
|
|
|
41
46
|
} // namespace oscadeval
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
#include <cmath>
|
|
7
7
|
#include <filesystem>
|
|
8
8
|
#include <fstream>
|
|
9
|
+
#include <map>
|
|
9
10
|
#include <sstream>
|
|
10
11
|
|
|
11
12
|
namespace oscadeval {
|
|
@@ -26,7 +27,9 @@ struct Entity {
|
|
|
26
27
|
std::string layer;
|
|
27
28
|
std::string name; // group 1: DIMENSION's text override
|
|
28
29
|
int dimType = 0; // group 70
|
|
29
|
-
double angle = 0.0; // group 50
|
|
30
|
+
double angle = 0.0; // group 50 (an ARC's start angle)
|
|
31
|
+
double angle2 = 0.0; // group 51: an ARC's end angle
|
|
32
|
+
double radius = 0.0; // group 40: an ARC's radius
|
|
30
33
|
double coords[7][2] = {}; // groups 10-16 / 20-26
|
|
31
34
|
std::vector<double> xverts, yverts; // groups 10/11 and 20/21, in order
|
|
32
35
|
};
|
|
@@ -91,6 +94,8 @@ std::vector<Entity> readEntities(const std::string& path, double xorigin, double
|
|
|
91
94
|
case 20:
|
|
92
95
|
case 21: cur.yverts.push_back((num() - yorigin) * scale); break;
|
|
93
96
|
case 50: cur.angle = num(); break;
|
|
97
|
+
case 51: cur.angle2 = num(); break;
|
|
98
|
+
case 40: cur.radius = num() * scale; break;
|
|
94
99
|
case 70:
|
|
95
100
|
try {
|
|
96
101
|
cur.dimType = std::stoi(data);
|
|
@@ -188,16 +193,34 @@ Value builtinDxfCross(Evaluator& ev, const CallArgs& args, const oscad::ASTNode&
|
|
|
188
193
|
return Value{};
|
|
189
194
|
}
|
|
190
195
|
|
|
191
|
-
// The first two
|
|
192
|
-
// reference walks
|
|
193
|
-
//
|
|
194
|
-
//
|
|
196
|
+
// The first two 2-POINT PATHS on the layer are the cross, as the
|
|
197
|
+
// reference walks them: a LINE sharing an end with another LINE or an
|
|
198
|
+
// ARC is joined into a longer path, part of an outline, and no stroke of
|
|
199
|
+
// a cross. Taking the first two LINEs outright found a "cross" in any
|
|
200
|
+
// outline -- example009.dxf's fan_top, where the reference finds none.
|
|
201
|
+
// ponytail: LINE and ARC ends only; polylines and splines don't
|
|
202
|
+
// disqualify a LINE here.
|
|
203
|
+
const std::vector<Entity> entities = readEntities(c.path, c.xorigin, c.yorigin, c.scale);
|
|
204
|
+
std::map<std::pair<long long, long long>, int> ends;
|
|
205
|
+
const auto endKey = [](double x, double y) { return std::make_pair(std::llround(x * 1e6), std::llround(y * 1e6)); };
|
|
206
|
+
for (const Entity& e : entities) {
|
|
207
|
+
if (!c.layer.empty() && c.layer != e.layer) continue;
|
|
208
|
+
if (e.type == "LINE" && e.xverts.size() >= 2 && e.yverts.size() >= 2) {
|
|
209
|
+
++ends[endKey(e.xverts[0], e.yverts[0])];
|
|
210
|
+
++ends[endKey(e.xverts[1], e.yverts[1])];
|
|
211
|
+
} else if (e.type == "ARC") {
|
|
212
|
+
for (double a : {e.angle, e.angle2}) {
|
|
213
|
+
++ends[endKey(e.coords[0][0] + e.radius * std::cos(rad(a)), e.coords[0][1] + e.radius * std::sin(rad(a)))];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
195
217
|
double p[4][2];
|
|
196
218
|
int found = 0;
|
|
197
|
-
for (const Entity& e :
|
|
219
|
+
for (const Entity& e : entities) {
|
|
198
220
|
if (e.type != "LINE") continue;
|
|
199
221
|
if (!c.layer.empty() && c.layer != e.layer) continue;
|
|
200
222
|
if (e.xverts.size() < 2 || e.yverts.size() < 2) continue;
|
|
223
|
+
if (ends[endKey(e.xverts[0], e.yverts[0])] > 1 || ends[endKey(e.xverts[1], e.yverts[1])] > 1) continue;
|
|
201
224
|
p[found][0] = e.xverts[0];
|
|
202
225
|
p[found][1] = e.yverts[0];
|
|
203
226
|
++found;
|
|
@@ -199,8 +199,18 @@ CSGParams resolveImport(Evaluator& ev, const oscad::ModularCall& node, EvalConte
|
|
|
199
199
|
const std::string path = resolveFilePath(fileArg, node);
|
|
200
200
|
const std::string ext = lowerExt(path);
|
|
201
201
|
|
|
202
|
+
if (!std::filesystem::exists(path)) {
|
|
203
|
+
// A missing file warns and imports nothing, as in OpenSCAD; it
|
|
204
|
+
// aborted the whole render. Warned at generate, where OpenSCAD does,
|
|
205
|
+
// so it follows the script's echoes.
|
|
206
|
+
params["kind"] = Value{std::string("missing")};
|
|
207
|
+
params["path"] = Value{path};
|
|
208
|
+
params["dxf"] = Value{ext == ".dxf"};
|
|
209
|
+
return params;
|
|
210
|
+
}
|
|
202
211
|
if (ext == ".dxf" || ext == ".svg" || ext == ".pdf") {
|
|
203
212
|
std::vector<Contour2d> contours;
|
|
213
|
+
bool filteredMiss = false;
|
|
204
214
|
try {
|
|
205
215
|
if (ext == ".dxf") {
|
|
206
216
|
std::optional<std::string> layer;
|
|
@@ -211,7 +221,10 @@ CSGParams resolveImport(Evaluator& ev, const oscad::ModularCall& node, EvalConte
|
|
|
211
221
|
if (const std::string* v = std::get_if<std::string>(&idArg)) filter.id = *v;
|
|
212
222
|
if (const std::string* v = std::get_if<std::string>(&classArg)) filter.cls = *v;
|
|
213
223
|
bool matched = true;
|
|
214
|
-
|
|
224
|
+
const Value dpiArg = getArg(args, std::nullopt, "dpi", Value{72.0});
|
|
225
|
+
contours = loadSvgContours(path, filter, &matched,
|
|
226
|
+
std::holds_alternative<double>(dpiArg) ? std::get<double>(dpiArg) : 72.0,
|
|
227
|
+
truthy(getArg(args, std::nullopt, "center", Value{false})));
|
|
215
228
|
if (!matched) {
|
|
216
229
|
// Nothing imported, rather than silently falling back to
|
|
217
230
|
// the whole drawing -- a cut layer that quietly became
|
|
@@ -222,14 +235,15 @@ CSGParams resolveImport(Evaluator& ev, const oscad::ModularCall& node, EvalConte
|
|
|
222
235
|
if (!what.empty()) what += ", ";
|
|
223
236
|
what += "class = \"" + *filter.cls + "\"";
|
|
224
237
|
}
|
|
225
|
-
|
|
226
|
-
|
|
238
|
+
// OpenSCAD's words, which name no file.
|
|
239
|
+
ev.warn("import() filter " + what + " did not match anything", &node.position());
|
|
240
|
+
filteredMiss = true;
|
|
227
241
|
}
|
|
228
242
|
}
|
|
229
243
|
} catch (const std::exception& e) {
|
|
230
244
|
ev.error(std::string("import: ") + e.what(), node);
|
|
231
245
|
}
|
|
232
|
-
if (contours.empty()) {
|
|
246
|
+
if (contours.empty() && !filteredMiss) {
|
|
233
247
|
ev.error(ext == ".dxf" ? "import: no closed contours found in DXF file" : "import: no shapes found in SVG file", node);
|
|
234
248
|
}
|
|
235
249
|
params["kind"] = Value{std::string("region")};
|
|
@@ -276,6 +290,16 @@ std::vector<ColoredBody> generateImport(Evaluator& ev, const CSGParams& params,
|
|
|
276
290
|
const auto kindIt = params.find("kind");
|
|
277
291
|
if (kindIt == params.end()) return {};
|
|
278
292
|
const std::string& kind = std::get<std::string>(kindIt->second);
|
|
293
|
+
if (kind == "missing") {
|
|
294
|
+
// OpenSCAD's own wording, which names a line rather than a file.
|
|
295
|
+
const std::string& path = std::get<std::string>(params.at("path"));
|
|
296
|
+
ev.warn(std::get<bool>(params.at("dxf"))
|
|
297
|
+
? "Can't open DXF file '" + path + "'."
|
|
298
|
+
: "Can't open import file '" + path + "', import() at line " +
|
|
299
|
+
std::to_string(node.position().line),
|
|
300
|
+
nullptr);
|
|
301
|
+
return {};
|
|
302
|
+
}
|
|
279
303
|
if (kind == "region") {
|
|
280
304
|
const std::vector<Contour2d> contours = valueToContours(params.at("contours"));
|
|
281
305
|
manifold::Polygons polys;
|
|
@@ -397,6 +421,10 @@ Value importAsValue(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& n
|
|
|
397
421
|
}
|
|
398
422
|
const std::string path = resolveFilePath(fileArg, node);
|
|
399
423
|
const std::string ext = lowerExt(path);
|
|
424
|
+
if (!std::filesystem::exists(path)) {
|
|
425
|
+
ev.warn("Could not read file '" + path + "'", &node.position());
|
|
426
|
+
return Value{};
|
|
427
|
+
}
|
|
400
428
|
|
|
401
429
|
try {
|
|
402
430
|
if (ext == ".json") return loadJsonAsValue(path);
|
|
@@ -412,7 +440,10 @@ Value importAsValue(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& n
|
|
|
412
440
|
if (const std::string* v = std::get_if<std::string>(&idArg)) filter.id = *v;
|
|
413
441
|
if (const std::string* v = std::get_if<std::string>(&classArg)) filter.cls = *v;
|
|
414
442
|
bool matched = true;
|
|
415
|
-
|
|
443
|
+
const Value dpiArg = getArg(args, std::nullopt, "dpi", Value{72.0});
|
|
444
|
+
contours = loadSvgContours(path, filter, &matched,
|
|
445
|
+
std::holds_alternative<double>(dpiArg) ? std::get<double>(dpiArg) : 72.0,
|
|
446
|
+
truthy(getArg(args, std::nullopt, "center", Value{false})));
|
|
416
447
|
if (!matched) {
|
|
417
448
|
// Nothing imported, rather than silently falling back to
|
|
418
449
|
// the whole drawing -- a cut layer that quietly became
|
|
@@ -423,8 +454,7 @@ Value importAsValue(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& n
|
|
|
423
454
|
if (!what.empty()) what += ", ";
|
|
424
455
|
what += "class = \"" + *filter.cls + "\"";
|
|
425
456
|
}
|
|
426
|
-
ev.warn("import() filter " + what + " did not match anything
|
|
427
|
-
+ path, &node.position());
|
|
457
|
+
ev.warn("import() filter " + what + " did not match anything", &node.position());
|
|
428
458
|
}
|
|
429
459
|
}
|
|
430
460
|
return contoursToValue(contours);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
#include <algorithm>
|
|
9
9
|
#include <map>
|
|
10
|
+
#include <tuple>
|
|
10
11
|
#include <unordered_set>
|
|
11
12
|
|
|
12
13
|
namespace oscadeval {
|
|
@@ -354,8 +355,22 @@ CoverageResult buildCoverageResult(const std::vector<const ASTNode*>& roots,
|
|
|
354
355
|
const std::vector<const ASTNode*>& extraStatements,
|
|
355
356
|
const CoverageRecorder& recorder) {
|
|
356
357
|
CoverageResult result;
|
|
358
|
+
// One span per source position. A file several files use<> is parsed
|
|
359
|
+
// once per use, so its nodes arrive once per parse -- BOSL2's
|
|
360
|
+
// builtins.scad three times, two of them with zero hits -- which
|
|
361
|
+
// counted its spans three times over and pulled its percentages down.
|
|
362
|
+
// Merged here, hits summed: each copy's executions are real ones.
|
|
363
|
+
std::map<std::tuple<std::string, int, int, CoverageKind>, size_t> index;
|
|
357
364
|
for (const Coverable& c : collectCoverable(roots, extraStatements)) {
|
|
358
365
|
const oscad::Position& p = c.node->position();
|
|
366
|
+
const auto key = std::make_tuple(p.origin, p.start_offset, p.end_offset, c.kind);
|
|
367
|
+
const auto [it, fresh] = index.emplace(key, result.spans.size());
|
|
368
|
+
if (!fresh) {
|
|
369
|
+
CoverageSpan& s = result.spans[it->second];
|
|
370
|
+
s.hits += recorder.hitsFor(*c.node);
|
|
371
|
+
s.arm = s.arm || c.arm;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
359
374
|
result.spans.push_back(CoverageSpan{p.origin, p.line, p.column, p.start_offset, p.end_offset, c.kind, c.arm,
|
|
360
375
|
recorder.hitsFor(*c.node)});
|
|
361
376
|
}
|
|
@@ -663,7 +663,11 @@ void writePly(const std::string& path, const std::vector<ExportObject>& objects)
|
|
|
663
663
|
colors.push_back(rgb(o.triColors[t], 0));
|
|
664
664
|
colors.push_back(rgb(o.triColors[t], 1));
|
|
665
665
|
colors.push_back(rgb(o.triColors[t], 2));
|
|
666
|
-
|
|
666
|
+
// The vertex just pushed. Not faces.size() + base: faces
|
|
667
|
+
// already holds every earlier object's entries, so that
|
|
668
|
+
// counted `base` twice and pointed past the vertex list
|
|
669
|
+
// on any object after the first.
|
|
670
|
+
faces.push_back(static_cast<int32_t>(verts.size() / 3) - 1);
|
|
667
671
|
}
|
|
668
672
|
}
|
|
669
673
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#include <cmath>
|
|
6
6
|
#include <cstring>
|
|
7
7
|
#include <fstream>
|
|
8
|
+
#include <optional>
|
|
8
9
|
#include <sstream>
|
|
9
10
|
#include <stdexcept>
|
|
10
11
|
|
|
@@ -194,7 +195,7 @@ Mat3 compose(const Mat3& m, const Mat3& acc) {
|
|
|
194
195
|
std::array<double, 2> applyMat(const std::array<double, 2>& pt, const Mat3& m) {
|
|
195
196
|
const double x = m.a * pt[0] + m.c * pt[1] + m.e;
|
|
196
197
|
const double y = m.b * pt[0] + m.d * pt[1] + m.f;
|
|
197
|
-
return {x,
|
|
198
|
+
return {x, y}; // SVG user units; pageMap places them on the page
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
std::vector<double> parseNumberList(const std::string& s) {
|
|
@@ -489,11 +490,109 @@ void walkFiltered(const XmlNode& el, const Mat3& mat, const SvgFilter& f,
|
|
|
489
490
|
for (const XmlNode& child : el.children) walkFiltered(child, m, f, out, matched);
|
|
490
491
|
}
|
|
491
492
|
|
|
493
|
+
// "12", "12.5mm", "3in" -> (number, unit); unit empty when unitless.
|
|
494
|
+
// nullopt for anything else, which OpenSCAD treats as absent.
|
|
495
|
+
std::optional<std::pair<double, std::string>> parseLength(const std::string& text) {
|
|
496
|
+
const size_t b = text.find_first_not_of(" \t\r\n");
|
|
497
|
+
if (b == std::string::npos) return std::nullopt;
|
|
498
|
+
const size_t e = text.find_last_not_of(" \t\r\n");
|
|
499
|
+
const std::string t = text.substr(b, e - b + 1);
|
|
500
|
+
size_t used = 0;
|
|
501
|
+
double n = 0.0;
|
|
502
|
+
try {
|
|
503
|
+
n = std::stod(t, &used);
|
|
504
|
+
} catch (...) {
|
|
505
|
+
return std::nullopt;
|
|
506
|
+
}
|
|
507
|
+
std::string unit = t.substr(used);
|
|
508
|
+
unit.erase(0, unit.find_first_not_of(" \t"));
|
|
509
|
+
static const char* known[] = {"", "em", "ex", "px", "in", "cm", "mm", "pt", "pc", "%"};
|
|
510
|
+
for (const char* k : known) {
|
|
511
|
+
if (unit == k) return std::make_pair(n, unit);
|
|
512
|
+
}
|
|
513
|
+
return std::nullopt;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Place SVG user-unit contours as OpenSCAD's import_svg.cc does: the page's
|
|
517
|
+
// width/height to millimetres (a unitless length at `dpi`, px at 96), the
|
|
518
|
+
// viewBox scaled onto it under preserveAspectRatio (default xMidYMid meet),
|
|
519
|
+
// and Y flipped about the page height -- or, with center, about the
|
|
520
|
+
// drawing's own centre. Without this a unitless 100-unit drawing came in
|
|
521
|
+
// 2.8x too large and below the X axis.
|
|
522
|
+
void pageMap(const XmlNode& root, double dpi, bool center, std::vector<Contour2d>& contours) {
|
|
523
|
+
std::vector<double> vb;
|
|
524
|
+
{
|
|
525
|
+
std::string v = root.getAttr("viewBox");
|
|
526
|
+
std::replace(v.begin(), v.end(), ',', ' ');
|
|
527
|
+
std::istringstream in(v);
|
|
528
|
+
double x = 0.0;
|
|
529
|
+
while (in >> x) vb.push_back(x);
|
|
530
|
+
}
|
|
531
|
+
const bool valid = vb.size() == 4 && vb[2] >= 0.0 && vb[3] >= 0.0;
|
|
532
|
+
const auto toMm = [&](const char* attr, double viewbox) {
|
|
533
|
+
const auto len = parseLength(root.getAttr(attr));
|
|
534
|
+
if (!len) return valid ? 25.4 * viewbox / dpi : 0.0; // absent: rely on dpi, as old Illustrator files do
|
|
535
|
+
const auto& [n, u] = *len;
|
|
536
|
+
if (u.empty()) return 25.4 * n / dpi;
|
|
537
|
+
if (u == "px") return 25.4 * n / 96.0;
|
|
538
|
+
if (u == "pt") return 25.4 * n / 72.0;
|
|
539
|
+
if (u == "pc") return 25.4 * n / 6.0;
|
|
540
|
+
if (u == "in") return 25.4 * n;
|
|
541
|
+
if (u == "cm") return 10.0 * n;
|
|
542
|
+
if (u == "mm") return n;
|
|
543
|
+
if (u == "%") return valid ? 25.4 * n / 100.0 * viewbox / dpi : 0.0;
|
|
544
|
+
return valid ? viewbox : 0.0; // em, ex
|
|
545
|
+
};
|
|
546
|
+
const double widthMm = toMm("width", valid ? vb[2] : 0.0);
|
|
547
|
+
const double heightMm = toMm("height", valid ? vb[3] : 0.0);
|
|
548
|
+
double sx = 1.0, sy = 1.0, vbx = 0.0, vby = 0.0, ax = 0.0, ay = 0.0;
|
|
549
|
+
if (valid) {
|
|
550
|
+
const auto w = parseLength(root.getAttr("width")), h = parseLength(root.getAttr("height"));
|
|
551
|
+
vbx = vb[0] * (w && w->second == "%" ? w->first / 100.0 : 1.0);
|
|
552
|
+
vby = vb[1] * (h && h->second == "%" ? h->first / 100.0 : 1.0);
|
|
553
|
+
sx = vb[2] != 0.0 ? widthMm / vb[2] : 0.0;
|
|
554
|
+
sy = vb[3] != 0.0 ? heightMm / vb[3] : 0.0;
|
|
555
|
+
std::istringstream par(root.getAttr("preserveAspectRatio"));
|
|
556
|
+
std::string align, meet;
|
|
557
|
+
par >> align;
|
|
558
|
+
if (align == "defer") par >> align;
|
|
559
|
+
par >> meet;
|
|
560
|
+
if (align.empty()) align = "xMidYMid";
|
|
561
|
+
if (align != "none") {
|
|
562
|
+
const double scaling = meet == "slice" ? std::max(sx, sy) : std::min(sx, sy);
|
|
563
|
+
sx = sy = scaling;
|
|
564
|
+
const auto frac = [](const std::string& a) { return a == "Min" ? 0.0 : a == "Max" ? 1.0 : 0.5; };
|
|
565
|
+
const bool wellFormed = align.size() == 8 && align[0] == 'x' && align[4] == 'Y';
|
|
566
|
+
const double fx = wellFormed ? frac(align.substr(1, 3)) : 0.5;
|
|
567
|
+
const double fy = wellFormed ? frac(align.substr(5, 3)) : 0.5;
|
|
568
|
+
ax = fx * (widthMm - sx * vb[2]);
|
|
569
|
+
ay = fy * (heightMm - sy * vb[3]);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
double cx = -ax, cy = heightMm - ay;
|
|
573
|
+
if (center) {
|
|
574
|
+
double lo[2] = {INFINITY, INFINITY}, hi[2] = {-INFINITY, -INFINITY};
|
|
575
|
+
for (const Contour2d& c : contours)
|
|
576
|
+
for (const auto& p : c) {
|
|
577
|
+
lo[0] = std::min(lo[0], sx * p[0]);
|
|
578
|
+
hi[0] = std::max(hi[0], sx * p[0]);
|
|
579
|
+
lo[1] = std::min(lo[1], sy * p[1]);
|
|
580
|
+
hi[1] = std::max(hi[1], sy * p[1]);
|
|
581
|
+
}
|
|
582
|
+
cx = std::isfinite(lo[0]) ? (lo[0] + hi[0]) / 2.0 : 0.0;
|
|
583
|
+
cy = std::isfinite(lo[1]) ? (lo[1] + hi[1]) / 2.0 : 0.0;
|
|
584
|
+
}
|
|
585
|
+
// -vby - y, not y - vby: OpenSCAD's own formula, kept for parity.
|
|
586
|
+
for (Contour2d& c : contours)
|
|
587
|
+
for (auto& p : c) p = {sx * (p[0] - vbx) - cx, sy * (-vby - p[1]) + cy};
|
|
588
|
+
}
|
|
589
|
+
|
|
492
590
|
} // namespace
|
|
493
591
|
|
|
494
592
|
std::vector<Contour2d> loadSvgContours(const std::string& path,
|
|
495
593
|
const SvgFilter& filter,
|
|
496
|
-
bool* matched) {
|
|
594
|
+
bool* matched, double dpi, bool center) {
|
|
595
|
+
if (!(dpi > 0.0)) dpi = 72.0;
|
|
497
596
|
std::ifstream in(path);
|
|
498
597
|
if (!in) throw std::runtime_error("could not open '" + path + "'");
|
|
499
598
|
std::stringstream buf;
|
|
@@ -505,6 +604,7 @@ std::vector<Contour2d> loadSvgContours(const std::string& path,
|
|
|
505
604
|
if (!filter.id && !filter.cls) {
|
|
506
605
|
if (matched) *matched = true; // no filter, nothing to miss
|
|
507
606
|
walk(*root, Mat3{}, out);
|
|
607
|
+
pageMap(*root, dpi, center, out);
|
|
508
608
|
return out;
|
|
509
609
|
}
|
|
510
610
|
bool hit = false;
|
|
@@ -513,6 +613,7 @@ std::vector<Contour2d> loadSvgContours(const std::string& path,
|
|
|
513
613
|
// back to the whole drawing, which would be the silent-wrong-geometry
|
|
514
614
|
// answer. The caller turns this into the warning.
|
|
515
615
|
if (matched) *matched = hit;
|
|
616
|
+
pageMap(*root, dpi, center, out);
|
|
516
617
|
return out;
|
|
517
618
|
}
|
|
518
619
|
|
|
@@ -263,4 +263,40 @@ TEST(Coverage, IncludedAndUsedFilesCarryTheirOwnOrigin) {
|
|
|
263
263
|
fs::remove_all(dir);
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
// A file two included files each use<> is parsed once per use; its spans
|
|
267
|
+
// must still be listed once, with the hits of every copy, or its
|
|
268
|
+
// percentages count the unexecuted copies as missed code.
|
|
269
|
+
TEST(Coverage, AFileUsedTwiceIsListedOnce) {
|
|
270
|
+
namespace fs = std::filesystem;
|
|
271
|
+
const fs::path dir = fs::temp_directory_path() / "oscad_cov_twice";
|
|
272
|
+
fs::create_directories(dir);
|
|
273
|
+
const fs::path lib = dir / "lib.scad", a = dir / "a.scad", b = dir / "b.scad", main = dir / "main.scad";
|
|
274
|
+
std::ofstream(lib) << "function hit(x) = x;\nfunction miss(x) = x;\n";
|
|
275
|
+
std::ofstream(a) << "use <lib.scad>\nfunction fa() = hit(1);\n";
|
|
276
|
+
std::ofstream(b) << "use <lib.scad>\nfunction fb() = hit(2);\n";
|
|
277
|
+
std::ofstream(main) << "include <a.scad>\ninclude <b.scad>\necho(fa(), fb());\n";
|
|
278
|
+
auto ast = oscad::getASTFromFile(main.string());
|
|
279
|
+
ResolvedUseScopes uses = resolveUseScopes(ast, main.string(), {});
|
|
280
|
+
Evaluator ev({}, nullptr, nullptr, {}, false, /*coverage=*/true);
|
|
281
|
+
ev.setUsedFileGlobals(uses.usedFileGlobals);
|
|
282
|
+
EvalContext ctx = EvalContext::makeRoot(uses.rootScope.get());
|
|
283
|
+
ev.evaluate(uses.processedNodes, ctx, {}, false);
|
|
284
|
+
ASSERT_TRUE(ev.coverageResult.has_value());
|
|
285
|
+
std::map<int, std::vector<std::uint32_t>> libBodies;
|
|
286
|
+
for (const CoverageSpan& s : ev.coverageResult->spans) {
|
|
287
|
+
if (s.kind == CoverageKind::Body && fs::path(s.origin).filename() == "lib.scad") libBodies[s.line].push_back(s.hits);
|
|
288
|
+
}
|
|
289
|
+
ASSERT_EQ(libBodies[1].size(), 1u) << "one span per position, not one per parse";
|
|
290
|
+
EXPECT_EQ(libBodies[1][0], 2u);
|
|
291
|
+
ASSERT_EQ(libBodies[2].size(), 1u);
|
|
292
|
+
EXPECT_EQ(libBodies[2][0], 0u);
|
|
293
|
+
for (const CoverageFileSummary& f : ev.coverageResult->files) {
|
|
294
|
+
if (fs::path(f.origin).filename() == "lib.scad") {
|
|
295
|
+
EXPECT_EQ(f.bodies, 2u);
|
|
296
|
+
EXPECT_NEAR(f.body_percent(), 50.0, 1e-9);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
fs::remove_all(dir);
|
|
300
|
+
}
|
|
301
|
+
|
|
266
302
|
} // namespace oscadeval
|
{openscad_cpp_evaluator-1.27.0 → openscad_cpp_evaluator-1.28.0}/tests/test_dxf_svg_import.cpp
RENAMED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#include "openscad_cpp_evaluator/dxf_svg_import.hpp"
|
|
1
2
|
#include "openscad_cpp_evaluator/evaluator.hpp"
|
|
2
3
|
|
|
3
4
|
#include "test_helpers.hpp"
|
|
@@ -459,6 +460,14 @@ const char* kDimDxf =
|
|
|
459
460
|
// two parallel LINEs, which have no crossing point
|
|
460
461
|
"0\nLINE\n8\npar\n10\n0\n20\n0\n11\n10\n21\n0\n"
|
|
461
462
|
"0\nLINE\n8\npar\n10\n0\n20\n5\n11\n10\n21\n5\n"
|
|
463
|
+
// an outline: two LINEs joined end to end cross mid-way, but form one path
|
|
464
|
+
"0\nLINE\n8\noutline\n10\n0\n20\n0\n11\n10\n21\n10\n"
|
|
465
|
+
"0\nLINE\n8\noutline\n10\n10\n20\n10\n11\n0\n21\n10\n"
|
|
466
|
+
"0\nLINE\n8\noutline\n10\n0\n20\n10\n11\n10\n21\n0\n"
|
|
467
|
+
// a LINE closed by an ARC (centre (0,0), r 10, 0..90 degrees): a path too
|
|
468
|
+
"0\nLINE\n8\narc\n10\n10\n20\n0\n11\n0\n21\n10\n"
|
|
469
|
+
"0\nARC\n8\narc\n10\n0\n20\n0\n40\n10\n50\n0\n51\n90\n"
|
|
470
|
+
"0\nLINE\n8\narc\n10\n0\n20\n0\n11\n10\n21\n10\n"
|
|
462
471
|
"0\nENDSEC\n0\nEOF\n";
|
|
463
472
|
|
|
464
473
|
double dimOf(const std::string& call, Evaluator& ev) { return std::get<double>(asExpr(call, ev)); }
|
|
@@ -535,6 +544,22 @@ TEST(DxfCross, FindsTheCrossingPoint) {
|
|
|
535
544
|
std::filesystem::remove(path);
|
|
536
545
|
}
|
|
537
546
|
|
|
547
|
+
// The reference counts only 2-point paths: a LINE sharing an end with
|
|
548
|
+
// another LINE or an ARC belongs to an outline and is no stroke of a cross,
|
|
549
|
+
// even where two such lines do intersect. Checked against OpenSCAD
|
|
550
|
+
// 2026.02.01 on example009.dxf's fan_top layer.
|
|
551
|
+
TEST(DxfCross, LinesJoinedIntoAPathAreNoCross) {
|
|
552
|
+
const auto path = tempPath("dxfcross_paths.dxf");
|
|
553
|
+
writeFile(path, kDimDxf);
|
|
554
|
+
for (const char* layer : {"outline", "arc"}) {
|
|
555
|
+
std::string last;
|
|
556
|
+
Evaluator ev([&](const std::string& m) { last = m; });
|
|
557
|
+
EXPECT_TRUE(std::holds_alternative<std::monostate>(asExpr("dxf_cross(file=\"" + path.generic_string() + "\", layer=\"" + layer + "\")", ev)))
|
|
558
|
+
<< layer;
|
|
559
|
+
EXPECT_NE(last.find("Can't find cross"), std::string::npos) << layer << ": " << last;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
538
563
|
TEST(DxfCross, ParallelOrTooFewLinesWarns) {
|
|
539
564
|
const auto path = tempPath("cross_par.dxf");
|
|
540
565
|
writeFile(path, kDimDxf);
|
|
@@ -629,7 +654,8 @@ TEST(SvgFilter, AnEnclosingTransformStillApplies) {
|
|
|
629
654
|
ASSERT_EQ(pts.size(), 4u);
|
|
630
655
|
double minx = 1e9;
|
|
631
656
|
for (const auto& p : pts) minx = std::min(minx, std::get<double>(std::get<ListPtr>(p)->items[0]));
|
|
632
|
-
|
|
657
|
+
// x = 10 on a unitless 100-unit page, which OpenSCAD reads at 72 dpi.
|
|
658
|
+
EXPECT_NEAR(minx, 10.0 * 25.4 / 72.0, 1e-9);
|
|
633
659
|
std::filesystem::remove(path);
|
|
634
660
|
}
|
|
635
661
|
|
|
@@ -652,3 +678,74 @@ TEST(SvgFilter, IdAndClassTogetherMatchEither) {
|
|
|
652
678
|
EXPECT_EQ(contourCount(path.generic_string(), ", id=\"other\", class=\"cut\"", ev), 2u);
|
|
653
679
|
std::filesystem::remove(path);
|
|
654
680
|
}
|
|
681
|
+
|
|
682
|
+
namespace oscadeval {
|
|
683
|
+
namespace {
|
|
684
|
+
|
|
685
|
+
// SVG placement as OpenSCAD 2026.02.01 does it: page units to mm, the
|
|
686
|
+
// viewBox under preserveAspectRatio, Y flipped about the page height (or
|
|
687
|
+
// about the drawing's centre with center=true). Bounds read off the
|
|
688
|
+
// reference's own export for each document, with and without center.
|
|
689
|
+
struct SvgPlacementCase {
|
|
690
|
+
const char* attrs;
|
|
691
|
+
const char* body;
|
|
692
|
+
bool center;
|
|
693
|
+
double bounds[4];
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
TEST(SvgPlacement, MatchesTheReference) {
|
|
697
|
+
const SvgPlacementCase cases[] = {
|
|
698
|
+
{R"(width="100" height="100" viewBox="0 0 100 100")",
|
|
699
|
+
R"(<rect x="0" y="0" width="10" height="10"/><rect x="20" y="50" width="30" height="5"/>)", false,
|
|
700
|
+
{0.0, 15.875, 17.639, 35.278}},
|
|
701
|
+
{R"(width="100" height="100" viewBox="0 0 100 100")",
|
|
702
|
+
R"(<rect x="0" y="0" width="10" height="10"/><rect x="20" y="50" width="30" height="5"/>)", true,
|
|
703
|
+
{-8.819, -9.701, 8.819, 9.701}},
|
|
704
|
+
{R"(width="50mm" height="20mm" viewBox="10 5 200 40")",
|
|
705
|
+
R"(<rect x="10" y="5" width="20" height="10"/><rect x="92" y="17" width="16" height="16"/>)", false,
|
|
706
|
+
{0.0, 5.5, 24.5, 12.5}},
|
|
707
|
+
{R"(width="4in" height="2in" viewBox="0 0 100 100" preserveAspectRatio="xMaxYMin meet")",
|
|
708
|
+
R"(<rect x="0" y="0" width="100" height="100"/>)", false, {50.8, 0.0, 101.6, 50.8}},
|
|
709
|
+
{R"(width="4in" height="2in" viewBox="0 0 100 100" preserveAspectRatio="xMaxYMin meet")",
|
|
710
|
+
R"(<rect x="0" y="0" width="100" height="100"/>)", true, {-25.4, -25.4, 25.4, 25.4}},
|
|
711
|
+
{R"(width="300px" height="150px")", R"(<rect x="10" y="10" width="30" height="20"/>)", false,
|
|
712
|
+
{10.0, 9.688, 40.0, 29.688}},
|
|
713
|
+
{R"(viewBox="0 0 200 100")", R"(<rect x="0" y="0" width="200" height="100"/>)", false,
|
|
714
|
+
{0.0, 0.0, 70.556, 35.278}},
|
|
715
|
+
{R"(viewBox="0 0 200 100")", R"(<rect x="0" y="0" width="200" height="100"/>)", true,
|
|
716
|
+
{-35.278, -17.639, 35.278, 17.639}},
|
|
717
|
+
};
|
|
718
|
+
int n = 0;
|
|
719
|
+
for (const SvgPlacementCase& c : cases) {
|
|
720
|
+
const auto path = std::filesystem::temp_directory_path() / ("svg_place_" + std::to_string(n++) + ".svg");
|
|
721
|
+
std::ofstream(path) << "<svg xmlns=\"http://www.w3.org/2000/svg\" " << c.attrs << ">" << c.body << "</svg>";
|
|
722
|
+
const std::vector<Contour2d> contours = loadSvgContours(path.string(), {}, nullptr, 72.0, c.center);
|
|
723
|
+
double lo[2] = {1e18, 1e18}, hi[2] = {-1e18, -1e18};
|
|
724
|
+
for (const Contour2d& ct : contours)
|
|
725
|
+
for (const auto& p : ct)
|
|
726
|
+
for (int a = 0; a < 2; ++a) {
|
|
727
|
+
lo[a] = std::min(lo[a], p[a]);
|
|
728
|
+
hi[a] = std::max(hi[a], p[a]);
|
|
729
|
+
}
|
|
730
|
+
EXPECT_NEAR(lo[0], c.bounds[0], 1e-3) << c.attrs << " center=" << c.center;
|
|
731
|
+
EXPECT_NEAR(lo[1], c.bounds[1], 1e-3) << c.attrs << " center=" << c.center;
|
|
732
|
+
EXPECT_NEAR(hi[0], c.bounds[2], 1e-3) << c.attrs << " center=" << c.center;
|
|
733
|
+
EXPECT_NEAR(hi[1], c.bounds[3], 1e-3) << c.attrs << " center=" << c.center;
|
|
734
|
+
std::filesystem::remove(path);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
TEST(SvgPlacement, DpiScalesUnitlessLengths) {
|
|
739
|
+
const auto path = std::filesystem::temp_directory_path() / "svg_place_dpi.svg";
|
|
740
|
+
std::ofstream(path) << R"(<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">)"
|
|
741
|
+
<< R"(<rect x="0" y="0" width="10" height="10"/><rect x="20" y="50" width="30" height="5"/></svg>)";
|
|
742
|
+
const std::vector<Contour2d> contours = loadSvgContours(path.string(), {}, nullptr, 96.0, false);
|
|
743
|
+
double maxy = -1e18;
|
|
744
|
+
for (const Contour2d& ct : contours)
|
|
745
|
+
for (const auto& p : ct) maxy = std::max(maxy, p[1]);
|
|
746
|
+
EXPECT_NEAR(maxy, 26.458, 1e-3);
|
|
747
|
+
std::filesystem::remove(path);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
} // namespace
|
|
751
|
+
} // namespace oscadeval
|