From 04b3fcb479f5aaae06d18b315a8bdc8c298f4eae Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 5 Aug 2025 11:28:41 +0200 Subject: removed clustering --- src/glamac/glamac.c | 140 ++++++++++++---- src/glamac/glamac.d | 10 ++ src/glamac/glamac_errors.c | 6 +- src/glamac/glamac_errors.d | 3 + src/glamac/glamac_events.c | 142 +++++++++++----- src/glamac/glamac_events.d | 10 ++ src/glamac/glamac_render.c | 364 +++++++++++++++++++++++++++++------------ src/glamac/glamac_render.d | 9 ++ src/glamac/glamac_view.c | 393 +-------------------------------------------- src/glamac/glamac_view.d | 8 + src/glamac/glass_data.c | 280 +++++++++++++++++++++++++++++--- src/glamac/glass_data.d | 7 + src/glautils/fgla.c | 88 +++++----- src/glautils/fgla.d | 7 + 14 files changed, 842 insertions(+), 625 deletions(-) create mode 100644 src/glamac/glamac.d create mode 100644 src/glamac/glamac_errors.d create mode 100644 src/glamac/glamac_events.d create mode 100644 src/glamac/glamac_render.d create mode 100644 src/glamac/glamac_view.d create mode 100644 src/glamac/glass_data.d create mode 100644 src/glautils/fgla.d (limited to 'src') diff --git a/src/glamac/glamac.c b/src/glamac/glamac.c index dba2fa1..dd06626 100644 --- a/src/glamac/glamac.c +++ b/src/glamac/glamac.c @@ -5,39 +5,121 @@ #include #include #include -#include "glamacdef.h" // Type definitions -#include "glass_data.h" // Glass catalog -#include "glamac_view.h" // View management -#include "glamac_render.h" // Rendering +#include "glamac/core/glamacdef.h" // Type definitions +#include "glamac/data/glass_data.h" // Glass catalog +#include "glamac/graphics/glamac_view.h" // View management +#include "glamac/graphics/glamac_render.h" // Rendering // External function declarations from glamac_events.c extern b32 process_events(SDL_Event *event, ViewState *view, SDL_Window *window, i32 *lastMouseX, i32 *lastMouseY, b32 *dragging, b32 *quit); -// Function to reload fonts on window resize +// Function to reload fonts on window resize with improved error handling and hysteresis b32 reload_fonts_if_needed(FontSet *fonts, ViewState *view, f32 dpi) { static i32 lastWidth = 0; static i32 lastHeight = 0; + static u32 lastReloadTime = 0; + static i32 pendingWidth = 0; + static i32 pendingHeight = 0; - // Check if window size changed significantly (more than 50% change) - if (abs(view->windowWidth - lastWidth) > view->windowWidth * 0.5 || - abs(view->windowHeight - lastHeight) > view->windowHeight * 0.5) { - - // Clear text cache first - clear_text_cache(); - - // Free existing fonts - free_fonts(fonts); + // Validate inputs + if (!fonts || !view) { + printf("Error: Invalid parameters for font reload\n"); + return 0; + } + + // Validate window dimensions + if (view->windowWidth <= 0 || view->windowHeight <= 0) { + printf("Error: Invalid window dimensions for font reload: %dx%d\n", + view->windowWidth, view->windowHeight); + return 0; + } + + // Hysteresis parameters + const f32 threshold = 0.25f; // 25% change threshold + const u32 minReloadInterval = 500; // Minimum 500ms between font reloads + const u32 maxPendingTime = 2000; // Maximum time to wait for pending reload + + u32 currentTime = SDL_GetTicks(); + + // Check if window size changed significantly + i32 widthDiff = abs(view->windowWidth - lastWidth); + i32 heightDiff = abs(view->windowHeight - lastHeight); + b32 significantChange = (widthDiff > (i32)(view->windowWidth * threshold) || + heightDiff > (i32)(view->windowHeight * threshold)); + + if (significantChange) { + // Update pending dimensions + pendingWidth = view->windowWidth; + pendingHeight = view->windowHeight; - // Reload with new size - if (!load_adaptive_fonts(fonts, view->windowWidth, view->windowHeight, dpi)) { - printf("Failed to reload fonts after window resize!\n"); - return 0; + // Check if enough time has passed since last reload + if (currentTime - lastReloadTime >= minReloadInterval) { + printf("Window size changed significantly: %dx%d -> %dx%d, reloading fonts...\n", + lastWidth, lastHeight, view->windowWidth, view->windowHeight); + + // Clear text cache first + clear_text_cache(); + + // Store current fonts as backup + FontSet backup_fonts = *fonts; + + // Try to reload with new size + if (!load_adaptive_fonts(fonts, view->windowWidth, view->windowHeight, dpi)) { + printf("Error: Failed to reload fonts after window resize, restoring backup\n"); + + // Restore backup fonts + *fonts = backup_fonts; + + // If backup is also invalid, try loading basic fonts + if (!fonts->regular || !fonts->title || !fonts->label) { + printf("Warning: Backup fonts invalid, attempting basic font loading\n"); + if (!load_fonts(fonts)) { + printf("Critical error: All font loading attempts failed\n"); + return 0; + } + } + + // Don't update lastWidth/lastHeight on failure to trigger retry later + return 1; // Continue running with backup fonts + } + + // Free backup fonts if new loading succeeded + if (backup_fonts.regular != fonts->regular && backup_fonts.regular) { + TTF_CloseFont(backup_fonts.regular); + } + if (backup_fonts.title != fonts->title && backup_fonts.title) { + TTF_CloseFont(backup_fonts.title); + } + if (backup_fonts.label != fonts->label && backup_fonts.label) { + TTF_CloseFont(backup_fonts.label); + } + + // Update state after successful reload + lastWidth = view->windowWidth; + lastHeight = view->windowHeight; + lastReloadTime = currentTime; + pendingWidth = pendingHeight = 0; // Clear pending state + + printf("Fonts successfully reloaded for window size: %dx%d\n", + view->windowWidth, view->windowHeight); + } else { + // Can't reload yet due to rate limiting, check if we should force it + if (pendingWidth != 0 && pendingHeight != 0 && + currentTime - lastReloadTime >= maxPendingTime) { + printf("Forcing delayed font reload after %ums\n", maxPendingTime); + // Recursive call will now pass the time check + return reload_fonts_if_needed(fonts, view, dpi); + } + printf("Font reload rate limited, pending: %dx%d\n", pendingWidth, pendingHeight); } - - lastWidth = view->windowWidth; - lastHeight = view->windowHeight; - printf("Fonts reloaded for new window size: %dx%d\n", view->windowWidth, view->windowHeight); + } else if (pendingWidth != 0 && pendingHeight != 0 && + currentTime - lastReloadTime >= minReloadInterval) { + // Handle pending resize that wasn't significant enough initially + // but has been waiting for the rate limit + view->windowWidth = pendingWidth; + view->windowHeight = pendingHeight; + return reload_fonts_if_needed(fonts, view, dpi); } return 1; @@ -136,11 +218,7 @@ int main(int argc, char* argv[]) { printf("Loaded %u glasses from %u catalogs\n", get_glass_count(), get_catalog_count()); - // Create tight clusters for the initial catalog - create_tight_clusters(&view); - // Create loose clusters for the initial catalog - create_loose_clusters(&view); // Main loop flag @@ -165,7 +243,13 @@ int main(int argc, char* argv[]) { // Reload fonts if window size changed significantly if (!reload_fonts_if_needed(&fonts, &view, dpi)) { - quit = 1; // Exit if font reloading fails + printf("Warning: Font reloading failed, continuing with current fonts\n"); + // Don't quit immediately - try to continue with current fonts + // Only quit if fonts are completely invalid + if (!fonts.regular || !fonts.title || !fonts.label) { + printf("Critical error: No valid fonts available, exiting\n"); + quit = 1; + } } // Render everything @@ -176,8 +260,6 @@ int main(int argc, char* argv[]) { SDL_StopTextInput(window); // Clean up resources - free_tight_clusters(&view); - free_loose_clusters(&view); cleanup_glass_data(); free_fonts(&fonts); clear_text_cache(); diff --git a/src/glamac/glamac.d b/src/glamac/glamac.d new file mode 100644 index 0000000..596c63f --- /dev/null +++ b/src/glamac/glamac.d @@ -0,0 +1,10 @@ +glamac.o glamac.d: ../src/glamac/glamac.c ../include/glamac/core/glamacdef.h \ + ../include/glamac/data/glass_data.h \ + ../include/glamac/data/../core/glamacdef.h \ + ../include/glamac/data/../core/glamac_errors.h \ + ../include/glamac/data/../core/glamacdef.h \ + ../include/glamac/data/../core/security.h \ + ../include/glamac/graphics/glamac_view.h \ + ../include/glamac/graphics/../core/glamacdef.h \ + ../include/glamac/graphics/glamac_render.h \ + ../include/glamac/graphics/glamac_view.h diff --git a/src/glamac/glamac_errors.c b/src/glamac/glamac_errors.c index d32d369..854404d 100644 --- a/src/glamac/glamac_errors.c +++ b/src/glamac/glamac_errors.c @@ -1,7 +1,7 @@ /** * glamac_errors.c - Unified error handling implementation */ -#include "glamac_errors.h" +#include "glamac/core/glamac_errors.h" const char* glamac_error_string(GlamacResult error) { switch (error) { @@ -25,6 +25,10 @@ const char* glamac_error_string(GlamacResult error) { return "No glasses found for manufacturer"; case GLAMAC_ERROR_INVALID_ARGUMENT: return "Invalid argument provided"; + case GLAMAC_ERROR_INVALID_GLASS_DATA: + return "Invalid glass data - failed validation checks"; + case GLAMAC_ERROR_INVALID_MANUFACTURER: + return "Invalid manufacturer name or data"; default: return "Unknown error"; } diff --git a/src/glamac/glamac_errors.d b/src/glamac/glamac_errors.d new file mode 100644 index 0000000..04a5576 --- /dev/null +++ b/src/glamac/glamac_errors.d @@ -0,0 +1,3 @@ +glamac_errors.o glamac_errors.d: ../src/glamac/glamac_errors.c \ + ../include/glamac/core/glamac_errors.h \ + ../include/glamac/core/glamacdef.h ../include/glamac/core/security.h diff --git a/src/glamac/glamac_events.c b/src/glamac/glamac_events.c index 51d1e9d..5966efa 100644 --- a/src/glamac/glamac_events.c +++ b/src/glamac/glamac_events.c @@ -4,9 +4,9 @@ #include #include #include -#include "glamac_view.h" -#include "glass_data.h" -#include "glamac_render.h" // For clear_text_cache +#include "glamac/graphics/glamac_view.h" +#include "glamac/data/glass_data.h" +#include "glamac/graphics/glamac_render.h" // For clear_text_cache // External debug mode function extern b32 is_debug_mode(void); @@ -35,10 +35,9 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo break; case SDLK_ESCAPE: - // ESC closes glass/cluster selection first, then help window, finally quits - if (view->selectedGlass >= 0 || view->selectedCluster >= 0) { + // ESC closes glass selection first, then help window, finally quits + if (view->selectedGlass >= 0) { view->selectedGlass = -1; // Clear selection - view->selectedCluster = -1; } else if (view->showHelp) { view->showHelp = 0; } else { @@ -62,14 +61,12 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo // Zoom in at center view->zoomLevel *= ZOOM_FACTOR; if (view->zoomLevel > MAX_ZOOM) view->zoomLevel = MAX_ZOOM; - create_loose_clusters(view); // Recreate loose clusters on zoom return 1; case SDLK_MINUS: // Zoom out at center view->zoomLevel /= ZOOM_FACTOR; if (view->zoomLevel < MIN_ZOOM) view->zoomLevel = MIN_ZOOM; - create_loose_clusters(view); // Recreate loose clusters on zoom view->gKeyPressed = 0; // Reset g key state return 1; @@ -92,10 +89,7 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo cycle_catalog(-1); refresh_view_data_range(view); // Update view for new catalog view->selectedGlass = -1; // Clear selection - view->selectedCluster = -1; // Clear cluster selection clear_text_cache(); // Clear text cache when switching catalogs - create_tight_clusters(view); // Recreate tight clusters for new catalog - create_loose_clusters(view); // Recreate loose clusters for new catalog printf("Switched to catalog: %s (%u glasses)\n", get_current_catalog_name(), get_glass_count()); } else { @@ -111,10 +105,7 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo cycle_catalog(1); refresh_view_data_range(view); // Update view for new catalog view->selectedGlass = -1; // Clear selection - view->selectedCluster = -1; // Clear cluster selection clear_text_cache(); // Clear text cache when switching catalogs - create_tight_clusters(view); // Recreate tight clusters for new catalog - create_loose_clusters(view); // Recreate loose clusters for new catalog printf("Switched to catalog: %s (%u glasses)\n", get_current_catalog_name(), get_glass_count()); } else { // Move right @@ -152,22 +143,11 @@ b32 process_mouse_button(SDL_MouseButtonEvent *button, ViewState *view, i32 *las i32 nearestGlass = find_nearest_glass((i32)mouseX, (i32)mouseY, view, 15); if (nearestGlass >= 0) { - // Check if clicked glass is part of a tight cluster - i32 clusterIndex = find_tight_cluster_for_glass(nearestGlass, view); - - if (clusterIndex >= 0 && view->tightClusters[clusterIndex].count > 1) { - // Clicked on a multi-glass tight cluster - view->selectedCluster = clusterIndex; - view->selectedGlass = -1; // Clear individual glass selection - } else { - // Clicked on individual glass or single-glass cluster - view->selectedGlass = nearestGlass; - view->selectedCluster = -1; // Clear cluster selection - } + // Found a glass within click tolerance - select it + view->selectedGlass = nearestGlass; } else { - // No glass clicked + // No glass clicked - clear selection view->selectedGlass = -1; - view->selectedCluster = -1; } if (nearestGlass < 0) { @@ -187,17 +167,48 @@ b32 process_mouse_button(SDL_MouseButtonEvent *button, ViewState *view, i32 *las return 0; } -// Process mouse motion event +// Process mouse motion event with bounds checking b32 process_mouse_motion(SDL_MouseMotionEvent *motion __attribute__((unused)), ViewState *view, i32 *lastMouseX, i32 *lastMouseY, b32 dragging) { + if (!view || !lastMouseX || !lastMouseY) { + return 0; + } + if (dragging) { // Get current mouse position f32 mouseX, mouseY; SDL_GetMouseState(&mouseX, &mouseY); - // Calculate normalized delta + // Validate window dimensions to prevent division by zero + if (view->windowWidth <= 0 || view->windowHeight <= 0) { + printf("Warning: Invalid window dimensions in mouse motion: %dx%d\n", + view->windowWidth, view->windowHeight); + return 0; + } + + // Calculate normalized delta with bounds checking const i32 padding = get_adaptive_padding(view); - const f32 dx = (mouseX - *lastMouseX) / (view->windowWidth - 2 * padding); - const f32 dy = (mouseY - *lastMouseY) / (view->windowHeight - 2 * padding); + const i32 effectiveWidth = view->windowWidth - 2 * padding; + const i32 effectiveHeight = view->windowHeight - 2 * padding; + + // Prevent division by zero or very small values + if (effectiveWidth <= 10 || effectiveHeight <= 10) { + printf("Warning: Window too small for mouse interaction: effective size %dx%d\n", + effectiveWidth, effectiveHeight); + return 0; + } + + const f32 dx = (mouseX - *lastMouseX) / (f32)effectiveWidth; + const f32 dy = (mouseY - *lastMouseY) / (f32)effectiveHeight; + + // Validate delta values to prevent extreme jumps + const f32 maxDelta = 2.0f; // Maximum reasonable delta per frame + if (fabsf(dx) > maxDelta || fabsf(dy) > maxDelta) { + printf("Warning: Extreme mouse delta detected: dx=%.3f, dy=%.3f\n", dx, dy); + // Update position but don't apply the movement + *lastMouseX = (i32)mouseX; + *lastMouseY = (i32)mouseY; + return 1; + } // Update offset - inverted movement for natural feel view->offsetX += dx; @@ -213,13 +224,44 @@ b32 process_mouse_motion(SDL_MouseMotionEvent *motion __attribute__((unused)), V return 0; } -// Process window event +// Process window event with validation b32 process_window_event(SDL_WindowEvent *window_event, ViewState *view) { + if (!window_event || !view) { + return 0; + } + switch (window_event->type) { case SDL_EVENT_WINDOW_RESIZED: - view->windowWidth = (i32)window_event->data1; - view->windowHeight = (i32)window_event->data2; - return 1; + { + i32 newWidth = (i32)window_event->data1; + i32 newHeight = (i32)window_event->data2; + + // Validate new dimensions + const i32 minWidth = 200; // Minimum usable width + const i32 minHeight = 150; // Minimum usable height + const i32 maxWidth = 8192; // Maximum reasonable width + const i32 maxHeight = 8192; // Maximum reasonable height + + if (newWidth < minWidth || newHeight < minHeight) { + printf("Warning: Window size too small: %dx%d, clamping to minimum\n", + newWidth, newHeight); + newWidth = (newWidth < minWidth) ? minWidth : newWidth; + newHeight = (newHeight < minHeight) ? minHeight : newHeight; + } + + if (newWidth > maxWidth || newHeight > maxHeight) { + printf("Warning: Window size too large: %dx%d, clamping to maximum\n", + newWidth, newHeight); + newWidth = (newWidth > maxWidth) ? maxWidth : newWidth; + newHeight = (newHeight > maxHeight) ? maxHeight : newHeight; + } + + view->windowWidth = newWidth; + view->windowHeight = newHeight; + + printf("Window resized to: %dx%d\n", newWidth, newHeight); + return 1; + } default: // Ignore other window events break; @@ -254,9 +296,31 @@ b32 process_events(SDL_Event *event, ViewState *view, SDL_Window *window, return process_mouse_motion(&event->motion, view, lastMouseX, lastMouseY, *dragging); case SDL_EVENT_WINDOW_RESIZED: - view->windowWidth = (i32)event->window.data1; - view->windowHeight = (i32)event->window.data2; - return 1; + { + i32 newWidth = (i32)event->window.data1; + i32 newHeight = (i32)event->window.data2; + + // Validate new dimensions + const i32 minWidth = 200; // Minimum usable width + const i32 minHeight = 150; // Minimum usable height + const i32 maxWidth = 8192; // Maximum reasonable width + const i32 maxHeight = 8192; // Maximum reasonable height + + if (newWidth < minWidth || newHeight < minHeight || + newWidth > maxWidth || newHeight > maxHeight) { + printf("Warning: Invalid window resize dimensions: %dx%d\n", + newWidth, newHeight); + // Still update but within bounds + newWidth = (newWidth < minWidth) ? minWidth : + (newWidth > maxWidth) ? maxWidth : newWidth; + newHeight = (newHeight < minHeight) ? minHeight : + (newHeight > maxHeight) ? maxHeight : newHeight; + } + + view->windowWidth = newWidth; + view->windowHeight = newHeight; + return 1; + } } return 0; diff --git a/src/glamac/glamac_events.d b/src/glamac/glamac_events.d new file mode 100644 index 0000000..11d79c7 --- /dev/null +++ b/src/glamac/glamac_events.d @@ -0,0 +1,10 @@ +glamac_events.o glamac_events.d: ../src/glamac/glamac_events.c \ + ../include/glamac/graphics/glamac_view.h \ + ../include/glamac/graphics/../core/glamacdef.h \ + ../include/glamac/data/glass_data.h \ + ../include/glamac/data/../core/glamacdef.h \ + ../include/glamac/data/../core/glamac_errors.h \ + ../include/glamac/data/../core/glamacdef.h \ + ../include/glamac/data/../core/security.h \ + ../include/glamac/graphics/glamac_render.h \ + ../include/glamac/graphics/glamac_view.h diff --git a/src/glamac/glamac_render.c b/src/glamac/glamac_render.c index 5230f1b..1fdaf30 100644 --- a/src/glamac/glamac_render.c +++ b/src/glamac/glamac_render.c @@ -7,29 +7,123 @@ #include #include #include -#include "glamac_render.h" -#include "glass_data.h" +#include "glamac/graphics/glamac_render.h" +#include "glamac/data/glass_data.h" + +// Font path constants and validation +#define MAX_FONT_PATHS 8 +#define MAX_FONT_PATH_LEN 512 +#define MIN_FONT_SIZE 8 +#define MAX_FONT_SIZE 72 +#define DEFAULT_FONT_SIZE 16 +#define AXIS_FONT_SIZE 12 +#define TITLE_FONT_SIZE 20 +#define LABEL_FONT_SIZE 11 + +// Grid and rendering constants +#define GRID_DIVISIONS 10 +#define CIRCLE_RADIUS 5 +#define LABEL_OFFSET_X 12 +// LABEL_OFFSET_Y is defined in glamac_view.h +#define AXIS_LABEL_OFFSET 15 +#define AXIS_MARGIN 45 + +// Safe font paths (in order of preference) +static const char* SAFE_FONT_PATHS[] = { + "/usr/share/fonts/TTF/DejaVuSans.ttf", + "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", + "/System/Library/Fonts/Arial.ttf", // macOS + "C:\\Windows\\Fonts\\arial.ttf", // Windows + "fonts/DejaVuSans.ttf", // Local fallback + "DejaVuSans.ttf", // Current directory + NULL +}; + +static const char* SAFE_BOLD_FONT_PATHS[] = { + "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", + "/System/Library/Fonts/Arial Bold.ttf", // macOS + "C:\\Windows\\Fonts\\arialbd.ttf", // Windows + "fonts/DejaVuSans-Bold.ttf", // Local fallback + "DejaVuSans-Bold.ttf", // Current directory + NULL +}; + +// Text rendering cache with LRU eviction +#define TEXT_CACHE_SIZE 512 +#define MAX_TEXT_LENGTH 128 -// Text rendering cache typedef struct { - char text[128]; + char text[MAX_TEXT_LENGTH]; SDL_Texture* texture; i32 width, height; SDL_Color color; TTF_Font* font; + u32 last_used_time; // For LRU eviction + b32 in_use; // Slot availability flag } CachedText; -static CachedText textCache[512]; +static CachedText textCache[TEXT_CACHE_SIZE]; static i32 cacheSize = 0; +static u32 cache_access_counter = 0; // Global counter for LRU + +// Find least recently used cache entry for eviction +static i32 find_lru_cache_entry(void) { + i32 lru_index = 0; + u32 oldest_time = textCache[0].last_used_time; + + for (i32 i = 1; i < TEXT_CACHE_SIZE; i++) { + if (!textCache[i].in_use) { + return i; // Found empty slot + } + if (textCache[i].last_used_time < oldest_time) { + oldest_time = textCache[i].last_used_time; + lru_index = i; + } + } + + return lru_index; +} -// Find cached text texture +// Evict cache entry and free its resources +static void evict_cache_entry(i32 index) { + if (index >= 0 && index < TEXT_CACHE_SIZE && textCache[index].in_use) { + if (textCache[index].texture) { + SDL_DestroyTexture(textCache[index].texture); + textCache[index].texture = NULL; + } + textCache[index].in_use = 0; + textCache[index].text[0] = '\0'; + textCache[index].font = NULL; + textCache[index].last_used_time = 0; + } +} + +// Find cached text texture with LRU management static SDL_Texture* get_cached_text(SDL_Renderer* renderer, TTF_Font* font, const char* text, SDL_Color color, i32* width, i32* height) { - for (i32 i = 0; i < cacheSize; i++) { - if (textCache[i].font == font && + if (!renderer || !font || !text || !width || !height) { + return NULL; + } + + // Validate text length + size_t text_len = strlen(text); + if (text_len == 0 || text_len >= MAX_TEXT_LENGTH) { + return NULL; + } + + cache_access_counter++; + + // Search for existing cache entry + for (i32 i = 0; i < TEXT_CACHE_SIZE; i++) { + if (textCache[i].in_use && + textCache[i].font == font && textCache[i].color.r == color.r && textCache[i].color.g == color.g && textCache[i].color.b == color.b && strcmp(textCache[i].text, text) == 0) { + + // Update LRU timestamp + textCache[i].last_used_time = cache_access_counter; *width = textCache[i].width; *height = textCache[i].height; return textCache[i].texture; @@ -37,37 +131,60 @@ static SDL_Texture* get_cached_text(SDL_Renderer* renderer, TTF_Font* font, cons } // Create new cached texture - if (cacheSize < 512) { - SDL_Surface* surface = TTF_RenderText_Blended(font, text, strlen(text), color); - if (surface) { - SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); - if (texture) { - strncpy(textCache[cacheSize].text, text, sizeof(textCache[cacheSize].text) - 1); - textCache[cacheSize].text[sizeof(textCache[cacheSize].text) - 1] = '\0'; - textCache[cacheSize].texture = texture; - textCache[cacheSize].width = surface->w; - textCache[cacheSize].height = surface->h; - textCache[cacheSize].color = color; - textCache[cacheSize].font = font; - *width = surface->w; - *height = surface->h; - SDL_DestroySurface(surface); - cacheSize++; - return texture; - } - SDL_DestroySurface(surface); - } + SDL_Surface* surface = TTF_RenderText_Blended(font, text, text_len, color); + if (!surface) { + return NULL; } - return NULL; + + SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); + if (!texture) { + SDL_DestroySurface(surface); + return NULL; + } + + // Find cache slot (either empty or LRU) + i32 cache_index; + if (cacheSize < TEXT_CACHE_SIZE) { + // Use next available slot + cache_index = cacheSize; + cacheSize++; + } else { + // Cache is full, evict LRU entry + cache_index = find_lru_cache_entry(); + evict_cache_entry(cache_index); + } + + // Store in cache + strncpy(textCache[cache_index].text, text, sizeof(textCache[cache_index].text) - 1); + textCache[cache_index].text[sizeof(textCache[cache_index].text) - 1] = '\0'; + textCache[cache_index].texture = texture; + textCache[cache_index].width = surface->w; + textCache[cache_index].height = surface->h; + textCache[cache_index].color = color; + textCache[cache_index].font = font; + textCache[cache_index].last_used_time = cache_access_counter; + textCache[cache_index].in_use = 1; + + *width = surface->w; + *height = surface->h; + SDL_DestroySurface(surface); + + return texture; } void clear_text_cache(void) { - for (i32 i = 0; i < cacheSize; i++) { - if (textCache[i].texture) { + for (i32 i = 0; i < TEXT_CACHE_SIZE; i++) { + if (textCache[i].in_use && textCache[i].texture) { SDL_DestroyTexture(textCache[i].texture); + textCache[i].texture = NULL; } + textCache[i].in_use = 0; + textCache[i].text[0] = '\0'; + textCache[i].font = NULL; + textCache[i].last_used_time = 0; } cacheSize = 0; + cache_access_counter = 0; } // Draw text helper @@ -126,12 +243,48 @@ void draw_grid(SDL_Renderer *renderer, const ViewState* view) { } } +// Safe font loading helper +static TTF_Font* load_font_safe(const char* const* font_paths, int size) { + if (!font_paths || size <= 0) return NULL; + + // Validate size range + if (size < 8 || size > 72) { + printf("Warning: Font size %d outside safe range (8-72), clamping\n", size); + size = (size < 8) ? 8 : 72; + } + + for (int i = 0; font_paths[i] != NULL; i++) { + const char* path = font_paths[i]; + + // Basic path validation + if (!path || strlen(path) == 0 || strlen(path) >= MAX_FONT_PATH_LEN) { + continue; + } + + // Check for directory traversal attempts + if (strstr(path, "..") || strstr(path, "//")) { + printf("Warning: Skipping potentially unsafe font path: %s\n", path); + continue; + } + + TTF_Font* font = TTF_OpenFont(path, size); + if (font) { + printf("Successfully loaded font: %s (size %d)\n", path, size); + return font; + } + } + + printf("Warning: Failed to load any font from provided paths\n"); + return NULL; +} + // Draw axes void draw_axes(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) { - // Create smaller font for axis scales - TTF_Font *axisFont = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 12); + // Create smaller font for axis scales with safe loading + TTF_Font *axisFont = load_font_safe(SAFE_FONT_PATHS, 12); if (!axisFont) { axisFont = font; // Fallback to regular font if loading fails + printf("Warning: Using fallback font for axes\n"); } const i32 padding = get_adaptive_padding(view); const i32 plotWidth = view->windowWidth - 2 * padding; @@ -252,11 +405,9 @@ void draw_glass_points(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewSt // Draw glass point (larger) draw_filled_circle(renderer, x, y, 5); - // Draw label only if it should be shown (tight clustering logic) - if (should_show_glass_label((i32)i, view)) { - const char* glassName = (const char*)glass->name; - draw_text(renderer, labelFont, glassName, x + 12, y - 12, labelColor); - } + // Show all glass labels (clustering removed) + const char* glassName = (const char*)glass->name; + draw_text(renderer, labelFont, glassName, x + 12, y - 12, labelColor); } } @@ -282,52 +433,6 @@ void calculate_smart_window_position(i32 glassX, i32 glassY, i32 windowWidth, i3 } } -// Draw cluster properties window (shows all glasses in tight cluster) -void draw_cluster_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) { - if (view->selectedCluster < 0 || !view->tightClusters) return; - - const TightCluster* cluster = &view->tightClusters[view->selectedCluster]; - if (cluster->count == 0) return; - - // Calculate cluster position on screen using representative glass - const Glass* repGlass = get_glass(cluster->representativeIndex); - if (!repGlass) return; - - i32 clusterX, clusterY; - data_to_screen_coords(repGlass->abbeNumber, repGlass->refractiveIndex, view, &clusterX, &clusterY); - - // Window properties - const i32 windowWidth = 320; - const i32 windowHeight = 20 + cluster->count * 25; - - i32 windowX, windowY; - calculate_smart_window_position(clusterX, clusterY, windowWidth, windowHeight, view, &windowX, &windowY); - - // Draw background - SDL_SetRenderDrawColor(renderer, 255, 255, 255, 220); - SDL_FRect bgRect = {(f32)windowX, (f32)windowY, (f32)windowWidth, (f32)windowHeight}; - SDL_RenderFillRect(renderer, &bgRect); - - // Draw border - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); - SDL_RenderRect(renderer, &bgRect); - - // Draw text - SDL_Color black = {0, 0, 0, 255}; - char buffer[256]; - - // Draw each glass in cluster directly (no title) - for (i32 i = 0; i < cluster->count; i++) { - const Glass* glass = get_glass(cluster->glassIndices[i]); - if (glass) { - i32 yPos = windowY + 10 + i * 25; - - snprintf(buffer, sizeof(buffer), "%s: nd=%.4f, vd=%.2f", - (const char*)glass->name, glass->refractiveIndex, glass->abbeNumber); - draw_text(renderer, font, buffer, windowX + 10, yPos, black); - } - } -} // Draw glass properties window void draw_glass_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) { @@ -439,8 +544,6 @@ void render(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, TTF_Fon // Draw glass properties if selected draw_glass_properties(renderer, font, titleFont, view); - // Draw cluster properties if selected - draw_cluster_properties(renderer, font, titleFont, view); // Draw help window if needed draw_help_window(renderer, font, titleFont, view); @@ -451,33 +554,96 @@ void render(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, TTF_Fon // Font management functions b32 load_fonts(FontSet *fonts) { - fonts->regular = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 16); - fonts->title = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", 20); - fonts->label = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 14); + if (!fonts) return 0; + + // Initialize to NULL for safe cleanup + fonts->regular = NULL; + fonts->title = NULL; + fonts->label = NULL; - return fonts->regular && fonts->title && fonts->label; + fonts->regular = load_font_safe(SAFE_FONT_PATHS, 16); + fonts->title = load_font_safe(SAFE_BOLD_FONT_PATHS, 20); + fonts->label = load_font_safe(SAFE_FONT_PATHS, 11); + + // Check if at least one font loaded successfully + if (!fonts->regular && !fonts->title && !fonts->label) { + printf("Critical error: No fonts could be loaded\n"); + return 0; + } + + // Use fallbacks if specific fonts failed + if (!fonts->title && fonts->regular) { + fonts->title = fonts->regular; + printf("Warning: Using regular font as title font fallback\n"); + } + if (!fonts->label && fonts->regular) { + fonts->label = fonts->regular; + printf("Warning: Using regular font as label font fallback\n"); + } + if (!fonts->regular && fonts->title) { + fonts->regular = fonts->title; + printf("Warning: Using title font as regular font fallback\n"); + } + + return (fonts->regular && fonts->title && fonts->label) ? 1 : 0; } b32 load_adaptive_fonts(FontSet *fonts, i32 windowWidth, i32 windowHeight, f32 dpi) { (void)windowHeight; // Suppress unused parameter warning - // Calculate font sizes based on window size and DPI (increased sizes) + + if (!fonts) return 0; + + // Validate input parameters + if (windowWidth <= 0 || dpi <= 0) { + printf("Warning: Invalid window dimensions or DPI, using defaults\n"); + windowWidth = 800; + dpi = 96.0f; + } + + // Calculate font sizes based on window size and DPI i32 regularSize = (i32)(16.0f * dpi / 96.0f * windowWidth / 800.0f); i32 titleSize = (i32)(20.0f * dpi / 96.0f * windowWidth / 800.0f); - i32 labelSize = (i32)(14.0f * dpi / 96.0f * windowWidth / 800.0f); + // Reduce label size for better overview visibility (was 14.0f) + i32 labelSize = (i32)(11.0f * dpi / 96.0f * windowWidth / 800.0f); - // Clamp font sizes + // Clamp font sizes to safe ranges if (regularSize < 12) regularSize = 12; if (regularSize > 32) regularSize = 32; if (titleSize < 16) titleSize = 16; if (titleSize > 36) titleSize = 36; - if (labelSize < 10) labelSize = 10; - if (labelSize > 24) labelSize = 24; + if (labelSize < 8) labelSize = 8; + if (labelSize > 20) labelSize = 20; + + // Initialize to NULL for safe cleanup + fonts->regular = NULL; + fonts->title = NULL; + fonts->label = NULL; + + fonts->regular = load_font_safe(SAFE_FONT_PATHS, regularSize); + fonts->title = load_font_safe(SAFE_BOLD_FONT_PATHS, titleSize); + fonts->label = load_font_safe(SAFE_FONT_PATHS, labelSize); + + // Check if at least one font loaded successfully + if (!fonts->regular && !fonts->title && !fonts->label) { + printf("Critical error: No adaptive fonts could be loaded\n"); + return 0; + } - fonts->regular = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", regularSize); - fonts->title = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", titleSize); - fonts->label = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", labelSize); + // Use fallbacks if specific fonts failed + if (!fonts->title && fonts->regular) { + fonts->title = fonts->regular; + printf("Warning: Using regular font as title font fallback\n"); + } + if (!fonts->label && fonts->regular) { + fonts->label = fonts->regular; + printf("Warning: Using regular font as label font fallback\n"); + } + if (!fonts->regular && fonts->title) { + fonts->regular = fonts->title; + printf("Warning: Using title font as regular font fallback\n"); + } - return fonts->regular && fonts->title && fonts->label; + return (fonts->regular && fonts->title && fonts->label) ? 1 : 0; } void free_fonts(FontSet *fonts) { diff --git a/src/glamac/glamac_render.d b/src/glamac/glamac_render.d new file mode 100644 index 0000000..738174c --- /dev/null +++ b/src/glamac/glamac_render.d @@ -0,0 +1,9 @@ +glamac_render.o glamac_render.d: ../src/glamac/glamac_render.c \ + ../include/glamac/graphics/glamac_render.h \ + ../include/glamac/graphics/../core/glamacdef.h \ + ../include/glamac/graphics/glamac_view.h \ + ../include/glamac/data/glass_data.h \ + ../include/glamac/data/../core/glamacdef.h \ + ../include/glamac/data/../core/glamac_errors.h \ + ../include/glamac/data/../core/glamacdef.h \ + ../include/glamac/data/../core/security.h diff --git a/src/glamac/glamac_view.c b/src/glamac/glamac_view.c index ff85b71..244523e 100644 --- a/src/glamac/glamac_view.c +++ b/src/glamac/glamac_view.c @@ -6,21 +6,12 @@ #include #include #include -#include "glamac_view.h" -#include "glass_data.h" +#include "glamac/graphics/glamac_view.h" +#include "glamac/data/glass_data.h" // External debug mode function extern b32 is_debug_mode(void); -// Global tight cluster threshold parameters -f32 g_tight_cluster_nd_threshold = DEFAULT_TIGHT_CLUSTER_ND_THRESHOLD; -f32 g_tight_cluster_vd_threshold = DEFAULT_TIGHT_CLUSTER_VD_THRESHOLD; - -// Global loose cluster parameters (zoom-dependent) -f32 g_loose_cluster_nd_threshold = DEFAULT_LOOSE_CLUSTER_ND_THRESHOLD; // Base threshold -f32 g_loose_cluster_vd_threshold = DEFAULT_LOOSE_CLUSTER_VD_THRESHOLD; // Base threshold -f32 g_loose_cluster_nd_fraction = DEFAULT_LOOSE_CLUSTER_ND_FRACTION; // Zoom scaling fraction -f32 g_loose_cluster_vd_fraction = DEFAULT_LOOSE_CLUSTER_VD_FRACTION; // Zoom scaling fraction // Initialize a view state with default values void init_view_state(ViewState* view, i32 windowWidth, i32 windowHeight) { @@ -33,22 +24,6 @@ void init_view_state(ViewState* view, i32 windowWidth, i32 windowHeight) { view->gKeyPressed = 0; view->gKeyTime = 0; view->selectedGlass = -1; // No glass selected initially - view->selectedCluster = -1; // No cluster selected initially - - // Initialize tight cluster data - view->tightClusters = NULL; - view->tightClusterCount = 0; - - // Initialize loose cluster data - view->looseClusters = NULL; - view->looseClusterCount = 0; - - // Initialize label recalculation tracking (force initial calculation) - view->lastZoomLevel = -1.0f; - view->lastWindowWidth = -1; - view->lastWindowHeight = -1; - view->lastOffsetX = -999.0f; - view->lastOffsetY = -999.0f; // Calculate data range find_glass_data_range(&view->minAbbe, &view->maxAbbe, &view->minRI, &view->maxRI); @@ -58,8 +33,6 @@ void init_view_state(ViewState* view, i32 windowWidth, i32 windowHeight) { void refresh_view_data_range(ViewState* view) { find_glass_data_range(&view->minAbbe, &view->maxAbbe, &view->minRI, &view->maxRI); - // Force label recalculation on catalog change - view->lastZoomLevel = -1.0f; // Invalid values to force recalc } // Calculate visible data range @@ -133,8 +106,6 @@ void handle_mouse_wheel_zoom(i32 wheelY, i32 mouseX, i32 mouseY, ViewState* view view->offsetX += (f32)(mouseX - newMouseScreenX) / (view->windowWidth - 2 * padding); view->offsetY += (f32)(newMouseScreenY - mouseY) / (view->windowHeight - 2 * padding); - // Recreate loose clusters since zoom changed (they are zoom-dependent) - create_loose_clusters(view); } // Toggle fullscreen @@ -155,373 +126,13 @@ void reset_view(ViewState* view) { view->offsetY = 0.0f; view->selectedGlass = -1; - // Recreate loose clusters since zoom changed - create_loose_clusters(view); } -// Check if two glasses should be clustered based on separate nd and vd thresholds -static inline b32 should_cluster_glasses(const Glass* glass1, const Glass* glass2) { - f32 nd_diff = fabsf(glass1->refractiveIndex - glass2->refractiveIndex); - f32 vd_diff = fabsf(glass1->abbeNumber - glass2->abbeNumber); - - return (nd_diff <= g_tight_cluster_nd_threshold) && (vd_diff <= g_tight_cluster_vd_threshold); -} -// Find glass with shortest name in a list -static i32 find_shortest_name_glass(const i32* glassIndices, i32 count) { - if (count == 0) return -1; - - i32 shortestIndex = glassIndices[0]; - size_t shortestLength = strlen((const char*)get_glass(shortestIndex)->name); - - for (i32 i = 1; i < count; i++) { - const Glass* glass = get_glass(glassIndices[i]); - if (glass) { - size_t nameLength = strlen((const char*)glass->name); - if (nameLength < shortestLength) { - shortestLength = nameLength; - shortestIndex = glassIndices[i]; - } - } - } - - return shortestIndex; -} -// Create tight clusters for current catalog -void create_tight_clusters(ViewState* view) { - // Free existing clusters - free_tight_clusters(view); - - const u32 glassCount = get_glass_count(); - if (glassCount == 0) return; - - // Allocate temporary arrays - TightCluster* clusters = (TightCluster*)calloc(glassCount, sizeof(TightCluster)); - b32* processed = (b32*)calloc(glassCount, sizeof(b32)); - i32 clusterCount = 0; - - if (!clusters || !processed) { - free(clusters); - free(processed); - return; - } - - printf("Creating tight clusters for %s catalog (%u glasses, nd_threshold=%.6f, vd_threshold=%.3f)...\n", - get_current_catalog_name(), glassCount, g_tight_cluster_nd_threshold, g_tight_cluster_vd_threshold); - - // Simple clustering algorithm - for (u32 i = 0; i < glassCount; i++) { - if (processed[i]) continue; - - const Glass* glass1 = get_glass(i); - if (!glass1) continue; - - // Start new cluster with this glass - TightCluster* cluster = &clusters[clusterCount]; - cluster->glassIndices[0] = (i32)i; - cluster->count = 1; - cluster->avgAbbeNumber = glass1->abbeNumber; - cluster->avgRefractiveIndex = glass1->refractiveIndex; - processed[i] = 1; - - // Find nearby glasses within tight clustering distance - for (u32 j = i + 1; j < glassCount && cluster->count < MAX_CLUSTER_SIZE; j++) { - if (processed[j]) continue; - - const Glass* glass2 = get_glass(j); - if (!glass2) continue; - - if (should_cluster_glasses(glass1, glass2)) { - // Add to cluster - cluster->glassIndices[cluster->count] = (i32)j; - cluster->count++; - - // Update average position - cluster->avgAbbeNumber = (cluster->avgAbbeNumber * (cluster->count - 1) + glass2->abbeNumber) / cluster->count; - cluster->avgRefractiveIndex = (cluster->avgRefractiveIndex * (cluster->count - 1) + glass2->refractiveIndex) / cluster->count; - - processed[j] = 1; - } - } - - // Set representative glass (shortest name) - cluster->representativeIndex = find_shortest_name_glass(cluster->glassIndices, cluster->count); - - clusterCount++; - } - - // Count multi-glass clusters - i32 multiGlassClusters = 0; - for (i32 i = 0; i < clusterCount; i++) { - if (clusters[i].count > 1) { - multiGlassClusters++; - } - } - - printf("Created %d tight clusters (%d with multiple glasses)\n", clusterCount, multiGlassClusters); - - // Debug output: show detailed cluster information - if (is_debug_mode()) { - printf("\n=== TIGHT CLUSTER DEBUG INFO ===\n"); - printf("Catalog: %s, nd_threshold: %.6f, vd_threshold: %.3f\n", - get_current_catalog_name(), g_tight_cluster_nd_threshold, g_tight_cluster_vd_threshold); - - for (i32 i = 0; i < clusterCount; i++) { - const TightCluster* cluster = &clusters[i]; - if (cluster->count > 1) { - printf("\nCluster %d (%d glasses):\n", i, cluster->count); - for (i32 j = 0; j < cluster->count; j++) { - const Glass* glass = get_glass(cluster->glassIndices[j]); - if (glass) { - printf(" %s: nd=%.6f, vd=%.3f", - (const char*)glass->name, glass->refractiveIndex, glass->abbeNumber); - if (cluster->glassIndices[j] == cluster->representativeIndex) { - printf(" [REPRESENTATIVE]"); - } - printf("\n"); - } - } - - // Calculate and display pairwise distances within cluster - printf(" Pairwise distances:\n"); - for (i32 j = 0; j < cluster->count; j++) { - for (i32 k = j + 1; k < cluster->count; k++) { - const Glass* glass1 = get_glass(cluster->glassIndices[j]); - const Glass* glass2 = get_glass(cluster->glassIndices[k]); - if (glass1 && glass2) { - f32 nd_diff = fabsf(glass1->refractiveIndex - glass2->refractiveIndex); - f32 vd_diff = fabsf(glass1->abbeNumber - glass2->abbeNumber); - printf(" %s <-> %s: nd_diff=%.6f, vd_diff=%.3f\n", - (const char*)glass1->name, (const char*)glass2->name, nd_diff, vd_diff); - } - } - } - } - } - printf("=== END CLUSTER DEBUG INFO ===\n\n"); - } - - // Resize to actual number of clusters and store - if (clusterCount > 0) { - TightCluster* resizedClusters = (TightCluster*)realloc(clusters, clusterCount * sizeof(TightCluster)); - if (!resizedClusters) { - free(clusters); - view->tightClusters = NULL; - view->tightClusterCount = 0; - } else { - view->tightClusters = resizedClusters; - view->tightClusterCount = clusterCount; - } - } else { - free(clusters); - view->tightClusters = NULL; - view->tightClusterCount = 0; - } - - free(processed); -} - -// Free tight clusters -void free_tight_clusters(ViewState* view) { - if (view->tightClusters) { - free(view->tightClusters); - view->tightClusters = NULL; - } - view->tightClusterCount = 0; - view->selectedCluster = -1; -} -// Create loose clusters for current catalog (zoom-dependent) -void create_loose_clusters(ViewState* view) { - // Free existing clusters - free_loose_clusters(view); - - const u32 glassCount = get_glass_count(); - if (glassCount == 0) return; - - // Calculate zoom-dependent thresholds that reach ZERO at maximum zoom - f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI; - get_visible_data_range(view, &visibleMinAbbe, &visibleMaxAbbe, &visibleMinRI, &visibleMaxRI); - - // Calculate zoom factor based on zoom level (0.0 at max zoom, 1.0 at min zoom) - // At MAX_ZOOM, factor = 0; At MIN_ZOOM, factor = 1 - f32 zoomRange = MAX_ZOOM - MIN_ZOOM; - f32 currentZoomNormalized = (view->zoomLevel - MIN_ZOOM) / zoomRange; - - // Invert so that factor = 0 at max zoom, factor = 1 at min zoom - f32 zoomFactor = 1.0f - (currentZoomNormalized > 1.0f ? 1.0f : currentZoomNormalized); - - // Ensure we reach exactly zero at maximum zoom - if (view->zoomLevel >= MAX_ZOOM) { - zoomFactor = 0.0f; - } - - // Calculate thresholds: ZERO at max zoom, base*fraction at min zoom - f32 ndThreshold = zoomFactor * g_loose_cluster_nd_threshold * g_loose_cluster_nd_fraction; - f32 vdThreshold = zoomFactor * g_loose_cluster_vd_threshold * g_loose_cluster_vd_fraction; - - // Allocate temporary arrays - LooseCluster* clusters = (LooseCluster*)calloc(glassCount, sizeof(LooseCluster)); - b32* processed = (b32*)calloc(glassCount, sizeof(b32)); - i32 clusterCount = 0; - - if (!clusters || !processed) { - free(clusters); - free(processed); - return; - } - - printf("Creating loose clusters for %s catalog (%u glasses)...\n", get_current_catalog_name(), glassCount); - printf(" zoom level: %.2f, zoom factor: %.4f\n", view->zoomLevel, zoomFactor); - printf(" nd: %.3f * %.1f * %.4f = %.6f\n", - g_loose_cluster_nd_threshold, g_loose_cluster_nd_fraction, zoomFactor, ndThreshold); - printf(" vd: %.2f * %.1f * %.4f = %.3f\n", - g_loose_cluster_vd_threshold, g_loose_cluster_vd_fraction, zoomFactor, vdThreshold); - - // Simple clustering algorithm (same as tight clustering but with zoom-dependent thresholds) - for (u32 i = 0; i < glassCount; i++) { - if (processed[i]) continue; - - const Glass* glass1 = get_glass(i); - if (!glass1) continue; - - // Start new cluster with this glass - LooseCluster* cluster = &clusters[clusterCount]; - cluster->glassIndices[0] = (i32)i; - cluster->count = 1; - cluster->avgAbbeNumber = glass1->abbeNumber; - cluster->avgRefractiveIndex = glass1->refractiveIndex; - processed[i] = 1; - - // Find nearby glasses within loose clustering distance - for (u32 j = i + 1; j < glassCount && cluster->count < MAX_CLUSTER_SIZE; j++) { - if (processed[j]) continue; - - const Glass* glass2 = get_glass(j); - if (!glass2) continue; - - // Check if glasses should be clustered (separate nd/vd thresholds) - f32 nd_diff = fabsf(glass1->refractiveIndex - glass2->refractiveIndex); - f32 vd_diff = fabsf(glass1->abbeNumber - glass2->abbeNumber); - - if (nd_diff <= ndThreshold && vd_diff <= vdThreshold) { - // Add to cluster - cluster->glassIndices[cluster->count] = (i32)j; - cluster->count++; - - // Update average position - cluster->avgAbbeNumber = (cluster->avgAbbeNumber * (cluster->count - 1) + glass2->abbeNumber) / cluster->count; - cluster->avgRefractiveIndex = (cluster->avgRefractiveIndex * (cluster->count - 1) + glass2->refractiveIndex) / cluster->count; - - processed[j] = 1; - } - } - - // Set representative glass (shortest name) - cluster->representativeIndex = find_shortest_name_glass(cluster->glassIndices, cluster->count); - - clusterCount++; - } - - // Count multi-glass clusters - i32 multiGlassClusters = 0; - for (i32 i = 0; i < clusterCount; i++) { - if (clusters[i].count > 1) { - multiGlassClusters++; - } - } - - printf("Created %d loose clusters (%d with multiple glasses)\n", clusterCount, multiGlassClusters); - - // Resize to actual number of clusters and store - if (clusterCount > 0) { - LooseCluster* resizedClusters = (LooseCluster*)realloc(clusters, clusterCount * sizeof(LooseCluster)); - if (!resizedClusters) { - free(clusters); - view->looseClusters = NULL; - view->looseClusterCount = 0; - } else { - view->looseClusters = resizedClusters; - view->looseClusterCount = clusterCount; - } - } else { - free(clusters); - view->looseClusters = NULL; - view->looseClusterCount = 0; - } - - free(processed); -} -// Free loose clusters -void free_loose_clusters(ViewState* view) { - if (view->looseClusters) { - free(view->looseClusters); - view->looseClusters = NULL; - } - view->looseClusterCount = 0; -} -// Find tight cluster that contains a specific glass -i32 find_tight_cluster_for_glass(i32 glassIndex, const ViewState* view) { - if (!view->tightClusters) return -1; - - for (i32 i = 0; i < view->tightClusterCount; i++) { - for (i32 j = 0; j < view->tightClusters[i].count; j++) { - if (view->tightClusters[i].glassIndices[j] == glassIndex) { - return i; // Return cluster index - } - } - } - - return -1; // Not in any cluster -} -// Find loose cluster that contains a specific glass -i32 find_loose_cluster_for_glass(i32 glassIndex, const ViewState* view) { - if (!view->looseClusters) return -1; - - for (i32 i = 0; i < view->looseClusterCount; i++) { - for (i32 j = 0; j < view->looseClusters[i].count; j++) { - if (view->looseClusters[i].glassIndices[j] == glassIndex) { - return i; // Return cluster index - } - } - } - - return -1; // Not in any cluster -} -// Check if a glass label should be shown (combined tight and loose clustering logic) -b32 should_show_glass_label(i32 glassIndex, const ViewState* view) { - // First check tight clusters (they have priority) - i32 tightClusterIndex = find_tight_cluster_for_glass(glassIndex, view); - - if (tightClusterIndex >= 0) { - // Glass is in a tight cluster - const TightCluster* cluster = &view->tightClusters[tightClusterIndex]; - - if (cluster->count > 1) { - // Multi-glass tight cluster: only show representative (shortest name) - return glassIndex == cluster->representativeIndex; - } - } - - // Then check loose clusters (only if not in tight cluster) - i32 looseClusterIndex = find_loose_cluster_for_glass(glassIndex, view); - - if (looseClusterIndex >= 0) { - // Glass is in a loose cluster - const LooseCluster* cluster = &view->looseClusters[looseClusterIndex]; - - if (cluster->count > 1) { - // Multi-glass loose cluster: only show representative (shortest name) - return glassIndex == cluster->representativeIndex; - } - } - - // Single glass or not in any cluster: always show - return 1; -} diff --git a/src/glamac/glamac_view.d b/src/glamac/glamac_view.d new file mode 100644 index 0000000..81ffa71 --- /dev/null +++ b/src/glamac/glamac_view.d @@ -0,0 +1,8 @@ +glamac_view.o glamac_view.d: ../src/glamac/glamac_view.c \ + ../include/glamac/graphics/glamac_view.h \ + ../include/glamac/graphics/../core/glamacdef.h \ + ../include/glamac/data/glass_data.h \ + ../include/glamac/data/../core/glamacdef.h \ + ../include/glamac/data/../core/glamac_errors.h \ + ../include/glamac/data/../core/glamacdef.h \ + ../include/glamac/data/../core/security.h diff --git a/src/glamac/glass_data.c b/src/glamac/glass_data.c index 32077de..c6e9e10 100644 --- a/src/glamac/glass_data.c +++ b/src/glamac/glass_data.c @@ -10,9 +10,9 @@ * * See the COPYING file for the full license text. */ -#include "glamacdef.h" -#include "glass_data.h" -#include "glamac_errors.h" +#include "glamac/core/glamacdef.h" +#include "glamac/data/glass_data.h" +#include "glamac/core/glamac_errors.h" #include #include #include @@ -55,7 +55,157 @@ static const Glass default_glasses[] = { #define DEFAULT_GLASS_COUNT (sizeof(default_glasses) / sizeof(default_glasses[0])) -// Security validation functions +// Enhanced glass data validation +static GlamacResult validate_glass_properties(const char* name, double nd, double vd, const char* glass_code) { + // Validate glass name + if (!name || strlen(name) == 0 || strlen(name) >= 50) { + return GLAMAC_ERROR_INVALID_GLASS_DATA; + } + + // Check for valid characters in glass name (alphanumeric, dash, underscore, space) + for (const char* p = name; *p; p++) { + if (!isalnum(*p) && *p != '-' && *p != '_' && *p != ' ' && *p != '+') { + return GLAMAC_ERROR_INVALID_GLASS_DATA; + } + } + + // Validate refractive index (nd) - typical range for optical glasses + if (nd < 1.0 || nd > 4.0) { + return GLAMAC_ERROR_INVALID_GLASS_DATA; + } + + // Validate Abbe number (vd) - typical range for optical glasses + if (vd < 10.0 || vd > 100.0) { + return GLAMAC_ERROR_INVALID_GLASS_DATA; + } + + // Additional physics-based validation: check for reasonable nd/vd relationship + // High refractive index glasses typically have lower Abbe numbers + if (nd > 2.0 && vd > 80.0) { + return GLAMAC_ERROR_INVALID_GLASS_DATA; // Unusual combination + } + + // Validate glass code format if provided + if (glass_code && strlen(glass_code) > 0) { + size_t code_len = strlen(glass_code); + if (code_len >= 20) { // Glass code field size limit + return GLAMAC_ERROR_INVALID_GLASS_DATA; + } + + // Glass code should be primarily numeric (allowing some special chars) + int digit_count = 0; + for (const char* p = glass_code; *p; p++) { + if (isdigit(*p)) { + digit_count++; + } else if (!isalnum(*p) && *p != '-' && *p != '.') { + return GLAMAC_ERROR_INVALID_GLASS_DATA; + } + } + + // Glass code should have at least some digits + if (digit_count == 0) { + return GLAMAC_ERROR_INVALID_GLASS_DATA; + } + } + + return GLAMAC_SUCCESS; +} + +static GlamacResult validate_json_structure(const char* json, size_t json_len) { + if (!json || json_len == 0) { + return GLAMAC_ERROR_INVALID_JSON; + } + + // Basic JSON structure validation + int brace_count = 0; + int bracket_count = 0; + int quote_count = 0; + bool in_string = false; + bool escaped = false; + + for (size_t i = 0; i < json_len; i++) { + char c = json[i]; + + if (escaped) { + escaped = false; + continue; + } + + if (c == '\\' && in_string) { + escaped = true; + continue; + } + + if (c == '"') { + in_string = !in_string; + quote_count++; + } + + if (!in_string) { + if (c == '{') brace_count++; + else if (c == '}') brace_count--; + else if (c == '[') bracket_count++; + else if (c == ']') bracket_count--; + + // Check for balanced braces/brackets + if (brace_count < 0 || bracket_count < 0) { + return GLAMAC_ERROR_INVALID_JSON; + } + } + } + + // Final balance check + if (brace_count != 0 || bracket_count != 0 || (quote_count % 2) != 0) { + return GLAMAC_ERROR_INVALID_JSON; + } + + // Check for required top-level structure + if (!strstr(json, "\"manufacturers\"") && !strstr(json, "\"glasses\"")) { + return GLAMAC_ERROR_INVALID_JSON; + } + + return GLAMAC_SUCCESS; +} + +static GlamacResult validate_manufacturer_data(const char* manufacturer_name) { + if (!manufacturer_name || strlen(manufacturer_name) == 0) { + return GLAMAC_ERROR_INVALID_MANUFACTURER; + } + + size_t name_len = strlen(manufacturer_name); + if (name_len >= MAX_MANUFACTURER_NAME_LEN) { + return GLAMAC_ERROR_INVALID_MANUFACTURER; + } + + // Validate manufacturer name characters + for (const char* p = manufacturer_name; *p; p++) { + if (!isalnum(*p) && *p != '-' && *p != '_' && *p != ' ' && *p != '&') { + return GLAMAC_ERROR_INVALID_MANUFACTURER; + } + } + + // Check against known manufacturers list for additional validation + const char* known_manufacturers[] = { + "SCHOTT", "HOYA", "CDGM", "Ohara", "SUMITA", "HIKARI", NULL + }; + + bool is_known = false; + for (int i = 0; known_manufacturers[i]; i++) { + if (strcasecmp(manufacturer_name, known_manufacturers[i]) == 0) { + is_known = true; + break; + } + } + + // For now, just log unknown manufacturers but don't reject them + if (!is_known) { + printf("Warning: Unknown manufacturer '%s' - data may be incorrect\n", manufacturer_name); + } + + return GLAMAC_SUCCESS; +} + +// Enhanced security validation functions static GlamacResult validate_file_path(const char* path) { if (!path) return GLAMAC_ERROR_INVALID_ARGUMENT; @@ -64,9 +214,58 @@ static GlamacResult validate_file_path(const char* path) { return GLAMAC_ERROR_INVALID_PATH; } - // Check for directory traversal attempts - if (strstr(path, "..") || strstr(path, "//") || - strchr(path, '|') || strchr(path, ';') || strchr(path, '&')) { + // Check for various directory traversal attempts + const char* dangerous_patterns[] = { + "..", // Basic traversal + "./.", // Current directory traversal + "/..", // Unix traversal + "\\..", // Windows traversal + "//", // Double slash + "\\\\", // Double backslash + "%2e%2e", // URL-encoded .. + "%2f", // URL-encoded / + "%5c", // URL-encoded backslash + "<", // XML/HTML injection + ">", // XML/HTML injection + "|", // Shell pipe + ";", // Command separator + "&", // Command separator + "`", // Command substitution + "$", // Variable expansion + "~", // Home directory expansion + NULL + }; + + // Check for dangerous patterns + for (int i = 0; dangerous_patterns[i]; i++) { + if (strstr(path, dangerous_patterns[i])) { + printf("Security warning: Dangerous pattern '%s' found in path: %s\n", + dangerous_patterns[i], path); + return GLAMAC_ERROR_INVALID_PATH; + } + } + + // Additional checks for path structure + const char* p = path; + while (*p) { + // Check for control characters + if (*p < 32 && *p != '\t') { + printf("Security warning: Control character found in path\n"); + return GLAMAC_ERROR_INVALID_PATH; + } + + // Check for high-bit characters that might be encoding attempts + if ((unsigned char)*p > 127) { + printf("Security warning: Non-ASCII character found in path\n"); + return GLAMAC_ERROR_INVALID_PATH; + } + + p++; + } + + // Path should end with .json for this application + if (len < 5 || strcmp(path + len - 5, ".json") != 0) { + printf("Security warning: Path does not end with .json extension\n"); return GLAMAC_ERROR_INVALID_PATH; } @@ -265,7 +464,9 @@ static GlamacResult parse_manufacturer_glasses(const char* manufacturer_section, double vd = safe_find_json_number(obj_start, "vd", obj_len); char* glass_code = safe_find_json_string(obj_start, "glass_code", obj_len); - if (name && nd > 0.0 && vd > 0.0 && nd < 10.0 && vd < 200.0) { + // Enhanced validation using the new validation functions + GlamacResult validation_result = validate_glass_properties(name, nd, vd, glass_code); + if (name && validation_result == GLAMAC_SUCCESS) { // Expand glasses array if needed if (catalog->count >= catalog->capacity) { u32 new_capacity = catalog->capacity * 2; @@ -281,31 +482,55 @@ static GlamacResult parse_manufacturer_glasses(const char* manufacturer_section, catalog->capacity = new_capacity; } - // Copy glass data safely + // Copy glass data safely with proper bounds checking size_t name_len = strlen(name); - if (name_len < sizeof(catalog->glasses[catalog->count].name)) { - strcpy((char*)catalog->glasses[catalog->count].name, name); + if (name_len > 0 && name_len < sizeof(catalog->glasses[catalog->count].name)) { + // Use strncpy with explicit null termination for safety + strncpy((char*)catalog->glasses[catalog->count].name, name, + sizeof(catalog->glasses[catalog->count].name) - 1); + catalog->glasses[catalog->count].name[sizeof(catalog->glasses[catalog->count].name) - 1] = '\0'; + catalog->glasses[catalog->count].abbeNumber = (f32)vd; catalog->glasses[catalog->count].refractiveIndex = (f32)nd; - // Copy glass code safely - if (glass_code) { + // Copy glass code safely with bounds checking + if (glass_code && strlen(glass_code) > 0) { size_t code_len = strlen(glass_code); if (code_len < sizeof(catalog->glasses[catalog->count].glass_code)) { - strcpy((char*)catalog->glasses[catalog->count].glass_code, glass_code); + strncpy((char*)catalog->glasses[catalog->count].glass_code, glass_code, + sizeof(catalog->glasses[catalog->count].glass_code) - 1); + catalog->glasses[catalog->count].glass_code[sizeof(catalog->glasses[catalog->count].glass_code) - 1] = '\0'; } else { + // Truncate if too long strncpy((char*)catalog->glasses[catalog->count].glass_code, glass_code, sizeof(catalog->glasses[catalog->count].glass_code) - 1); catalog->glasses[catalog->count].glass_code[sizeof(catalog->glasses[catalog->count].glass_code) - 1] = '\0'; + printf("Warning: Glass code truncated for %s\n", name); } } else { - strcpy((char*)catalog->glasses[catalog->count].glass_code, "N/A"); + // Safe copy of N/A string + strncpy((char*)catalog->glasses[catalog->count].glass_code, "N/A", + sizeof(catalog->glasses[catalog->count].glass_code) - 1); + catalog->glasses[catalog->count].glass_code[sizeof(catalog->glasses[catalog->count].glass_code) - 1] = '\0'; } - // Copy manufacturer name - strcpy((char*)catalog->glasses[catalog->count].manufacturer, catalog->name); + // Copy manufacturer name with bounds checking + size_t mfg_len = strlen(catalog->name); + if (mfg_len < sizeof(catalog->glasses[catalog->count].manufacturer)) { + strncpy((char*)catalog->glasses[catalog->count].manufacturer, catalog->name, + sizeof(catalog->glasses[catalog->count].manufacturer) - 1); + catalog->glasses[catalog->count].manufacturer[sizeof(catalog->glasses[catalog->count].manufacturer) - 1] = '\0'; + } else { + // Fallback to "Unknown" if manufacturer name is too long + strncpy((char*)catalog->glasses[catalog->count].manufacturer, "Unknown", + sizeof(catalog->glasses[catalog->count].manufacturer) - 1); + catalog->glasses[catalog->count].manufacturer[sizeof(catalog->glasses[catalog->count].manufacturer) - 1] = '\0'; + printf("Warning: Manufacturer name too long, using 'Unknown'\n"); + } catalog->count++; + } else { + printf("Warning: Glass name invalid or too long: %s\n", name ? name : "NULL"); } } @@ -329,6 +554,12 @@ static GlamacResult parse_manufacturer_glasses(const char* manufacturer_section, // Load single manufacturer into a specific catalog static GlamacResult load_single_manufacturer_to_catalog(const char* json_content, size_t json_len, const char* manufacturer_name, GlassCatalog* catalog) { + // Validate manufacturer name first + GlamacResult result = validate_manufacturer_data(manufacturer_name); + if (result != GLAMAC_SUCCESS) { + return result; + } + char manufacturer_key[MAX_MANUFACTURER_NAME_LEN + 16]; int ret = snprintf(manufacturer_key, sizeof(manufacturer_key), "\"%s\":", manufacturer_name); if (ret < 0 || ret >= (int)sizeof(manufacturer_key)) { @@ -344,7 +575,7 @@ static GlamacResult load_single_manufacturer_to_catalog(const char* json_content size_t section_len = json_len - (mfg_section - json_content); // Initialize catalog with full capacity to avoid realloc during parsing - GlamacResult result = allocate_catalog_glasses(catalog, 2000, manufacturer_name); + result = allocate_catalog_glasses(catalog, 2000, manufacturer_name); if (result != GLAMAC_SUCCESS) { return result; } @@ -440,6 +671,13 @@ static GlamacResult load_glasses_from_json_secure(const char* json_path, const c json_content[bytes_read] = '\0'; + // Validate JSON structure before parsing + result = validate_json_structure(json_content, bytes_read); + if (result != GLAMAC_SUCCESS) { + free(json_content); + return result; + } + // Handle both single manufacturer and all manufacturers cases if (manufacturer_filter) { // Load specific manufacturer into first catalog @@ -566,9 +804,11 @@ void initialize_glass_data(void) { g_glass_context.current_catalog = 0; g_glass_context.using_json_data = 0; - // Set manufacturer for default glasses + // Set manufacturer for default glasses with bounds checking for (u32 i = 0; i < DEFAULT_GLASS_COUNT; i++) { - strcpy((char*)g_glass_context.catalogs[0].glasses[i].manufacturer, "SCHOTT"); + strncpy((char*)g_glass_context.catalogs[0].glasses[i].manufacturer, "SCHOTT", + sizeof(g_glass_context.catalogs[0].glasses[i].manufacturer) - 1); + g_glass_context.catalogs[0].glasses[i].manufacturer[sizeof(g_glass_context.catalogs[0].glasses[i].manufacturer) - 1] = '\0'; } } else { printf("Critical error: Failed to allocate fallback glass data\n"); diff --git a/src/glamac/glass_data.d b/src/glamac/glass_data.d new file mode 100644 index 0000000..6ecf97d --- /dev/null +++ b/src/glamac/glass_data.d @@ -0,0 +1,7 @@ +glass_data.o glass_data.d: ../src/glamac/glass_data.c \ + ../include/glamac/core/glamacdef.h ../include/glamac/data/glass_data.h \ + ../include/glamac/data/../core/glamacdef.h \ + ../include/glamac/data/../core/glamac_errors.h \ + ../include/glamac/data/../core/glamacdef.h \ + ../include/glamac/data/../core/security.h \ + ../include/glamac/core/glamac_errors.h diff --git a/src/glautils/fgla.c b/src/glautils/fgla.c index baf2fef..c8cd139 100644 --- a/src/glautils/fgla.c +++ b/src/glautils/fgla.c @@ -15,25 +15,26 @@ #include #include #include -#include "glass_data.h" -#include "glamacdef.h" +#include "glamac/data/glass_data.h" +#include "glamac/core/glamacdef.h" +#include "glautils/fgla.h" -// Security constants -#define MAX_SEARCH_TERM_LEN 256 -#define MAX_GLASS_NAME_LEN 256 -#define MAX_CATALOG_COUNT 10 -#define MAX_NORMALIZED_LEN 512 -#define GLASS_CODE_LEN 6 +// Use constants from header +#define MAX_SEARCH_TERM_LEN FGLA_MAX_SEARCH_TERM_LEN +#define MAX_GLASS_NAME_LEN FGLA_MAX_GLASS_NAME_LEN +#define MAX_CATALOG_COUNT FGLA_MAX_CATALOG_COUNT +#define MAX_NORMALIZED_LEN FGLA_MAX_NORMALIZED_LEN +#define GLASS_CODE_LEN FGLA_GLASS_CODE_LEN -// Output format types -typedef enum { - OUTPUT_CSV = 0, - OUTPUT_TABLE = 1, - OUTPUT_JSON = 2 -} OutputFormat; +// Use output format types from header +typedef FglaOutputFormat OutputFormat; +#define OUTPUT_CSV FGLA_OUTPUT_CSV +#define OUTPUT_TABLE FGLA_OUTPUT_TABLE +#define OUTPUT_JSON FGLA_OUTPUT_JSON // Function to safely convert string to lowercase with length limit -void to_lowercase_safe(char* str, size_t max_len) { +// Functions are now always non-static since they're declared in the header +void fgla_to_lowercase_safe(char* str, size_t max_len) { if (!str) return; for (size_t i = 0; i < max_len && str[i]; i++) { @@ -42,7 +43,7 @@ void to_lowercase_safe(char* str, size_t max_len) { } // Function to safely normalize string by removing dashes and converting to lowercase -int normalize_string_safe(const char* input, char* output, size_t output_size) { +int fgla_normalize_string_safe(const char* input, char* output, size_t output_size) { if (!input || !output || output_size < 2) return -1; size_t input_len = strnlen(input, MAX_GLASS_NAME_LEN); @@ -58,7 +59,7 @@ int normalize_string_safe(const char* input, char* output, size_t output_size) { } // Function to safely check if needle is found in haystack (case-insensitive, dash-insensitive) -int contains_substring_safe(const char* haystack, const char* needle) { +int fgla_contains_substring_safe(const char* haystack, const char* needle) { if (!haystack || !needle) return 0; // Validate input lengths @@ -80,8 +81,8 @@ int contains_substring_safe(const char* haystack, const char* needle) { } // Create normalized copies (lowercase, no dashes) - if (normalize_string_safe(haystack, haystack_normalized, h_len + 1) != 0 || - normalize_string_safe(needle, needle_normalized, n_len + 1) != 0) { + if (fgla_normalize_string_safe(haystack, haystack_normalized, h_len + 1) != 0 || + fgla_normalize_string_safe(needle, needle_normalized, n_len + 1) != 0) { free(haystack_normalized); free(needle_normalized); return 0; @@ -96,7 +97,7 @@ int contains_substring_safe(const char* haystack, const char* needle) { } // Function to check if manufacturer matches any of the specified catalogs -int matches_catalog(const char* manufacturer, const char* catalog_list[], int catalog_count) { +int fgla_matches_catalog(const char* manufacturer, const char* catalog_list[], int catalog_count) { if (catalog_count == 0) return 1; // No filter = show all for (int i = 0; i < catalog_count; i++) { @@ -109,7 +110,7 @@ int matches_catalog(const char* manufacturer, const char* catalog_list[], int ca } // Function to validate search term input -int validate_search_term(const char* term) { +int fgla_validate_search_term(const char* term) { if (!term) return 0; size_t len = strnlen(term, MAX_SEARCH_TERM_LEN); @@ -130,7 +131,7 @@ int validate_search_term(const char* term) { // Function to check if a search term is a glass code pattern // Glass code pattern: exactly 6 characters, contains only digits and/or 'x' -int is_glass_code_pattern(const char* term) { +int fgla_is_glass_code_pattern(const char* term) { if (!term) return 0; size_t len = strnlen(term, GLASS_CODE_LEN + 1); @@ -148,7 +149,7 @@ int is_glass_code_pattern(const char* term) { // Function to safely check if a glass code matches a pattern // Pattern can contain 'x' as wildcards, or be an exact 6-digit match -int matches_glass_code_pattern_safe(const char* glass_code, const char* pattern) { +int fgla_matches_glass_code_pattern_safe(const char* glass_code, const char* pattern) { if (!glass_code || !pattern) return 0; size_t code_len = strnlen(glass_code, 20); // Glass codes can be longer than 6 @@ -193,7 +194,7 @@ int matches_glass_code_pattern_safe(const char* glass_code, const char* pattern) } } -void print_usage(const char* program_name) { +void fgla_print_usage(const char* program_name) { printf("fgla - Find Glass utility\n"); printf("Usage: %s [OPTIONS] \n", program_name); printf("\n"); @@ -217,16 +218,9 @@ void print_usage(const char* program_name) { } // Error handling with detailed messages -typedef enum { - FGLA_SUCCESS = 0, - FGLA_ERROR_INVALID_ARGS = 1, - FGLA_ERROR_INVALID_SEARCH_TERM = 2, - FGLA_ERROR_NO_DATABASE = 3, - FGLA_ERROR_NO_MATCHES = 4, - FGLA_ERROR_MEMORY = 5 -} FglaResult; +// Use FglaResult from header -void print_error_with_suggestion(FglaResult error, const char* context) { +void fgla_print_error_with_suggestion(FglaResult error, const char* context) { switch (error) { case FGLA_ERROR_INVALID_SEARCH_TERM: fprintf(stderr, "Error: Invalid search term '%s'\n", context ? context : ""); @@ -323,6 +317,7 @@ void print_footer(OutputFormat format) { } } +#ifndef TEST_BUILD int main(int argc, char* argv[]) { const char* search_term = NULL; const char* catalog_list[MAX_CATALOG_COUNT]; // Support up to MAX_CATALOG_COUNT catalogs @@ -361,7 +356,7 @@ int main(int argc, char* argv[]) { } if (catalog_count == 0) { fprintf(stderr, "Error: -c option requires at least one catalog name\n"); - print_usage(argv[0]); + fgla_print_usage(argv[0]); return FGLA_ERROR_INVALID_ARGS; } } else if (strcmp(argv[i], "-f") == 0) { @@ -369,7 +364,7 @@ int main(int argc, char* argv[]) { i++; // Skip -f if (i >= argc) { fprintf(stderr, "Error: -f option requires a format argument\n"); - print_usage(argv[0]); + fgla_print_usage(argv[0]); return FGLA_ERROR_INVALID_ARGS; } @@ -386,12 +381,12 @@ int main(int argc, char* argv[]) { i++; } else if (argv[i][0] == '-') { fprintf(stderr, "Error: Unknown option '%s'\n", argv[i]); - print_usage(argv[0]); + fgla_print_usage(argv[0]); return FGLA_ERROR_INVALID_ARGS; } else { if (search_term != NULL) { fprintf(stderr, "Error: Multiple search terms not allowed\n"); - print_usage(argv[0]); + fgla_print_usage(argv[0]); return FGLA_ERROR_INVALID_ARGS; } search_term = argv[i]; @@ -402,13 +397,13 @@ int main(int argc, char* argv[]) { catalog_list[catalog_count] = NULL; // NULL-terminate the list if (search_term == NULL) { - print_usage(argv[0]); + fgla_print_usage(argv[0]); return FGLA_ERROR_INVALID_ARGS; } // Validate search term - if (!validate_search_term(search_term)) { - print_error_with_suggestion(FGLA_ERROR_INVALID_SEARCH_TERM, search_term); + if (!fgla_validate_search_term(search_term)) { + fgla_print_error_with_suggestion(FGLA_ERROR_INVALID_SEARCH_TERM, search_term); return FGLA_ERROR_INVALID_SEARCH_TERM; } @@ -431,7 +426,7 @@ int main(int argc, char* argv[]) { } if (!successful_path) { - print_error_with_suggestion(FGLA_ERROR_NO_DATABASE, NULL); + fgla_print_error_with_suggestion(FGLA_ERROR_NO_DATABASE, NULL); fprintf(stderr, "Tried these locations:\n"); for (int i = 0; json_paths[i]; i++) { fprintf(stderr, " - %s\n", json_paths[i]); @@ -447,7 +442,7 @@ int main(int argc, char* argv[]) { u32 found_count = 0; // Determine search type - int is_glass_code_search = is_glass_code_pattern(search_term); + int is_glass_code_search = fgla_is_glass_code_pattern(search_term); // Pre-allocate matches array for performance - estimate max size across all catalogs u32* matching_indices = malloc(10000 * sizeof(u32)); // Generous allocation for all glasses @@ -478,18 +473,18 @@ int main(int argc, char* argv[]) { if (is_glass_code_search) { // Glass code pattern search const char* glass_code = (const char*)glass->glass_code; - if (glass_code && matches_glass_code_pattern_safe(glass_code, search_term)) { + if (glass_code && fgla_matches_glass_code_pattern_safe(glass_code, search_term)) { matches = 1; } } else { // Name search - if (contains_substring_safe(glass_name, search_term)) { + if (fgla_contains_substring_safe(glass_name, search_term)) { matches = 1; } } // Check if this glass matches the search term and catalog filter - if (matches && matches_catalog((const char*)glass->manufacturer, catalog_list, catalog_count)) { + if (matches && fgla_matches_catalog((const char*)glass->manufacturer, catalog_list, catalog_count)) { matching_indices[found_count] = i; matching_catalogs[found_count] = catalog_idx; found_count++; @@ -499,7 +494,7 @@ int main(int argc, char* argv[]) { // Output results if (found_count == 0) { - print_error_with_suggestion(FGLA_ERROR_NO_MATCHES, search_term); + fgla_print_error_with_suggestion(FGLA_ERROR_NO_MATCHES, search_term); free(matching_indices); free(matching_catalogs); cleanup_glass_data(); @@ -536,3 +531,4 @@ int main(int argc, char* argv[]) { return FGLA_SUCCESS; } +#endif /* TEST_BUILD */ diff --git a/src/glautils/fgla.d b/src/glautils/fgla.d new file mode 100644 index 0000000..69b10cd --- /dev/null +++ b/src/glautils/fgla.d @@ -0,0 +1,7 @@ +fgla.o fgla.d: ../src/glautils/fgla.c ../include/glamac/data/glass_data.h \ + ../include/glamac/data/../core/glamacdef.h \ + ../include/glamac/data/../core/glamac_errors.h \ + ../include/glamac/data/../core/glamacdef.h \ + ../include/glamac/data/../core/security.h \ + ../include/glamac/core/glamacdef.h ../include/glautils/fgla.h \ + ../include/glamac/core/security.h -- cgit v1.2.3