KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_clipboard.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
22
23#include <cstdlib>
24#include <vector>
25
26#include <clipboard.h>
27#include <wx/clipbrd.h>
28#include <wx/display.h>
29#include <wx/image.h>
30#include <wx/string.h>
31#include <wx/filename.h>
32#include <wx/mstream.h>
33
34#if defined( __WXGTK__ )
35#include <gdk/gdk.h> // For gdk_display_get_default()
36#endif
37
38
43#define SKIP_IF_HEADLESS() \
44 do \
45 { \
46 if( !KI_TEST::CanDoDisplayTests() ) \
47 { \
48 BOOST_TEST_MESSAGE( "Skipping test - no display available (headless environment)" ); \
49 return; \
50 } \
51 } while( 0 )
52
53BOOST_AUTO_TEST_SUITE( ClipboardTests )
54
55BOOST_AUTO_TEST_CASE( SaveClipboard_BasicText )
56{
58
59 std::string testText = "Basic clipboard test";
60 bool result = SaveClipboard( testText );
61
62 if( result )
63 {
64 std::string retrieved = GetClipboardUTF8();
65 BOOST_CHECK_EQUAL( retrieved, testText );
66 }
67 // Note: Test may fail on headless systems where clipboard isn't available
68}
69
70BOOST_AUTO_TEST_CASE( SaveClipboard_EmptyString )
71{
73
74 std::string emptyText = "";
75 bool result = SaveClipboard( emptyText );
76
77 if( result )
78 {
79 std::string retrieved = GetClipboardUTF8();
80 BOOST_CHECK_EQUAL( retrieved, emptyText );
81 }
82}
83
84BOOST_AUTO_TEST_CASE( SaveClipboard_UTF8Characters )
85{
87
88 std::string utf8Text = "Héllo Wörld! 你好 🚀";
89 bool result = SaveClipboard( utf8Text );
90
91 if( result )
92 {
93 std::string retrieved = GetClipboardUTF8();
94 BOOST_CHECK_EQUAL( retrieved, utf8Text );
95 }
96}
97
98BOOST_AUTO_TEST_CASE( SaveClipboard_LargeText )
99{
101
102 std::string largeText( 10000, 'A' );
103 largeText += "END";
104 bool result = SaveClipboard( largeText );
105
106 if( result )
107 {
108 std::string retrieved = GetClipboardUTF8();
109 BOOST_CHECK_EQUAL( retrieved, largeText );
110 }
111}
112
113BOOST_AUTO_TEST_CASE( SaveClipboard_SpecialCharacters )
114{
115#ifdef _WIN32
116 BOOST_TEST_MESSAGE( "Skipping test - Windows normalizes clipboard text line endings" );
117#elif defined( __WXMAC__ )
118 BOOST_TEST_MESSAGE( "Skipping test - doesn't work on macOS" );
119#else
121
122 std::string specialText = "Line1\nLine2\tTabbed\r\nWindows newline";
123 bool result = SaveClipboard( specialText );
124
125 if( result )
126 {
127 std::string retrieved = GetClipboardUTF8();
128 BOOST_CHECK_EQUAL( retrieved, specialText );
129 }
130#endif
131}
132
133BOOST_AUTO_TEST_CASE( GetClipboardUTF8_EmptyClipboard )
134{
136
137 // Clear clipboard first
138 if( wxTheClipboard->Open() )
139 {
140 wxTheClipboard->Clear();
141 wxTheClipboard->Close();
142 }
143
144 std::string result = GetClipboardUTF8();
145 BOOST_CHECK( result.empty() );
146}
147
148BOOST_AUTO_TEST_CASE( GetClipboardUTF8_NonTextData )
149{
151
152 // This test verifies behavior when clipboard contains non-text data
153 // Implementation depends on system behavior - may return empty string
154 std::string result = GetClipboardUTF8();
155 // No specific assertion - just ensure it doesn't crash
156 BOOST_CHECK( true );
157}
158
159BOOST_AUTO_TEST_CASE( SaveTabularData_SimpleGrid )
160{
162
163 std::vector<std::vector<wxString>> testData = {
164 { wxS("A1"), wxS("B1"), wxS("C1") },
165 { wxS("A2"), wxS("B2"), wxS("C2") },
166 { wxS("A3"), wxS("B3"), wxS("C3") }
167 };
168
169 bool result = SaveTabularDataToClipboard( testData );
170
171 if( result )
172 {
173 std::vector<std::vector<wxString>> retrieved;
174 bool parseResult = GetTabularDataFromClipboard( retrieved );
175
176 if( parseResult )
177 {
178 BOOST_CHECK_EQUAL( retrieved.size(), testData.size() );
179 for( size_t i = 0; i < testData.size() && i < retrieved.size(); ++i )
180 {
181 BOOST_CHECK_EQUAL( retrieved[i].size(), testData[i].size() );
182 for( size_t j = 0; j < testData[i].size() && j < retrieved[i].size(); ++j )
183 {
184 BOOST_CHECK_EQUAL( retrieved[i][j], testData[i][j] );
185 }
186 }
187 }
188 }
189}
190
191BOOST_AUTO_TEST_CASE( SaveTabularData_EmptyGrid )
192{
194
195 std::vector<std::vector<wxString>> emptyData;
196 bool result = SaveTabularDataToClipboard( emptyData );
197
198 if( result )
199 {
200 std::vector<std::vector<wxString>> retrieved;
201 bool parseResult = GetTabularDataFromClipboard( retrieved );
202
203 if( parseResult )
204 {
205 BOOST_CHECK( retrieved.empty() );
206 }
207 }
208}
209
210BOOST_AUTO_TEST_CASE( SaveTabularData_SingleCell )
211{
213
214 std::vector<std::vector<wxString>> singleCell = {
215 { wxS("OnlyCell") }
216 };
217
218 bool result = SaveTabularDataToClipboard( singleCell );
219
220 if( result )
221 {
222 std::vector<std::vector<wxString>> retrieved;
223 bool parseResult = GetTabularDataFromClipboard( retrieved );
224
225 if( parseResult )
226 {
227 BOOST_CHECK_EQUAL( retrieved.size(), 1 );
228 BOOST_CHECK_EQUAL( retrieved[0].size(), 1 );
229 BOOST_CHECK_EQUAL( retrieved[0][0], wxString( wxS("OnlyCell") ) );
230 }
231 }
232}
233
234BOOST_AUTO_TEST_CASE( SaveTabularData_WithCommas )
235{
237
238 std::vector<std::vector<wxString>> dataWithCommas = {
239 { wxS("Value, with comma"), wxS("Normal") },
240 { wxS("Another, comma"), wxS("Also normal") }
241 };
242
243 bool result = SaveTabularDataToClipboard( dataWithCommas );
244
245 if( result )
246 {
247 std::vector<std::vector<wxString>> retrieved;
248 bool parseResult = GetTabularDataFromClipboard( retrieved );
249
250 if( parseResult )
251 {
252 BOOST_CHECK_EQUAL( retrieved.size(), dataWithCommas.size() );
253 for( size_t i = 0; i < dataWithCommas.size() && i < retrieved.size(); ++i )
254 {
255 BOOST_CHECK_EQUAL( retrieved[i].size(), dataWithCommas[i].size() );
256 for( size_t j = 0; j < dataWithCommas[i].size() && j < retrieved[i].size(); ++j )
257 {
258 BOOST_CHECK_EQUAL( retrieved[i][j], dataWithCommas[i][j] );
259 }
260 }
261 }
262 }
263}
264
265BOOST_AUTO_TEST_CASE( SaveTabularData_WithQuotes )
266{
268
269 std::vector<std::vector<wxString>> dataWithQuotes = {
270 { wxS("\"Quoted value\""), wxS("Normal") },
271 { wxS("Value with \"inner\" quotes"), wxS("Plain") }
272 };
273
274 bool result = SaveTabularDataToClipboard( dataWithQuotes );
275
276 if( result )
277 {
278 std::vector<std::vector<wxString>> retrieved;
279 bool parseResult = GetTabularDataFromClipboard( retrieved );
280
281 if( parseResult )
282 {
283 BOOST_CHECK_EQUAL( retrieved.size(), dataWithQuotes.size() );
284 // Note: Exact quote handling depends on CSV parser implementation
285 }
286 }
287}
288
289BOOST_AUTO_TEST_CASE( SaveTabularData_WithNewlines )
290{
292
293#ifdef __WXMAC__
294 BOOST_TEST_MESSAGE( "Skipping test - doesn't work on macOS" );
295#else
296
297 std::vector<std::vector<wxString>> dataWithNewlines = {
298 { wxS("Line1\nLine2"), wxS("Normal") },
299 { wxS("Single line"), wxS("Another\nmultiline") }
300 };
301
302 bool result = SaveTabularDataToClipboard( dataWithNewlines );
303
304 if( result )
305 {
306 std::vector<std::vector<wxString>> retrieved;
307 bool parseResult = GetTabularDataFromClipboard( retrieved );
308
309 if( parseResult )
310 {
311 BOOST_CHECK_EQUAL( retrieved.size(), dataWithNewlines.size() );
312 // Note: Newline handling depends on CSV parser implementation
313 }
314 }
315#endif
316}
317
318BOOST_AUTO_TEST_CASE( SaveTabularData_IrregularGrid )
319{
321
322 std::vector<std::vector<wxString>> irregularData = {
323 { wxS("A1"), wxS("B1"), wxS("C1"), wxS("D1") },
324 { wxS("A2"), wxS("B2") },
325 { wxS("A3"), wxS("B3"), wxS("C3") }
326 };
327
328 bool result = SaveTabularDataToClipboard( irregularData );
329
330 if( result )
331 {
332 std::vector<std::vector<wxString>> retrieved;
333 bool parseResult = GetTabularDataFromClipboard( retrieved );
334
335 if( parseResult )
336 {
337 BOOST_CHECK_EQUAL( retrieved.size(), irregularData.size() );
338 // Each row should maintain its individual size
339 for( size_t i = 0; i < irregularData.size() && i < retrieved.size(); ++i )
340 {
341 for( size_t j = 0; j < irregularData[i].size() && j < retrieved[i].size(); ++j )
342 {
343 BOOST_CHECK_EQUAL( retrieved[i][j], irregularData[i][j] );
344 }
345 }
346 }
347 }
348}
349
350BOOST_AUTO_TEST_CASE( GetTabularDataFromClipboard_InvalidData )
351{
353
354 // Save non-tabular text to clipboard
355 std::string invalidText = "This is not tabular data\nJust some text";
356 SaveClipboard( invalidText );
357
358 std::vector<std::vector<wxString>> retrieved;
359 bool result = GetTabularDataFromClipboard( retrieved );
360
361 // Should either parse as single-column data or return appropriate result
362 // Exact behavior depends on AutoDecodeCSV implementation
363 BOOST_CHECK( true ); // Test that it doesn't crash
364}
365
366BOOST_AUTO_TEST_CASE( GetImageFromClipboard_NoImage )
367{
369
370 // Clear clipboard
371 if( wxTheClipboard->Open() )
372 {
373 wxTheClipboard->Clear();
374 wxTheClipboard->Close();
375 }
376
377 std::unique_ptr<wxBitmap> image = GetImageFromClipboard();
378 BOOST_CHECK( !image || !image->IsOk() );
379}
380
381BOOST_AUTO_TEST_CASE( GetImageFromClipboard_TextInClipboard )
382{
384
385 // Put text in clipboard
386 SaveClipboard( "This is text, not an image" );
387
388 std::unique_ptr<wxBitmap> image = GetImageFromClipboard();
389 BOOST_CHECK( !image || !image->IsOk() );
390}
391
392BOOST_AUTO_TEST_CASE( Clipboard_MultipleSaveOperations )
393{
395
396 // Test multiple sequential save operations
397 std::vector<std::string> testStrings = {
398 "First string",
399 "Second string with 特殊字符",
400 "Third string\nwith\nnewlines",
401 ""
402 };
403
404 for( const auto& testString : testStrings )
405 {
406 bool saved = SaveClipboard( testString );
407 if( saved )
408 {
409 std::string retrieved = GetClipboardUTF8();
410 BOOST_CHECK_EQUAL( retrieved, testString );
411 }
412 }
413}
414
415BOOST_AUTO_TEST_CASE( Clipboard_ConcurrentAccess )
416{
418
419 // Test that clipboard operations are properly synchronized
420 std::string testText1 = "Concurrent test 1";
421 std::string testText2 = "Concurrent test 2";
422
423 bool result1 = SaveClipboard( testText1 );
424 bool result2 = SaveClipboard( testText2 );
425
426 if( result2 )
427 {
428 std::string retrieved = GetClipboardUTF8();
429 BOOST_CHECK_EQUAL( retrieved, testText2 ); // Should have the last saved value
430 }
431}
432
433BOOST_AUTO_TEST_CASE( Clipboard_FlushBehavior )
434{
436
437 // Test that Flush() allows data to persist after the application
438 std::string persistentText = "This should persist after flush";
439 bool result = SaveClipboard( persistentText );
440
441 if( result )
442 {
443 // Data should still be available
444 std::string retrieved = GetClipboardUTF8();
445 BOOST_CHECK_EQUAL( retrieved, persistentText );
446 }
447}
448
449BOOST_AUTO_TEST_CASE( SaveClipboard_WithMimeData_EmptyMimeDataFallsBack )
450{
452
453 // When MIME data is empty, should fall back to basic SaveClipboard
454 std::string testText = "Fallback test with empty MIME data";
455 std::vector<CLIPBOARD_MIME_DATA> emptyMimeData;
456
457 bool result = SaveClipboard( testText, emptyMimeData );
458
459 if( result )
460 {
461 std::string retrieved = GetClipboardUTF8();
462 BOOST_CHECK_EQUAL( retrieved, testText );
463 }
464}
465
466BOOST_AUTO_TEST_CASE( SaveClipboard_WithMimeData_KicadFormat )
467{
469
470 // Test that application/kicad MIME type is prioritized when reading
471 std::string textData = "Plain text representation";
472 std::string kicadData = "KiCad native format data";
473
474 std::vector<CLIPBOARD_MIME_DATA> mimeData;
475 CLIPBOARD_MIME_DATA kicadEntry;
476 kicadEntry.m_mimeType = wxS( "application/kicad" );
477 kicadEntry.m_data.AppendData( kicadData.data(), kicadData.size() );
478 mimeData.push_back( kicadEntry );
479
480 bool result = SaveClipboard( textData, mimeData );
481
482 if( result )
483 {
484 // GetClipboardUTF8 should prioritize application/kicad format
485 std::string retrieved = GetClipboardUTF8();
486 BOOST_CHECK_EQUAL( retrieved, kicadData );
487 }
488}
489
490BOOST_AUTO_TEST_CASE( SaveClipboard_WithMimeData_MultipleMimeTypes )
491{
493
494 // Test saving with multiple MIME types
495 std::string textData = "Text for clipboard";
496 std::string kicadData = "KiCad data for clipboard";
497 std::string svgData = "<svg></svg>";
498 std::string pngData = "\x89PNG\r\n"; // PNG magic bytes (truncated for test)
499
500 std::vector<CLIPBOARD_MIME_DATA> mimeData;
501
502 CLIPBOARD_MIME_DATA kicadEntry;
503 kicadEntry.m_mimeType = wxS( "application/kicad" );
504 kicadEntry.m_data.AppendData( kicadData.data(), kicadData.size() );
505 mimeData.push_back( kicadEntry );
506
507 CLIPBOARD_MIME_DATA svgEntry;
508 svgEntry.m_mimeType = wxS( "image/svg+xml" );
509 svgEntry.m_data.AppendData( svgData.data(), svgData.size() );
510 mimeData.push_back( svgEntry );
511
512 CLIPBOARD_MIME_DATA pngEntry;
513 pngEntry.m_mimeType = wxS( "image/png" );
514 pngEntry.m_data.AppendData( pngData.data(), pngData.size() );
515 mimeData.push_back( pngEntry );
516
517 bool result = SaveClipboard( textData, mimeData );
518
519 if( result )
520 {
521 // Verify at least the KiCad format is retrievable
522 std::string retrieved = GetClipboardUTF8();
523 BOOST_CHECK_EQUAL( retrieved, kicadData );
524 }
525}
526
527BOOST_AUTO_TEST_CASE( SaveClipboard_WithMimeData_NoKicadFormat )
528{
530
531 // When no application/kicad format, should fall back to text
532 std::string textData = "Text for clipboard without kicad format";
533 std::string svgData = "<svg></svg>";
534
535 std::vector<CLIPBOARD_MIME_DATA> mimeData;
536 CLIPBOARD_MIME_DATA svgEntry;
537 svgEntry.m_mimeType = wxS( "image/svg+xml" );
538 svgEntry.m_data.AppendData( svgData.data(), svgData.size() );
539 mimeData.push_back( svgEntry );
540
541 bool result = SaveClipboard( textData, mimeData );
542
543 if( result )
544 {
545 // Should get the text since no application/kicad format is present
546 std::string retrieved = GetClipboardUTF8();
547 BOOST_CHECK_EQUAL( retrieved, textData );
548 }
549}
550
551BOOST_AUTO_TEST_CASE( SaveClipboard_WithMimeData_UTF8InKicadFormat )
552{
554
555 // Test that UTF8 characters are preserved in KiCad MIME format
556 std::string textData = "Plain text";
557 std::string kicadData = "KiCad data with UTF8: 你好世界 🔧";
558
559 std::vector<CLIPBOARD_MIME_DATA> mimeData;
560 CLIPBOARD_MIME_DATA kicadEntry;
561 kicadEntry.m_mimeType = wxS( "application/kicad" );
562 kicadEntry.m_data.AppendData( kicadData.data(), kicadData.size() );
563 mimeData.push_back( kicadEntry );
564
565 bool result = SaveClipboard( textData, mimeData );
566
567 if( result )
568 {
569 std::string retrieved = GetClipboardUTF8();
570 BOOST_CHECK_EQUAL( retrieved, kicadData );
571 }
572}
573
574BOOST_AUTO_TEST_CASE( SaveClipboard_WithMimeData_SExpressionRoundTrip )
575{
577
578 // Test that S-expression data (like schematic content) round-trips correctly
579 // This simulates the actual copy/paste flow for schematic elements
580 std::string sExprData =
581 "(kicad_sch (version 20231120) (generator \"eeschema\")\n"
582 " (symbol (lib_id \"Device:R\") (at 100.33 50.8 0)\n"
583 " (property \"Reference\" \"R1\" (at 101.6 49.53 0))\n"
584 " (property \"Value\" \"10kΩ\" (at 101.6 52.07 0))\n"
585 " )\n"
586 ")\n";
587
588 std::vector<CLIPBOARD_MIME_DATA> mimeData;
589 CLIPBOARD_MIME_DATA kicadEntry;
590 kicadEntry.m_mimeType = wxS( "application/kicad" );
591 kicadEntry.m_data.AppendData( sExprData.data(), sExprData.size() );
592 mimeData.push_back( kicadEntry );
593
594 bool result = SaveClipboard( sExprData, mimeData );
595
596 if( result )
597 {
598 std::string retrieved = GetClipboardUTF8();
599 // Verify exact byte-for-byte match for S-expression parsing
600 BOOST_CHECK_EQUAL( retrieved.size(), sExprData.size() );
601 BOOST_CHECK_EQUAL( retrieved, sExprData );
602 }
603}
604
605BOOST_AUTO_TEST_CASE( SaveClipboard_WithMimeData_EmptyDataSkipped )
606{
608
609 // Test that entries with empty data are skipped without error
610 std::string textData = "Text with empty MIME entries";
611
612 std::vector<CLIPBOARD_MIME_DATA> mimeData;
613
614 // Add an entry with empty data - should be skipped
615 CLIPBOARD_MIME_DATA emptyEntry;
616 emptyEntry.m_mimeType = wxS( "application/empty" );
617 // Note: m_data is empty by default
618 mimeData.push_back( emptyEntry );
619
620 // Add a valid entry
621 CLIPBOARD_MIME_DATA kicadEntry;
622 kicadEntry.m_mimeType = wxS( "application/kicad" );
623 kicadEntry.m_data.AppendData( textData.data(), textData.size() );
624 mimeData.push_back( kicadEntry );
625
626 bool result = SaveClipboard( textData, mimeData );
627
628 if( result )
629 {
630 std::string retrieved = GetClipboardUTF8();
631 BOOST_CHECK_EQUAL( retrieved, textData );
632 }
633}
634
635BOOST_AUTO_TEST_CASE( SaveClipboard_WithMimeData_PngHandledAsBitmap )
636{
638
639 // Test that PNG data is handled correctly (converted to bitmap format)
640 // Note: This test verifies the function doesn't crash with PNG data
641 // Full bitmap round-trip testing requires a display connection
642
643 std::string textData = "Text with PNG data";
644
645 // Create minimal valid PNG data (1x1 transparent pixel)
646 // PNG header + IHDR + IDAT + IEND
647 static const unsigned char minimalPng[] = {
648 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
649 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, // IHDR chunk
650 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, // 1x1 size
651 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, // 8-bit RGBA
652 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, // IDAT chunk
653 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, // compressed data
654 0x05, 0x00, 0x01, 0x0D, 0x0A, 0x2D, 0xB4, 0x00, // checksum
655 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, // IEND chunk
656 0x42, 0x60, 0x82 // IEND checksum
657 };
658
659 std::vector<CLIPBOARD_MIME_DATA> mimeData;
660
661 CLIPBOARD_MIME_DATA pngEntry;
662 pngEntry.m_mimeType = wxS( "image/png" );
663 pngEntry.m_data.AppendData( minimalPng, sizeof( minimalPng ) );
664 mimeData.push_back( pngEntry );
665
666 CLIPBOARD_MIME_DATA kicadEntry;
667 kicadEntry.m_mimeType = wxS( "application/kicad" );
668 kicadEntry.m_data.AppendData( textData.data(), textData.size() );
669 mimeData.push_back( kicadEntry );
670
671 // This should not crash or assert
672 bool result = SaveClipboard( textData, mimeData );
673
674 if( result )
675 {
676 // Verify we can still get the KiCad data back
677 std::string retrieved = GetClipboardUTF8();
678 BOOST_CHECK_EQUAL( retrieved, textData );
679 }
680}
681
690BOOST_AUTO_TEST_CASE( DualBufferAlpha_FullyOpaque )
691{
692 // Test fully opaque pixels (alpha = 255)
693 // For a red pixel (255, 0, 0) with alpha=1:
694 // On white: (255, 255, 255) blended with (255, 0, 0) at α=1 -> (255, 0, 0)
695 // On black: (0, 0, 0) blended with (255, 0, 0) at α=1 -> (255, 0, 0)
696 int rW = 255, gW = 0, bW = 0; // Red on white
697 int rB = 255, gB = 0, bB = 0; // Red on black (same because fully opaque)
698
699 int diffR = rW - rB;
700 int diffG = gW - gB;
701 int diffB = bW - bB;
702 int avgDiff = (diffR + diffG + diffB) / 3;
703 int alpha = 255 - avgDiff;
704
705 BOOST_CHECK_EQUAL( alpha, 255 );
706
707 // Recover color
708 if( alpha > 0 )
709 {
710 int recoveredR = std::min( 255, rB * 255 / alpha );
711 int recoveredG = std::min( 255, gB * 255 / alpha );
712 int recoveredB = std::min( 255, bB * 255 / alpha );
713 BOOST_CHECK_EQUAL( recoveredR, 255 );
714 BOOST_CHECK_EQUAL( recoveredG, 0 );
715 BOOST_CHECK_EQUAL( recoveredB, 0 );
716 }
717}
718
719BOOST_AUTO_TEST_CASE( DualBufferAlpha_FullyTransparent )
720{
721 // Test fully transparent pixels (alpha = 0)
722 // On white: just white (255, 255, 255)
723 // On black: just black (0, 0, 0)
724 int rW = 255, gW = 255, bW = 255; // White background
725 int rB = 0, gB = 0, bB = 0; // Black background
726
727 int diffR = rW - rB;
728 int diffG = gW - gB;
729 int diffB = bW - bB;
730 int avgDiff = (diffR + diffG + diffB) / 3;
731 int alpha = 255 - avgDiff;
732
733 BOOST_CHECK_EQUAL( alpha, 0 );
734}
735
736BOOST_AUTO_TEST_CASE( DualBufferAlpha_SemiTransparent )
737{
738 // Test semi-transparent pixel (alpha = 0.5, i.e., 128)
739 // Foreground color: (100, 150, 200)
740 // On white: 0.5*F + 0.5*255 = 0.5*(100,150,200) + (127.5,127.5,127.5) = (177, 202, 227) approx
741 // On black: 0.5*F + 0.5*0 = 0.5*(100,150,200) = (50, 75, 100)
742 int rW = 177, gW = 202, bW = 227;
743 int rB = 50, gB = 75, bB = 100;
744
745 int diffR = rW - rB; // 127
746 int diffG = gW - gB; // 127
747 int diffB = bW - bB; // 127
748 int avgDiff = (diffR + diffG + diffB) / 3; // 127
749 int alpha = 255 - avgDiff; // 128
750
751 BOOST_CHECK_CLOSE( (double)alpha, 128.0, 1.0 ); // Allow 1% tolerance
752
753 // Recover color: F = black_result / α * 255
754 if( alpha > 0 )
755 {
756 int recoveredR = std::min( 255, rB * 255 / alpha );
757 int recoveredG = std::min( 255, gB * 255 / alpha );
758 int recoveredB = std::min( 255, bB * 255 / alpha );
759 BOOST_CHECK_CLOSE( (double)recoveredR, 99.0, 2.0 ); // Should be ~100
760 BOOST_CHECK_CLOSE( (double)recoveredG, 149.0, 2.0 ); // Should be ~150
761 BOOST_CHECK_CLOSE( (double)recoveredB, 199.0, 2.0 ); // Should be ~200
762 }
763}
764
765BOOST_AUTO_TEST_CASE( DualBufferAlpha_AntiAliasedEdge )
766{
767 // Test anti-aliased edge with varying alpha
768 // Simulates a gray line with alpha gradient at edges
769 // High alpha region (α ≈ 0.9)
770 int rW1 = 128, gW1 = 128, bW1 = 128; // Gray on white (dimmed by 0.9 blend)
771 int rB1 = 115, gB1 = 115, bB1 = 115; // Gray on black
772
773 int diff1 = (rW1 - rB1 + gW1 - gB1 + bW1 - bB1) / 3;
774 int alpha1 = 255 - diff1;
775 BOOST_CHECK( alpha1 > 200 ); // High alpha
776
777 // Low alpha region at edge (α ≈ 0.3)
778 int rW2 = 210, gW2 = 210, bW2 = 210; // Mostly white with some gray
779 int rB2 = 38, gB2 = 38, bB2 = 38; // Mostly black with some gray
780
781 int diff2 = (rW2 - rB2 + gW2 - gB2 + bW2 - bB2) / 3;
782 int alpha2 = 255 - diff2;
783 BOOST_CHECK( alpha2 < 100 ); // Low alpha
784 BOOST_CHECK( alpha2 > 0 ); // But not fully transparent
785}
786
787BOOST_AUTO_TEST_CASE( BitmapSizeCalculation_MatchesViewScale )
788{
789 // Test that bitmap size is correctly calculated from bbox and view scale
790 // This tests the formula: bitmapSize = bbox_IU * viewScale
791
792 // Simulate a 1000x500 IU bounding box at various zoom levels
793 int bboxWidth = 1000;
794 int bboxHeight = 500;
795
796 // At viewScale = 1.0 (1:1 zoom)
797 double viewScale1 = 1.0;
798 int bitmapWidth1 = (int)(bboxWidth * viewScale1 + 0.5);
799 int bitmapHeight1 = (int)(bboxHeight * viewScale1 + 0.5);
800 BOOST_CHECK_EQUAL( bitmapWidth1, 1000 );
801 BOOST_CHECK_EQUAL( bitmapHeight1, 500 );
802
803 // At viewScale = 2.0 (zoomed in 2x)
804 double viewScale2 = 2.0;
805 int bitmapWidth2 = (int)(bboxWidth * viewScale2 + 0.5);
806 int bitmapHeight2 = (int)(bboxHeight * viewScale2 + 0.5);
807 BOOST_CHECK_EQUAL( bitmapWidth2, 2000 );
808 BOOST_CHECK_EQUAL( bitmapHeight2, 1000 );
809
810 // At viewScale = 0.5 (zoomed out)
811 double viewScale3 = 0.5;
812 int bitmapWidth3 = (int)(bboxWidth * viewScale3 + 0.5);
813 int bitmapHeight3 = (int)(bboxHeight * viewScale3 + 0.5);
814 BOOST_CHECK_EQUAL( bitmapWidth3, 500 );
815 BOOST_CHECK_EQUAL( bitmapHeight3, 250 );
816}
817
818BOOST_AUTO_TEST_CASE( BitmapSizeCalculation_ClampToMaxSize )
819{
820 // Test that bitmap size is clamped while preserving aspect ratio
821 const int maxBitmapSize = 4096;
822
823 // Large bbox that would exceed max size
824 int bboxWidth = 10000;
825 int bboxHeight = 5000;
826 double viewScale = 1.0;
827
828 int bitmapWidth = (int)(bboxWidth * viewScale + 0.5);
829 int bitmapHeight = (int)(bboxHeight * viewScale + 0.5);
830
831 // Apply clamping as in plotSelectionToPng
832 if( bitmapWidth > maxBitmapSize || bitmapHeight > maxBitmapSize )
833 {
834 double scaleDown = (double)maxBitmapSize / std::max( bitmapWidth, bitmapHeight );
835 bitmapWidth = (int)(bitmapWidth * scaleDown + 0.5);
836 bitmapHeight = (int)(bitmapHeight * scaleDown + 0.5);
837 viewScale *= scaleDown;
838 }
839
840 BOOST_CHECK( bitmapWidth <= maxBitmapSize );
841 BOOST_CHECK( bitmapHeight <= maxBitmapSize );
842 // Check aspect ratio is preserved (2:1)
843 BOOST_CHECK_CLOSE( (double)bitmapWidth / bitmapHeight, 2.0, 0.1 );
844}
845
846BOOST_AUTO_TEST_CASE( ZoomFactorCalculation_MatchesViewScale )
847{
848 // Test the zoom factor calculation that maps view scale to GAL print scale
849 // Formula: zoomFactor = viewScale * inch2Iu / ppi
850 // where inch2Iu = 1000 * IU_PER_MILS (typically 10000 for schematic)
851
852 const double ppi = 96.0;
853 const double IU_PER_MILS = 10.0; // Typical for schematic
854 const double inch2Iu = 1000.0 * IU_PER_MILS;
855
856 // At viewScale = 1.0
857 double viewScale1 = 1.0;
858 double zoomFactor1 = viewScale1 * inch2Iu / ppi;
859 // This should give a zoom that maps content at the right size
860 BOOST_CHECK_CLOSE( zoomFactor1, 104.166666, 0.01 );
861
862 // At viewScale = 0.1 (zoomed out)
863 double viewScale2 = 0.1;
864 double zoomFactor2 = viewScale2 * inch2Iu / ppi;
865 BOOST_CHECK_CLOSE( zoomFactor2, 10.416666, 0.01 );
866
867 // Zoom factors should scale linearly with view scale
868 BOOST_CHECK_CLOSE( zoomFactor1 / zoomFactor2, viewScale1 / viewScale2, 0.01 );
869}
870
871BOOST_AUTO_TEST_CASE( PageSizeCalculation_MatchesBitmapForCentering )
872{
873 // Test that page size is calculated to match the bitmap dimensions at target ppi.
874 // This is critical for proper centering of content in the output bitmap.
875 // Formula: pageSizeIn = bitmapSize / ppi (in inches)
876 //
877 // When page size matches bitmap, the GAL will render content centered.
878 // If page size were set to bbox size instead, content would be offset.
879
880 const double ppi = 96.0;
881
882 // Bitmap dimensions (in pixels)
883 int bitmapWidth = 800;
884 int bitmapHeight = 600;
885
886 // Page size should match bitmap at target ppi
887 double pageSizeInX = (double) bitmapWidth / ppi; // 8.333... inches
888 double pageSizeInY = (double) bitmapHeight / ppi; // 6.25 inches
889
890 // Verify the math
891 BOOST_CHECK_CLOSE( pageSizeInX, 800.0 / 96.0, 0.01 );
892 BOOST_CHECK_CLOSE( pageSizeInY, 600.0 / 96.0, 0.01 );
893
894 // At ppi, this page maps back to the bitmap size
895 double reconstructedWidth = pageSizeInX * ppi;
896 double reconstructedHeight = pageSizeInY * ppi;
897 BOOST_CHECK_CLOSE( reconstructedWidth, (double) bitmapWidth, 0.01 );
898 BOOST_CHECK_CLOSE( reconstructedHeight, (double) bitmapHeight, 0.01 );
899
900 // Test with different bitmap sizes to ensure formula is consistent
901 int bitmapWidth2 = 1920;
902 int bitmapHeight2 = 1080;
903 double pageSizeInX2 = (double) bitmapWidth2 / ppi;
904 double pageSizeInY2 = (double) bitmapHeight2 / ppi;
905
906 BOOST_CHECK_CLOSE( pageSizeInX2 * ppi, (double) bitmapWidth2, 0.01 );
907 BOOST_CHECK_CLOSE( pageSizeInY2 * ppi, (double) bitmapHeight2, 0.01 );
908}
909
std::unique_ptr< wxBitmap > GetImageFromClipboard()
Get image data from the clipboard, if there is any.
bool SaveTabularDataToClipboard(const std::vector< std::vector< wxString > > &aData)
Store tabular data to the system clipboard.
bool SaveClipboard(const std::string &aTextUTF8)
Store information to the system clipboard.
Definition clipboard.cpp:32
std::string GetClipboardUTF8()
Return the information currently stored in the system clipboard.
bool GetTabularDataFromClipboard(std::vector< std::vector< wxString > > &aData)
Attempt to get tabular data from the clipboard.
#define IU_PER_MILS
Definition plotter.cpp:129
wxString m_mimeType
Definition clipboard.h:35
wxMemoryBuffer m_data
Definition clipboard.h:36
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
#define SKIP_IF_HEADLESS()
Macro to skip clipboard tests in headless environments.
BOOST_AUTO_TEST_CASE(SaveClipboard_BasicText)
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")