diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/glamac/glamac.c | 140 | ||||
-rw-r--r-- | src/glamac/glamac.d | 10 | ||||
-rw-r--r-- | src/glamac/glamac_errors.c | 6 | ||||
-rw-r--r-- | src/glamac/glamac_errors.d | 3 | ||||
-rw-r--r-- | src/glamac/glamac_events.c | 142 | ||||
-rw-r--r-- | src/glamac/glamac_events.d | 10 | ||||
-rw-r--r-- | src/glamac/glamac_render.c | 364 | ||||
-rw-r--r-- | src/glamac/glamac_render.d | 9 | ||||
-rw-r--r-- | src/glamac/glamac_view.c | 393 | ||||
-rw-r--r-- | src/glamac/glamac_view.d | 8 | ||||
-rw-r--r-- | src/glamac/glass_data.c | 280 | ||||
-rw-r--r-- | src/glamac/glass_data.d | 7 | ||||
-rw-r--r-- | src/glautils/fgla.c | 88 | ||||
-rw-r--r-- | src/glautils/fgla.d | 7 |
14 files changed, 842 insertions, 625 deletions
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 <SDL3_ttf/SDL_ttf.h>
#include <stdio.h>
#include <stdlib.h>
-#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 <SDL3/SDL.h>
#include <stdio.h>
#include <math.h>
-#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 <stdlib.h>
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
-#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 <stdlib.h>
#include <string.h>
#include <SDL3/SDL.h>
-#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 <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -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 <stdlib.h>
#include <string.h>
#include <ctype.h>
-#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] <search_term>\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 |