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