| #ifdef WITH_RIVE_SCRIPTING | |
| #include "rive/math/vec2d.hpp" | |
| #include "rive/lua/rive_lua_libs.hpp" | |
| #include "rive/math/raw_path.hpp" | |
| #include "rive/math/contour_measure.hpp" | |
| #include "rive/math/path_measure.hpp" | |
| #include "rive/factory.hpp" | |
| #include <math.h> | |
| #include <stdio.h> | |
| using namespace rive; | |
| RenderPath* ScriptedPathData::renderPath(lua_State* L) | |
| { | |
| if (m_isRenderPathDirty) | |
| { | |
| m_isRenderPathDirty = false; | |
| const bool sameFrameRebuild = | |
| m_renderPath && m_renderFrameId == Artboard::frameId(); | |
| m_renderFrameId = Artboard::frameId(); | |
| ScriptingContext* context = | |
| static_cast<ScriptingContext*>(lua_getthreaddata(L)); | |
| if (!m_renderPath || sameFrameRebuild) | |
| { | |
| m_renderPath = context->factory()->makeEmptyRenderPath(); | |
| m_renderPath->fillRule(FillRule::clockwise); | |
| } | |
| else | |
| { | |
| m_renderPath->rewind(); | |
| } | |
| m_renderPath->addUntrustedRawPath(rawPath); | |
| } | |
| return m_renderPath.get(); | |
| } | |
| ScriptedPathData::ScriptedPathData(const RawPath* path) | |
| { | |
| rawPath.addPath(*path); | |
| } | |
| int ScriptedPathData::totalCommands() { return (int)rawPath.verbs().size(); } | |
| static ScriptedPath* lua_pushpath(lua_State* L) | |
| { | |
| return lua_newrive<ScriptedPath>(L); | |
| } | |
| static int path_new(lua_State* L) | |
| { | |
| lua_pushpath(L); | |
| return 1; | |
| } | |
| static int path_moveTo(lua_State* L) | |
| { | |
| auto scriptedPath = lua_torive<ScriptedPath>(L, 1); | |
| auto vec = lua_checkvec2d(L, 2); | |
| scriptedPath->rawPath.move(*vec); | |
| scriptedPath->markDirty(); | |
| return 0; | |
| } | |
| static int path_lineTo(lua_State* L) | |
| { | |
| auto scriptedPath = lua_torive<ScriptedPath>(L, 1); | |
| auto vec = lua_checkvec2d(L, 2); | |
| scriptedPath->rawPath.line(*vec); | |
| scriptedPath->markDirty(); | |
| return 0; | |
| } | |
| static int path_quadTo(lua_State* L) | |
| { | |
| auto scriptedPath = lua_torive<ScriptedPath>(L, 1); | |
| auto p1 = lua_checkvec2d(L, 2); | |
| auto p2 = lua_checkvec2d(L, 3); | |
| scriptedPath->rawPath.quad(*p1, *p2); | |
| scriptedPath->markDirty(); | |
| return 0; | |
| } | |
| static int path_cubicTo(lua_State* L) | |
| { | |
| auto scriptedPath = lua_torive<ScriptedPath>(L, 1); | |
| auto p1 = lua_checkvec2d(L, 2); | |
| auto p2 = lua_checkvec2d(L, 3); | |
| auto p3 = lua_checkvec2d(L, 4); | |
| scriptedPath->rawPath.cubic(*p1, *p2, *p3); | |
| scriptedPath->markDirty(); | |
| return 0; | |
| } | |
| static int path_close(lua_State* L) | |
| { | |
| auto scriptedPath = lua_torive<ScriptedPath>(L, 1); | |
| scriptedPath->rawPath.close(); | |
| scriptedPath->markDirty(); | |
| return 0; | |
| } | |
| static int path_reset(lua_State* L) | |
| { | |
| auto scriptedPath = lua_torive<ScriptedPath>(L, 1); | |
| scriptedPath->rawPath.reset(); | |
| scriptedPath->markDirty(); | |
| return 0; | |
| } | |
| static int path_add(lua_State* L) | |
| { | |
| auto scriptedPath = (ScriptedPathData*)lua_touserdata(L, 1); | |
| auto scriptedPathToAdd = (ScriptedPathData*)lua_touserdata(L, 2); | |
| const Mat2D* transform = nullptr; | |
| int nargs = lua_gettop(L); | |
| if (nargs == 3) | |
| { | |
| auto matrix = lua_torive<ScriptedMat2D>(L, 3); | |
| transform = &matrix->value; | |
| } | |
| scriptedPath->rawPath.addPath(scriptedPathToAdd->rawPath, transform); | |
| scriptedPath->markDirty(); | |
| return 0; | |
| } | |
| static int path_command(lua_State* L) | |
| { | |
| auto scriptedPath = (ScriptedPathData*)lua_touserdata(L, 1); | |
| auto rawPath = scriptedPath->rawPath; | |
| auto verbs = rawPath.verbs(); | |
| auto points = rawPath.points(); | |
| auto verbIndex = int(luaL_checknumber(L, 2)); | |
| auto verbName = "none"; | |
| std::vector<Vec2D> verbPoints; | |
| if (verbIndex >= 0 && verbIndex < (int)verbs.size()) | |
| { | |
| auto verb = verbs[verbIndex]; | |
| // Calculate the point index for this verb by summing point counts of | |
| // previous verbs | |
| int pointIndex = 0; | |
| for (int i = 0; i < verbIndex; i++) | |
| { | |
| pointIndex += path_verb_to_point_count(verbs[i]); | |
| } | |
| switch (verb) | |
| { | |
| case PathVerb::move: | |
| verbName = "moveTo"; | |
| if (pointIndex < (int)points.size()) | |
| { | |
| verbPoints.push_back(points[pointIndex]); | |
| } | |
| break; | |
| case PathVerb::line: | |
| verbName = "lineTo"; | |
| if (pointIndex < (int)points.size()) | |
| { | |
| verbPoints.push_back(points[pointIndex]); | |
| } | |
| break; | |
| case PathVerb::quad: | |
| verbName = "quadTo"; | |
| if (pointIndex + 1 < (int)points.size()) | |
| { | |
| verbPoints.push_back(points[pointIndex]); | |
| verbPoints.push_back(points[pointIndex + 1]); | |
| } | |
| break; | |
| case PathVerb::cubic: | |
| verbName = "cubicTo"; | |
| if (pointIndex + 2 < (int)points.size()) | |
| { | |
| verbPoints.push_back(points[pointIndex]); | |
| verbPoints.push_back(points[pointIndex + 1]); | |
| verbPoints.push_back(points[pointIndex + 2]); | |
| } | |
| break; | |
| case PathVerb::close: | |
| verbName = "close"; | |
| // close has no points | |
| break; | |
| } | |
| } | |
| lua_newrive<ScriptedPathCommand>(L, verbName, verbPoints); | |
| return 1; | |
| } | |
| static int path_contours(lua_State* L) | |
| { | |
| auto scriptedPath = (ScriptedPathData*)lua_touserdata(L, 1); | |
| // Use the copy constructor to ensure ContourMeasure outlives the path | |
| auto iter = make_rcp<RefCntContourMeasureIter>(scriptedPath->rawPath); | |
| auto firstContour = iter->get()->next(); | |
| if (firstContour) | |
| { | |
| lua_newrive<ScriptedContourMeasure>(L, firstContour, iter); | |
| return 1; | |
| } | |
| lua_pushnil(L); | |
| return 1; | |
| } | |
| static int path_measure(lua_State* L) | |
| { | |
| auto scriptedPath = (ScriptedPathData*)lua_touserdata(L, 1); | |
| PathMeasure pathMeasure(&scriptedPath->rawPath); | |
| lua_newrive<ScriptedPathMeasure>(L, std::move(pathMeasure)); | |
| return 1; | |
| } | |
| static int path_namecall(lua_State* L) | |
| { | |
| int atom; | |
| const char* str = lua_namecallatom(L, &atom); | |
| if (str != nullptr) | |
| { | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::moveTo: | |
| return path_moveTo(L); | |
| case (int)LuaAtoms::lineTo: | |
| return path_lineTo(L); | |
| case (int)LuaAtoms::quadTo: | |
| return path_quadTo(L); | |
| case (int)LuaAtoms::cubicTo: | |
| return path_cubicTo(L); | |
| case (int)LuaAtoms::close: | |
| return path_close(L); | |
| case (int)LuaAtoms::reset: | |
| return path_reset(L); | |
| case (int)LuaAtoms::add: | |
| return path_add(L); | |
| case (int)LuaAtoms::contours: | |
| return path_contours(L); | |
| case (int)LuaAtoms::measure: | |
| return path_measure(L); | |
| } | |
| } | |
| luaL_error(L, "%s is not a valid method of %s", str, ScriptedPath::luaName); | |
| return 0; | |
| } | |
| static void contour_measure_direct_length(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setnumber( | |
| result, | |
| ((ScriptedContourMeasure*)udata)->measure()->length()); | |
| } | |
| static void contour_measure_direct_isClosed(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setboolean( | |
| result, | |
| ((ScriptedContourMeasure*)udata)->measure()->isClosed() ? 1 : 0); | |
| } | |
| static void path_measure_direct_length(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setnumber( | |
| result, | |
| ((ScriptedPathMeasure*)udata)->measure()->length()); | |
| } | |
| static void path_measure_direct_isClosed(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setboolean( | |
| result, | |
| ((ScriptedPathMeasure*)udata)->measure()->isClosed() ? 1 : 0); | |
| } | |
| // ContourMeasure methods | |
| static int contour_measure_length(lua_State* L) | |
| { | |
| auto scripted = lua_torive<ScriptedContourMeasure>(L, 1); | |
| lua_pushnumber(L, scripted->measure()->length()); | |
| return 1; | |
| } | |
| static int contour_measure_isClosed(lua_State* L) | |
| { | |
| auto scripted = lua_torive<ScriptedContourMeasure>(L, 1); | |
| lua_pushboolean(L, scripted->measure()->isClosed()); | |
| return 1; | |
| } | |
| static int contour_measure_positionAndTangent(lua_State* L) | |
| { | |
| auto scripted = lua_torive<ScriptedContourMeasure>(L, 1); | |
| float distance = (float)luaL_checknumber(L, 2); | |
| auto posTan = scripted->measure()->getPosTan(distance); | |
| lua_pushvec2d(L, posTan.pos); | |
| lua_pushvec2d(L, posTan.tan); | |
| return 2; | |
| } | |
| static int contour_measure_warp(lua_State* L) | |
| { | |
| auto scripted = lua_torive<ScriptedContourMeasure>(L, 1); | |
| auto src = lua_checkvec2d(L, 2); | |
| Vec2D result = scripted->measure()->warp(*src); | |
| lua_pushvec2d(L, result); | |
| return 1; | |
| } | |
| static int contour_measure_extract(lua_State* L) | |
| { | |
| auto scripted = lua_torive<ScriptedContourMeasure>(L, 1); | |
| float startDistance = (float)luaL_checknumber(L, 2); | |
| float endDistance = (float)luaL_checknumber(L, 3); | |
| auto destPath = lua_torive<ScriptedPath>(L, 4); | |
| bool startWithMove = lua_isboolean(L, 5) ? lua_toboolean(L, 5) : true; | |
| scripted->measure()->getSegment(startDistance, | |
| endDistance, | |
| &destPath->rawPath, | |
| startWithMove); | |
| destPath->markDirty(); | |
| return 0; | |
| } | |
| static int contour_measure_next(lua_State* L) | |
| { | |
| auto scripted = lua_torive<ScriptedContourMeasure>(L, 1); | |
| auto iter = scripted->iter(); | |
| if (iter) | |
| { | |
| auto nextContour = iter->get()->next(); | |
| if (nextContour) | |
| { | |
| // Create new ScriptedContourMeasure with the same rcp iter | |
| // The iter is already advanced, so we can reuse it | |
| lua_newrive<ScriptedContourMeasure>(L, nextContour, iter); | |
| return 1; | |
| } | |
| } | |
| lua_pushnil(L); | |
| return 1; | |
| } | |
| static int path_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| // If it's not a string/atom, treat it as a numeric index | |
| if (!key) | |
| { | |
| int index = luaL_checkinteger(L, 2); | |
| // Convert from 1-based Lua index to 0-based C++ index | |
| // Replace the index at position 2 with the 0-based version | |
| lua_pushinteger(L, index - 1); // Push 0-based index | |
| lua_replace(L, 2); // Replace the value at position 2 | |
| // Now stack has: 1=path, 2=0-based index, which is what path_command | |
| // expects | |
| return path_command(L); | |
| } | |
| // String indices are handled by __namecall for methods | |
| return 0; | |
| } | |
| static int path_length(lua_State* L) | |
| { | |
| auto scriptedPath = (ScriptedPathData*)lua_touserdata(L, 1); | |
| auto totalCommands = scriptedPath->totalCommands(); | |
| lua_pushnumber(L, totalCommands); | |
| return 1; | |
| } | |
| static int contour_measure_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::length: | |
| return contour_measure_length(L); | |
| case (int)LuaAtoms::isClosed: | |
| return contour_measure_isClosed(L); | |
| case (int)LuaAtoms::next: | |
| return contour_measure_next(L); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static int contour_measure_namecall(lua_State* L) | |
| { | |
| int atom; | |
| const char* str = lua_namecallatom(L, &atom); | |
| if (str != nullptr) | |
| { | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::positionAndTangent: | |
| return contour_measure_positionAndTangent(L); | |
| case (int)LuaAtoms::warp: | |
| return contour_measure_warp(L); | |
| case (int)LuaAtoms::extract: | |
| return contour_measure_extract(L); | |
| } | |
| } | |
| luaL_error(L, | |
| "%s is not a valid method of %s", | |
| str, | |
| ScriptedContourMeasure::luaName); | |
| return 0; | |
| } | |
| // PathMeasure methods | |
| static int path_measure_length(lua_State* L) | |
| { | |
| auto scripted = lua_torive<ScriptedPathMeasure>(L, 1); | |
| lua_pushnumber(L, scripted->measure()->length()); | |
| return 1; | |
| } | |
| static int path_measure_isClosed(lua_State* L) | |
| { | |
| auto scripted = lua_torive<ScriptedPathMeasure>(L, 1); | |
| lua_pushboolean(L, scripted->measure()->isClosed()); | |
| return 1; | |
| } | |
| static int path_measure_positionAndTangent(lua_State* L) | |
| { | |
| auto scripted = lua_torive<ScriptedPathMeasure>(L, 1); | |
| float distance = (float)luaL_checknumber(L, 2); | |
| auto posTanDist = scripted->measure()->atDistance(distance); | |
| lua_pushvec2d(L, posTanDist.pos); | |
| lua_pushvec2d(L, posTanDist.tan); | |
| return 2; | |
| } | |
| static int path_measure_warp(lua_State* L) | |
| { | |
| auto scripted = lua_torive<ScriptedPathMeasure>(L, 1); | |
| auto src = lua_checkvec2d(L, 2); | |
| // Use atDistance to get position and tangent, then apply warp formula | |
| auto posTanDist = scripted->measure()->atDistance(src->x); | |
| Vec2D result = { | |
| posTanDist.pos.x - posTanDist.tan.y * src->y, | |
| posTanDist.pos.y + posTanDist.tan.x * src->y, | |
| }; | |
| lua_pushvec2d(L, result); | |
| return 1; | |
| } | |
| static int path_measure_extract(lua_State* L) | |
| { | |
| auto scripted = lua_torive<ScriptedPathMeasure>(L, 1); | |
| float startDistance = (float)luaL_checknumber(L, 2); | |
| float endDistance = (float)luaL_checknumber(L, 3); | |
| auto destPath = lua_torive<ScriptedPath>(L, 4); | |
| bool startWithMove = lua_isboolean(L, 5) ? lua_toboolean(L, 5) : true; | |
| scripted->measure()->getSegment(startDistance, | |
| endDistance, | |
| &destPath->rawPath, | |
| startWithMove); | |
| destPath->markDirty(); | |
| return 0; | |
| } | |
| static int path_measure_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::length: | |
| return path_measure_length(L); | |
| case (int)LuaAtoms::isClosed: | |
| return path_measure_isClosed(L); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static int path_measure_namecall(lua_State* L) | |
| { | |
| int atom; | |
| const char* str = lua_namecallatom(L, &atom); | |
| if (str != nullptr) | |
| { | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::positionAndTangent: | |
| return path_measure_positionAndTangent(L); | |
| case (int)LuaAtoms::warp: | |
| return path_measure_warp(L); | |
| case (int)LuaAtoms::extract: | |
| return path_measure_extract(L); | |
| } | |
| } | |
| luaL_error(L, | |
| "%s is not a valid method of %s", | |
| str, | |
| ScriptedPathMeasure::luaName); | |
| return 0; | |
| } | |
| static int property_namecall_atom(lua_State* L, | |
| ScriptedPathCommand* pathCommand, | |
| uint8_t tag, | |
| int atom, | |
| bool& error) | |
| { | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::type: | |
| { | |
| lua_pushstring(L, pathCommand->type().c_str()); | |
| return 1; | |
| } | |
| } | |
| error = true; | |
| return 0; | |
| } | |
| static int pathCommand_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| // If it's not a string/atom, treat it as a numeric index | |
| if (!key) | |
| { | |
| int luaIndex = luaL_checkinteger(L, 2); | |
| int index = luaIndex - 1; | |
| auto pathCommand = (ScriptedPathCommand*)lua_touserdata(L, 1); | |
| auto points = pathCommand->points(); | |
| auto size = (int)points.size(); | |
| if (index >= 0 && index < size) | |
| { | |
| auto point = points[index]; | |
| lua_pushvec2d(L, point); | |
| return 1; | |
| } | |
| } | |
| else | |
| { | |
| int atom; | |
| lua_tostringatom(L, 2, &atom); | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| auto tag = lua_userdatatag(L, 1); | |
| auto pathCommand = (ScriptedPathCommand*)lua_touserdata(L, 1); | |
| bool error = false; | |
| int stackChange = | |
| property_namecall_atom(L, pathCommand, tag, atom, error); | |
| if (!error) | |
| { | |
| return stackChange; | |
| } | |
| luaL_error(L, "'%s' is not a valid index of PathCommand", name); | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| static int pathCommand_length(lua_State* L) | |
| { | |
| auto pathCommand = (ScriptedPathCommand*)lua_touserdata(L, 1); | |
| auto points = pathCommand->points(); | |
| lua_pushnumber(L, (int)points.size()); | |
| return 1; | |
| } | |
| static int pathCommand_namecall(lua_State* L) | |
| { | |
| int atom; | |
| const char* str = lua_namecallatom(L, &atom); | |
| luaL_error(L, | |
| "%s is not a valid method of %s", | |
| str, | |
| ScriptedPathCommand::luaName); | |
| return 0; | |
| } | |
| static const luaL_Reg pathStaticMethods[] = { | |
| {"new", path_new}, | |
| {NULL, NULL}, | |
| }; | |
| int luaopen_rive_path(lua_State* L) | |
| { | |
| { | |
| lua_register_rive<ScriptedPathData>(L); | |
| lua_pushcfunction(L, path_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, path_length, nullptr); | |
| lua_setfield(L, -2, "__len"); | |
| lua_pushcfunction(L, path_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| } | |
| { | |
| luaL_register(L, ScriptedPath::luaName, pathStaticMethods); | |
| lua_register_rive<ScriptedPath>(L); | |
| lua_pushcfunction(L, path_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, path_length, nullptr); | |
| lua_setfield(L, -2, "__len"); | |
| lua_pushcfunction(L, path_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| } | |
| { | |
| lua_register_rive<ScriptedPathCommand>(L); | |
| lua_pushcfunction(L, pathCommand_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, pathCommand_length, nullptr); | |
| lua_setfield(L, -2, "__len"); | |
| lua_pushcfunction(L, pathCommand_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| } | |
| // Register ContourMeasure | |
| lua_register_rive<ScriptedContourMeasure>(L); | |
| lua_pushcfunction(L, contour_measure_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, contour_measure_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedContourMeasure::luaTag, | |
| "length", | |
| contour_measure_direct_length); | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedContourMeasure::luaTag, | |
| "isClosed", | |
| contour_measure_direct_isClosed); | |
| // Register PathMeasure | |
| lua_register_rive<ScriptedPathMeasure>(L); | |
| lua_pushcfunction(L, path_measure_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, path_measure_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedPathMeasure::luaTag, | |
| "length", | |
| path_measure_direct_length); | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedPathMeasure::luaTag, | |
| "isClosed", | |
| path_measure_direct_isClosed); | |
| return 1; | |
| } | |
| #endif |