Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"

[project]
name = "openscad_cpp_evaluator"
version = "1.20.2"
version = "1.20.3"
description = "C++ OpenSCAD evaluator with Python bindings"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
10 changes: 10 additions & 0 deletions src/builtins/text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ CSGParams resolveText(Evaluator& ev, const oscad::ModularCall& node, EvalContext
}

CSGParams params;
// The handle is what generateText looks the face up by, but it is an
// index into THIS evaluator's FontProvider -- the first font resolved
// is handle 0 whatever it is. Two evaluators sharing a ManifoldCache
// (two tabs in a GUI, say) therefore agreed on "font_handle=0" for
// entirely different fonts, and the second render was served the
// first one's glyphs. The spec goes in the params too, so the cache
// key tells them apart: the same spec always resolves to the same
// face, and two specs that happen to resolve alike merely miss the
// cache rather than collide.
params["font_spec"] = Value{fontSpec};
params["font_handle"] = Value{static_cast<double>(handle)};
params["scale"] = Value{scale};
params["segs"] = Value{static_cast<double>(segs)};
Expand Down
22 changes: 22 additions & 0 deletions tests/test_manifold_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,28 @@ TEST(ManifoldCacheKey, BoolAndNumberParamsProduceDifferentCacheKeys) {
EXPECT_NE(cacheKey(a), cacheKey(b));
}

TEST(ManifoldCacheKey, TwoFontsWithTheSameHandleDoNotShareAKey) {
// A FontHandle indexes the EVALUATOR's own FontProvider, so the first
// font any evaluator resolves is handle 0 whatever it is. Two
// evaluators sharing one ManifoldCache -- two tabs in a GUI -- agreed
// on "font_handle=0" for entirely different fonts, and the second was
// served the first one's glyphs. text() puts the SPEC in the params
// too, so the key tells them apart.
CSGNode a;
a.kind = "text";
a.isBuiltin = true;
a.params["font_handle"] = Value{0.0};
a.params["font_spec"] = Value{std::string("Arial:style=Regular")};

CSGNode b;
b.kind = "text";
b.isBuiltin = true;
b.params["font_handle"] = Value{0.0}; // same handle, different font
b.params["font_spec"] = Value{std::string("Arial:style=Bold")};

EXPECT_NE(cacheKey(a), cacheKey(b));
}

TEST(ManifoldCacheKey, IdenticalStructureProducesIdenticalKeyRegardlessOfParamInsertionOrder) {
CSGNode a;
a.kind = "cube";
Expand Down
27 changes: 27 additions & 0 deletions tests/test_text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,30 @@ TEST(TextArgs, FontIsPositionalButTheRestAreNot) {
// NOTHING, and halign by name does move it.
EXPECT_EQ(echoes[0], "ECHO: true, false, true, false");
}

namespace oscadeval {

// Why text() must put the font SPEC in its CSG params and not just the
// handle: a handle numbers faces WITHIN one provider, and every Evaluator
// makes its own. Two evaluators sharing a ManifoldCache -- two GUI tabs --
// hand the cache the same number for different fonts (BelfrySCAD#434).
TEST(FontHandles, AreOnlyMeaningfulWithinOneProvider) {
FreetypeFontProvider a, b;
const FontHandle aReg = a.resolveFont("Arial:style=Regular");
const FontHandle aBold = a.resolveFont("Arial:style=Bold");
if (aReg == aBold) {
GTEST_SKIP() << "this machine resolves both specs to one face";
}
EXPECT_NE(aReg, aBold) << "within one provider, two fonts get two handles";
// What a second provider numbers them is NOT asserted: it depends on
// which faces that machine has and the order they are opened in. On
// macOS a fresh provider resolving Bold first returns 4 -- the same
// number the first provider gave REGULAR, which is the collision this
// whole change is about. On a Linux runner with only the bundled faces
// the numbering happens to line up instead (0 and 1 either way). That
// it CAN collide is the point, and it is not a property to pin to a
// number here; the end-to-end regression test lives in BelfrySCAD,
// where the collision reproduces in both directions.
}

} // namespace oscadeval