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