diff --git a/libs/sftdlib/libsftd/include/sftd.h b/libs/sftdlib/libsftd/include/sftd.h index 54fe73a..9e3facc 100644 --- a/libs/sftdlib/libsftd/include/sftd.h +++ b/libs/sftdlib/libsftd/include/sftd.h @@ -105,6 +105,50 @@ void sftd_draw_wtext(sftd_font *font, int x, int y, unsigned int color, unsigned */ void sftd_draw_wtextf(sftd_font *font, int x, int y, unsigned int color, unsigned int size, const wchar_t *text, ...); +/** + * @brief Returns the width of the given text in pixels + * @param font the font used to calculate the width + * @param size the font size + * @param text a pointer to the text that will be used to calculate the length + */ +int sftd_get_text_width(sftd_font *font, unsigned int size, const char *text); + +/** + * @brief Draws text using a font. The text will wrap after the pixels specified in lineWidth. + * @param font the font to use + * @param x the x coordinate to draw the text to + * @param y the y coordinate to draw the text to + * @param color the color to draw the font + * @param size the font size + * @param lineWidth The length of one line before a line break accours. + * @param text a pointer to the text to draw + */ +void sftd_draw_text_wrap(sftd_font *font, int x, int y, unsigned int color, unsigned int size, unsigned int lineWidth, const char *text); + +/** + * @brief Calculates the bounding box of the text wih the given attributes. + * @param boundingWidth Pointer to the address where the width will be stored. + * @param boundingHeight Pointer to the address where the height will be stored. + * @param font the font to use + * @param size the font size + * @param lineWidth The length of one line before a line break accours. + * @param text a pointer to the text to draw + */ +void sftd_calc_bounding_box(int *boundingWidth, int *boundingHeight, sftd_font *font, unsigned int size, unsigned int lineWidth, const char *text); + +/** + * @brief Draws formatted text using a font. The text will wrap after the pixels specified in lineWidth. + * @param font the font to use + * @param x the x coordinate to draw the text to + * @param y the y coordinate to draw the text to + * @param color the color to draw the font + * @param size the font size + * @param lineWidth The length of one line before a line break accours. + * @param text a pointer to the text to draw + * @param ... variable arguments + */ +void sftd_draw_textf_wrap(sftd_font *font, int x, int y, unsigned int color, unsigned int size, unsigned int lineWidth, const char *text, ...); + // (ctruLua addition) Based on sftd_draw_wtext, returns the width of the text drawn. int sftd_width_wtext(sftd_font *font, unsigned int size, const wchar_t *text); diff --git a/libs/sftdlib/libsftd/source/sftd.c b/libs/sftdlib/libsftd/source/sftd.c index 5b98af4..f8f2b4f 100644 --- a/libs/sftdlib/libsftd/source/sftd.c +++ b/libs/sftdlib/libsftd/source/sftd.c @@ -4,6 +4,7 @@ #include #include #include +#include #include FT_CACHE_H #include FT_FREETYPE_H @@ -28,6 +29,7 @@ struct sftd_font { unsigned int buffer_size; }; }; + FTC_Manager ftcmanager; FTC_CMapCache cmapcache; FTC_ImageCache imagecache; texture_atlas *tex_atlas; @@ -65,20 +67,6 @@ int sftd_init() return 0; } - error = FTC_Manager_New( - ftlibrary, - 0, /* use default */ - 0, /* use default */ - 0, /* use default */ - &ftc_face_requester, /* use our requester */ - NULL, /* user data */ - &ftcmanager); - - if (error != FT_Err_Ok) { - FT_Done_FreeType(ftlibrary); - return 0; - } - sftd_initialized = 1; return 1; } @@ -92,15 +80,17 @@ int sftd_fini() return 0; } - FTC_Manager_Done(ftcmanager); - sftd_initialized = 0; return 1; } sftd_font *sftd_load_font_file(const char *filename) { + FT_Error error; + sftd_font *font = malloc(sizeof(*font)); + if (!font) + return NULL; size_t len = strlen(filename); @@ -108,8 +98,23 @@ sftd_font *sftd_load_font_file(const char *filename) strcpy(font->filename, filename); font->filename[len] = '\0'; - FTC_CMapCache_New(ftcmanager, &font->cmapcache); - FTC_ImageCache_New(ftcmanager, &font->imagecache); + error = FTC_Manager_New( + ftlibrary, + 0, /* use default */ + 0, /* use default */ + 0, /* use default */ + &ftc_face_requester, /* use our requester */ + NULL, /* user data */ + &font->ftcmanager); + + if (error != FT_Err_Ok) { + free(font->filename); + free(font); + return NULL; + } + + FTC_CMapCache_New(font->ftcmanager, &font->cmapcache); + FTC_ImageCache_New(font->ftcmanager, &font->imagecache); font->from = SFTD_LOAD_FROM_FILE; font->tex_atlas = texture_atlas_create(ATLAS_DEFAULT_W, ATLAS_DEFAULT_H, @@ -120,12 +125,31 @@ sftd_font *sftd_load_font_file(const char *filename) sftd_font *sftd_load_font_mem(const void *buffer, unsigned int size) { + FT_Error error; + sftd_font *font = malloc(sizeof(*font)); + if (!font) + return NULL; + font->font_buffer = buffer; font->buffer_size = size; - FTC_CMapCache_New(ftcmanager, &font->cmapcache); - FTC_ImageCache_New(ftcmanager, &font->imagecache); + error = FTC_Manager_New( + ftlibrary, + 0, /* use default */ + 0, /* use default */ + 0, /* use default */ + &ftc_face_requester, /* use our requester */ + NULL, /* user data */ + &font->ftcmanager); + + if (error != FT_Err_Ok) { + free(font); + return NULL; + } + + FTC_CMapCache_New(font->ftcmanager, &font->cmapcache); + FTC_ImageCache_New(font->ftcmanager, &font->imagecache); font->from = SFTD_LOAD_FROM_MEM; font->tex_atlas = texture_atlas_create(ATLAS_DEFAULT_W, ATLAS_DEFAULT_H, @@ -138,7 +162,8 @@ void sftd_free_font(sftd_font *font) { if (font) { FTC_FaceID face_id = (FTC_FaceID)font; - FTC_Manager_RemoveFaceID(ftcmanager, face_id); + FTC_Manager_RemoveFaceID(font->ftcmanager, face_id); + FTC_Manager_Done(font->ftcmanager); if (font->from == SFTD_LOAD_FROM_FILE) { free(font->filename); } @@ -183,7 +208,7 @@ void sftd_draw_text(sftd_font *font, int x, int y, unsigned int color, unsigned { FTC_FaceID face_id = (FTC_FaceID)font; FT_Face face; - FTC_Manager_LookupFace(ftcmanager, face_id, &face); + FTC_Manager_LookupFace(font->ftcmanager, face_id, &face); FT_Int charmap_index; charmap_index = FT_Get_Charmap_Index(face->charmap); @@ -245,6 +270,7 @@ void sftd_draw_text(sftd_font *font, int x, int y, unsigned int color, unsigned text++; } } + void sftd_draw_textf(sftd_font *font, int x, int y, unsigned int color, unsigned int size, const char *text, ...) { char buffer[256]; @@ -259,7 +285,7 @@ void sftd_draw_wtext(sftd_font *font, int x, int y, unsigned int color, unsigned { FTC_FaceID face_id = (FTC_FaceID)font; FT_Face face; - FTC_Manager_LookupFace(ftcmanager, face_id, &face); + FTC_Manager_LookupFace(font->ftcmanager, face_id, &face); FT_Int charmap_index; charmap_index = FT_Get_Charmap_Index(face->charmap); @@ -332,6 +358,260 @@ void sftd_draw_wtextf(sftd_font *font, int x, int y, unsigned int color, unsigne va_end(args); } +int sftd_get_text_width(sftd_font *font, unsigned int size, const char *text) +{ + FTC_FaceID face_id = (FTC_FaceID)font; + FT_Face face; + FTC_Manager_LookupFace(font->ftcmanager, face_id, &face); + + FT_Int charmap_index; + charmap_index = FT_Get_Charmap_Index(face->charmap); + + FT_Glyph glyph; + FT_Bool use_kerning = FT_HAS_KERNING(face); + FT_UInt glyph_index, previous = 0; + int pen_x = 0; + int pen_y = size; + + FTC_ScalerRec scaler; + scaler.face_id = face_id; + scaler.width = size; + scaler.height = size; + scaler.pixel = 1; + + FT_ULong flags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL; + + while (*text) { + glyph_index = FTC_CMapCache_Lookup(font->cmapcache, (FTC_FaceID)font, charmap_index, *text); + + if (use_kerning && previous && glyph_index) { + FT_Vector delta; + FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &delta); + pen_x += delta.x >> 6; + } + + if (!texture_atlas_exists(font->tex_atlas, glyph_index)) { + FTC_ImageCache_LookupScaler(font->imagecache, &scaler, flags, glyph_index, &glyph, NULL); + + if (!atlas_add_glyph(font->tex_atlas, glyph_index, (FT_BitmapGlyph)glyph, size)) { + continue; + } + } + + bp2d_rectangle rect; + int bitmap_left, bitmap_top; + int advance_x, advance_y; + int glyph_size; + + texture_atlas_get(font->tex_atlas, glyph_index, + &rect, &bitmap_left, &bitmap_top, + &advance_x, &advance_y, &glyph_size); + + const float draw_scale = size/(float)glyph_size; + + pen_x += (advance_x >> 16) * draw_scale; + pen_y += (advance_y >> 16) * draw_scale; + + previous = glyph_index; + text++; + } + return pen_x; +} + +void sftd_draw_text_wrap(sftd_font *font, int x, int y, unsigned int color, unsigned int size, unsigned int lineWidth, const char *text) +{ + FTC_FaceID face_id = (FTC_FaceID)font; + FT_Face face; + FTC_Manager_LookupFace(font->ftcmanager, face_id, &face); + + FT_Int charmap_index; + charmap_index = FT_Get_Charmap_Index(face->charmap); + + FT_Glyph glyph; + FT_Bool use_kerning = FT_HAS_KERNING(face); + FT_UInt glyph_index, previous = 0; + int pen_x = x; + int pen_y = y + size; + + FTC_ScalerRec scaler; + scaler.face_id = face_id; + scaler.width = size; + scaler.height = size; + scaler.pixel = 1; + + FT_ULong flags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL; + + bool isFirstLine = true; + char buffer[strlen(text)]; + sprintf(buffer, text); + char *currentWord; + int currentWordLength; + int currentCharIndex; + + currentWord = strtok(buffer, " "); + while (currentWord) { + currentWordLength = strlen(currentWord); + if(pen_x + sftd_get_text_width(font, size, currentWord) >= lineWidth && !isFirstLine) { + pen_x = x; + pen_y += size; + } + isFirstLine = false; + for(currentCharIndex = 0; currentCharIndex < currentWordLength + 1; currentCharIndex++) { + if(currentCharIndex < currentWordLength) { + glyph_index = FTC_CMapCache_Lookup(font->cmapcache, (FTC_FaceID)font, charmap_index, currentWord[currentCharIndex]); + } + else { + glyph_index = FTC_CMapCache_Lookup(font->cmapcache, (FTC_FaceID)font, charmap_index, ' '); + } + + // TODO get word size and linewrap if needed + + if (use_kerning && previous && glyph_index) { + FT_Vector delta; + FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &delta); + pen_x += delta.x >> 6; + } + + if (!texture_atlas_exists(font->tex_atlas, glyph_index)) { + FTC_ImageCache_LookupScaler(font->imagecache, &scaler, flags, glyph_index, &glyph, NULL); + + if (!atlas_add_glyph(font->tex_atlas, glyph_index, (FT_BitmapGlyph)glyph, size)) { + continue; + } + } + + bp2d_rectangle rect; + int bitmap_left, bitmap_top; + int advance_x, advance_y; + int glyph_size; + + texture_atlas_get(font->tex_atlas, glyph_index, + &rect, &bitmap_left, &bitmap_top, + &advance_x, &advance_y, &glyph_size); + + const float draw_scale = size/(float)glyph_size; + + sf2d_draw_texture_part_scale_blend(font->tex_atlas->tex, + pen_x + bitmap_left * draw_scale, + pen_y - bitmap_top * draw_scale, + rect.x, rect.y, rect.w, rect.h, + draw_scale, + draw_scale, + color); + + pen_x += (advance_x >> 16) * draw_scale; + pen_y += (advance_y >> 16) * draw_scale; + + + previous = glyph_index; + + + } + currentWord = strtok(NULL, " "); + } +} + +void sftd_calc_bounding_box(int *boundingWidth, int *boundingHeight, sftd_font *font, unsigned int size, unsigned int lineWidth, const char *text) +{ + FTC_FaceID face_id = (FTC_FaceID)font; + FT_Face face; + FTC_Manager_LookupFace(font->ftcmanager, face_id, &face); + + FT_Int charmap_index; + charmap_index = FT_Get_Charmap_Index(face->charmap); + + FT_Glyph glyph; + FT_Bool use_kerning = FT_HAS_KERNING(face); + FT_UInt glyph_index, previous = 0; + int pen_x = 0; + int pen_y = size; + + FTC_ScalerRec scaler; + scaler.face_id = face_id; + scaler.width = size; + scaler.height = size; + scaler.pixel = 1; + + FT_ULong flags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL; + + bool isFirstLine = true; + char buffer[strlen(text)]; + sprintf(buffer, text); + char *currentWord; + int currentWordLength; + int currentCharIndex; + int greatesLineWidth = 0; + + currentWord = strtok(buffer, " "); + while (currentWord) { + currentWordLength = strlen(currentWord); + if(pen_x + sftd_get_text_width(font, size, currentWord) >= lineWidth && !isFirstLine) { + if(pen_x > greatesLineWidth) { + greatesLineWidth = pen_x; + } + pen_x = 0; + pen_y += size; + } + isFirstLine = false; + for(currentCharIndex = 0; currentCharIndex < currentWordLength + 1; currentCharIndex++) { + if(currentCharIndex < currentWordLength) { + glyph_index = FTC_CMapCache_Lookup(font->cmapcache, (FTC_FaceID)font, charmap_index, currentWord[currentCharIndex]); + } + else { + glyph_index = FTC_CMapCache_Lookup(font->cmapcache, (FTC_FaceID)font, charmap_index, ' '); + } + + // TODO get word size and linewrap if needed + + if (use_kerning && previous && glyph_index) { + FT_Vector delta; + FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &delta); + pen_x += delta.x >> 6; + } + + if (!texture_atlas_exists(font->tex_atlas, glyph_index)) { + FTC_ImageCache_LookupScaler(font->imagecache, &scaler, flags, glyph_index, &glyph, NULL); + + if (!atlas_add_glyph(font->tex_atlas, glyph_index, (FT_BitmapGlyph)glyph, size)) { + continue; + } + } + + bp2d_rectangle rect; + int bitmap_left, bitmap_top; + int advance_x, advance_y; + int glyph_size; + + texture_atlas_get(font->tex_atlas, glyph_index, + &rect, &bitmap_left, &bitmap_top, + &advance_x, &advance_y, &glyph_size); + + const float draw_scale = size/(float)glyph_size; + + pen_x += (advance_x >> 16) * draw_scale; + pen_y += (advance_y >> 16) * draw_scale; + + + previous = glyph_index; + + + } + currentWord = strtok(NULL, " "); + } + *boundingWidth = greatesLineWidth; + *boundingHeight = pen_y; +} + +void sftd_draw_textf_wrap(sftd_font *font, int x, int y, unsigned int color, unsigned int size, unsigned int lineWidth, const char *text, ...) +{ + char buffer[256]; + va_list args; + va_start(args, text); + vsnprintf(buffer, 256, text, args); + sftd_draw_text_wrap(font, x, y, color, size, lineWidth, buffer); + va_end(args); +} + // (ctruLua addition) Based on sftd_draw_wtext, returns the width of the text drawn. int sftd_width_wtext(sftd_font *font, unsigned int size, const wchar_t *text) { @@ -390,4 +670,4 @@ int sftd_width_wtext(sftd_font *font, unsigned int size, const wchar_t *text) } return pen_x; -} \ No newline at end of file +} diff --git a/libs/sftdlib/sample/source/main.c b/libs/sftdlib/sample/source/main.c index c402119..6558a8e 100644 --- a/libs/sftdlib/sample/source/main.c +++ b/libs/sftdlib/sample/source/main.c @@ -16,6 +16,10 @@ int main() sftd_init(); sftd_font *font = sftd_load_font_mem(FreeSans_ttf, FreeSans_ttf_size); + const char *someText = "Font drawing on the top screen! Text wraps after 300 pixels... Lorem ipsum dolor sit amet, consetetur sadipscing elit."; + int textWidth = 0; + int textHeight = 0; + while (aptMainLoop()) { hidScanInput(); @@ -24,18 +28,9 @@ int main() sf2d_start_frame(GFX_TOP, GFX_LEFT); sftd_draw_textf(font, 10, 10, RGBA8(0, 255, 0, 255), 20, "FPS %f", sf2d_get_fps()); - - sftd_draw_text(font, 10, 30, RGBA8(255, 0, 0, 255), 20, "Font drawing on the top screen!"); - sftd_draw_text(font, 10, 50, RGBA8(0, 255, 0, 255), 15, "Font drawing on the top screen!"); - sftd_draw_text(font, 10, 68, RGBA8(0, 0, 255, 255), 25, "Font drawing on the top screen!"); - sftd_draw_text(font, 10, 90, RGBA8(255, 255, 0, 255), 10, "Font drawing on the top screen!"); - sftd_draw_text(font, 10, 105, RGBA8(255, 0, 255, 255), 8, "Font drawing on the top screen!"); - sftd_draw_text(font, 10, 120, RGBA8(0, 255, 255, 255), 30, "Font drawing on the top screen!"); - - sftd_draw_text(font, 10, 155, RGBA8(255, 0, 0, 255), 20, "Font drawing on the top screen!"); - sftd_draw_text(font, 10, 170, RGBA8(0, 255, 0, 255), 2, "Font drawing on the top screen!"); - sftd_draw_text(font, 10, 180, RGBA8(0, 0, 255, 255), 10, "Font drawing on the top screen!"); - sftd_draw_text(font, 10, 205, RGBA8(255, 255, 0, 255), 170, "Font drawing on the top screen!"); + sftd_calc_bounding_box(&textWidth, &textHeight, font, 20, 300, someText); + sf2d_draw_rectangle(10, 40, textWidth, textHeight, RGBA8(0, 100, 0, 255)); + sftd_draw_text_wrap(font, 10, 40, RGBA8(255, 255, 255, 255), 20, 300, someText); sf2d_end_frame(); diff --git a/source/gfx.c b/source/gfx.c index be1310a..821fe02 100644 --- a/source/gfx.c +++ b/source/gfx.c @@ -121,6 +121,19 @@ static int gfx_get3D(lua_State *L) { return 1; } +/*** +Enable or disable the VBlank waiting. +@function setVBlankWait +@tparam boolean true to enable, false to disable +*/ +static int gfx_setVBlankWait(lua_State *L) { + bool enable = lua_toboolean(L, 1); + + sf2d_set_vblank_wait(enable); + + return 0; +} + /*** Get free VRAM space. @function vramSpaceFree @@ -255,20 +268,94 @@ static int gfx_text(lua_State *L) { return 0; } +/*** +Draw a text with a new line after a certain number of pixels. +Warning: No UTF32 support. +@function wrappedText +@tparam integer x text drawing origin horizontal coordinate, in pixels +@tparam integer y text drawing origin vertical coordinate, in pixels +@tparam string text the text to draw +@tparam integer width width of a line, in pixels +@tparam[opt=9] integer size drawing size, in pixels +@tparam[opt=default color] integer color drawing color +@tparam[opt=default font] font font to use +*/ +static int gfx_wrappedText(lua_State *L) { + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + size_t len; + const char *text = luaL_checklstring(L, 3, &len); + unsigned int lineWidth = luaL_checkinteger(L, 4); + + int size = luaL_optinteger(L, 5, 9); + u32 color = luaL_optinteger(L, 6, color_default); + font_userdata *font = luaL_testudata(L, 7, "LFont"); + if (font == NULL) { + lua_getfield(L, LUA_REGISTRYINDEX, "LFontDefault"); + font = luaL_testudata(L, -1, "LFont"); + if (font == NULL) luaL_error(L, "No default font set and no font object passed"); + } + if (font->font == NULL) luaL_error(L, "The font object was unloaded"); + + // Wide caracters support. (wchar = UTF32 on 3DS.) + // Disabled as sftd_draw_wtext_wrap() doesn't exist. + /*wchar_t wtext[len]; + len = mbstowcs(wtext, text, len); + *(wtext+len) = 0x0; // text end */ + + sftd_draw_text_wrap(font->font, x, y, color, size, lineWidth, text); + + return 0; +} + +/*** +Calculate the size of a text draw with `wrappedText`. +@function calcBoundingBox +@tparam string text The text to check +@tparam integer lineWidth width of a line, in pixels +@tparam[opt=9] integer size drawing size, in pixels +@tparam[opt=default font] font font to use +@treturn integer width of the text, in pixels +@treturn integer height of the text, in pixels +*/ +static int gfx_calcBoundingBox(lua_State *L) { + size_t len; + const char *text = luaL_checklstring(L, 1, &len); + unsigned int lineWidth = luaL_checkinteger(L, 2); + int size = luaL_optinteger(L, 3, 9); + font_userdata *font = luaL_testudata(L, 4, "LFont"); + if (font == NULL) { + lua_getfield(L, LUA_REGISTRYINDEX, "LFontDefault"); + font = luaL_testudata(L, -1, "LFont"); + if (font == NULL) luaL_error(L, "No default font set and no font object passed"); + } + if (font->font == NULL) luaL_error(L, "The font object was unloaded"); + + int w, h = 0; + sftd_calc_bounding_box(&w, &h, font->font, size, lineWidth, text); + + lua_pushinteger(L, w); + lua_pushinteger(L, h); + return 2; +} + // Functions static const struct luaL_Reg gfx_lib[] = { - { "startFrame", gfx_startFrame }, - { "endFrame", gfx_endFrame }, - { "render", gfx_render }, - { "getFPS", gfx_getFPS }, - { "set3D", gfx_set3D }, - { "get3D", gfx_get3D }, - { "vramSpaceFree", gfx_vramSpaceFree }, - { "line", gfx_line }, - { "point", gfx_point }, - { "rectangle", gfx_rectangle }, - { "circle", gfx_circle }, - { "text", gfx_text }, + { "startFrame", gfx_startFrame }, + { "endFrame", gfx_endFrame }, + { "render", gfx_render }, + { "getFPS", gfx_getFPS }, + { "set3D", gfx_set3D }, + { "get3D", gfx_get3D }, + { "setVBlankWait", gfx_setVBlankWait }, + { "vramSpaceFree", gfx_vramSpaceFree }, + { "line", gfx_line }, + { "point", gfx_point }, + { "rectangle", gfx_rectangle }, + { "circle", gfx_circle }, + { "text", gfx_text }, + { "wrappedText", gfx_wrappedText }, + { "calcBoundingBox", gfx_calcBoundingBox }, { NULL, NULL } }; diff --git a/source/texture.c b/source/texture.c index a9da11f..a369737 100644 --- a/source/texture.c +++ b/source/texture.c @@ -76,6 +76,35 @@ static int texture_load(lua_State *L) { return 1; } +/*** +Create an empty texture. +@function new +@tparam number width Texture width +@tparam number height Texture height +@tparam[opt=PLACE_RAM] number place where to put the loaded texture +@treturn texture the loaded texture object +*/ +static int texture_new(lua_State *L) { + int w = luaL_checkinteger(L, 1); + int h = luaL_checkinteger(L, 2); + u8 place = luaL_checkinteger(L, 3); + + texture_userdata *texture; + texture = (texture_userdata *)lua_newuserdata(L, sizeof(*texture)); + + luaL_getmetatable(L, "LTexture"); + lua_setmetatable(L, -2); + + texture->texture = sf2d_create_texture(w, h, TEXFMT_RGBA8, place); + sf2d_texture_tile32(texture->texture); + + texture->scaleX = 1.0f; + texture->scaleY = 1.0f; + texture->blendColor = 0xffffffff; + + return 1; +} + /*** Texture object @section Methods @@ -242,6 +271,7 @@ static const struct luaL_Reg texture_methods[] = { // module static const struct luaL_Reg texture_functions[] = { {"load", texture_load}, + {"new", texture_new }, {NULL, NULL} };