diff --git a/tests/gal-regression/baseline/gal-bitmap.png b/tests/gal-regression/baseline/gal-bitmap.png new file mode 100644 index 0000000..61d9672 Binary files /dev/null and b/tests/gal-regression/baseline/gal-bitmap.png differ diff --git a/tests/gal-regression/baseline/gal-clear-colors.png b/tests/gal-regression/baseline/gal-clear-colors.png new file mode 100644 index 0000000..fe69b3c Binary files /dev/null and b/tests/gal-regression/baseline/gal-clear-colors.png differ diff --git a/tests/gal-regression/baseline/gal-complex-scene.png b/tests/gal-regression/baseline/gal-complex-scene.png index 1e5dbb2..2d92d14 100644 Binary files a/tests/gal-regression/baseline/gal-complex-scene.png and b/tests/gal-regression/baseline/gal-complex-scene.png differ diff --git a/tests/gal-regression/baseline/gal-cursor-native.png b/tests/gal-regression/baseline/gal-cursor-native.png new file mode 100644 index 0000000..d220a42 Binary files /dev/null and b/tests/gal-regression/baseline/gal-cursor-native.png differ diff --git a/tests/gal-regression/baseline/gal-depth-testing.png b/tests/gal-regression/baseline/gal-depth-testing.png new file mode 100644 index 0000000..28e7d8a Binary files /dev/null and b/tests/gal-regression/baseline/gal-depth-testing.png differ diff --git a/tests/gal-regression/baseline/gal-glyphs.png b/tests/gal-regression/baseline/gal-glyphs.png new file mode 100644 index 0000000..688e01c Binary files /dev/null and b/tests/gal-regression/baseline/gal-glyphs.png differ diff --git a/tests/gal-regression/baseline/gal-grid-native.png b/tests/gal-regression/baseline/gal-grid-native.png new file mode 100644 index 0000000..429eb09 Binary files /dev/null and b/tests/gal-regression/baseline/gal-grid-native.png differ diff --git a/tests/gal-regression/baseline/gal-group-caching.png b/tests/gal-regression/baseline/gal-group-caching.png index 98dd602..3b1be82 100644 Binary files a/tests/gal-regression/baseline/gal-group-caching.png and b/tests/gal-regression/baseline/gal-group-caching.png differ diff --git a/tests/gal-regression/baseline/gal-hole-walls.png b/tests/gal-regression/baseline/gal-hole-walls.png new file mode 100644 index 0000000..5dc3169 Binary files /dev/null and b/tests/gal-regression/baseline/gal-hole-walls.png differ diff --git a/tests/gal-regression/baseline/gal-line-widths.png b/tests/gal-regression/baseline/gal-line-widths.png index 4ce63b3..d1aa0ce 100644 Binary files a/tests/gal-regression/baseline/gal-line-widths.png and b/tests/gal-regression/baseline/gal-line-widths.png differ diff --git a/tests/gal-regression/baseline/gal-negative-mode.png b/tests/gal-regression/baseline/gal-negative-mode.png new file mode 100644 index 0000000..116e54c Binary files /dev/null and b/tests/gal-regression/baseline/gal-negative-mode.png differ diff --git a/tests/gal-regression/baseline/gal-polylines-multi.png b/tests/gal-regression/baseline/gal-polylines-multi.png new file mode 100644 index 0000000..996bdeb Binary files /dev/null and b/tests/gal-regression/baseline/gal-polylines-multi.png differ diff --git a/tests/gal-regression/baseline/gal-render-targets.png b/tests/gal-regression/baseline/gal-render-targets.png new file mode 100644 index 0000000..42c524c Binary files /dev/null and b/tests/gal-regression/baseline/gal-render-targets.png differ diff --git a/tests/gal-regression/baseline/gal-screen-transform.png b/tests/gal-regression/baseline/gal-screen-transform.png new file mode 100644 index 0000000..ce94144 Binary files /dev/null and b/tests/gal-regression/baseline/gal-screen-transform.png differ diff --git a/tests/gal-regression/baseline/gal-text-attrs.png b/tests/gal-regression/baseline/gal-text-attrs.png new file mode 100644 index 0000000..b77b332 Binary files /dev/null and b/tests/gal-regression/baseline/gal-text-attrs.png differ diff --git a/tests/gal-regression/baseline/gal-transform-api.png b/tests/gal-regression/baseline/gal-transform-api.png new file mode 100644 index 0000000..d75e58b Binary files /dev/null and b/tests/gal-regression/baseline/gal-transform-api.png differ diff --git a/tests/gal-regression/native/CMakeLists.txt b/tests/gal-regression/native/CMakeLists.txt index 4a2a523..e3dd91f 100644 --- a/tests/gal-regression/native/CMakeLists.txt +++ b/tests/gal-regression/native/CMakeLists.txt @@ -72,6 +72,20 @@ set(SCENARIO_SOURCES ${CMAKE_SOURCE_DIR}/../scenarios/scenario_arc_segments.cpp ${CMAKE_SOURCE_DIR}/../scenarios/scenario_segment_chain.cpp ${CMAKE_SOURCE_DIR}/../scenarios/scenario_group_caching.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_polylines_multi.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_hole_walls.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_grid_native.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_cursor_native.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_render_targets.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_screen_transform.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_clear_colors.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_depth_testing.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_negative_mode.cpp + # Additional scenarios (24-27) + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_text_attrs.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_glyphs.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_bitmap.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_transform.cpp ) # Main executable diff --git a/tests/gal-regression/native/bitmap_base_stub.h b/tests/gal-regression/native/bitmap_base_stub.h new file mode 100644 index 0000000..669de21 --- /dev/null +++ b/tests/gal-regression/native/bitmap_base_stub.h @@ -0,0 +1,415 @@ +/** + * BITMAP_BASE Test Patterns for GAL Test + * + * Provides helper functions to create wxImage test patterns that can be + * used with KiCad's real BITMAP_BASE class for testing DrawBitmap(). + * + * Usage: + * wxImage img = CreateCheckerboardImage(64, 64); + * BITMAP_BASE bitmap; + * bitmap.SetImage(img); + * gal->DrawBitmap(bitmap); + */ + +#ifndef BITMAP_BASE_STUB_H +#define BITMAP_BASE_STUB_H + +#include +#include +#include +#include + +//============================================================================= +// Image pattern generation functions +//============================================================================= + +/** + * Create a solid color image + */ +inline wxImage CreateSolidImage(int width, int height, + uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) +{ + wxImage img(width, height); + img.InitAlpha(); + + unsigned char* data = img.GetData(); + unsigned char* alpha = img.GetAlpha(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int idx = (y * width + x) * 3; + data[idx + 0] = r; + data[idx + 1] = g; + data[idx + 2] = b; + alpha[y * width + x] = a; + } + } + + return img; +} + +/** + * Create a checkerboard pattern image + */ +inline wxImage CreateCheckerboardImage(int width, int height, int squareSize = 8, + uint8_t r1 = 255, uint8_t g1 = 255, uint8_t b1 = 255, + uint8_t r2 = 0, uint8_t g2 = 0, uint8_t b2 = 0) +{ + wxImage img(width, height); + img.InitAlpha(); + + unsigned char* data = img.GetData(); + unsigned char* alpha = img.GetAlpha(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int idx = (y * width + x) * 3; + bool isLight = ((x / squareSize) + (y / squareSize)) % 2 == 0; + + if (isLight) + { + data[idx + 0] = r1; + data[idx + 1] = g1; + data[idx + 2] = b1; + } + else + { + data[idx + 0] = r2; + data[idx + 1] = g2; + data[idx + 2] = b2; + } + alpha[y * width + x] = 255; + } + } + + return img; +} + +/** + * Create a horizontal gradient image + */ +inline wxImage CreateGradientHImage(int width, int height, + uint8_t r1, uint8_t g1, uint8_t b1, + uint8_t r2, uint8_t g2, uint8_t b2) +{ + wxImage img(width, height); + img.InitAlpha(); + + unsigned char* data = img.GetData(); + unsigned char* alpha = img.GetAlpha(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int idx = (y * width + x) * 3; + float t = (float)x / (width - 1); + + data[idx + 0] = (uint8_t)(r1 + t * (r2 - r1)); + data[idx + 1] = (uint8_t)(g1 + t * (g2 - g1)); + data[idx + 2] = (uint8_t)(b1 + t * (b2 - b1)); + alpha[y * width + x] = 255; + } + } + + return img; +} + +/** + * Create a vertical gradient image + */ +inline wxImage CreateGradientVImage(int width, int height, + uint8_t r1, uint8_t g1, uint8_t b1, + uint8_t r2, uint8_t g2, uint8_t b2) +{ + wxImage img(width, height); + img.InitAlpha(); + + unsigned char* data = img.GetData(); + unsigned char* alpha = img.GetAlpha(); + + for (int y = 0; y < height; y++) + { + float t = (float)y / (height - 1); + + for (int x = 0; x < width; x++) + { + int idx = (y * width + x) * 3; + + data[idx + 0] = (uint8_t)(r1 + t * (r2 - r1)); + data[idx + 1] = (uint8_t)(g1 + t * (g2 - g1)); + data[idx + 2] = (uint8_t)(b1 + t * (b2 - b1)); + alpha[y * width + x] = 255; + } + } + + return img; +} + +/** + * Create a radial gradient image + */ +inline wxImage CreateRadialGradientImage(int width, int height, + uint8_t r1, uint8_t g1, uint8_t b1, + uint8_t r2, uint8_t g2, uint8_t b2) +{ + wxImage img(width, height); + img.InitAlpha(); + + unsigned char* data = img.GetData(); + unsigned char* alpha = img.GetAlpha(); + + float cx = width / 2.0f; + float cy = height / 2.0f; + float maxDist = std::sqrt(cx * cx + cy * cy); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int idx = (y * width + x) * 3; + float dx = x - cx; + float dy = y - cy; + float dist = std::sqrt(dx * dx + dy * dy); + float t = std::min(1.0f, dist / maxDist); + + data[idx + 0] = (uint8_t)(r1 + t * (r2 - r1)); + data[idx + 1] = (uint8_t)(g1 + t * (g2 - g1)); + data[idx + 2] = (uint8_t)(b1 + t * (b2 - b1)); + alpha[y * width + x] = 255; + } + } + + return img; +} + +/** + * Create a striped pattern image + */ +inline wxImage CreateStripedImage(int width, int height, int stripeWidth, bool horizontal, + uint8_t r1, uint8_t g1, uint8_t b1, + uint8_t r2, uint8_t g2, uint8_t b2) +{ + wxImage img(width, height); + img.InitAlpha(); + + unsigned char* data = img.GetData(); + unsigned char* alpha = img.GetAlpha(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int idx = (y * width + x) * 3; + int pos = horizontal ? y : x; + bool isFirst = (pos / stripeWidth) % 2 == 0; + + if (isFirst) + { + data[idx + 0] = r1; + data[idx + 1] = g1; + data[idx + 2] = b1; + } + else + { + data[idx + 0] = r2; + data[idx + 1] = g2; + data[idx + 2] = b2; + } + alpha[y * width + x] = 255; + } + } + + return img; +} + +/** + * Create a "K" logo-style image (simplified KiCad logo) + */ +inline wxImage CreateKiCadLogoImage(int width, int height, + uint8_t bgR = 30, uint8_t bgG = 60, uint8_t bgB = 30, + uint8_t fgR = 255, uint8_t fgG = 200, uint8_t fgB = 50) +{ + wxImage img(width, height); + img.InitAlpha(); + + unsigned char* data = img.GetData(); + unsigned char* alpha = img.GetAlpha(); + + // Fill background + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int idx = (y * width + x) * 3; + data[idx + 0] = bgR; + data[idx + 1] = bgG; + data[idx + 2] = bgB; + alpha[y * width + x] = 255; + } + } + + // Draw a simplified "K" shape + int centerX = width / 2; + int centerY = height / 2; + int thickness = std::max(2, std::min(width, height) / 8); + int halfH = height / 3; + int halfW = width / 3; + + // Vertical bar of K + for (int y = centerY - halfH; y <= centerY + halfH; y++) + { + if (y < 0 || y >= height) continue; + for (int dx = -thickness/2; dx <= thickness/2; dx++) + { + int x = centerX - halfW/2 + dx; + if (x < 0 || x >= width) continue; + int idx = (y * width + x) * 3; + data[idx + 0] = fgR; + data[idx + 1] = fgG; + data[idx + 2] = fgB; + } + } + + // Upper diagonal of K + for (int i = 0; i <= halfH; i++) + { + int y = centerY - i; + int x = centerX - halfW/2 + (halfW * i / halfH); + if (y < 0 || y >= height) continue; + + for (int dy = -thickness/2; dy <= thickness/2; dy++) + { + for (int dx = -thickness/2; dx <= thickness/2; dx++) + { + int py = y + dy; + int px = x + dx; + if (py < 0 || py >= height) continue; + if (px < 0 || px >= width) continue; + int idx = (py * width + px) * 3; + data[idx + 0] = fgR; + data[idx + 1] = fgG; + data[idx + 2] = fgB; + } + } + } + + // Lower diagonal of K + for (int i = 0; i <= halfH; i++) + { + int y = centerY + i; + int x = centerX - halfW/2 + (halfW * i / halfH); + if (y < 0 || y >= height) continue; + + for (int dy = -thickness/2; dy <= thickness/2; dy++) + { + for (int dx = -thickness/2; dx <= thickness/2; dx++) + { + int py = y + dy; + int px = x + dx; + if (py < 0 || py >= height) continue; + if (px < 0 || px >= width) continue; + int idx = (py * width + px) * 3; + data[idx + 0] = fgR; + data[idx + 1] = fgG; + data[idx + 2] = fgB; + } + } + } + + return img; +} + +/** + * Add a border to an existing image + */ +inline void AddBorderToImage(wxImage& img, int borderWidth, + uint8_t r, uint8_t g, uint8_t b) +{ + int width = img.GetWidth(); + int height = img.GetHeight(); + unsigned char* data = img.GetData(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + bool isBorder = (x < borderWidth || x >= width - borderWidth || + y < borderWidth || y >= height - borderWidth); + + if (isBorder) + { + int idx = (y * width + x) * 3; + data[idx + 0] = r; + data[idx + 1] = g; + data[idx + 2] = b; + } + } + } +} + +//============================================================================= +// Factory functions to create BITMAP_BASE with test patterns +//============================================================================= + +/** + * Create a BITMAP_BASE with checkerboard pattern + */ +inline std::unique_ptr CreateCheckerboardBitmap(int width, int height, int squareSize = 8) +{ + auto bitmap = std::make_unique(); + wxImage img = CreateCheckerboardImage(width, height, squareSize); + bitmap->SetImage(img); + return bitmap; +} + +/** + * Create a BITMAP_BASE with horizontal gradient (red to blue) + */ +inline std::unique_ptr CreateGradientBitmap(int width, int height) +{ + auto bitmap = std::make_unique(); + wxImage img = CreateGradientHImage(width, height, 255, 50, 50, 50, 50, 255); + bitmap->SetImage(img); + return bitmap; +} + +/** + * Create a BITMAP_BASE with KiCad logo pattern + */ +inline std::unique_ptr CreateKiCadLogoBitmap(int width, int height) +{ + auto bitmap = std::make_unique(); + wxImage img = CreateKiCadLogoImage(width, height); + AddBorderToImage(img, 2, 200, 150, 50); + bitmap->SetImage(img); + return bitmap; +} + +/** + * Create a BITMAP_BASE with radial gradient + */ +inline std::unique_ptr CreateRadialBitmap(int width, int height) +{ + auto bitmap = std::make_unique(); + wxImage img = CreateRadialGradientImage(width, height, 255, 255, 200, 50, 50, 150); + bitmap->SetImage(img); + return bitmap; +} + +/** + * Create a BITMAP_BASE with stripes + */ +inline std::unique_ptr CreateStripedBitmap(int width, int height, bool horizontal = true) +{ + auto bitmap = std::make_unique(); + wxImage img = CreateStripedImage(width, height, 8, horizontal, 100, 150, 200, 200, 100, 50); + bitmap->SetImage(img); + return bitmap; +} + +#endif // BITMAP_BASE_STUB_H diff --git a/tests/gal-regression/native/gal_native_test.cpp b/tests/gal-regression/native/gal_native_test.cpp index 01f8c70..9b1026e 100644 --- a/tests/gal-regression/native/gal_native_test.cpp +++ b/tests/gal-regression/native/gal_native_test.cpp @@ -225,6 +225,9 @@ public: // Render the scenario using GAL API GALTest::RenderScenario(m_gal, i, g_width, g_height); + // Test Flush() API - flushes vertex buffer to GPU + m_gal->Flush(); + // EndDrawing renders vertices to FBO, composites to screen, swaps buffers m_gal->EndDrawing(); m_gal->UnlockContext(i); diff --git a/tests/gal-regression/native/kicad_stubs.cpp b/tests/gal-regression/native/kicad_stubs.cpp index 6912650..7c114ae 100644 --- a/tests/gal-regression/native/kicad_stubs.cpp +++ b/tests/gal-regression/native/kicad_stubs.cpp @@ -267,3 +267,86 @@ namespace KIFONT { void FONT::Draw(KIGFX::GAL*, const wxString&, const VECTOR2I&, const VECTOR2I&, const TEXT_ATTRIBUTES&, const METRICS&) const {} } // namespace KIFONT + +//============================================================================= +// BITMAP_BASE stubs for DrawBitmap testing +// Only define symbols NOT already inline in bitmap_base.h +//============================================================================= + +#include + +// Constructor - not inline in header +BITMAP_BASE::BITMAP_BASE( const VECTOR2I& pos ) +{ + m_scale = 1.0; + m_imageType = wxBITMAP_TYPE_PNG; + m_bitmap = nullptr; + m_image = nullptr; + m_originalImage = nullptr; + // Use 91 PPI to match screen DPI - makes 1 bitmap pixel = 1 screen pixel + // (worldUnitLength = 1/91, so scale = 1/(91 * 1/91) = 1) + m_ppi = 91; + m_pixelSizeIu = 254000.0 / m_ppi; + m_isMirroredX = false; + m_isMirroredY = false; + m_rotation = ANGLE_0; +} + +// SetImage - not inline in header (declared, not defined) +bool BITMAP_BASE::SetImage( const wxImage& aImage ) +{ + delete m_image; + delete m_originalImage; + delete m_bitmap; + + m_image = new wxImage( aImage ); + m_originalImage = new wxImage( aImage ); + m_bitmap = new wxBitmap( *m_image ); + m_imageType = wxBITMAP_TYPE_PNG; + return true; +} + +//============================================================================= +// KIFONT::STROKE_GLYPH stubs for DrawGlyph testing +// Only define symbols NOT already inline in glyph.h +//============================================================================= + +#include + +namespace KIFONT { + +// Copy constructor - not inline +STROKE_GLYPH::STROKE_GLYPH( const STROKE_GLYPH& aGlyph ) +{ + reserve( aGlyph.size() ); + for( const std::vector& pointList : aGlyph ) + push_back( pointList ); + m_boundingBox = aGlyph.m_boundingBox; + m_penIsDown = false; +} + +// AddPoint - not inline +void STROKE_GLYPH::AddPoint( const VECTOR2D& aPoint ) +{ + if( !m_penIsDown ) + { + emplace_back(); + back().reserve( 16 ); + m_penIsDown = true; + } + back().push_back( aPoint ); +} + +// RaisePen - not inline +void STROKE_GLYPH::RaisePen() +{ + m_penIsDown = false; +} + +// Finalize - not inline +void STROKE_GLYPH::Finalize() +{ + // No-op for test stub +} + +} // namespace KIFONT diff --git a/tests/gal-regression/native/kifont_stub.h b/tests/gal-regression/native/kifont_stub.h new file mode 100644 index 0000000..4e380f7 --- /dev/null +++ b/tests/gal-regression/native/kifont_stub.h @@ -0,0 +1,176 @@ +/** + * KIFONT Helpers for GAL Test + * + * Provides factory functions to create STROKE_GLYPH instances for testing + * DrawGlyph() and DrawGlyphs() APIs. + * + * Uses KiCad's actual KIFONT::GLYPH and KIFONT::STROKE_GLYPH classes from + * font/glyph.h. This file only provides helper functions to create test glyphs. + * + * STROKE_GLYPH inherits from std::vector>, where each + * inner vector represents a stroke path (pen down to pen up). + */ + +#ifndef KIFONT_STUB_H +#define KIFONT_STUB_H + +#include +#include +#include +#include +#include + +namespace KIFONT +{ + +/** + * Helper function to create a stroke glyph from raw polyline data + */ +inline std::unique_ptr MakeStrokeGlyph( + const std::vector>& aStrokes) +{ + auto glyph = std::make_unique(); + + for (const auto& stroke : aStrokes) + { + for (size_t i = 0; i < stroke.size(); i++) + { + glyph->AddPoint(stroke[i]); + } + glyph->RaisePen(); + } + + glyph->Finalize(); + return glyph; +} + + +/** + * Create letter "F" as a stroke glyph + */ +inline std::unique_ptr MakeLetterF(double scale = 1.0, VECTOR2D offset = VECTOR2D(0, 0)) +{ + std::vector> strokes = { + // Vertical stroke + { VECTOR2D(0, 0) * scale + offset, VECTOR2D(0, 100) * scale + offset }, + // Top horizontal + { VECTOR2D(0, 0) * scale + offset, VECTOR2D(60, 0) * scale + offset }, + // Middle horizontal + { VECTOR2D(0, 40) * scale + offset, VECTOR2D(40, 40) * scale + offset } + }; + return MakeStrokeGlyph(strokes); +} + + +/** + * Create letter "L" as a stroke glyph + */ +inline std::unique_ptr MakeLetterL(double scale = 1.0, VECTOR2D offset = VECTOR2D(0, 0)) +{ + std::vector> strokes = { + // Vertical stroke + { VECTOR2D(0, 0) * scale + offset, VECTOR2D(0, 100) * scale + offset }, + // Bottom horizontal + { VECTOR2D(0, 100) * scale + offset, VECTOR2D(50, 100) * scale + offset } + }; + return MakeStrokeGlyph(strokes); +} + + +/** + * Create letter "K" as a stroke glyph + */ +inline std::unique_ptr MakeLetterK(double scale = 1.0, VECTOR2D offset = VECTOR2D(0, 0)) +{ + std::vector> strokes = { + // Vertical stroke + { VECTOR2D(0, 0) * scale + offset, VECTOR2D(0, 100) * scale + offset }, + // Upper diagonal + { VECTOR2D(0, 50) * scale + offset, VECTOR2D(50, 0) * scale + offset }, + // Lower diagonal + { VECTOR2D(0, 50) * scale + offset, VECTOR2D(50, 100) * scale + offset } + }; + return MakeStrokeGlyph(strokes); +} + + +/** + * Create letter "I" as a stroke glyph + */ +inline std::unique_ptr MakeLetterI(double scale = 1.0, VECTOR2D offset = VECTOR2D(0, 0)) +{ + std::vector> strokes = { + // Top horizontal + { VECTOR2D(-20, 0) * scale + offset, VECTOR2D(20, 0) * scale + offset }, + // Vertical stroke + { VECTOR2D(0, 0) * scale + offset, VECTOR2D(0, 100) * scale + offset }, + // Bottom horizontal + { VECTOR2D(-20, 100) * scale + offset, VECTOR2D(20, 100) * scale + offset } + }; + return MakeStrokeGlyph(strokes); +} + + +/** + * Create letter "C" as a stroke glyph + */ +inline std::unique_ptr MakeLetterC(double scale = 1.0, VECTOR2D offset = VECTOR2D(0, 0)) +{ + // Approximate C with connected line segments + std::vector> strokes = { + { + VECTOR2D(50, 10) * scale + offset, + VECTOR2D(30, 0) * scale + offset, + VECTOR2D(10, 10) * scale + offset, + VECTOR2D(0, 30) * scale + offset, + VECTOR2D(0, 70) * scale + offset, + VECTOR2D(10, 90) * scale + offset, + VECTOR2D(30, 100) * scale + offset, + VECTOR2D(50, 90) * scale + offset + } + }; + return MakeStrokeGlyph(strokes); +} + + +/** + * Create letter "A" as a stroke glyph + */ +inline std::unique_ptr MakeLetterA(double scale = 1.0, VECTOR2D offset = VECTOR2D(0, 0)) +{ + std::vector> strokes = { + // Left diagonal + { VECTOR2D(0, 100) * scale + offset, VECTOR2D(30, 0) * scale + offset }, + // Right diagonal + { VECTOR2D(30, 0) * scale + offset, VECTOR2D(60, 100) * scale + offset }, + // Crossbar + { VECTOR2D(15, 60) * scale + offset, VECTOR2D(45, 60) * scale + offset } + }; + return MakeStrokeGlyph(strokes); +} + + +/** + * Create letter "D" as a stroke glyph + */ +inline std::unique_ptr MakeLetterD(double scale = 1.0, VECTOR2D offset = VECTOR2D(0, 0)) +{ + std::vector> strokes = { + // Vertical stroke + { VECTOR2D(0, 0) * scale + offset, VECTOR2D(0, 100) * scale + offset }, + // Curved part (approximated) + { + VECTOR2D(0, 0) * scale + offset, + VECTOR2D(30, 0) * scale + offset, + VECTOR2D(50, 20) * scale + offset, + VECTOR2D(50, 80) * scale + offset, + VECTOR2D(30, 100) * scale + offset, + VECTOR2D(0, 100) * scale + offset + } + }; + return MakeStrokeGlyph(strokes); +} + +} // namespace KIFONT + +#endif // KIFONT_STUB_H diff --git a/tests/gal-regression/scenarios/gal_test_scenarios.cpp b/tests/gal-regression/scenarios/gal_test_scenarios.cpp index 3771096..6038281 100644 --- a/tests/gal-regression/scenarios/gal_test_scenarios.cpp +++ b/tests/gal-regression/scenarios/gal_test_scenarios.cpp @@ -10,6 +10,19 @@ * - scenario_arc_segments.cpp (12) * - scenario_segment_chain.cpp (13) * - scenario_group_caching.cpp (14) + * - scenario_polylines_multi.cpp (15) + * - scenario_hole_walls.cpp (16) + * - scenario_grid_native.cpp (17) + * - scenario_cursor_native.cpp (18) + * - scenario_render_targets.cpp (19) + * - scenario_screen_transform.cpp (20) + * - scenario_clear_colors.cpp (21) + * - scenario_depth_testing.cpp (22) + * - scenario_negative_mode.cpp (23) + * - scenario_text_attrs.cpp (24) + * - scenario_glyphs.cpp (25) + * - scenario_bitmap.cpp (26) + * - scenario_transform.cpp (27) */ #include "gal_test_scenarios.h" @@ -37,8 +50,21 @@ void RenderBezierCurves(GAL* gal, int width, int height); void RenderArcSegments(GAL* gal, int width, int height); void RenderSegmentChain(GAL* gal, int width, int height); void RenderGroupCaching(GAL* gal, int width, int height); +void RenderPolylinesMulti(GAL* gal, int width, int height); +void RenderHoleWalls(GAL* gal, int width, int height); +void RenderGridNative(GAL* gal, int width, int height); +void RenderCursorNative(GAL* gal, int width, int height); +void RenderRenderTargets(GAL* gal, int width, int height); +void RenderScreenTransform(GAL* gal, int width, int height); +void RenderClearColors(GAL* gal, int width, int height); +void RenderDepthTesting(GAL* gal, int width, int height); +void RenderNegativeMode(GAL* gal, int width, int height); +void RenderTextAttrs(GAL* gal, int width, int height); +void RenderGlyphs(GAL* gal, int width, int height); +void RenderBitmap(GAL* gal, int width, int height); +void RenderTransformAPI(GAL* gal, int width, int height); -// Scenario names - original 11 + 4 new scenarios +// Scenario names - original 11 + 17 new scenarios static const char* SCENARIO_NAMES[] = { // Original scenarios (0-10) "basic-lines", @@ -52,11 +78,25 @@ static const char* SCENARIO_NAMES[] = { "grid-cursor", "segments", "complex-scene", - // New scenarios (11-14) - defined in separate files + // New scenarios (11-23) - defined in separate files "bezier-curves", "arc-segments", "segment-chain", - "group-caching" + "group-caching", + "polylines-multi", + "hole-walls", + "grid-native", + "cursor-native", + "render-targets", + "screen-transform", + "clear-colors", + "depth-testing", + "negative-mode", + // Additional scenarios (24-27) - defined in separate files + "text-attrs", + "glyphs", + "bitmap", + "transform-api" }; static const int SCENARIO_COUNT = sizeof(SCENARIO_NAMES) / sizeof(SCENARIO_NAMES[0]); @@ -111,17 +151,18 @@ static void RenderBasicLines(KIGFX::GAL* gal, int width, int height) { } } -// Scenario 1: Line widths +// Scenario 1: Line widths (tests SetLineWidth and SetMinLineWidth) static void RenderLineWidths(KIGFX::GAL* gal, int width, int height) { double widths[] = {0.5, 1.0, 2.0, 3.0, 5.0, 8.0, 12.0}; int count = sizeof(widths) / sizeof(widths[0]); double margin = 50.0; - double spacing = (height - 2 * margin) / (count + 1); + double spacing = (height - 2 * margin) / (count + 3); // +3 for min width demos gal->SetIsFill(false); gal->SetIsStroke(true); + // First section: Normal line widths for (int i = 0; i < count; i++) { double y = margin + (i + 1) * spacing; @@ -129,8 +170,35 @@ static void RenderLineWidths(KIGFX::GAL* gal, int width, int height) { double t = (double)i / (count - 1); gal->SetStrokeColor(COLOR4D(1.0 - t * 0.5, 0.3 + t * 0.4, 0.2 + t * 0.6, 1.0)); gal->SetLineWidth(widths[i]); - gal->DrawLine(VECTOR2D(margin, y), VECTOR2D(width - margin, y)); + gal->DrawLine(VECTOR2D(margin, y), VECTOR2D(width * 0.45, y)); } + + // Second section: Test SetMinLineWidth + // Lines with width 0.5 but different min line widths + double baseY = margin + (count + 1) * spacing; + + // Very thin line (0.1) without min width - may be invisible + gal->SetMinLineWidth(0.0); // No minimum + gal->SetLineWidth(0.1); + gal->SetStrokeColor(COLOR4D(1.0, 0.3, 0.3, 1.0)); + gal->DrawLine(VECTOR2D(width * 0.55, baseY), VECTOR2D(width - margin, baseY)); + + // Very thin line (0.1) with min width 1.0 - should be visible + baseY += spacing; + gal->SetMinLineWidth(1.0); // Minimum 1 pixel + gal->SetLineWidth(0.1); + gal->SetStrokeColor(COLOR4D(0.3, 1.0, 0.3, 1.0)); + gal->DrawLine(VECTOR2D(width * 0.55, baseY), VECTOR2D(width - margin, baseY)); + + // Very thin line (0.1) with min width 3.0 - should be thicker + baseY += spacing; + gal->SetMinLineWidth(3.0); // Minimum 3 pixels + gal->SetLineWidth(0.1); + gal->SetStrokeColor(COLOR4D(0.3, 0.3, 1.0, 1.0)); + gal->DrawLine(VECTOR2D(width * 0.55, baseY), VECTOR2D(width - margin, baseY)); + + // Reset min line width + gal->SetMinLineWidth(0.0); } // Scenario 2: Circles @@ -360,7 +428,8 @@ static void RenderAlphaBlending(KIGFX::GAL* gal, int width, int height) { } } -// Scenario 7: Transforms +// Scenario 7: Transforms (tests Translate, Rotate, Scale, Save/Restore) +// NOTE: Transform() with MATRIX3x3D doesn't work with OPENGL_GAL's shader pipeline static void RenderTransforms(KIGFX::GAL* gal, int width, int height) { double cx = width / 2.0; double cy = height / 2.0; @@ -491,7 +560,7 @@ static void RenderSegments(KIGFX::GAL* gal, int width, int height) { VECTOR2D(width / 2.0, height - margin - 150), 15.0); } -// Scenario 10: Complex scene (PCB-like) +// Scenario 10: Complex scene (PCB-like, tests SetLayerDepth and AdvanceDepth) static void RenderComplexScene(KIGFX::GAL* gal, int width, int height) { // Use layer depths to ensure proper z-ordering // Lower depth = closer to camera (drawn on top with GL_LESS) @@ -560,6 +629,35 @@ static void RenderComplexScene(KIGFX::GAL* gal, int width, int height) { gal->SetStrokeColor(COLOR4D(1.0, 1.0, 1.0, 1.0)); gal->DrawRectangle(VECTOR2D(width / 2 - 20, height / 2 - 15), VECTOR2D(width / 2 + 20, height / 2 + 15)); + + // Demonstrate AdvanceDepth() - auto-incrementing depth + // Draw a stack of overlapping circles using AdvanceDepth + gal->SetLayerDepth(80); // Start at depth 80 + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Each call to AdvanceDepth moves closer to camera (decrements depth) + double stackX = width - 120; + double stackY = height - 120; + + // First circle (deepest in stack) + gal->SetFillColor(COLOR4D(0.8, 0.2, 0.2, 0.9)); + gal->DrawCircle(VECTOR2D(stackX, stackY), 35); + gal->AdvanceDepth(); // Move closer to camera + + // Second circle + gal->SetFillColor(COLOR4D(0.2, 0.8, 0.2, 0.9)); + gal->DrawCircle(VECTOR2D(stackX + 15, stackY - 10), 30); + gal->AdvanceDepth(); + + // Third circle + gal->SetFillColor(COLOR4D(0.2, 0.2, 0.8, 0.9)); + gal->DrawCircle(VECTOR2D(stackX + 30, stackY - 20), 25); + gal->AdvanceDepth(); + + // Fourth circle (closest to camera) + gal->SetFillColor(COLOR4D(0.8, 0.8, 0.2, 0.9)); + gal->DrawCircle(VECTOR2D(stackX + 45, stackY - 30), 20); } //============================================================================= @@ -580,11 +678,25 @@ void RenderScenario(KIGFX::GAL* gal, int index, int width, int height) { case 8: RenderGridCursor(gal, width, height); break; case 9: RenderSegments(gal, width, height); break; case 10: RenderComplexScene(gal, width, height); break; - // New scenarios (11-14) - defined in separate files + // New scenarios (11-20) - defined in separate files case 11: RenderBezierCurves(gal, width, height); break; case 12: RenderArcSegments(gal, width, height); break; case 13: RenderSegmentChain(gal, width, height); break; case 14: RenderGroupCaching(gal, width, height); break; + case 15: RenderPolylinesMulti(gal, width, height); break; + case 16: RenderHoleWalls(gal, width, height); break; + case 17: RenderGridNative(gal, width, height); break; + case 18: RenderCursorNative(gal, width, height); break; + case 19: RenderRenderTargets(gal, width, height); break; + case 20: RenderScreenTransform(gal, width, height); break; + case 21: RenderClearColors(gal, width, height); break; + case 22: RenderDepthTesting(gal, width, height); break; + case 23: RenderNegativeMode(gal, width, height); break; + // Additional scenarios (24-27) - defined in separate files + case 24: RenderTextAttrs(gal, width, height); break; + case 25: RenderGlyphs(gal, width, height); break; + case 26: RenderBitmap(gal, width, height); break; + case 27: RenderTransformAPI(gal, width, height); break; default: break; } } diff --git a/tests/gal-regression/scenarios/scenario_bitmap.cpp b/tests/gal-regression/scenarios/scenario_bitmap.cpp new file mode 100644 index 0000000..be4a101 --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_bitmap.cpp @@ -0,0 +1,332 @@ +/** + * Bitmap Scenario + * + * Tests GAL DrawBitmap() method using BITMAP_BASE with various test patterns. + * + * DrawBitmap() renders a raster image centered at the current transformation origin. + * In OPENGL_GAL, it uses GL_BITMAP_CACHE to create GPU textures from wxImage data. + * Position is controlled via Save/Translate/Restore, not by arguments to DrawBitmap. + * + * This scenario demonstrates: + * 1. Basic bitmap rendering with checkerboard pattern + * 2. Gradient patterns (horizontal, vertical, radial) + * 3. Custom KiCad logo-style pattern + * 4. Different bitmap sizes + * 5. Multiple bitmaps in a scene + */ + +#include +#include "../native/bitmap_base_stub.h" +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderBitmap(GAL* gal, int width, int height) { + gal->SetLayerDepth(100); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background + gal->SetFillColor(COLOR4D(0.1, 0.1, 0.12, 1.0)); + gal->DrawRectangle(VECTOR2D(0, 0), VECTOR2D(width, height)); + + //========================================================================= + // Section 1: Basic checkerboard bitmap + //========================================================================= + gal->SetLayerDepth(50); + + // Create checkerboard bitmap (64x64) + auto checkerboard = CreateCheckerboardBitmap(64, 64, 8); + + // Position bitmap at (80, 80) - use transform + gal->Save(); + gal->Translate(VECTOR2D(100, 100)); // Center position + gal->DrawBitmap(*checkerboard, 1.0); + gal->Restore(); + + // Section label frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(20, 20), VECTOR2D(180, 180)); + + //========================================================================= + // Section 2: Gradient bitmap (horizontal) + //========================================================================= + gal->SetLayerDepth(50); + + auto gradient = CreateGradientBitmap(80, 60); + + gal->Save(); + gal->Translate(VECTOR2D(300, 100)); + gal->DrawBitmap(*gradient, 1.0); + gal->Restore(); + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.5, 0.4, 0.4, 0.8)); + gal->DrawRectangle(VECTOR2D(200, 20), VECTOR2D(400, 180)); + + //========================================================================= + // Section 3: KiCad logo pattern + //========================================================================= + gal->SetLayerDepth(50); + + auto logo = CreateKiCadLogoBitmap(80, 80); + + gal->Save(); + gal->Translate(VECTOR2D(520, 100)); + gal->DrawBitmap(*logo, 1.0); + gal->Restore(); + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.4, 0.5, 0.3, 0.8)); + gal->DrawRectangle(VECTOR2D(420, 20), VECTOR2D(620, 180)); + + //========================================================================= + // Section 4: Radial gradient + //========================================================================= + gal->SetLayerDepth(50); + + auto radial = CreateRadialBitmap(64, 64); + + gal->Save(); + gal->Translate(VECTOR2D(720, 100)); + gal->DrawBitmap(*radial, 1.0); + gal->Restore(); + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.4, 0.4, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(640, 20), VECTOR2D(800, 180)); + + //========================================================================= + // Section 5: Different bitmap sizes + //========================================================================= + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background panel + gal->SetFillColor(COLOR4D(0.12, 0.12, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(20, 200), VECTOR2D(380, 380)); + + // Small bitmap (32x32) + auto small = CreateCheckerboardBitmap(32, 32, 4); + gal->Save(); + gal->Translate(VECTOR2D(80, 280)); + gal->DrawBitmap(*small, 1.0); + gal->Restore(); + + // Medium bitmap (64x64) + auto medium = CreateCheckerboardBitmap(64, 64, 8); + gal->Save(); + gal->Translate(VECTOR2D(180, 290)); + gal->DrawBitmap(*medium, 1.0); + gal->Restore(); + + // Large bitmap (96x96) + auto large = CreateCheckerboardBitmap(96, 96, 12); + gal->Save(); + gal->Translate(VECTOR2D(300, 290)); + gal->DrawBitmap(*large, 1.0); + gal->Restore(); + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.4, 0.8)); + gal->DrawRectangle(VECTOR2D(20, 200), VECTOR2D(380, 380)); + + //========================================================================= + // Section 6: Striped patterns + //========================================================================= + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background panel + gal->SetFillColor(COLOR4D(0.15, 0.12, 0.12, 1.0)); + gal->DrawRectangle(VECTOR2D(400, 200), VECTOR2D(600, 380)); + + // Horizontal stripes + auto hStripes = CreateStripedBitmap(64, 64, true); + gal->Save(); + gal->Translate(VECTOR2D(460, 290)); + gal->DrawBitmap(*hStripes, 1.0); + gal->Restore(); + + // Vertical stripes + auto vStripes = CreateStripedBitmap(64, 64, false); + gal->Save(); + gal->Translate(VECTOR2D(550, 290)); + gal->DrawBitmap(*vStripes, 1.0); + gal->Restore(); + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.5, 0.4, 0.4, 0.8)); + gal->DrawRectangle(VECTOR2D(400, 200), VECTOR2D(600, 380)); + + //========================================================================= + // Section 7: Multiple bitmaps composition - colored checkerboards + //========================================================================= + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background panel + gal->SetFillColor(COLOR4D(0.12, 0.15, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(620, 200), VECTOR2D(800, 380)); + + // Create a variety of colored checkerboards in a grid + wxImage img1 = CreateCheckerboardImage(40, 40, 5, 255, 200, 200, 100, 50, 50); + wxImage img2 = CreateCheckerboardImage(40, 40, 5, 200, 255, 200, 50, 100, 50); + wxImage img3 = CreateCheckerboardImage(40, 40, 5, 200, 200, 255, 50, 50, 100); + wxImage img4 = CreateCheckerboardImage(40, 40, 5, 255, 255, 200, 100, 100, 50); + + auto bmp1 = std::make_unique(); bmp1->SetImage(img1); + auto bmp2 = std::make_unique(); bmp2->SetImage(img2); + auto bmp3 = std::make_unique(); bmp3->SetImage(img3); + auto bmp4 = std::make_unique(); bmp4->SetImage(img4); + + gal->Save(); + gal->Translate(VECTOR2D(670, 260)); + gal->DrawBitmap(*bmp1, 1.0); + gal->Restore(); + + gal->Save(); + gal->Translate(VECTOR2D(750, 260)); + gal->DrawBitmap(*bmp2, 1.0); + gal->Restore(); + + gal->Save(); + gal->Translate(VECTOR2D(670, 330)); + gal->DrawBitmap(*bmp3, 1.0); + gal->Restore(); + + gal->Save(); + gal->Translate(VECTOR2D(750, 330)); + gal->DrawBitmap(*bmp4, 1.0); + gal->Restore(); + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.4, 0.5, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(620, 200), VECTOR2D(800, 380)); + + //========================================================================= + // Section 8: Bitmap with surrounding graphics + //========================================================================= + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background panel + gal->SetFillColor(COLOR4D(0.1, 0.12, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(20, 400), VECTOR2D(400, 580)); + + // Central bitmap + auto central = CreateKiCadLogoBitmap(100, 100); + gal->Save(); + gal->Translate(VECTOR2D(210, 490)); + gal->DrawBitmap(*central, 1.0); + gal->Restore(); + + // Decorative circles around bitmap + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + + COLOR4D circleColors[] = { + COLOR4D(0.9, 0.4, 0.4, 0.7), + COLOR4D(0.4, 0.9, 0.4, 0.7), + COLOR4D(0.4, 0.4, 0.9, 0.7), + COLOR4D(0.9, 0.9, 0.4, 0.7) + }; + + for (int i = 0; i < 4; i++) { + gal->SetStrokeColor(circleColors[i]); + double angle = i * M_PI / 2; + double cx = 210 + cos(angle) * 80; + double cy = 490 + sin(angle) * 80; + gal->DrawCircle(VECTOR2D(cx, cy), 15); + } + + // Connecting lines + gal->SetLineWidth(1.5); + gal->SetStrokeColor(COLOR4D(0.6, 0.6, 0.7, 0.5)); + for (int i = 0; i < 4; i++) { + double angle1 = i * M_PI / 2; + double angle2 = ((i + 1) % 4) * M_PI / 2; + VECTOR2D p1(210 + cos(angle1) * 80, 490 + sin(angle1) * 80); + VECTOR2D p2(210 + cos(angle2) * 80, 490 + sin(angle2) * 80); + gal->DrawLine(p1, p2); + } + + // Section frame + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.4, 0.4, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(20, 400), VECTOR2D(400, 580)); + + //========================================================================= + // Section 9: Gradient showcase + //========================================================================= + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background panel + gal->SetFillColor(COLOR4D(0.12, 0.1, 0.12, 1.0)); + gal->DrawRectangle(VECTOR2D(420, 400), VECTOR2D(800, 580)); + + // Horizontal gradient + wxImage hGradImg = CreateGradientHImage(100, 40, 255, 100, 100, 100, 100, 255); + auto hGradBitmap = std::make_unique(); + hGradBitmap->SetImage(hGradImg); + gal->Save(); + gal->Translate(VECTOR2D(520, 450)); + gal->DrawBitmap(*hGradBitmap, 1.0); + gal->Restore(); + + // Vertical gradient + wxImage vGradImg = CreateGradientVImage(100, 40, 100, 255, 100, 100, 100, 255); + auto vGradBitmap = std::make_unique(); + vGradBitmap->SetImage(vGradImg); + gal->Save(); + gal->Translate(VECTOR2D(520, 510)); + gal->DrawBitmap(*vGradBitmap, 1.0); + gal->Restore(); + + // Radial gradient (larger) + wxImage radialImg = CreateRadialGradientImage(80, 80, 255, 255, 100, 100, 50, 150); + auto radialBitmap = std::make_unique(); + radialBitmap->SetImage(radialImg); + gal->Save(); + gal->Translate(VECTOR2D(700, 490)); + gal->DrawBitmap(*radialBitmap, 1.0); + gal->Restore(); + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.5, 0.4, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(420, 400), VECTOR2D(800, 580)); +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_clear_colors.cpp b/tests/gal-regression/scenarios/scenario_clear_colors.cpp new file mode 100644 index 0000000..1adf624 --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_clear_colors.cpp @@ -0,0 +1,169 @@ +/** + * Clear Colors Scenario + * + * Tests GAL::SetClearColor() - background color setting + * + * Note: SetClearColor affects the background when ClearScreen is called. + * Since we can only have one background per frame, this demonstrates + * the concept by drawing colored rectangles to show different clear colors. + */ + +#include +#include + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderClearColors(GAL* gal, int width, int height) { + // Demonstrate SetClearColor by showing what different backgrounds look like + // We simulate this with filled rectangles since we can only clear once per frame + + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetLayerDepth(100); + + double boxW = 180; + double boxH = 120; + double margin = 20; + + // Row 1: Standard backgrounds + // Dark theme + gal->SetFillColor(COLOR4D(0.1, 0.1, 0.12, 1.0)); + gal->DrawRectangle(VECTOR2D(margin, margin), VECTOR2D(margin + boxW, margin + boxH)); + + // Light theme + gal->SetFillColor(COLOR4D(0.95, 0.95, 0.95, 1.0)); + gal->DrawRectangle(VECTOR2D(margin + boxW + 20, margin), + VECTOR2D(margin + boxW * 2 + 20, margin + boxH)); + + // Blue-gray + gal->SetFillColor(COLOR4D(0.15, 0.18, 0.22, 1.0)); + gal->DrawRectangle(VECTOR2D(margin + (boxW + 20) * 2, margin), + VECTOR2D(margin + boxW * 3 + 40, margin + boxH)); + + // Green tint (PCB style) + gal->SetFillColor(COLOR4D(0.08, 0.15, 0.08, 1.0)); + gal->DrawRectangle(VECTOR2D(margin + (boxW + 20) * 3, margin), + VECTOR2D(margin + boxW * 4 + 60, margin + boxH)); + + // Row 2: More color options + double row2Y = margin + boxH + 30; + + // Warm gray + gal->SetFillColor(COLOR4D(0.2, 0.18, 0.16, 1.0)); + gal->DrawRectangle(VECTOR2D(margin, row2Y), VECTOR2D(margin + boxW, row2Y + boxH)); + + // Deep blue + gal->SetFillColor(COLOR4D(0.05, 0.08, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(margin + boxW + 20, row2Y), + VECTOR2D(margin + boxW * 2 + 20, row2Y + boxH)); + + // Pure white + gal->SetFillColor(COLOR4D(1.0, 1.0, 1.0, 1.0)); + gal->DrawRectangle(VECTOR2D(margin + (boxW + 20) * 2, row2Y), + VECTOR2D(margin + boxW * 3 + 40, row2Y + boxH)); + + // Pure black + gal->SetFillColor(COLOR4D(0.0, 0.0, 0.0, 1.0)); + gal->DrawRectangle(VECTOR2D(margin + (boxW + 20) * 3, row2Y), + VECTOR2D(margin + boxW * 4 + 60, row2Y + boxH)); + + // Row 3: Demonstrate content on different backgrounds + double row3Y = row2Y + boxH + 30; + + // Dark background with light content + gal->SetFillColor(COLOR4D(0.1, 0.1, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(margin, row3Y), VECTOR2D(margin + boxW, row3Y + boxH)); + + gal->SetLayerDepth(50); + gal->SetFillColor(COLOR4D(0.8, 0.6, 0.2, 1.0)); + gal->DrawCircle(VECTOR2D(margin + boxW/2 - 30, row3Y + boxH/2), 20); + gal->DrawCircle(VECTOR2D(margin + boxW/2 + 30, row3Y + boxH/2), 20); + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.9, 0.9, 0.2, 1.0)); + gal->DrawRectangle(VECTOR2D(margin + 30, row3Y + 30), VECTOR2D(margin + boxW - 30, row3Y + boxH - 30)); + + // Light background with dark content + gal->SetLayerDepth(100); + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.95, 0.95, 0.92, 1.0)); + gal->DrawRectangle(VECTOR2D(margin + boxW + 20, row3Y), + VECTOR2D(margin + boxW * 2 + 20, row3Y + boxH)); + + gal->SetLayerDepth(50); + gal->SetFillColor(COLOR4D(0.2, 0.2, 0.3, 1.0)); + gal->DrawCircle(VECTOR2D(margin + boxW * 1.5 + 20 - 30, row3Y + boxH/2), 20); + gal->DrawCircle(VECTOR2D(margin + boxW * 1.5 + 20 + 30, row3Y + boxH/2), 20); + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.1, 0.1, 0.2, 1.0)); + gal->DrawRectangle(VECTOR2D(margin + boxW + 50, row3Y + 30), + VECTOR2D(margin + boxW * 2 - 10, row3Y + boxH - 30)); + + // PCB green background + gal->SetLayerDepth(100); + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.05, 0.2, 0.05, 1.0)); + gal->DrawRectangle(VECTOR2D(margin + (boxW + 20) * 2, row3Y), + VECTOR2D(margin + boxW * 3 + 40, row3Y + boxH)); + + gal->SetLayerDepth(50); + gal->SetFillColor(COLOR4D(0.8, 0.6, 0.2, 1.0)); + double pcbX = margin + (boxW + 20) * 2 + boxW/2; + gal->DrawSegment(VECTOR2D(pcbX - 50, row3Y + 40), VECTOR2D(pcbX + 50, row3Y + 40), 6); + gal->DrawSegment(VECTOR2D(pcbX - 50, row3Y + boxH - 40), VECTOR2D(pcbX + 50, row3Y + boxH - 40), 6); + gal->SetFillColor(COLOR4D(0.9, 0.7, 0.3, 1.0)); + gal->DrawCircle(VECTOR2D(pcbX - 30, row3Y + 40), 10); + gal->DrawCircle(VECTOR2D(pcbX + 30, row3Y + 40), 10); + gal->DrawCircle(VECTOR2D(pcbX - 30, row3Y + boxH - 40), 10); + gal->DrawCircle(VECTOR2D(pcbX + 30, row3Y + boxH - 40), 10); + + // Blue schematic background + gal->SetLayerDepth(100); + gal->SetFillColor(COLOR4D(0.9, 0.95, 1.0, 1.0)); + gal->DrawRectangle(VECTOR2D(margin + (boxW + 20) * 3, row3Y), + VECTOR2D(margin + boxW * 4 + 60, row3Y + boxH)); + + gal->SetLayerDepth(50); + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.0, 0.4, 0.0, 1.0)); + double schX = margin + (boxW + 20) * 3 + boxW/2; + gal->DrawLine(VECTOR2D(schX - 60, row3Y + boxH/2), VECTOR2D(schX - 20, row3Y + boxH/2)); + gal->DrawLine(VECTOR2D(schX + 20, row3Y + boxH/2), VECTOR2D(schX + 60, row3Y + boxH/2)); + gal->DrawRectangle(VECTOR2D(schX - 20, row3Y + boxH/2 - 25), VECTOR2D(schX + 20, row3Y + boxH/2 + 25)); + gal->SetStrokeColor(COLOR4D(0.8, 0.0, 0.0, 1.0)); + gal->DrawCircle(VECTOR2D(schX - 60, row3Y + boxH/2), 5); + gal->DrawCircle(VECTOR2D(schX + 60, row3Y + boxH/2), 5); + + // Row 4: Test actual SetClearColor API (affects next frame) + double row4Y = row3Y + boxH + 30; + gal->SetLayerDepth(100); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Show the API being called (visual marker) + gal->SetFillColor(COLOR4D(0.2, 0.2, 0.25, 1.0)); + gal->DrawRectangle(VECTOR2D(margin, row4Y), VECTOR2D(width - margin, row4Y + 60)); + + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.6, 0.8)); + gal->DrawRectangle(VECTOR2D(margin, row4Y), VECTOR2D(width - margin, row4Y + 60)); + + // Demonstrate SetClearColor API call + COLOR4D clearColor(0.1, 0.1, 0.15, 1.0); + gal->SetClearColor(clearColor); + // Note: The clear color will be used on next ClearScreen() call +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_cursor_native.cpp b/tests/gal-regression/scenarios/scenario_cursor_native.cpp new file mode 100644 index 0000000..ad86634 --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_cursor_native.cpp @@ -0,0 +1,173 @@ +/** + * Cursor Native Scenario + * + * Tests GAL cursor-related methods: + * - SetCursorEnabled() / IsCursorEnabled() + * - SetCursorColor() + * - DrawCursor() + * + * The cursor is typically a crosshair drawn at a specific location. + * This scenario demonstrates different cursor styles and colors. + */ + +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderCursorNative(GAL* gal, int width, int height) { + // Background + gal->SetLayerDepth(100); + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.12, 0.12, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(0, 0), VECTOR2D(width, height)); + + // Draw a grid to give context for cursor positions + gal->SetLayerDepth(90); + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(0.25, 0.25, 0.3, 0.5)); + + for (int x = 0; x < width; x += 40) { + gal->DrawLine(VECTOR2D(x, 0), VECTOR2D(x, height)); + } + for (int y = 0; y < height; y += 40) { + gal->DrawLine(VECTOR2D(0, y), VECTOR2D(width, y)); + } + + // Test 1: Default cursor (white) + gal->SetLayerDepth(10); + gal->SetCursorEnabled(true); + gal->SetCursorColor(COLOR4D(1.0, 1.0, 1.0, 1.0)); + gal->DrawCursor(VECTOR2D(100, 100)); + + // Label + gal->SetStrokeColor(COLOR4D(0.6, 0.6, 0.6, 1.0)); + gal->SetLineWidth(1.0); + gal->DrawRectangle(VECTOR2D(60, 60), VECTOR2D(140, 70)); + + // Test 2: Red cursor + gal->SetCursorColor(COLOR4D(1.0, 0.3, 0.3, 1.0)); + gal->DrawCursor(VECTOR2D(250, 100)); + + gal->SetStrokeColor(COLOR4D(0.6, 0.6, 0.6, 1.0)); + gal->DrawRectangle(VECTOR2D(210, 60), VECTOR2D(290, 70)); + + // Test 3: Green cursor + gal->SetCursorColor(COLOR4D(0.3, 1.0, 0.3, 1.0)); + gal->DrawCursor(VECTOR2D(400, 100)); + + gal->SetStrokeColor(COLOR4D(0.6, 0.6, 0.6, 1.0)); + gal->DrawRectangle(VECTOR2D(360, 60), VECTOR2D(440, 70)); + + // Test 4: Blue cursor + gal->SetCursorColor(COLOR4D(0.3, 0.3, 1.0, 1.0)); + gal->DrawCursor(VECTOR2D(550, 100)); + + gal->SetStrokeColor(COLOR4D(0.6, 0.6, 0.6, 1.0)); + gal->DrawRectangle(VECTOR2D(510, 60), VECTOR2D(590, 70)); + + // Test 5: Yellow cursor (selection color) + gal->SetCursorColor(COLOR4D(1.0, 1.0, 0.2, 1.0)); + gal->DrawCursor(VECTOR2D(700, 100)); + + gal->SetStrokeColor(COLOR4D(0.6, 0.6, 0.6, 1.0)); + gal->DrawRectangle(VECTOR2D(660, 60), VECTOR2D(740, 70)); + + // Test 6: Semi-transparent cursors + gal->SetCursorColor(COLOR4D(1.0, 1.0, 1.0, 0.3)); + gal->DrawCursor(VECTOR2D(100, 250)); + + gal->SetCursorColor(COLOR4D(1.0, 1.0, 1.0, 0.5)); + gal->DrawCursor(VECTOR2D(200, 250)); + + gal->SetCursorColor(COLOR4D(1.0, 1.0, 1.0, 0.7)); + gal->DrawCursor(VECTOR2D(300, 250)); + + gal->SetCursorColor(COLOR4D(1.0, 1.0, 1.0, 1.0)); + gal->DrawCursor(VECTOR2D(400, 250)); + + // Label for alpha row + gal->SetStrokeColor(COLOR4D(0.6, 0.6, 0.6, 1.0)); + gal->DrawRectangle(VECTOR2D(60, 210), VECTOR2D(440, 220)); + + // Test 7: Cursor positions along a path + gal->SetCursorColor(COLOR4D(0.8, 0.5, 0.2, 1.0)); + for (int i = 0; i < 8; i++) { + double t = (double)i / 7.0; + double x = 100 + t * 600; + double y = 380 + sin(t * M_PI * 2) * 50; + gal->DrawCursor(VECTOR2D(x, y)); + } + + // Draw the path itself + gal->SetStrokeColor(COLOR4D(0.4, 0.4, 0.4, 0.5)); + gal->SetLineWidth(1.0); + std::vector path; + for (int i = 0; i <= 50; i++) { + double t = (double)i / 50.0; + double x = 100 + t * 600; + double y = 380 + sin(t * M_PI * 2) * 50; + path.push_back(VECTOR2D(x, y)); + } + gal->DrawPolyline(path); + + // Test 8: Cursor with different context - on objects + // Draw some objects + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Pad + gal->SetFillColor(COLOR4D(0.8, 0.6, 0.2, 1.0)); + gal->DrawCircle(VECTOR2D(600, 250), 30); + + // Trace + gal->DrawSegment(VECTOR2D(550, 250), VECTOR2D(650, 250), 8); + + // Cursor on the pad + gal->SetLayerDepth(5); + gal->SetCursorColor(COLOR4D(1.0, 1.0, 1.0, 1.0)); + gal->DrawCursor(VECTOR2D(600, 250)); + + // Test 9: Multiple cursors showing cursor enabled/disabled + gal->SetLayerDepth(5); + + // Enabled cursor + gal->SetCursorEnabled(true); + gal->SetCursorColor(COLOR4D(0.3, 1.0, 0.3, 1.0)); + gal->DrawCursor(VECTOR2D(700, 350)); + + // Label + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.3, 0.8, 0.3, 0.8)); + gal->SetLineWidth(1.0); + gal->DrawRectangle(VECTOR2D(660, 310), VECTOR2D(740, 320)); + + // "Disabled" cursor (we still draw it but with different color to show the state) + // Note: SetCursorEnabled(false) would prevent DrawCursor from rendering + // So we show it as a dimmed cursor instead + gal->SetCursorColor(COLOR4D(0.5, 0.5, 0.5, 0.3)); + gal->DrawCursor(VECTOR2D(700, 450)); + + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.5, 0.5)); + gal->DrawRectangle(VECTOR2D(660, 410), VECTOR2D(740, 420)); + + // Border around entire test area + gal->SetLayerDepth(1); + gal->SetStrokeColor(COLOR4D(0.4, 0.4, 0.4, 1.0)); + gal->SetLineWidth(2.0); + gal->DrawRectangle(VECTOR2D(10, 10), VECTOR2D(width - 10, height - 10)); +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_depth_testing.cpp b/tests/gal-regression/scenarios/scenario_depth_testing.cpp new file mode 100644 index 0000000..41aa9dc --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_depth_testing.cpp @@ -0,0 +1,200 @@ +/** + * Depth Testing Scenario + * + * Tests GAL::EnableDepthTest() - explicit depth test control + * + * Depth testing determines whether fragments are drawn based on their + * depth value. When enabled with GL_LESS, closer fragments overwrite + * farther ones. This test demonstrates depth ordering with overlapping shapes. + */ + +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderDepthTesting(GAL* gal, int width, int height) { + // Enable depth testing + gal->EnableDepthTest(true); + + // Test SetDepthRange() API - sets the near/far depth range for rendering + // x = near, y = far - this maps layer depths to NDC z-values + // Default is typically VECTOR2D(0.1, 100) for KiCad + gal->SetDepthRange(VECTOR2D(0.1, 100)); + + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Section 1: Overlapping circles with explicit depth ordering + // Using SetLayerDepth - lower values are closer to camera + + // Background reference + gal->SetLayerDepth(100); + gal->SetFillColor(COLOR4D(0.15, 0.15, 0.18, 1.0)); + gal->DrawRectangle(VECTOR2D(20, 20), VECTOR2D(280, 200)); + + // Draw circles from back to front + gal->SetLayerDepth(80); + gal->SetFillColor(COLOR4D(0.8, 0.2, 0.2, 0.9)); + gal->DrawCircle(VECTOR2D(100, 100), 50); + + gal->SetLayerDepth(60); + gal->SetFillColor(COLOR4D(0.2, 0.8, 0.2, 0.9)); + gal->DrawCircle(VECTOR2D(140, 110), 50); + + gal->SetLayerDepth(40); + gal->SetFillColor(COLOR4D(0.2, 0.2, 0.8, 0.9)); + gal->DrawCircle(VECTOR2D(180, 100), 50); + + gal->SetLayerDepth(20); + gal->SetFillColor(COLOR4D(0.8, 0.8, 0.2, 0.9)); + gal->DrawCircle(VECTOR2D(220, 90), 50); + + // Section 2: Depth ordering with rectangles + gal->SetLayerDepth(100); + gal->SetFillColor(COLOR4D(0.15, 0.15, 0.18, 1.0)); + gal->DrawRectangle(VECTOR2D(300, 20), VECTOR2D(560, 200)); + + // Stack of rectangles + for (int i = 0; i < 5; i++) { + double t = (double)i / 4.0; + gal->SetLayerDepth(90 - i * 15); + gal->SetFillColor(COLOR4D(0.3 + t * 0.5, 0.3 + t * 0.2, 0.8 - t * 0.3, 0.9)); + gal->DrawRectangle( + VECTOR2D(320 + i * 25, 40 + i * 20), + VECTOR2D(420 + i * 25, 120 + i * 20) + ); + } + + // Section 3: Complex depth scene (PCB-like) + gal->SetLayerDepth(100); + gal->SetFillColor(COLOR4D(0.1, 0.2, 0.1, 1.0)); + gal->DrawRectangle(VECTOR2D(580, 20), VECTOR2D(780, 200)); + + // Traces at depth 70 + gal->SetLayerDepth(70); + gal->SetFillColor(COLOR4D(0.7, 0.5, 0.2, 1.0)); + gal->DrawSegment(VECTOR2D(600, 60), VECTOR2D(760, 60), 8); + gal->DrawSegment(VECTOR2D(600, 110), VECTOR2D(760, 110), 8); + gal->DrawSegment(VECTOR2D(600, 160), VECTOR2D(760, 160), 8); + + // Pads at depth 50 (above traces) + gal->SetLayerDepth(50); + gal->SetFillColor(COLOR4D(0.85, 0.65, 0.25, 1.0)); + gal->DrawCircle(VECTOR2D(620, 60), 15); + gal->DrawCircle(VECTOR2D(680, 60), 15); + gal->DrawCircle(VECTOR2D(740, 60), 15); + gal->DrawCircle(VECTOR2D(620, 110), 15); + gal->DrawCircle(VECTOR2D(680, 110), 15); + gal->DrawCircle(VECTOR2D(740, 110), 15); + gal->DrawCircle(VECTOR2D(620, 160), 15); + gal->DrawCircle(VECTOR2D(680, 160), 15); + gal->DrawCircle(VECTOR2D(740, 160), 15); + + // Holes at depth 30 (above pads) + gal->SetLayerDepth(30); + gal->SetFillColor(COLOR4D(0.1, 0.1, 0.1, 1.0)); + gal->DrawCircle(VECTOR2D(620, 60), 6); + gal->DrawCircle(VECTOR2D(680, 60), 6); + gal->DrawCircle(VECTOR2D(740, 60), 6); + gal->DrawCircle(VECTOR2D(620, 110), 6); + gal->DrawCircle(VECTOR2D(680, 110), 6); + gal->DrawCircle(VECTOR2D(740, 110), 6); + gal->DrawCircle(VECTOR2D(620, 160), 6); + gal->DrawCircle(VECTOR2D(680, 160), 6); + gal->DrawCircle(VECTOR2D(740, 160), 6); + + // Section 4: Interleaved depths + gal->SetLayerDepth(100); + gal->SetFillColor(COLOR4D(0.18, 0.15, 0.18, 1.0)); + gal->DrawRectangle(VECTOR2D(20, 220), VECTOR2D(280, 400)); + + // Create a checkerboard-like depth pattern + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 3; col++) { + int depth = ((row + col) % 2 == 0) ? 60 : 40; + double t = (row * 3 + col) / 8.0; + gal->SetLayerDepth(depth); + gal->SetFillColor(COLOR4D(0.8 * t + 0.2, 0.3 + 0.5 * (1 - t), 0.5, 0.9)); + gal->DrawCircle(VECTOR2D(70 + col * 70, 270 + row * 50), 25); + } + } + + // Section 5: AdvanceDepth demonstration + gal->SetLayerDepth(100); + gal->SetFillColor(COLOR4D(0.15, 0.18, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(300, 220), VECTOR2D(560, 400)); + + // Use AdvanceDepth to automatically increment depth + gal->SetLayerDepth(90); + double cx = 430; + double cy = 310; + + for (int i = 0; i < 8; i++) { + double angle = i * M_PI / 4; + double r = 60; + double x = cx + cos(angle) * r; + double y = cy + sin(angle) * r; + + double t = (double)i / 7.0; + gal->SetFillColor(COLOR4D(1.0 - t * 0.5, 0.3 + t * 0.4, 0.3 + t * 0.5, 0.9)); + gal->DrawCircle(VECTOR2D(x, y), 30); + gal->AdvanceDepth(); // Each subsequent circle is closer + } + + // Center circle (closest) + gal->SetFillColor(COLOR4D(1.0, 1.0, 1.0, 0.95)); + gal->DrawCircle(VECTOR2D(cx, cy), 25); + + // Section 6: Depth test off comparison + // This section shows what happens without proper depth ordering + gal->SetLayerDepth(100); + gal->SetFillColor(COLOR4D(0.18, 0.18, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(580, 220), VECTOR2D(780, 400)); + + // All at same depth - draw order determines visibility + gal->SetLayerDepth(50); + + gal->SetFillColor(COLOR4D(0.8, 0.2, 0.2, 0.9)); + gal->DrawCircle(VECTOR2D(640, 300), 40); + + gal->SetFillColor(COLOR4D(0.2, 0.8, 0.2, 0.9)); + gal->DrawCircle(VECTOR2D(680, 310), 40); + + gal->SetFillColor(COLOR4D(0.2, 0.2, 0.8, 0.9)); + gal->DrawCircle(VECTOR2D(720, 300), 40); + + // Labels/frames + gal->SetLayerDepth(10); + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + + gal->SetStrokeColor(COLOR4D(0.4, 0.4, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(20, 20), VECTOR2D(280, 200)); + gal->DrawRectangle(VECTOR2D(300, 20), VECTOR2D(560, 200)); + gal->DrawRectangle(VECTOR2D(580, 20), VECTOR2D(780, 200)); + gal->DrawRectangle(VECTOR2D(20, 220), VECTOR2D(280, 400)); + gal->DrawRectangle(VECTOR2D(300, 220), VECTOR2D(560, 400)); + gal->DrawRectangle(VECTOR2D(580, 220), VECTOR2D(780, 400)); + + // Bottom info bar + gal->SetLayerDepth(100); + gal->SetIsFill(true); + gal->SetFillColor(COLOR4D(0.12, 0.12, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(20, 420), VECTOR2D(780, 480)); + + gal->SetIsFill(false); + gal->SetStrokeColor(COLOR4D(0.3, 0.3, 0.4, 0.8)); + gal->SetLineWidth(1.0); + gal->DrawRectangle(VECTOR2D(20, 420), VECTOR2D(780, 480)); +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_glyphs.cpp b/tests/gal-regression/scenarios/scenario_glyphs.cpp new file mode 100644 index 0000000..b952f95 --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_glyphs.cpp @@ -0,0 +1,245 @@ +/** + * Glyphs Scenario + * + * Tests GAL DrawGlyph() and DrawGlyphs() methods using STROKE_GLYPH. + * + * STROKE_GLYPH inherits from std::vector>, where each + * inner vector is a stroke path (pen down to pen up). DrawGlyph() for stroke + * glyphs internally calls DrawPolylines() to render the strokes. + * + * This scenario demonstrates: + * 1. Single glyph rendering with DrawGlyph() + * 2. Multiple glyph rendering with DrawGlyphs() + * 3. Different glyph sizes and positions + * 4. "KICAD" text spelling using stroke glyphs + */ + +#include +#include "../native/kifont_stub.h" +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderGlyphs(GAL* gal, int width, int height) { + gal->SetLayerDepth(100); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background + gal->SetFillColor(COLOR4D(0.12, 0.12, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(0, 0), VECTOR2D(width, height)); + + //========================================================================= + // Section 1: Single DrawGlyph() calls + //========================================================================= + gal->SetLayerDepth(50); + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(3.0); + + // Letter F + gal->SetStrokeColor(COLOR4D(0.9, 0.3, 0.3, 1.0)); + auto glyphF = KIFONT::MakeLetterF(0.8, VECTOR2D(50, 50)); + gal->DrawGlyph(*glyphF, 0, 1); + + // Letter L + gal->SetStrokeColor(COLOR4D(0.3, 0.9, 0.3, 1.0)); + auto glyphL = KIFONT::MakeLetterL(0.8, VECTOR2D(130, 50)); + gal->DrawGlyph(*glyphL, 0, 1); + + // Letter K + gal->SetStrokeColor(COLOR4D(0.3, 0.3, 0.9, 1.0)); + auto glyphK = KIFONT::MakeLetterK(0.8, VECTOR2D(200, 50)); + gal->DrawGlyph(*glyphK, 0, 1); + + // Section frame + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.4, 0.4, 0.8)); + gal->DrawRectangle(VECTOR2D(20, 20), VECTOR2D(280, 180)); + + //========================================================================= + // Section 2: DrawGlyphs() with vector of glyphs + //========================================================================= + gal->SetLayerDepth(50); + gal->SetLineWidth(3.0); + gal->SetStrokeColor(COLOR4D(0.9, 0.7, 0.2, 1.0)); + + // Create "KICAD" using stroke glyphs + std::vector> kicadGlyphs; + + kicadGlyphs.push_back(KIFONT::MakeLetterK(0.7, VECTOR2D(320, 60))); + kicadGlyphs.push_back(KIFONT::MakeLetterI(0.7, VECTOR2D(390, 60))); + kicadGlyphs.push_back(KIFONT::MakeLetterC(0.7, VECTOR2D(440, 60))); + kicadGlyphs.push_back(KIFONT::MakeLetterA(0.7, VECTOR2D(510, 60))); + kicadGlyphs.push_back(KIFONT::MakeLetterD(0.7, VECTOR2D(590, 60))); + + // Draw all glyphs at once + gal->DrawGlyphs(kicadGlyphs); + + // Section frame + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.6, 0.5, 0.2, 0.8)); + gal->DrawRectangle(VECTOR2D(300, 20), VECTOR2D(680, 180)); + + //========================================================================= + // Section 3: Different sizes demonstration + //========================================================================= + gal->SetLayerDepth(50); + gal->SetLineWidth(2.0); + + double scales[] = {0.3, 0.5, 0.8, 1.2}; + COLOR4D colors[] = { + COLOR4D(0.6, 0.8, 0.9, 1.0), + COLOR4D(0.7, 0.9, 0.8, 1.0), + COLOR4D(0.9, 0.8, 0.7, 1.0), + COLOR4D(0.9, 0.7, 0.8, 1.0) + }; + + double xPos = 50; + for (int i = 0; i < 4; i++) { + gal->SetStrokeColor(colors[i]); + gal->SetLineWidth(1.5 + i * 0.5); + + auto glyph = KIFONT::MakeLetterF(scales[i], VECTOR2D(xPos, 220)); + gal->DrawGlyph(*glyph, i, 4); // Pass aNth and aTotal + + xPos += 60 * scales[i] + 30; + } + + // Section frame + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.6, 0.6, 0.8)); + gal->DrawRectangle(VECTOR2D(20, 200), VECTOR2D(380, 360)); + + //========================================================================= + // Section 4: Complex glyph composition + //========================================================================= + gal->SetLayerDepth(50); + + // Background panel + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.15, 0.18, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(400, 200), VECTOR2D(760, 360)); + + // Draw multiple letters in a grid pattern + gal->SetIsFill(false); + gal->SetIsStroke(true); + + std::vector> gridGlyphs; + + // Row 1: FLKA + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.7, 0.9, 0.6, 1.0)); + gridGlyphs.push_back(KIFONT::MakeLetterF(0.5, VECTOR2D(420, 220))); + gridGlyphs.push_back(KIFONT::MakeLetterL(0.5, VECTOR2D(480, 220))); + gridGlyphs.push_back(KIFONT::MakeLetterK(0.5, VECTOR2D(540, 220))); + gridGlyphs.push_back(KIFONT::MakeLetterA(0.5, VECTOR2D(600, 220))); + + gal->DrawGlyphs(gridGlyphs); + gridGlyphs.clear(); + + // Row 2: ICDA + gal->SetStrokeColor(COLOR4D(0.6, 0.7, 0.9, 1.0)); + gridGlyphs.push_back(KIFONT::MakeLetterI(0.5, VECTOR2D(420, 290))); + gridGlyphs.push_back(KIFONT::MakeLetterC(0.5, VECTOR2D(480, 290))); + gridGlyphs.push_back(KIFONT::MakeLetterD(0.5, VECTOR2D(540, 290))); + gridGlyphs.push_back(KIFONT::MakeLetterA(0.5, VECTOR2D(600, 290))); + + gal->DrawGlyphs(gridGlyphs); + + // Section frame + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.4, 0.6, 0.4, 0.8)); + gal->DrawRectangle(VECTOR2D(400, 200), VECTOR2D(760, 360)); + + //========================================================================= + // Section 5: Styled text with transforms + //========================================================================= + gal->SetLayerDepth(50); + + // Background panel + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.18, 0.15, 0.18, 1.0)); + gal->DrawRectangle(VECTOR2D(20, 380), VECTOR2D(380, 560)); + + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(3.0); + + // Rotated letters + double angles[] = {-15, 0, 15, 30}; + double xPositions[] = {60, 130, 200, 270}; + + for (int i = 0; i < 4; i++) { + gal->Save(); + gal->Translate(VECTOR2D(xPositions[i] + 30, 470)); + gal->Rotate(angles[i] * M_PI / 180.0); + + double t = (double)i / 3.0; + gal->SetStrokeColor(COLOR4D(0.9 - t * 0.3, 0.4 + t * 0.4, 0.6 + t * 0.3, 1.0)); + + auto glyph = KIFONT::MakeLetterF(0.6, VECTOR2D(-20, -40)); + gal->DrawGlyph(*glyph, i, 4); + + gal->Restore(); + } + + // Section frame + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.6, 0.4, 0.6, 0.8)); + gal->DrawRectangle(VECTOR2D(20, 380), VECTOR2D(380, 560)); + + //========================================================================= + // Section 6: Full "KICAD" banner + //========================================================================= + gal->SetLayerDepth(40); + + // Background panel + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.1, 0.15, 0.2, 1.0)); + gal->DrawRectangle(VECTOR2D(400, 380), VECTOR2D(760, 560)); + + // Large KICAD text + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(4.0); + gal->SetStrokeColor(COLOR4D(1.0, 0.85, 0.3, 1.0)); + + std::vector> bannerGlyphs; + double bannerScale = 1.0; + double bannerX = 420; + double bannerY = 420; + double spacing = 70; + + bannerGlyphs.push_back(KIFONT::MakeLetterK(bannerScale, VECTOR2D(bannerX, bannerY))); + bannerGlyphs.push_back(KIFONT::MakeLetterI(bannerScale, VECTOR2D(bannerX + spacing, bannerY))); + bannerGlyphs.push_back(KIFONT::MakeLetterC(bannerScale, VECTOR2D(bannerX + spacing * 2, bannerY))); + bannerGlyphs.push_back(KIFONT::MakeLetterA(bannerScale, VECTOR2D(bannerX + spacing * 3, bannerY))); + bannerGlyphs.push_back(KIFONT::MakeLetterD(bannerScale, VECTOR2D(bannerX + spacing * 4, bannerY))); + + gal->DrawGlyphs(bannerGlyphs); + + // Underline decoration + gal->SetLineWidth(3.0); + gal->SetStrokeColor(COLOR4D(0.8, 0.6, 0.2, 0.8)); + gal->DrawLine(VECTOR2D(bannerX, bannerY + 110), VECTOR2D(bannerX + spacing * 4 + 50, bannerY + 110)); + + // Section frame + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.3, 0.8)); + gal->DrawRectangle(VECTOR2D(400, 380), VECTOR2D(760, 560)); +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_grid_native.cpp b/tests/gal-regression/scenarios/scenario_grid_native.cpp new file mode 100644 index 0000000..ba3d620 --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_grid_native.cpp @@ -0,0 +1,214 @@ +/** + * Grid Native Scenario + * + * Tests GAL grid-related methods: + * - SetGridVisibility() / GetGridVisibility() + * - SetGridOrigin() + * - SetGridSize() + * - SetGridColor() + * - SetAxesEnabled() / SetAxesColor() + * - SetCoarseGrid() + * - DrawGrid() + * + * Note: The grid system in GAL is designed for the viewport, + * so we demonstrate grid properties through explicit DrawGrid() calls + * with different settings applied to different regions. + */ + +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderGridNative(GAL* gal, int width, int height) { + // First, let's demonstrate the grid API by setting up and drawing + // different grid configurations + + //========================================================================= + // Test GetGridPoint() API - snaps a world point to nearest grid point + //========================================================================= + gal->SetGridSize(VECTOR2D(20, 20)); + gal->SetGridOrigin(VECTOR2D(0, 0)); + + // Test GetGridPoint - should snap (55, 47) to nearest grid point (60, 40) + VECTOR2D testPoint(55, 47); + VECTOR2D snappedPoint = gal->GetGridPoint(testPoint); + // snappedPoint should now be (60, 40) or similar based on grid settings + + // Default background + gal->SetLayerDepth(100); + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.15, 0.15, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(0, 0), VECTOR2D(width, height)); + + // Region 1: Fine grid with axes + gal->SetLayerDepth(50); + gal->SetGridVisibility(true); + gal->SetGridOrigin(VECTOR2D(100, 100)); + gal->SetGridSize(VECTOR2D(20, 20)); // 20-pixel grid + gal->SetGridColor(COLOR4D(0.3, 0.3, 0.5, 0.5)); + gal->SetAxesEnabled(true); + gal->SetAxesColor(COLOR4D(0.8, 0.3, 0.3, 0.8)); + gal->SetCoarseGrid(5); // Every 5th line is coarse + + // Draw the grid (this uses the internal grid renderer) + gal->DrawGrid(); + + // Mark region 1 boundary + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.8, 0.8)); + gal->DrawRectangle(VECTOR2D(20, 20), VECTOR2D(320, 220)); + + // Since DrawGrid() renders based on viewport, let's also + // manually draw grid patterns to show different configurations + + // Region 2: Custom fine grid pattern + gal->SetLayerDepth(40); + gal->SetStrokeColor(COLOR4D(0.2, 0.5, 0.2, 0.4)); + gal->SetLineWidth(1.0); + + double gridX = 360; + double gridY = 20; + double gridW = 300; + double gridH = 200; + double step = 15; + + // Vertical lines + for (double x = gridX; x <= gridX + gridW; x += step) { + gal->DrawLine(VECTOR2D(x, gridY), VECTOR2D(x, gridY + gridH)); + } + // Horizontal lines + for (double y = gridY; y <= gridY + gridH; y += step) { + gal->DrawLine(VECTOR2D(gridX, y), VECTOR2D(gridX + gridW, y)); + } + + // Coarse grid overlay + gal->SetStrokeColor(COLOR4D(0.3, 0.7, 0.3, 0.6)); + gal->SetLineWidth(2.0); + double coarseStep = step * 5; + + for (double x = gridX; x <= gridX + gridW; x += coarseStep) { + gal->DrawLine(VECTOR2D(x, gridY), VECTOR2D(x, gridY + gridH)); + } + for (double y = gridY; y <= gridY + gridH; y += coarseStep) { + gal->DrawLine(VECTOR2D(gridX, y), VECTOR2D(gridX + gridW, y)); + } + + // Region 2 boundary + gal->SetStrokeColor(COLOR4D(0.5, 0.8, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(gridX - 5, gridY - 5), VECTOR2D(gridX + gridW + 5, gridY + gridH + 5)); + + // Region 3: Dot grid (alternative grid style) + gal->SetLayerDepth(30); + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.6, 0.6, 0.8, 0.6)); + + double dotGridX = 20; + double dotGridY = 260; + double dotStep = 25; + + for (double x = dotGridX; x <= dotGridX + 280; x += dotStep) { + for (double y = dotGridY; y <= dotGridY + 200; y += dotStep) { + gal->DrawCircle(VECTOR2D(x, y), 2); + } + } + + // Mark region 3 + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.6, 0.6, 0.8, 0.8)); + gal->SetLineWidth(2.0); + gal->DrawRectangle(VECTOR2D(dotGridX - 10, dotGridY - 10), + VECTOR2D(dotGridX + 290, dotGridY + 210)); + + // Region 4: Non-square grid (different X and Y spacing) + gal->SetLayerDepth(30); + gal->SetStrokeColor(COLOR4D(0.8, 0.5, 0.3, 0.5)); + gal->SetLineWidth(1.0); + + double nsGridX = 360; + double nsGridY = 260; + double nsGridW = 300; + double nsGridH = 200; + double xStep = 30; + double yStep = 15; + + // Vertical lines (wide spacing) + for (double x = nsGridX; x <= nsGridX + nsGridW; x += xStep) { + gal->DrawLine(VECTOR2D(x, nsGridY), VECTOR2D(x, nsGridY + nsGridH)); + } + // Horizontal lines (tight spacing) + for (double y = nsGridY; y <= nsGridY + nsGridH; y += yStep) { + gal->DrawLine(VECTOR2D(nsGridX, y), VECTOR2D(nsGridX + nsGridW, y)); + } + + // Mark region 4 + gal->SetStrokeColor(COLOR4D(0.8, 0.6, 0.4, 0.8)); + gal->SetLineWidth(2.0); + gal->DrawRectangle(VECTOR2D(nsGridX - 5, nsGridY - 5), + VECTOR2D(nsGridX + nsGridW + 5, nsGridY + nsGridH + 5)); + + // Region 5: Grid with different origin (offset) + gal->SetLayerDepth(30); + gal->SetStrokeColor(COLOR4D(0.7, 0.3, 0.7, 0.5)); + gal->SetLineWidth(1.0); + + double oGridX = 700; + double oGridY = 20; + double oGridW = 100; + double oGridH = 200; + double oStep = 20; + double originOffsetX = 7; // Origin offset from edge + double originOffsetY = 12; + + for (double x = oGridX + originOffsetX; x <= oGridX + oGridW; x += oStep) { + gal->DrawLine(VECTOR2D(x, oGridY), VECTOR2D(x, oGridY + oGridH)); + } + for (double y = oGridY + originOffsetY; y <= oGridY + oGridH; y += oStep) { + gal->DrawLine(VECTOR2D(oGridX, y), VECTOR2D(oGridX + oGridW, y)); + } + + // Mark origin point + gal->SetFillColor(COLOR4D(1.0, 0.3, 0.3, 1.0)); + gal->SetIsFill(true); + gal->DrawCircle(VECTOR2D(oGridX + originOffsetX, oGridY + originOffsetY), 5); + + // Mark region 5 + gal->SetIsFill(false); + gal->SetStrokeColor(COLOR4D(0.7, 0.4, 0.7, 0.8)); + gal->SetLineWidth(2.0); + gal->DrawRectangle(VECTOR2D(oGridX - 5, oGridY - 5), + VECTOR2D(oGridX + oGridW + 5, oGridY + oGridH + 5)); + + // Axes demonstration in center-bottom region + gal->SetLayerDepth(20); + double axesCx = 700; + double axesCy = 380; + + // X axis (red) + gal->SetStrokeColor(COLOR4D(1.0, 0.2, 0.2, 1.0)); + gal->SetLineWidth(2.0); + gal->DrawLine(VECTOR2D(axesCx - 80, axesCy), VECTOR2D(axesCx + 80, axesCy)); + + // Y axis (green) + gal->SetStrokeColor(COLOR4D(0.2, 1.0, 0.2, 1.0)); + gal->DrawLine(VECTOR2D(axesCx, axesCy - 80), VECTOR2D(axesCx, axesCy + 80)); + + // Origin marker + gal->SetFillColor(COLOR4D(1.0, 1.0, 0.2, 1.0)); + gal->SetIsFill(true); + gal->DrawCircle(VECTOR2D(axesCx, axesCy), 6); +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_group_caching.cpp b/tests/gal-regression/scenarios/scenario_group_caching.cpp index f24d17e..7413f54 100644 --- a/tests/gal-regression/scenarios/scenario_group_caching.cpp +++ b/tests/gal-regression/scenarios/scenario_group_caching.cpp @@ -186,6 +186,59 @@ void RenderGroupCaching(GAL* gal, int width, int height) { for (int y = 0; y < height; y += 50) { gal->DrawLine(VECTOR2D(0, y), VECTOR2D(width, y)); } + + // Test DeleteGroup - create a group, draw it, delete it, create another + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.9, 0.2, 0.9, 1.0)); + + int tempGroup = gal->BeginGroup(); + { + gal->DrawCircle(VECTOR2D(0, 0), 20); + } + gal->EndGroup(); + + // Draw the temporary group + gal->Save(); + gal->Translate(VECTOR2D(600, 400)); + gal->DrawGroup(tempGroup); + gal->Restore(); + + // Delete the temporary group + gal->DeleteGroup(tempGroup); + + // Create a new group after deletion (reuses ID potentially) + gal->SetFillColor(COLOR4D(0.2, 0.9, 0.9, 1.0)); + int newGroup = gal->BeginGroup(); + { + // Draw a diamond shape + std::deque diamond = { + VECTOR2D(0, -20), + VECTOR2D(15, 0), + VECTOR2D(0, 20), + VECTOR2D(-15, 0) + }; + gal->DrawPolygon(diamond); + } + gal->EndGroup(); + + // Draw the new group + gal->Save(); + gal->Translate(VECTOR2D(680, 400)); + gal->DrawGroup(newGroup); + gal->Restore(); + + // Test ClearCache - clears all cached groups + // We'll draw markers showing the groups existed before clear + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(1.0, 1.0, 1.0, 0.5)); + gal->DrawRectangle(VECTOR2D(580, 380), VECTOR2D(720, 420)); + + // Note: ClearCache() invalidates all groups, so we call it at the end + // In production code, you'd recreate groups after ClearCache + gal->ClearCache(); } } // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_hole_walls.cpp b/tests/gal-regression/scenarios/scenario_hole_walls.cpp new file mode 100644 index 0000000..469573a --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_hole_walls.cpp @@ -0,0 +1,125 @@ +/** + * Hole Walls Scenario + * + * Tests GAL::DrawHoleWall() - ring-shaped holes + * + * This is used for drawing plated through holes (PTH) in PCBs + * where there's a drill hole surrounded by copper plating. + * Parameters: center point, inner radius (hole), wall width + */ + +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderHoleWalls(GAL* gal, int width, int height) { + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Row 1: Different hole sizes with same wall width + gal->SetFillColor(COLOR4D(0.9, 0.7, 0.3, 1.0)); // Copper color + double holeRadii[] = {5.0, 10.0, 15.0, 20.0, 25.0}; + double wallWidth = 8.0; + + for (int i = 0; i < 5; i++) { + double x = 100 + i * 100; + gal->DrawHoleWall(VECTOR2D(x, 80), holeRadii[i], wallWidth); + } + + // Row 2: Same hole size with different wall widths + gal->SetFillColor(COLOR4D(0.8, 0.6, 0.2, 1.0)); + double holeRadius = 12.0; + double wallWidths[] = {3.0, 6.0, 10.0, 15.0, 20.0}; + + for (int i = 0; i < 5; i++) { + double x = 100 + i * 100; + gal->DrawHoleWall(VECTOR2D(x, 180), holeRadius, wallWidths[i]); + } + + // Row 3: Various colors (like different PCB layers) + double coloredRadius = 15.0; + double coloredWall = 10.0; + + // Front copper + gal->SetFillColor(COLOR4D(0.9, 0.2, 0.2, 1.0)); + gal->DrawHoleWall(VECTOR2D(100, 280), coloredRadius, coloredWall); + + // Back copper + gal->SetFillColor(COLOR4D(0.2, 0.2, 0.9, 1.0)); + gal->DrawHoleWall(VECTOR2D(200, 280), coloredRadius, coloredWall); + + // Inner layer 1 + gal->SetFillColor(COLOR4D(0.2, 0.8, 0.2, 1.0)); + gal->DrawHoleWall(VECTOR2D(300, 280), coloredRadius, coloredWall); + + // Inner layer 2 + gal->SetFillColor(COLOR4D(0.8, 0.8, 0.2, 1.0)); + gal->DrawHoleWall(VECTOR2D(400, 280), coloredRadius, coloredWall); + + // Via (smaller) + gal->SetFillColor(COLOR4D(0.6, 0.6, 0.6, 1.0)); + gal->DrawHoleWall(VECTOR2D(500, 280), 8.0, 6.0); + + // Row 4: Semi-transparent overlapping (like layer stack view) + gal->SetFillColor(COLOR4D(0.9, 0.3, 0.3, 0.5)); + gal->DrawHoleWall(VECTOR2D(150, 380), 20.0, 15.0); + + gal->SetFillColor(COLOR4D(0.3, 0.9, 0.3, 0.5)); + gal->DrawHoleWall(VECTOR2D(180, 380), 18.0, 13.0); + + gal->SetFillColor(COLOR4D(0.3, 0.3, 0.9, 0.5)); + gal->DrawHoleWall(VECTOR2D(210, 380), 16.0, 11.0); + + // Row 4 continued: Grid of small vias + gal->SetFillColor(COLOR4D(0.7, 0.7, 0.7, 1.0)); + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 5; col++) { + double x = 320 + col * 30; + double y = 350 + row * 30; + gal->DrawHoleWall(VECTOR2D(x, y), 5.0, 4.0); + } + } + + // Row 5: Very thin walls (micro vias) + gal->SetFillColor(COLOR4D(0.8, 0.5, 0.2, 1.0)); + for (int i = 0; i < 8; i++) { + double x = 100 + i * 70; + gal->DrawHoleWall(VECTOR2D(x, 470), 8.0, 2.0); + } + + // Large mounting hole example + gal->SetFillColor(COLOR4D(0.6, 0.6, 0.3, 1.0)); + gal->DrawHoleWall(VECTOR2D(650, 150), 30.0, 25.0); + + // Very thick annular ring + gal->SetFillColor(COLOR4D(0.4, 0.7, 0.4, 1.0)); + gal->DrawHoleWall(VECTOR2D(650, 300), 15.0, 35.0); + + // Stroked hole wall (outline mode) + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(1.0, 1.0, 0.0, 1.0)); + gal->DrawHoleWall(VECTOR2D(650, 430), 20.0, 15.0); + + // Labels area markers + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.5, 0.5)); + gal->SetLineWidth(1.0); + + // Row markers + gal->DrawLine(VECTOR2D(50, 40), VECTOR2D(550, 40)); + gal->DrawLine(VECTOR2D(50, 140), VECTOR2D(550, 140)); + gal->DrawLine(VECTOR2D(50, 240), VECTOR2D(550, 240)); + gal->DrawLine(VECTOR2D(50, 320), VECTOR2D(550, 320)); + gal->DrawLine(VECTOR2D(50, 440), VECTOR2D(650, 440)); +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_negative_mode.cpp b/tests/gal-regression/scenarios/scenario_negative_mode.cpp new file mode 100644 index 0000000..77efd01 --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_negative_mode.cpp @@ -0,0 +1,275 @@ +/** + * Negative Mode / Diff Layer Scenario + * + * Tests Gerber-style negative rendering concepts. + * + * IMPORTANT: In OPENGL_GAL these are mostly no-ops: + * - SetNegativeDrawMode() - NO-OP (Cairo uses CAIRO_OPERATOR_CLEAR) + * - StartNegativesLayer() / EndNegativesLayer() - NO-OP + * - StartDiffLayer() / EndDiffLayer() - Requires m_tempBuffer (compositor setup) + * + * This test: + * 1. Calls the APIs to verify they don't crash + * 2. Demonstrates what negative mode LOOKS like (simulated with layered drawing) + * 3. Shows PCB thermal relief patterns (common use case for negative mode) + * + * In Gerbview, negative objects "cut out" from the copper layer. + * The polarity is determined by: item->GetLayerPolarity() XOR image->m_ImageNegative + */ + +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderNegativeMode(GAL* gal, int width, int height) { + // Enable depth testing for proper layering + gal->EnableDepthTest(true); + + //========================================================================= + // Section 1: Simulated thermal relief (what negative mode produces) + //========================================================================= + // This shows the RESULT of negative mode - holes cut in copper pour + + gal->SetLayerDepth(100); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Dark background panel + gal->SetFillColor(COLOR4D(0.12, 0.12, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(20, 20), VECTOR2D(380, 280)); + + // Copper pour (ground plane) + gal->SetLayerDepth(80); + gal->SetFillColor(COLOR4D(0.7, 0.5, 0.2, 1.0)); + gal->DrawRectangle(VECTOR2D(40, 40), VECTOR2D(360, 260)); + + // Thermal relief pattern - simulated by drawing background color + // (In real Gerbview, SetNegativeDrawMode(true) + draw = erase) + gal->SetLayerDepth(60); + COLOR4D clearColor(0.12, 0.12, 0.15, 1.0); // "Cut through" to background + gal->SetFillColor(clearColor); + + // First pad thermal - cross pattern + double pad1X = 120, pad1Y = 150; + double spokeLen = 35, spokeW = 5, clearance = 18; + + // Clearance ring + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(8.0); + gal->SetStrokeColor(clearColor); + gal->DrawCircle(VECTOR2D(pad1X, pad1Y), clearance); + + // Thermal spokes (horizontal and vertical) + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(clearColor); + gal->DrawRectangle(VECTOR2D(pad1X - clearance - spokeLen, pad1Y - spokeW/2), + VECTOR2D(pad1X - clearance, pad1Y + spokeW/2)); + gal->DrawRectangle(VECTOR2D(pad1X + clearance, pad1Y - spokeW/2), + VECTOR2D(pad1X + clearance + spokeLen, pad1Y + spokeW/2)); + gal->DrawRectangle(VECTOR2D(pad1X - spokeW/2, pad1Y - clearance - spokeLen), + VECTOR2D(pad1X + spokeW/2, pad1Y - clearance)); + gal->DrawRectangle(VECTOR2D(pad1X - spokeW/2, pad1Y + clearance), + VECTOR2D(pad1X + spokeW/2, pad1Y + clearance + spokeLen)); + + // Pad on top + gal->SetLayerDepth(40); + gal->SetFillColor(COLOR4D(0.85, 0.65, 0.25, 1.0)); + gal->DrawCircle(VECTOR2D(pad1X, pad1Y), 14); + + // Second pad thermal - diagonal spokes + double pad2X = 280, pad2Y = 150; + gal->SetLayerDepth(60); + gal->SetFillColor(clearColor); + + // Diagonal thermal spokes + for (int i = 0; i < 4; i++) { + double angle = i * M_PI / 2 + M_PI / 4; + double x1 = pad2X + cos(angle) * clearance; + double y1 = pad2Y + sin(angle) * clearance; + double x2 = pad2X + cos(angle) * (clearance + spokeLen); + double y2 = pad2Y + sin(angle) * (clearance + spokeLen); + gal->DrawSegment(VECTOR2D(x1, y1), VECTOR2D(x2, y2), spokeW); + } + + // Clearance ring + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(8.0); + gal->SetStrokeColor(clearColor); + gal->DrawCircle(VECTOR2D(pad2X, pad2Y), clearance); + + // Pad + gal->SetLayerDepth(40); + gal->SetIsFill(true); + gal->SetFillColor(COLOR4D(0.85, 0.65, 0.25, 1.0)); + gal->DrawCircle(VECTOR2D(pad2X, pad2Y), 14); + + //========================================================================= + // Section 2: API calls test (verify they don't crash) + //========================================================================= + // These are NO-OPs in OpenGL but we call them to test the API + + gal->SetLayerDepth(100); + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.15, 0.12, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(400, 20), VECTOR2D(780, 280)); + + // Draw base content + gal->SetLayerDepth(80); + gal->SetFillColor(COLOR4D(0.6, 0.3, 0.6, 1.0)); + gal->DrawRectangle(VECTOR2D(420, 40), VECTOR2D(760, 260)); + + // Test SetNegativeDrawMode API (NO-OP in OpenGL) + gal->SetNegativeDrawMode(true); + + // In Cairo, this would ERASE. In OpenGL, it draws normally. + gal->SetLayerDepth(60); + gal->SetFillColor(COLOR4D(0.15, 0.12, 0.15, 1.0)); + gal->DrawCircle(VECTOR2D(500, 150), 35); + gal->DrawCircle(VECTOR2D(590, 150), 35); + gal->DrawCircle(VECTOR2D(680, 150), 35); + + // Disable negative mode + gal->SetNegativeDrawMode(false); + + // Draw something after to show mode was reset + gal->SetLayerDepth(50); + gal->SetFillColor(COLOR4D(0.9, 0.9, 0.3, 0.9)); + gal->DrawCircle(VECTOR2D(500, 150), 15); + gal->DrawCircle(VECTOR2D(590, 150), 15); + gal->DrawCircle(VECTOR2D(680, 150), 15); + + //========================================================================= + // Section 3: Negatives layer API test + //========================================================================= + + gal->SetLayerDepth(100); + gal->SetFillColor(COLOR4D(0.12, 0.15, 0.12, 1.0)); + gal->DrawRectangle(VECTOR2D(20, 300), VECTOR2D(380, 580)); + + // Base content before negatives layer + gal->SetLayerDepth(80); + gal->SetFillColor(COLOR4D(0.3, 0.6, 0.3, 1.0)); + gal->DrawRectangle(VECTOR2D(40, 320), VECTOR2D(360, 560)); + + // Test StartNegativesLayer / EndNegativesLayer (NO-OP in OpenGL) + gal->StartNegativesLayer(); + + // Content that would be on negatives layer + gal->SetLayerDepth(60); + gal->SetFillColor(COLOR4D(0.12, 0.15, 0.12, 1.0)); + + // Via clearances (simulated) + for (int row = 0; row < 2; row++) { + for (int col = 0; col < 4; col++) { + double x = 80 + col * 80; + double y = 380 + row * 100; + gal->DrawCircle(VECTOR2D(x, y), 20); + } + } + + gal->EndNegativesLayer(); + + // Vias on top + gal->SetLayerDepth(40); + gal->SetFillColor(COLOR4D(0.85, 0.65, 0.25, 1.0)); + for (int row = 0; row < 2; row++) { + for (int col = 0; col < 4; col++) { + double x = 80 + col * 80; + double y = 380 + row * 100; + gal->DrawCircle(VECTOR2D(x, y), 12); + } + } + + // Drill holes + gal->SetLayerDepth(20); + gal->SetFillColor(COLOR4D(0.1, 0.1, 0.1, 1.0)); + for (int row = 0; row < 2; row++) { + for (int col = 0; col < 4; col++) { + double x = 80 + col * 80; + double y = 380 + row * 100; + gal->DrawCircle(VECTOR2D(x, y), 5); + } + } + + //========================================================================= + // Section 4: What "show negative objects" mode looks like + //========================================================================= + // In Gerbview, there's an option to show negative objects in a highlight color + // instead of actually cutting them out + + gal->SetLayerDepth(100); + gal->SetFillColor(COLOR4D(0.15, 0.15, 0.12, 1.0)); + gal->DrawRectangle(VECTOR2D(400, 300), VECTOR2D(780, 580)); + + // Copper layer + gal->SetLayerDepth(80); + gal->SetFillColor(COLOR4D(0.6, 0.5, 0.2, 1.0)); + gal->DrawRectangle(VECTOR2D(420, 320), VECTOR2D(760, 560)); + + // "Negative objects" shown as semi-transparent overlay (like Gerbview's show_negative_objects mode) + gal->SetLayerDepth(60); + gal->SetFillColor(COLOR4D(0.2, 0.8, 0.8, 0.5)); // Cyan highlight for negatives + + // Trace clearance + gal->DrawSegment(VECTOR2D(440, 380), VECTOR2D(740, 380), 25); + + // Pad clearances + gal->DrawCircle(VECTOR2D(480, 380), 20); + gal->DrawCircle(VECTOR2D(590, 380), 20); + gal->DrawCircle(VECTOR2D(700, 380), 20); + + // Via clearances + for (int i = 0; i < 3; i++) { + gal->DrawCircle(VECTOR2D(480 + i * 110, 480), 18); + } + + // Actual traces and pads (drawn on top) + gal->SetLayerDepth(40); + gal->SetFillColor(COLOR4D(0.8, 0.6, 0.2, 1.0)); + gal->DrawSegment(VECTOR2D(440, 380), VECTOR2D(740, 380), 10); + + gal->SetFillColor(COLOR4D(0.9, 0.7, 0.3, 1.0)); + gal->DrawCircle(VECTOR2D(480, 380), 14); + gal->DrawCircle(VECTOR2D(590, 380), 14); + gal->DrawCircle(VECTOR2D(700, 380), 14); + gal->DrawCircle(VECTOR2D(480, 480), 12); + gal->DrawCircle(VECTOR2D(590, 480), 12); + gal->DrawCircle(VECTOR2D(700, 480), 12); + + // Holes + gal->SetLayerDepth(20); + gal->SetFillColor(COLOR4D(0.1, 0.1, 0.1, 1.0)); + gal->DrawCircle(VECTOR2D(480, 380), 5); + gal->DrawCircle(VECTOR2D(590, 380), 5); + gal->DrawCircle(VECTOR2D(700, 380), 5); + gal->DrawCircle(VECTOR2D(480, 480), 4); + gal->DrawCircle(VECTOR2D(590, 480), 4); + gal->DrawCircle(VECTOR2D(700, 480), 4); + + //========================================================================= + // Section frames + //========================================================================= + gal->SetLayerDepth(5); + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.6, 0.8)); + gal->DrawRectangle(VECTOR2D(20, 20), VECTOR2D(380, 280)); + gal->DrawRectangle(VECTOR2D(400, 20), VECTOR2D(780, 280)); + gal->DrawRectangle(VECTOR2D(20, 300), VECTOR2D(380, 580)); + gal->DrawRectangle(VECTOR2D(400, 300), VECTOR2D(780, 580)); +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_polylines_multi.cpp b/tests/gal-regression/scenarios/scenario_polylines_multi.cpp new file mode 100644 index 0000000..000ba21 --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_polylines_multi.cpp @@ -0,0 +1,155 @@ +/** + * Polylines Multi Scenario + * + * Tests GAL::DrawPolylines() - drawing multiple polylines in a single call + * + * This is more efficient than calling DrawPolyline() multiple times + * when rendering many polylines with the same style. + */ + +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderPolylinesMulti(GAL* gal, int width, int height) { + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + + // Test 1: Multiple horizontal lines at once + gal->SetStrokeColor(COLOR4D(1.0, 0.3, 0.3, 1.0)); + std::vector> horizontalLines; + for (int i = 0; i < 5; i++) { + std::vector line = { + VECTOR2D(50, 50 + i * 25), + VECTOR2D(250, 50 + i * 25) + }; + horizontalLines.push_back(line); + } + gal->DrawPolylines(horizontalLines); + + // Test 2: Multiple zigzag patterns + gal->SetStrokeColor(COLOR4D(0.3, 1.0, 0.3, 1.0)); + std::vector> zigzags; + for (int i = 0; i < 3; i++) { + std::vector zig; + double baseY = 200 + i * 60; + for (int j = 0; j <= 6; j++) { + double x = 50 + j * 40; + double y = baseY + ((j % 2 == 0) ? 0 : 30); + zig.push_back(VECTOR2D(x, y)); + } + zigzags.push_back(zig); + } + gal->DrawPolylines(zigzags); + + // Test 3: Multiple wave patterns + gal->SetStrokeColor(COLOR4D(0.3, 0.3, 1.0, 1.0)); + std::vector> waves; + for (int w = 0; w < 4; w++) { + std::vector wave; + double baseY = 420 + w * 30; + for (int i = 0; i <= 20; i++) { + double t = (double)i / 20.0; + double x = 50 + t * 300; + double y = baseY + sin(t * M_PI * 3 + w * 0.5) * 10; + wave.push_back(VECTOR2D(x, y)); + } + waves.push_back(wave); + } + gal->DrawPolylines(waves); + + // Test 4: Grid pattern using polylines + gal->SetStrokeColor(COLOR4D(0.8, 0.8, 0.2, 1.0)); + std::vector> gridLines; + + // Vertical grid lines + for (int i = 0; i < 8; i++) { + double x = 400 + i * 40; + gridLines.push_back({VECTOR2D(x, 50), VECTOR2D(x, 250)}); + } + + // Horizontal grid lines + for (int i = 0; i < 6; i++) { + double y = 50 + i * 40; + gridLines.push_back({VECTOR2D(400, y), VECTOR2D(680, y)}); + } + + gal->DrawPolylines(gridLines); + + // Test 5: Multiple concentric shapes + gal->SetStrokeColor(COLOR4D(0.8, 0.3, 0.8, 1.0)); + std::vector> concentricSquares; + double cx = 550; + double cy = 400; + + for (int i = 1; i <= 4; i++) { + double size = i * 25; + std::vector square = { + VECTOR2D(cx - size, cy - size), + VECTOR2D(cx + size, cy - size), + VECTOR2D(cx + size, cy + size), + VECTOR2D(cx - size, cy + size), + VECTOR2D(cx - size, cy - size) // close + }; + concentricSquares.push_back(square); + } + gal->DrawPolylines(concentricSquares); + + // Test 6: Star burst pattern + gal->SetStrokeColor(COLOR4D(1.0, 0.5, 0.0, 1.0)); + std::vector> starBurst; + double starCx = 720; + double starCy = 150; + double innerR = 20; + double outerR = 60; + + for (int i = 0; i < 12; i++) { + double angle = i * M_PI / 6; + std::vector ray = { + VECTOR2D(starCx + cos(angle) * innerR, starCy + sin(angle) * innerR), + VECTOR2D(starCx + cos(angle) * outerR, starCy + sin(angle) * outerR) + }; + starBurst.push_back(ray); + } + gal->DrawPolylines(starBurst); + + // Test 7: Parallel diagonal lines + gal->SetStrokeColor(COLOR4D(0.5, 0.8, 0.8, 1.0)); + gal->SetLineWidth(1.5); + std::vector> diagonals; + + for (int i = 0; i < 10; i++) { + double offset = i * 15; + diagonals.push_back({ + VECTOR2D(400 + offset, 280), + VECTOR2D(520 + offset, 370) + }); + } + gal->DrawPolylines(diagonals); + + // Test 8: Different line widths (multiple calls needed) + double lineWidths[] = {1.0, 2.0, 3.0, 5.0}; + for (int w = 0; w < 4; w++) { + double t = (double)w / 3.0; + gal->SetStrokeColor(COLOR4D(1.0 - t * 0.5, 0.3 + t * 0.4, 0.2 + t * 0.6, 1.0)); + gal->SetLineWidth(lineWidths[w]); + + std::vector> widthDemo; + double y = 280 + w * 25; + widthDemo.push_back({VECTOR2D(50, y), VECTOR2D(180, y)}); + widthDemo.push_back({VECTOR2D(200, y), VECTOR2D(330, y)}); + gal->DrawPolylines(widthDemo); + } +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_render_targets.cpp b/tests/gal-regression/scenarios/scenario_render_targets.cpp new file mode 100644 index 0000000..9dcbf4b --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_render_targets.cpp @@ -0,0 +1,261 @@ +/** + * Render Targets Scenario + * + * Tests GAL render target concepts: + * - SetTarget() / GetTarget() + * - ClearTarget() + * - HasTarget() + * + * NOTE: In this test harness context, we don't switch targets mid-frame + * as that requires specific compositor setup. Instead, we demonstrate + * the concept visually and test the API availability. + * + * RENDER_TARGET values: + * - TARGET_CACHED: Main rendering target (persistent) + * - TARGET_NONCACHED: Auxiliary target (cleared each frame) + * - TARGET_OVERLAY: Overlay items (cleared each frame) + * - TARGET_TEMP: Temporary target for special operations + */ + +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; +using KIGFX::RENDER_TARGET; +using KIGFX::TARGET_CACHED; +using KIGFX::TARGET_NONCACHED; +using KIGFX::TARGET_OVERLAY; +using KIGFX::TARGET_TEMP; + +void RenderRenderTargets(GAL* gal, int width, int height) { + // All drawing to the default target (NONCACHED in test harness) + + // Background grid + gal->SetLayerDepth(100); + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(0.2, 0.2, 0.25, 0.4)); + + for (int x = 0; x < width; x += 30) { + gal->DrawLine(VECTOR2D(x, 0), VECTOR2D(x, height)); + } + for (int y = 0; y < height; y += 30) { + gal->DrawLine(VECTOR2D(0, y), VECTOR2D(width, y)); + } + + // Visual representation of render target concept + // Show boxes representing each target type + + double boxW = 160; + double boxH = 200; + double startX = 40; + double startY = 50; + double spacing = 180; + + // Box 1: CACHED (persistent content) + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.15, 0.3, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(startX, startY), VECTOR2D(startX + boxW, startY + boxH)); + + // Static PCB elements (what would go in cached) + gal->SetFillColor(COLOR4D(0.7, 0.5, 0.2, 1.0)); + gal->DrawSegment(VECTOR2D(startX + 20, startY + 50), VECTOR2D(startX + boxW - 20, startY + 50), 6); + gal->DrawSegment(VECTOR2D(startX + 20, startY + 100), VECTOR2D(startX + boxW - 20, startY + 100), 6); + gal->DrawSegment(VECTOR2D(startX + 20, startY + 150), VECTOR2D(startX + boxW - 20, startY + 150), 6); + + gal->SetFillColor(COLOR4D(0.8, 0.6, 0.3, 1.0)); + gal->DrawCircle(VECTOR2D(startX + 40, startY + 50), 12); + gal->DrawCircle(VECTOR2D(startX + boxW - 40, startY + 50), 12); + gal->DrawCircle(VECTOR2D(startX + 40, startY + 100), 12); + gal->DrawCircle(VECTOR2D(startX + boxW - 40, startY + 100), 12); + gal->DrawCircle(VECTOR2D(startX + 40, startY + 150), 12); + gal->DrawCircle(VECTOR2D(startX + boxW - 40, startY + 150), 12); + + // Frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(3.0); + gal->SetStrokeColor(COLOR4D(0.3, 0.6, 0.3, 1.0)); + gal->DrawRectangle(VECTOR2D(startX, startY), VECTOR2D(startX + boxW, startY + boxH)); + + // Label + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.8, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(startX + 20, startY + boxH + 10), VECTOR2D(startX + boxW - 20, startY + boxH + 25)); + + // Box 2: NONCACHED (dynamic content) + double box2X = startX + spacing; + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.3, 0.25, 0.1, 1.0)); + gal->DrawRectangle(VECTOR2D(box2X, startY), VECTOR2D(box2X + boxW, startY + boxH)); + + // Dynamic elements (selection highlights, moving items) + gal->SetFillColor(COLOR4D(1.0, 1.0, 0.2, 0.4)); + gal->DrawRectangle(VECTOR2D(box2X + 30, startY + 40), VECTOR2D(box2X + boxW - 30, startY + 80)); + + gal->SetFillColor(COLOR4D(0.2, 0.8, 0.2, 0.6)); + gal->DrawRectangle(VECTOR2D(box2X + 50, startY + 90), VECTOR2D(box2X + boxW - 50, startY + 140)); + + // Movement arrows + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(1.0, 0.5, 0.0, 0.8)); + gal->DrawLine(VECTOR2D(box2X + 80, startY + 160), VECTOR2D(box2X + 80, startY + 180)); + gal->DrawLine(VECTOR2D(box2X + 80, startY + 180), VECTOR2D(box2X + 70, startY + 170)); + gal->DrawLine(VECTOR2D(box2X + 80, startY + 180), VECTOR2D(box2X + 90, startY + 170)); + + // Frame + gal->SetLineWidth(3.0); + gal->SetStrokeColor(COLOR4D(0.8, 0.7, 0.2, 1.0)); + gal->DrawRectangle(VECTOR2D(box2X, startY), VECTOR2D(box2X + boxW, startY + boxH)); + + // Label + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(0.9, 0.8, 0.3, 0.8)); + gal->DrawRectangle(VECTOR2D(box2X + 20, startY + boxH + 10), VECTOR2D(box2X + boxW - 20, startY + boxH + 25)); + + // Box 3: OVERLAY (crosshairs, measurements) + double box3X = startX + spacing * 2; + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.15, 0.15, 0.25, 1.0)); + gal->DrawRectangle(VECTOR2D(box3X, startY), VECTOR2D(box3X + boxW, startY + boxH)); + + // Crosshair overlay + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(1.0, 1.0, 1.0, 0.8)); + double crossX = box3X + boxW / 2; + double crossY = startY + boxH / 2; + gal->DrawLine(VECTOR2D(box3X + 10, crossY), VECTOR2D(box3X + boxW - 10, crossY)); + gal->DrawLine(VECTOR2D(crossX, startY + 10), VECTOR2D(crossX, startY + boxH - 10)); + + // Measurement line + gal->SetStrokeColor(COLOR4D(0.3, 1.0, 1.0, 0.9)); + gal->SetLineWidth(2.0); + gal->DrawLine(VECTOR2D(box3X + 30, startY + 160), VECTOR2D(box3X + boxW - 30, startY + 160)); + gal->SetIsFill(true); + gal->SetFillColor(COLOR4D(0.3, 1.0, 1.0, 0.9)); + gal->DrawCircle(VECTOR2D(box3X + 30, startY + 160), 4); + gal->DrawCircle(VECTOR2D(box3X + boxW - 30, startY + 160), 4); + + // Frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(3.0); + gal->SetStrokeColor(COLOR4D(0.4, 0.4, 0.8, 1.0)); + gal->DrawRectangle(VECTOR2D(box3X, startY), VECTOR2D(box3X + boxW, startY + boxH)); + + // Label + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.9, 0.8)); + gal->DrawRectangle(VECTOR2D(box3X + 20, startY + boxH + 10), VECTOR2D(box3X + boxW - 20, startY + boxH + 25)); + + // Box 4: TEMP (special operations) + double box4X = startX + spacing * 3; + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.25, 0.15, 0.25, 1.0)); + gal->DrawRectangle(VECTOR2D(box4X, startY), VECTOR2D(box4X + boxW, startY + boxH)); + + // Temporary rendering (drag preview, etc) + gal->SetFillColor(COLOR4D(0.8, 0.3, 0.8, 0.5)); + gal->DrawRectangle(VECTOR2D(box4X + 40, startY + 60), VECTOR2D(box4X + boxW - 40, startY + 120)); + + // Dotted outline showing "ghost" position + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(1.0, 0.5, 1.0, 0.6)); + // Draw dashed rectangle manually + for (int i = 0; i < 10; i++) { + double x1 = box4X + 40 + i * 8; + double x2 = x1 + 5; + if (x2 > box4X + boxW - 40) x2 = box4X + boxW - 40; + gal->DrawLine(VECTOR2D(x1, startY + 140), VECTOR2D(x2, startY + 140)); + gal->DrawLine(VECTOR2D(x1, startY + 180), VECTOR2D(x2, startY + 180)); + } + + // Frame + gal->SetLineWidth(3.0); + gal->SetStrokeColor(COLOR4D(0.7, 0.3, 0.7, 1.0)); + gal->DrawRectangle(VECTOR2D(box4X, startY), VECTOR2D(box4X + boxW, startY + boxH)); + + // Label + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(0.8, 0.4, 0.8, 0.8)); + gal->DrawRectangle(VECTOR2D(box4X + 20, startY + boxH + 10), VECTOR2D(box4X + boxW - 20, startY + boxH + 25)); + + // Bottom: API demonstration - HasTarget results + double apiY = 340; + gal->SetLayerDepth(40); + + // Check target availability (these are API calls) + bool hasCached = gal->HasTarget(TARGET_CACHED); + bool hasNoncached = gal->HasTarget(TARGET_NONCACHED); + bool hasOverlay = gal->HasTarget(TARGET_OVERLAY); + bool hasTemp = gal->HasTarget(TARGET_TEMP); + + // Show results visually + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Label area + gal->SetFillColor(COLOR4D(0.15, 0.15, 0.18, 1.0)); + gal->DrawRectangle(VECTOR2D(40, apiY), VECTOR2D(760, apiY + 100)); + + // Indicator for each target + double indicatorY = apiY + 50; + double indicatorSpacing = 180; + + // CACHED indicator + gal->SetFillColor(hasCached ? COLOR4D(0.2, 0.9, 0.2, 1.0) : COLOR4D(0.9, 0.2, 0.2, 1.0)); + gal->DrawCircle(VECTOR2D(startX + boxW / 2, indicatorY), 15); + + // NONCACHED indicator + gal->SetFillColor(hasNoncached ? COLOR4D(0.2, 0.9, 0.2, 1.0) : COLOR4D(0.9, 0.2, 0.2, 1.0)); + gal->DrawCircle(VECTOR2D(startX + spacing + boxW / 2, indicatorY), 15); + + // OVERLAY indicator + gal->SetFillColor(hasOverlay ? COLOR4D(0.2, 0.9, 0.2, 1.0) : COLOR4D(0.9, 0.2, 0.2, 1.0)); + gal->DrawCircle(VECTOR2D(startX + spacing * 2 + boxW / 2, indicatorY), 15); + + // TEMP indicator + gal->SetFillColor(hasTemp ? COLOR4D(0.2, 0.9, 0.2, 1.0) : COLOR4D(0.9, 0.2, 0.2, 1.0)); + gal->DrawCircle(VECTOR2D(startX + spacing * 3 + boxW / 2, indicatorY), 15); + + // Frame around API section + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.4, 0.4, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(40, apiY), VECTOR2D(760, apiY + 100)); + + // Bottom: GetTarget demonstration + RENDER_TARGET currentTarget = gal->GetTarget(); + (void)currentTarget; // Used - shows API works + + // Label for current target + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.6, 0.8)); + gal->DrawRectangle(VECTOR2D(40, apiY + 70), VECTOR2D(300, apiY + 85)); +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_screen_transform.cpp b/tests/gal-regression/scenarios/scenario_screen_transform.cpp new file mode 100644 index 0000000..565b869 --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_screen_transform.cpp @@ -0,0 +1,271 @@ +/** + * Screen Transform Scenario + * + * Tests GAL screen-level transformation methods: + * - SetRotation() / GetRotation() - screen rotation + * - SetFlip() - X/Y axis flipping + * - ToWorld() / ToScreen() - coordinate conversion + * + * These are viewport-level transforms that affect all rendering, + * different from the per-object Save/Restore/Transform methods. + */ + +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderScreenTransform(GAL* gal, int width, int height) { + // Note: SetRotation and SetFlip affect the world-to-screen matrix + // They need to be set before drawing and affect subsequent operations + + //========================================================================= + // Test SetFlip() and SetRotation() APIs + // These are screen-level transforms that affect the worldScreenMatrix + //========================================================================= + + // Test SetFlip() API - sets X and/or Y axis mirroring + // Note: In our test harness the matrix is already computed, so we demonstrate + // the API is callable. In real use, SetFlip must be called before ComputeWorldScreenMatrix + gal->SetFlip(false, false); // No flip - default state + + // Test SetRotation() API - sets screen rotation angle + // Note: Like SetFlip, affects worldScreenMatrix computation + gal->SetRotation(0.0); // No rotation - default state + + // First, draw reference content without any screen transforms + gal->SetLayerDepth(100); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background + gal->SetFillColor(COLOR4D(0.12, 0.12, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(0, 0), VECTOR2D(width, height)); + + // Reference grid + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(0.2, 0.2, 0.25, 0.4)); + + for (int x = 0; x < width; x += 40) { + gal->DrawLine(VECTOR2D(x, 0), VECTOR2D(x, height)); + } + for (int y = 0; y < height; y += 40) { + gal->DrawLine(VECTOR2D(0, y), VECTOR2D(width, y)); + } + + // Test 1: Normal orientation reference shape + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Draw an arrow-like shape that shows orientation + auto drawOrientationMarker = [&](double cx, double cy, double size, COLOR4D color) { + gal->SetFillColor(color); + + // Main body (rectangle) + gal->DrawRectangle(VECTOR2D(cx - size * 0.3, cy - size * 0.5), + VECTOR2D(cx + size * 0.3, cy + size * 0.3)); + + // Arrow head pointing up + std::deque arrow = { + VECTOR2D(cx, cy - size * 0.8), + VECTOR2D(cx - size * 0.5, cy - size * 0.3), + VECTOR2D(cx + size * 0.5, cy - size * 0.3) + }; + gal->DrawPolygon(arrow); + + // Small circle at base to show which end is bottom + gal->SetFillColor(COLOR4D(color.r * 0.5, color.g * 0.5, color.b * 0.5, 1.0)); + gal->DrawCircle(VECTOR2D(cx, cy + size * 0.15), size * 0.15); + }; + + // Reference marker (no transform) + drawOrientationMarker(120, 120, 60, COLOR4D(0.8, 0.3, 0.3, 1.0)); + + // Label + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.6, 0.3, 0.3, 0.8)); + gal->SetLineWidth(2.0); + gal->DrawRectangle(VECTOR2D(60, 50), VECTOR2D(180, 180)); + + // Test 2: Using Save/Restore with rotation (object-level transform) + gal->SetLayerDepth(50); + gal->Save(); + gal->Translate(VECTOR2D(280, 120)); + gal->Rotate(45.0 * M_PI / 180.0); + + gal->SetIsFill(true); + gal->SetFillColor(COLOR4D(0.3, 0.8, 0.3, 1.0)); + gal->DrawRectangle(VECTOR2D(-30, -50), VECTOR2D(30, 30)); + std::deque arrow2 = { + VECTOR2D(0, -80), + VECTOR2D(-50, -30), + VECTOR2D(50, -30) + }; + gal->DrawPolygon(arrow2); + gal->SetFillColor(COLOR4D(0.15, 0.4, 0.15, 1.0)); + gal->DrawCircle(VECTOR2D(0, 15), 15); + + gal->Restore(); + + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.3, 0.6, 0.3, 0.8)); + gal->SetLineWidth(2.0); + gal->DrawRectangle(VECTOR2D(200, 50), VECTOR2D(360, 200)); + + // Test 3: Demonstrate ToWorld/ToScreen coordinate conversion + gal->SetLayerDepth(40); + + // Draw a marker at a known world position + VECTOR2D worldPoint(500, 120); + + // Mark the world position + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.8, 0.8, 0.2, 1.0)); + gal->DrawCircle(worldPoint, 15); + + // Convert to screen and back + VECTOR2D screenPoint = gal->ToScreen(worldPoint); + VECTOR2D backToWorld = gal->ToWorld(screenPoint); + + // Draw indicator showing the conversion (should be at same spot) + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.2, 0.8, 0.8, 1.0)); + gal->DrawCircle(backToWorld, 20); + + // Label + gal->SetStrokeColor(COLOR4D(0.6, 0.6, 0.2, 0.8)); + gal->DrawRectangle(VECTOR2D(440, 50), VECTOR2D(560, 180)); + + // Test 4: Multiple rotated shapes showing different angles + gal->SetLayerDepth(50); + double angles[] = {0, 30, 60, 90, 120, 150}; + double baseX = 100; + double baseY = 300; + + for (int i = 0; i < 6; i++) { + double cx = baseX + i * 100; + double angle = angles[i] * M_PI / 180.0; + + gal->Save(); + gal->Translate(VECTOR2D(cx, baseY)); + gal->Rotate(angle); + + // Draw a simple "F" shape to show rotation clearly + gal->SetIsFill(true); + gal->SetIsStroke(false); + double t = (double)i / 5.0; + gal->SetFillColor(COLOR4D(0.8 - t * 0.3, 0.3 + t * 0.5, 0.3 + t * 0.3, 1.0)); + + // Vertical bar + gal->DrawRectangle(VECTOR2D(-5, -30), VECTOR2D(5, 30)); + // Top horizontal bar + gal->DrawRectangle(VECTOR2D(5, -30), VECTOR2D(25, -20)); + // Middle horizontal bar + gal->DrawRectangle(VECTOR2D(5, -5), VECTOR2D(18, 5)); + + gal->Restore(); + } + + // Frame around rotation demo + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.6, 0.8)); + gal->SetLineWidth(2.0); + gal->DrawRectangle(VECTOR2D(40, 230), VECTOR2D(660, 370)); + + // Test 5: Flip demonstration using object transforms + // (Note: SetFlip() affects the entire viewport, so we simulate with Scale) + gal->SetLayerDepth(50); + + // Original + gal->Save(); + gal->Translate(VECTOR2D(120, 450)); + gal->SetIsFill(true); + gal->SetFillColor(COLOR4D(0.7, 0.4, 0.7, 1.0)); + gal->DrawRectangle(VECTOR2D(-25, -40), VECTOR2D(25, 20)); + std::deque tri1 = { + VECTOR2D(0, -60), VECTOR2D(-30, -40), VECTOR2D(30, -40) + }; + gal->DrawPolygon(tri1); + gal->SetFillColor(COLOR4D(0.35, 0.2, 0.35, 1.0)); + gal->DrawCircle(VECTOR2D(0, 5), 10); + gal->Restore(); + + // X-flipped (mirror horizontally) + gal->Save(); + gal->Translate(VECTOR2D(280, 450)); + gal->Scale(VECTOR2D(-1.0, 1.0)); // Flip X + gal->SetIsFill(true); + gal->SetFillColor(COLOR4D(0.7, 0.4, 0.7, 1.0)); + gal->DrawRectangle(VECTOR2D(-25, -40), VECTOR2D(25, 20)); + std::deque tri2 = { + VECTOR2D(0, -60), VECTOR2D(-30, -40), VECTOR2D(30, -40) + }; + gal->DrawPolygon(tri2); + gal->SetFillColor(COLOR4D(0.35, 0.2, 0.35, 1.0)); + gal->DrawCircle(VECTOR2D(0, 5), 10); + gal->Restore(); + + // Y-flipped (mirror vertically) + gal->Save(); + gal->Translate(VECTOR2D(440, 450)); + gal->Scale(VECTOR2D(1.0, -1.0)); // Flip Y + gal->SetIsFill(true); + gal->SetFillColor(COLOR4D(0.7, 0.4, 0.7, 1.0)); + gal->DrawRectangle(VECTOR2D(-25, -40), VECTOR2D(25, 20)); + std::deque tri3 = { + VECTOR2D(0, -60), VECTOR2D(-30, -40), VECTOR2D(30, -40) + }; + gal->DrawPolygon(tri3); + gal->SetFillColor(COLOR4D(0.35, 0.2, 0.35, 1.0)); + gal->DrawCircle(VECTOR2D(0, 5), 10); + gal->Restore(); + + // Both flipped + gal->Save(); + gal->Translate(VECTOR2D(600, 450)); + gal->Scale(VECTOR2D(-1.0, -1.0)); // Flip both + gal->SetIsFill(true); + gal->SetFillColor(COLOR4D(0.7, 0.4, 0.7, 1.0)); + gal->DrawRectangle(VECTOR2D(-25, -40), VECTOR2D(25, 20)); + std::deque tri4 = { + VECTOR2D(0, -60), VECTOR2D(-30, -40), VECTOR2D(30, -40) + }; + gal->DrawPolygon(tri4); + gal->SetFillColor(COLOR4D(0.35, 0.2, 0.35, 1.0)); + gal->DrawCircle(VECTOR2D(0, 5), 10); + gal->Restore(); + + // Labels for flip demo + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.3, 0.5, 0.6)); + gal->DrawRectangle(VECTOR2D(70, 385), VECTOR2D(170, 395)); // Original + gal->DrawRectangle(VECTOR2D(230, 385), VECTOR2D(330, 395)); // X-flip + gal->DrawRectangle(VECTOR2D(390, 385), VECTOR2D(490, 395)); // Y-flip + gal->DrawRectangle(VECTOR2D(550, 385), VECTOR2D(650, 395)); // XY-flip + + // Frame around flip demo + gal->SetStrokeColor(COLOR4D(0.6, 0.4, 0.6, 0.8)); + gal->SetLineWidth(2.0); + gal->DrawRectangle(VECTOR2D(40, 380), VECTOR2D(700, 510)); +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_text_attrs.cpp b/tests/gal-regression/scenarios/scenario_text_attrs.cpp new file mode 100644 index 0000000..5bbc994 --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_text_attrs.cpp @@ -0,0 +1,377 @@ +/** + * Text Attributes Scenario + * + * Tests GAL text attribute methods: + * - SetGlyphSize() / GetGlyphSize() + * - SetFontBold() / IsFontBold() + * - SetFontItalic() / IsFontItalic() + * - SetFontUnderlined() / IsFontUnderlined() + * - SetTextMirrored() / IsTextMirrored() + * - SetHorizontalJustify() / GetHorizontalJustify() + * - SetVerticalJustify() / GetVerticalJustify() + * - ResetTextAttributes() + * + * Note: These methods set internal m_attributes member variables. + * Actual text rendering requires KIFONT infrastructure which we don't stub. + * This scenario tests the APIs are callable and demonstrates their purpose + * by drawing visual indicators of what the attributes would affect. + */ + +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderTextAttrs(GAL* gal, int width, int height) { + gal->SetLayerDepth(100); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background + gal->SetFillColor(COLOR4D(0.12, 0.12, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(0, 0), VECTOR2D(width, height)); + + //========================================================================= + // Section 1: SetGlyphSize() / GetGlyphSize() + //========================================================================= + gal->SetLayerDepth(50); + + // Test SetGlyphSize with different sizes + VECTOR2D smallSize(10, 12); + VECTOR2D mediumSize(20, 24); + VECTOR2D largeSize(40, 48); + + gal->SetGlyphSize(smallSize); + VECTOR2D currentSize = gal->GetGlyphSize(); + // currentSize should now be (10, 12) + + gal->SetGlyphSize(mediumSize); + gal->SetGlyphSize(largeSize); + + // Visual: Draw rectangles showing glyph sizes + double baseX = 50, baseY = 60; + gal->SetFillColor(COLOR4D(0.3, 0.6, 0.8, 0.8)); + gal->DrawRectangle(VECTOR2D(baseX, baseY), + VECTOR2D(baseX + smallSize.x, baseY + smallSize.y)); + + gal->SetFillColor(COLOR4D(0.4, 0.7, 0.8, 0.8)); + gal->DrawRectangle(VECTOR2D(baseX + 30, baseY), + VECTOR2D(baseX + 30 + mediumSize.x, baseY + mediumSize.y)); + + gal->SetFillColor(COLOR4D(0.5, 0.8, 0.8, 0.8)); + gal->DrawRectangle(VECTOR2D(baseX + 80, baseY), + VECTOR2D(baseX + 80 + largeSize.x, baseY + largeSize.y)); + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.4, 0.6, 0.7, 0.8)); + gal->DrawRectangle(VECTOR2D(20, 20), VECTOR2D(200, 130)); + + //========================================================================= + // Section 2: SetFontBold() / SetFontItalic() / SetFontUnderlined() + //========================================================================= + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Test font style APIs + gal->SetFontBold(false); + bool isBold = gal->IsFontBold(); // Should be false + + gal->SetFontItalic(false); + bool isItalic = gal->IsFontItalic(); // Should be false + + gal->SetFontUnderlined(false); + bool isUnderlined = gal->IsFontUnderlined(); // Should be false + + // Set all to true + gal->SetFontBold(true); + gal->SetFontItalic(true); + gal->SetFontUnderlined(true); + + // Visual: Draw styled "text" indicators + // Normal (N) + gal->SetFillColor(COLOR4D(0.7, 0.7, 0.7, 1.0)); + gal->DrawRectangle(VECTOR2D(240, 40), VECTOR2D(280, 80)); + + // Bold (B) - thicker + gal->SetFillColor(COLOR4D(0.9, 0.9, 0.9, 1.0)); + gal->DrawRectangle(VECTOR2D(300, 40), VECTOR2D(350, 80)); + + // Italic (I) - slanted parallelogram + std::deque italic = { + VECTOR2D(380, 80), + VECTOR2D(370, 40), + VECTOR2D(410, 40), + VECTOR2D(420, 80) + }; + gal->DrawPolygon(italic); + + // Underlined (U) + gal->SetFillColor(COLOR4D(0.7, 0.7, 0.9, 1.0)); + gal->DrawRectangle(VECTOR2D(440, 40), VECTOR2D(480, 80)); + gal->DrawRectangle(VECTOR2D(440, 85), VECTOR2D(480, 90)); // Underline + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.6, 0.6, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(220, 20), VECTOR2D(500, 110)); + + //========================================================================= + // Section 3: SetTextMirrored() + //========================================================================= + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Test mirroring API + gal->SetTextMirrored(false); + bool isMirrored = gal->IsTextMirrored(); // Should be false + + gal->SetTextMirrored(true); + isMirrored = gal->IsTextMirrored(); // Should be true + + // Visual: Show normal vs mirrored "F" shape + // Normal F + gal->SetFillColor(COLOR4D(0.7, 0.5, 0.8, 1.0)); + gal->DrawRectangle(VECTOR2D(560, 40), VECTOR2D(570, 100)); // Vertical + gal->DrawRectangle(VECTOR2D(570, 40), VECTOR2D(600, 50)); // Top horizontal + gal->DrawRectangle(VECTOR2D(570, 60), VECTOR2D(590, 70)); // Middle horizontal + + // Mirrored F + gal->SetFillColor(COLOR4D(0.8, 0.5, 0.7, 1.0)); + gal->DrawRectangle(VECTOR2D(680, 40), VECTOR2D(690, 100)); // Vertical + gal->DrawRectangle(VECTOR2D(650, 40), VECTOR2D(680, 50)); // Top horizontal (mirrored) + gal->DrawRectangle(VECTOR2D(660, 60), VECTOR2D(680, 70)); // Middle horizontal (mirrored) + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.6, 0.5, 0.6, 0.8)); + gal->DrawRectangle(VECTOR2D(540, 20), VECTOR2D(720, 120)); + + //========================================================================= + // Section 4: SetHorizontalJustify() / SetVerticalJustify() + //========================================================================= + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Test justify APIs + gal->SetHorizontalJustify(GR_TEXT_H_ALIGN_LEFT); + gal->SetVerticalJustify(GR_TEXT_V_ALIGN_TOP); + + gal->SetHorizontalJustify(GR_TEXT_H_ALIGN_CENTER); + gal->SetVerticalJustify(GR_TEXT_V_ALIGN_CENTER); + + gal->SetHorizontalJustify(GR_TEXT_H_ALIGN_RIGHT); + gal->SetVerticalJustify(GR_TEXT_V_ALIGN_BOTTOM); + + // Visual: Show alignment positions + double alignBaseX = 120; + double alignBaseY = 200; + double boxSize = 100; + + // Alignment box + gal->SetFillColor(COLOR4D(0.2, 0.2, 0.25, 1.0)); + gal->DrawRectangle(VECTOR2D(alignBaseX, alignBaseY), + VECTOR2D(alignBaseX + boxSize, alignBaseY + boxSize)); + + // Alignment indicators + gal->SetFillColor(COLOR4D(0.9, 0.6, 0.3, 1.0)); + + // Top-left + gal->DrawCircle(VECTOR2D(alignBaseX + 10, alignBaseY + 10), 6); + // Top-center + gal->DrawCircle(VECTOR2D(alignBaseX + boxSize/2, alignBaseY + 10), 6); + // Top-right + gal->DrawCircle(VECTOR2D(alignBaseX + boxSize - 10, alignBaseY + 10), 6); + + // Center-left + gal->DrawCircle(VECTOR2D(alignBaseX + 10, alignBaseY + boxSize/2), 6); + // Center-center + gal->SetFillColor(COLOR4D(1.0, 0.8, 0.3, 1.0)); + gal->DrawCircle(VECTOR2D(alignBaseX + boxSize/2, alignBaseY + boxSize/2), 8); + gal->SetFillColor(COLOR4D(0.9, 0.6, 0.3, 1.0)); + // Center-right + gal->DrawCircle(VECTOR2D(alignBaseX + boxSize - 10, alignBaseY + boxSize/2), 6); + + // Bottom-left + gal->DrawCircle(VECTOR2D(alignBaseX + 10, alignBaseY + boxSize - 10), 6); + // Bottom-center + gal->DrawCircle(VECTOR2D(alignBaseX + boxSize/2, alignBaseY + boxSize - 10), 6); + // Bottom-right + gal->DrawCircle(VECTOR2D(alignBaseX + boxSize - 10, alignBaseY + boxSize - 10), 6); + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.7, 0.5, 0.3, 0.8)); + gal->DrawRectangle(VECTOR2D(alignBaseX - 20, alignBaseY - 30), + VECTOR2D(alignBaseX + boxSize + 20, alignBaseY + boxSize + 20)); + + //========================================================================= + // Section 5: ResetTextAttributes() + //========================================================================= + gal->SetLayerDepth(50); + + // Set various attributes + gal->SetGlyphSize(VECTOR2D(50, 60)); + gal->SetFontBold(true); + gal->SetFontItalic(true); + gal->SetTextMirrored(true); + gal->SetHorizontalJustify(GR_TEXT_H_ALIGN_RIGHT); + + // Reset all attributes to defaults + gal->ResetTextAttributes(); + + // After reset, attributes should be back to defaults + VECTOR2D resetSize = gal->GetGlyphSize(); // Should be default + bool resetBold = gal->IsFontBold(); // Should be false + bool resetItalic = gal->IsFontItalic(); // Should be false + + // Visual: Show "reset" indicator + gal->SetIsFill(true); + gal->SetFillColor(COLOR4D(0.3, 0.7, 0.3, 0.8)); + gal->DrawCircle(VECTOR2D(350, 250), 30); + + // Checkmark inside circle + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(4.0); + gal->SetStrokeColor(COLOR4D(1.0, 1.0, 1.0, 1.0)); + std::vector check = { + VECTOR2D(335, 250), + VECTOR2D(345, 262), + VECTOR2D(368, 235) + }; + gal->DrawPolyline(check); + + // Section frame + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.3, 0.6, 0.3, 0.8)); + gal->DrawRectangle(VECTOR2D(280, 180), VECTOR2D(420, 310)); + + //========================================================================= + // Section 6: Comprehensive API coverage visual + //========================================================================= + gal->SetLayerDepth(40); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background panel + gal->SetFillColor(COLOR4D(0.15, 0.15, 0.2, 1.0)); + gal->DrawRectangle(VECTOR2D(20, 340), VECTOR2D(760, 560)); + + // Show all text attribute concepts visually + double rowY = 380; + double colSpacing = 150; + + // Column 1: Size variations + gal->SetFillColor(COLOR4D(0.5, 0.7, 0.9, 0.9)); + double sizes[] = {8, 16, 24, 32}; + for (int i = 0; i < 4; i++) { + double s = sizes[i]; + gal->DrawRectangle(VECTOR2D(40, rowY + i * 40), + VECTOR2D(40 + s * 2, rowY + i * 40 + s)); + } + + // Column 2: Style variations (bold = filled, italic = slant, underline = line below) + double col2X = 40 + colSpacing; + + // Regular + gal->SetFillColor(COLOR4D(0.6, 0.6, 0.6, 0.9)); + gal->DrawRectangle(VECTOR2D(col2X, rowY), VECTOR2D(col2X + 40, rowY + 30)); + + // Bold (wider/heavier) + gal->SetFillColor(COLOR4D(0.9, 0.9, 0.9, 1.0)); + gal->DrawRectangle(VECTOR2D(col2X, rowY + 45), VECTOR2D(col2X + 50, rowY + 80)); + + // Italic (parallelogram) + std::deque italicShape = { + VECTOR2D(col2X + 10, rowY + 125), + VECTOR2D(col2X, rowY + 90), + VECTOR2D(col2X + 40, rowY + 90), + VECTOR2D(col2X + 50, rowY + 125) + }; + gal->SetFillColor(COLOR4D(0.7, 0.7, 0.9, 0.9)); + gal->DrawPolygon(italicShape); + + // Underlined + gal->SetFillColor(COLOR4D(0.7, 0.9, 0.7, 0.9)); + gal->DrawRectangle(VECTOR2D(col2X, rowY + 140), VECTOR2D(col2X + 40, rowY + 165)); + gal->DrawRectangle(VECTOR2D(col2X, rowY + 170), VECTOR2D(col2X + 40, rowY + 175)); + + // Column 3: Mirror demonstration + double col3X = 40 + colSpacing * 2; + + // Normal "R" + gal->SetFillColor(COLOR4D(0.8, 0.6, 0.9, 0.9)); + gal->DrawRectangle(VECTOR2D(col3X, rowY), VECTOR2D(col3X + 10, rowY + 50)); + gal->DrawRectangle(VECTOR2D(col3X + 10, rowY), VECTOR2D(col3X + 35, rowY + 10)); + gal->DrawRectangle(VECTOR2D(col3X + 10, rowY + 20), VECTOR2D(col3X + 30, rowY + 30)); + gal->DrawRectangle(VECTOR2D(col3X + 25, rowY + 25), VECTOR2D(col3X + 40, rowY + 50)); + + // Mirrored "R" + gal->SetFillColor(COLOR4D(0.9, 0.6, 0.8, 0.9)); + double mirrorX = col3X + 80; + gal->DrawRectangle(VECTOR2D(mirrorX + 30, rowY + 80), VECTOR2D(mirrorX + 40, rowY + 130)); + gal->DrawRectangle(VECTOR2D(mirrorX + 5, rowY + 80), VECTOR2D(mirrorX + 30, rowY + 90)); + gal->DrawRectangle(VECTOR2D(mirrorX + 10, rowY + 100), VECTOR2D(mirrorX + 30, rowY + 110)); + gal->DrawRectangle(VECTOR2D(mirrorX, rowY + 105), VECTOR2D(mirrorX + 15, rowY + 130)); + + // Column 4: Justify grid + double col4X = 40 + colSpacing * 3; + double gridSize = 80; + + gal->SetFillColor(COLOR4D(0.25, 0.25, 0.3, 1.0)); + gal->DrawRectangle(VECTOR2D(col4X, rowY), VECTOR2D(col4X + gridSize, rowY + gridSize)); + + // 3x3 justify positions + gal->SetFillColor(COLOR4D(0.9, 0.7, 0.4, 1.0)); + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 3; col++) { + double px = col4X + 10 + col * 30; + double py = rowY + 10 + row * 30; + double radius = (row == 1 && col == 1) ? 8 : 5; + gal->DrawCircle(VECTOR2D(px, py), radius); + } + } + + // Column 5: Rotation demonstration + double col5X = 40 + colSpacing * 4; + + for (int i = 0; i < 4; i++) { + double angle = i * M_PI / 6; // 0, 30, 60, 90 degrees + double cx = col5X + 40; + double cy = rowY + 40 + i * 45; + + gal->Save(); + gal->Translate(VECTOR2D(cx, cy)); + gal->Rotate(angle); + + gal->SetFillColor(COLOR4D(0.6 + i * 0.1, 0.8 - i * 0.1, 0.5, 0.9)); + gal->DrawRectangle(VECTOR2D(-15, -8), VECTOR2D(15, 8)); + + gal->Restore(); + } + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.6, 0.8)); + gal->DrawRectangle(VECTOR2D(20, 340), VECTOR2D(760, 560)); +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_transform.cpp b/tests/gal-regression/scenarios/scenario_transform.cpp new file mode 100644 index 0000000..18d4f07 --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_transform.cpp @@ -0,0 +1,289 @@ +/** + * Transform() API Documentation Scenario + * + * Tests GAL::Transform(const MATRIX3x3D& aTransformation) + * + * IMPORTANT FINDING: Transform() is DEAD CODE in KiCad! + * + * Research revealed that: + * 1. Transform() is declared in GAL base class with empty default implementation + * 2. Implemented in both OPENGL_GAL (glMultMatrixd) and CAIRO_GAL + * 3. NEVER called anywhere in the entire KiCad codebase + * 4. KiCad uses Rotate(), Translate(), Scale() instead - which work correctly + * + * Why Transform() doesn't produce visible output in OPENGL_GAL: + * - Uses glMultMatrixd() which affects the legacy GL_MODELVIEW matrix stack + * - OPENGL_GAL renders via VERTEX_MANAGER which has its own m_transform member + * - The two transformation systems are completely independent + * + * This scenario: + * 1. Calls Transform() API to verify it doesn't crash + * 2. Documents the limitation + * 3. Shows what the expected result WOULD be if it worked + */ + +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderTransformAPI(GAL* gal, int width, int height) { + gal->SetLayerDepth(100); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background + gal->SetFillColor(COLOR4D(0.12, 0.12, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(0, 0), VECTOR2D(width, height)); + + //========================================================================= + // Section 1: Transform() API call demonstration + //========================================================================= + gal->SetLayerDepth(50); + + // Create a 3x3 identity transformation matrix + MATRIX3x3D identity; + identity.SetIdentity(); + + // Call Transform() with identity - should be a no-op + // This verifies the API is callable without crashing + gal->Transform(identity); + + // Create a rotation matrix (45 degrees) + MATRIX3x3D rotationMatrix; + rotationMatrix.SetIdentity(); + double angle = 45.0 * M_PI / 180.0; + rotationMatrix.m_data[0][0] = cos(angle); + rotationMatrix.m_data[0][1] = -sin(angle); + rotationMatrix.m_data[1][0] = sin(angle); + rotationMatrix.m_data[1][1] = cos(angle); + + // Call Transform() with rotation matrix + // NOTE: In OPENGL_GAL this affects GL_MODELVIEW but NOT the VERTEX_MANAGER + // So subsequent drawing will NOT be transformed + gal->Transform(rotationMatrix); + + // Draw a shape - it will NOT be rotated because VERTEX_MANAGER ignores GL_MODELVIEW + gal->SetFillColor(COLOR4D(0.8, 0.3, 0.3, 0.5)); + gal->DrawRectangle(VECTOR2D(100, 100), VECTOR2D(200, 150)); + + // Create a translation matrix + MATRIX3x3D translationMatrix; + translationMatrix.SetIdentity(); + translationMatrix.m_data[0][2] = 50; // Translate X by 50 + translationMatrix.m_data[1][2] = 30; // Translate Y by 30 + + // Call Transform() with translation + gal->Transform(translationMatrix); + + // Draw another shape - also NOT translated + gal->SetFillColor(COLOR4D(0.3, 0.8, 0.3, 0.5)); + gal->DrawRectangle(VECTOR2D(100, 180), VECTOR2D(200, 230)); + + // Reset with identity + gal->Transform(identity); + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.6, 0.3, 0.3, 0.8)); + gal->DrawRectangle(VECTOR2D(20, 20), VECTOR2D(300, 280)); + + //========================================================================= + // Section 2: What Transform() SHOULD do (simulated with working APIs) + //========================================================================= + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Reference shape (no transform) + gal->SetFillColor(COLOR4D(0.5, 0.5, 0.5, 0.7)); + gal->DrawRectangle(VECTOR2D(400, 100), VECTOR2D(500, 150)); + + // Using working transforms (Rotate/Translate/Scale via VERTEX_MANAGER) + gal->Save(); + gal->Translate(VECTOR2D(450, 200)); + gal->Rotate(45.0 * M_PI / 180.0); + + gal->SetFillColor(COLOR4D(0.3, 0.6, 0.9, 0.9)); + gal->DrawRectangle(VECTOR2D(-50, -25), VECTOR2D(50, 25)); + gal->Restore(); + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetStrokeColor(COLOR4D(0.3, 0.6, 0.6, 0.8)); + gal->DrawRectangle(VECTOR2D(320, 20), VECTOR2D(580, 280)); + + //========================================================================= + // Section 3: Matrix operations visualization + //========================================================================= + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background panel + gal->SetFillColor(COLOR4D(0.15, 0.15, 0.18, 1.0)); + gal->DrawRectangle(VECTOR2D(600, 20), VECTOR2D(780, 280)); + + // Show matrix element positions visually (3x3 grid) + double matrixX = 640; + double matrixY = 60; + double cellSize = 40; + + // Grid cells + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 3; col++) { + double x = matrixX + col * cellSize; + double y = matrixY + row * cellSize; + + // Diagonal elements (scale/rotation) in one color + if (row == col) { + gal->SetFillColor(COLOR4D(0.4, 0.6, 0.8, 0.8)); + } + // Translation column (last column) in another + else if (col == 2 && row < 2) { + gal->SetFillColor(COLOR4D(0.8, 0.6, 0.4, 0.8)); + } + // Other elements + else { + gal->SetFillColor(COLOR4D(0.3, 0.3, 0.35, 0.8)); + } + + gal->DrawRectangle(VECTOR2D(x, y), VECTOR2D(x + cellSize - 2, y + cellSize - 2)); + } + } + + // Frame around matrix + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.6, 0.6, 0.7, 0.8)); + gal->DrawRectangle(VECTOR2D(matrixX - 5, matrixY - 5), + VECTOR2D(matrixX + cellSize * 3 + 5, matrixY + cellSize * 3 + 5)); + + // Section frame + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.6, 0.8)); + gal->DrawRectangle(VECTOR2D(600, 20), VECTOR2D(780, 280)); + + //========================================================================= + // Section 4: Why Transform() doesn't work - architecture diagram + //========================================================================= + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background panel + gal->SetFillColor(COLOR4D(0.15, 0.12, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(20, 300), VECTOR2D(380, 580)); + + // "GL_MODELVIEW" box (what Transform affects) + gal->SetFillColor(COLOR4D(0.6, 0.3, 0.3, 0.7)); + gal->DrawRectangle(VECTOR2D(50, 340), VECTOR2D(170, 400)); + + // "VERTEX_MANAGER" box (what actually renders) + gal->SetFillColor(COLOR4D(0.3, 0.6, 0.3, 0.7)); + gal->DrawRectangle(VECTOR2D(50, 440), VECTOR2D(170, 500)); + + // Arrow from Transform to GL_MODELVIEW + gal->SetFillColor(COLOR4D(0.8, 0.4, 0.4, 0.9)); + gal->DrawSegment(VECTOR2D(240, 340), VECTOR2D(175, 365), 3); + + // X mark showing no connection to VERTEX_MANAGER + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(4.0); + gal->SetStrokeColor(COLOR4D(1.0, 0.3, 0.3, 1.0)); + gal->DrawLine(VECTOR2D(200, 430), VECTOR2D(220, 450)); + gal->DrawLine(VECTOR2D(220, 430), VECTOR2D(200, 450)); + + // Arrow from Rotate/Scale/Translate to VERTEX_MANAGER + gal->SetIsFill(true); + gal->SetFillColor(COLOR4D(0.4, 0.8, 0.4, 0.9)); + gal->DrawSegment(VECTOR2D(240, 520), VECTOR2D(175, 475), 3); + + // "Transform()" label box + gal->SetFillColor(COLOR4D(0.5, 0.3, 0.3, 0.6)); + gal->DrawRectangle(VECTOR2D(240, 320), VECTOR2D(350, 360)); + + // "Rotate/Scale/Translate" label box + gal->SetFillColor(COLOR4D(0.3, 0.5, 0.3, 0.6)); + gal->DrawRectangle(VECTOR2D(240, 500), VECTOR2D(350, 540)); + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.3, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(20, 300), VECTOR2D(380, 580)); + + //========================================================================= + // Section 5: Cairo comparison (where Transform DOES work) + //========================================================================= + gal->SetLayerDepth(50); + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Background panel + gal->SetFillColor(COLOR4D(0.12, 0.15, 0.15, 1.0)); + gal->DrawRectangle(VECTOR2D(400, 300), VECTOR2D(780, 580)); + + // OpenGL box (shows limitation) + gal->SetFillColor(COLOR4D(0.4, 0.25, 0.25, 0.8)); + gal->DrawRectangle(VECTOR2D(430, 350), VECTOR2D(560, 420)); + + // Cairo box (shows working) + gal->SetFillColor(COLOR4D(0.25, 0.4, 0.25, 0.8)); + gal->DrawRectangle(VECTOR2D(610, 350), VECTOR2D(740, 420)); + + // "OpenGL: NO-OP" indicator + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(3.0); + gal->SetStrokeColor(COLOR4D(1.0, 0.4, 0.4, 1.0)); + gal->DrawLine(VECTOR2D(470, 380), VECTOR2D(520, 390)); + gal->DrawLine(VECTOR2D(520, 380), VECTOR2D(470, 390)); + + // "Cairo: WORKS" indicator (checkmark) + gal->SetStrokeColor(COLOR4D(0.4, 1.0, 0.4, 1.0)); + std::vector check = { + VECTOR2D(650, 385), + VECTOR2D(670, 400), + VECTOR2D(710, 360) + }; + gal->DrawPolyline(check); + + // Show result shapes + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // OpenGL result (not transformed) + gal->SetFillColor(COLOR4D(0.7, 0.5, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(460, 460), VECTOR2D(530, 510)); + + // Cairo result (would be transformed) + gal->Save(); + gal->Translate(VECTOR2D(675, 485)); + gal->Rotate(30.0 * M_PI / 180.0); + gal->SetFillColor(COLOR4D(0.5, 0.7, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(-35, -25), VECTOR2D(35, 25)); + gal->Restore(); + + // Section frame + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.4, 0.5, 0.5, 0.8)); + gal->DrawRectangle(VECTOR2D(400, 300), VECTOR2D(780, 580)); +} + +} // namespace GALTest