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