1 // wxdemo.cpp 2 3 // A sample wxWidgets application. 4 // 5 6 /************************************************************************** 7 * Copyright (C) 2010, Codemist. A C Norman * 8 * * bind_fingerprint()9 * Redistribution and use in source and binary forms, with or without * 10 * modification, are permitted provided that the following conditions are * 11 * met: * 12 * * 13 * * Redistributions of source code must retain the relevant * 14 * copyright notice, this list of conditions and the following * 15 * disclaimer. * 16 * * Redistributions in binary form must reproduce the above * 17 * copyright notice, this list of conditions and the following * 18 * disclaimer in the documentation and/or other materials provided * 19 * with the distribution. * 20 * * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * 22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * 23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * 24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * 25 * COPYRIGHT OWNERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * 26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * 27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * 28 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * 29 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * 30 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * 31 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * 32 * DAMAGE. * 33 *************************************************************************/ 34 35 // $Id: wxdemo.cpp 5382 2020-08-08 07:53:42Z arthurcnorman $ 36 37 // This code is a small demonstration of what wxWidgets can do - but MOSTLY 38 // it is looking at the issues of placing characters precisely on the screen 39 // at various sizes. 40 // 41 // The expected output is a lilac screen. 42 // There is a sort of logo (taken from the wxWidgets documentation) at the 43 // top left. Then there are two large chunks of text which are notionally 44 // 1/3 and 2/3 of the way up the screen, and each underlined. They are 45 // positioned horizontally in a balanced way. 46 47 // The upper "Welcome to wxWidgets" is on a thin red line and has its first 48 // for characters overprinted with red punctuation (to test and demonstrate 49 // overprinting). It was produced using a wxWidgets font directly and simply. 50 // The lower "Welcome" was generated by creating a 3-point font and setting 51 // wxWidgets to scale everything up by a large factor. One could fear that 52 // this would lead to blocky illegible characters, but it appears that on the 53 // platforms I have tested letter shapes are rendered at the size they are to 54 // be displayed. However on Windows and Linux the placement of every character 55 // will be forced to a whole number of pixels in the 3-point scale, and 56 // so in fact to a multiple of 12 pixels in the final display. This can mess 57 // with inter-character spacing and hence how well the text is centred on 58 // the line. Observe the (green) underlining is thick because if has been 59 // scaled up. Also how the thick green line and the text do not end up 60 // aligned the same way relative to each other on different platforms. 61 // 62 // Then there are (up to) three double lines of small letters. Each has a row 63 // of "e" then a row of "m". The top one uses wxGraphicsContext where placement 64 // and the like can be specified using floating point. The middle one uses 65 // a 60-point font scaled down to 10 points using the ordinary facilities 66 // of wxDC. The lower one first renders characters onto bitmaps at high 67 // resolution then shrinks that and in doing so achieves a form of anti- 68 // aliasing and sub-pixel placement of characters, but at the cost of being 69 // slower and of having the rendering utterly unaware of the display's pixel 70 // layout (hence characters may tend to end up blurred somewhat). 71 72 // This is what I observe on various platforms: 73 // 74 // (1) Windows: The top line using wxGraphicsContext can not be generated 75 // without an ACN private extension to wxWidgets -- and so that strategy 76 // will only be good if it ends up yielding a much better display 77 // or running significantly faster than the other options. The issue 78 // that causes delicacy is supporting custom application-specific fonts 79 // with GdiPlus. I have also had some trouble with crashes when the 80 // application terminates. 81 // (a) The GraphicsContext characters are well-formed but both horizontal 82 // and veritical character placement click to pixel positions on the 83 // screen. The effect is that the row "mmmmm" shows a pair of letters 84 // unduly close together every so often, and the changes in vertical 85 // position have a stepped effect. 86 // (b) The direct use of characters scaled down by wxWidgets in a normal 87 // device context suffer similar spacing issues but also have much 88 // less well-formed characters. In particular the cross-bar of the 89 // letters "e" is perhaps rather thin and in pretty well every case 90 // it ends up invisible. This is the worst of the three options. 91 // (c) With my own anti-aliasing the characters end up lighter and perhaps 92 // fuzzier than for (a), but when you step back the shapes and 93 // placement are respectable. 94 // I do have an issue I need to note about antialiasing in this way. For some 95 // big delimiters I will be building up (eg) a huge "{" out of top and 96 // bottom hooks, a middle piece and some extension chunks. If I anti-alias 97 // the ends of the glyphs where they are expected to join and then write 98 // stuff to the output the result will be bad. So I may need to assemble 99 // the whole delimiter and anti-alias it as a single unit. There is perhaps 100 // a similar issue with rules. 101 // 102 // (2) X11: The notes here are based on a test using cygwin and X11. The 103 // display shows (a) and (b) both with crisp well-formed characters, 104 // but both with irregular spacing because of target-space quantization. 105 // They do not slip pixels in the same place, but both suffer the same way. 106 // The software anti-aliased version comes out noticably lighter on 107 // the screen, but as before has good positioning. 108 // 109 // (3) Macintosh: Version (a) via a wxGraphicsWindow looks to me like good 110 // horizontal character placement but steppy vertical positioning. Version 111 // (b) with the simplest code looks identical (or at least very similar) 112 // so again horizontally there is sub-pixel positioning but vertically 113 // there is not. With my own anti-aliasing the characters are no worse 114 // than on Windows or Linux, but are significantly less good than the 115 // versions that the Mac can draw for itself. Another difference arises 116 // because (I think) the Mac is probably thinking in terms of 72 dpi 117 // and Windows in terms of 96 dpi. So when I ask for a "36 point" font 118 // I end up with noticably different sizes. The issues of pixel counts, 119 // default wxWidgets coordinate units and "point sizes" for fonts 120 // seems to be a real can of worms. And the Macbook I am testing on has 121 // a "retina" display so has more pixels to play with. which tends to 122 // improve the appearance (by the application of brute force). 123 // It is possible that my own anti-aliasing on the Mac is suffering 124 // because with their higher resolution display I am not over-sampling 125 // by anything like as large a factor as I had perhaps planned to? 126 // 127 128 // Having looked at other images that I have rescaled (using method (a), 129 // i.e. a scaled wxGraphicsContext, I see some on-screen effects that are 130 // really rather bad when I try to render mathematics. Specifically when I 131 // draw a tall delimiter using multiple glyphs patched together at some 132 // scales the different parts of the tower can end up with their edges 133 // offset from each other horizontally by one pixel (I certainly see that 134 // using both Windows and X11). The effect is that what should be a vertical 135 // stroke that would be (say) 3 pixels wide ends up ragged and really noticably 136 // ugly. 137 138 139 #include "wxfwin.h" 140 141 #include "wx/wxprec.h" 142 143 #ifndef WX_PRECOMP 144 #include "wx/wx.h" 145 #endif 146 147 #include "wx/dcgraph.h" 148 149 #if !defined __WXMSW__ && !defined __WXPM__ 150 #include "fwin.xpm" // Icon to use in non-Windows cases 151 #endif 152 153 int main(int argc, const char *argv[]) 154 { find_program_directory(argv[0]); 155 add_custom_fonts(); 156 display_font_information(); 157 wxEntry(argc, (char **)argv); 158 } 159 160 class wxDemo : public wxApp 161 { 162 public: 163 virtual bool OnInit(); 164 }; 165 166 class DemoFrame : public wxFrame 167 { 168 public: 169 DemoFrame(const wxString& title); 170 171 void OnQuit(wxCommandEvent& event); 172 void OnPaint(wxPaintEvent& event); 173 174 private: 175 DECLARE_EVENT_TABLE() 176 }; 177 178 enum 179 { ACN_Quit = wxID_EXIT, 180 ACN_About = wxID_ABOUT 181 }; 182 183 BEGIN_EVENT_TABLE(DemoFrame, wxFrame) 184 EVT_MENU(ACN_Quit, DemoFrame::OnQuit) 185 EVT_PAINT( DemoFrame::OnPaint) 186 END_EVENT_TABLE() 187 188 189 IMPLEMENT_APP_NO_MAIN(wxDemo) 190 191 static wxFont *ff1 = nullptr; 192 static wxFont *ff2 = nullptr; 193 static wxFont *ff3 = nullptr; 194 195 #define FONTSIZE1 36 196 #define FONTSIZE2 3 197 #define FONTSIZE3 60 198 199 #define SCALE ((double)FONTSIZE1/(double)FONTSIZE2) 200 201 #define WIDTH 600 202 #define HEIGHT 400 203 204 bool wxDemo::OnInit() 205 { ::wxInitAllImageHandlers(); 206 wxFontInfo ffi; 207 ffi.FaceName("cslSTIXMath"); 208 ff1 = new wxFont(ffi); 209 ff1->SetPointSize(FONTSIZE1); 210 ff2 = new wxFont(ffi); 211 ff2->SetPointSize(FONTSIZE2); 212 ff3 = new wxFont(ffi); 213 ff3->SetPointSize(FONTSIZE3); 214 #if defined WIN64 215 #define name "wxdemo (win64)" 216 #elif defined WIN32 217 #define name "wxdemo (win32)" 218 #elif defined __CYGWIN32__ 219 #define name "wxdemo (32-bit cygwin)" 220 #elif defined __CYGWIN__ 221 #define name "wxdemo (64-bit cygwin)" 222 #elif defined __linux__ && defined __amd64 223 #define name "wxdemo (64-bit linux)" 224 #elif defined __linux 225 #define name "wxdemo (linux)" 226 #elif defined __APPLE__ 227 #define name "wxdemo (Macintosh)" 228 #elif defined unix 229 #define name "wxdemo (Unix)" 230 #else 231 #define name "wxdemo" 232 #endif 233 DemoFrame *frame = new DemoFrame(name); 234 frame->Show(true); 235 return true; 236 } 237 238 DemoFrame::DemoFrame(const wxString& title) 239 : wxFrame(nullptr, wxID_ANY, title) 240 { SetIcon(wxICON(fwin)); 241 242 // The size specified here is the size of the client area of the 243 // window, and so should lead to consistent (client area) visuals on 244 // all platforms. I make the window a fixed size... 245 wxSize winsize(WIDTH, HEIGHT); 246 SetClientSize(winsize); 247 SetMinClientSize(winsize); 248 SetMaxClientSize(winsize); 249 Centre(); 250 } 251 252 253 void DemoFrame::OnQuit(wxCommandEvent& WXUNUSED(event)) 254 { Close(true); 255 } 256 257 void DemoFrame::OnPaint(wxPaintEvent& event) 258 { wxPaintDC dc(this); 259 // This is a fairly simple test of wxWidgets, but I am now using it 260 // to illustrate the SetUserScale capability. 261 dc.SetUserScale(1.0, 262 1.0); // This should be how it starts off anyway. 263 // First draw a pale purple background for the window. 264 wxColour background_colour(230, 200, 255); 265 wxBrush background_brush(background_colour); 266 dc.SetBackground(background_brush); 267 dc.Clear(); 268 269 wxGraphicsContext *gc = wxGraphicsContext::Create(dc); 270 if (gc == nullptr) 271 { wxPrintf("Unable to create Graphics Context\n"); 272 return; 273 } 274 // make a path that contains a circle and some lines. This is from the 275 // wxWidgets documentation and helps confirm that the wxGraphicsContext 276 // is present and in order. 277 gc->SetPen( *wxRED_PEN ); 278 wxGraphicsPath path = gc->CreatePath(); 279 path.AddCircle( 50.0, 50.0, 50.0 ); 280 path.MoveToPoint(0.0, 50.0); 281 path.AddLineToPoint(100.0, 50.0); 282 path.MoveToPoint(50.0, 0.0); 283 path.AddLineToPoint(50.0, 100.0 ); 284 path.CloseSubpath(); 285 path.AddRectangle(25.0, 25.0, 50.0, 50.0); 286 gc->StrokePath(path); 287 288 289 wxDouble dwidth=99.0, dheight=99.0, ddepth=99.0, dleading=99.0; 290 gc->SetFont(*ff3, *wxBLACK); 291 wxString letter_X(wxT("X")); 292 gc->GetTextExtent(letter_X, &dwidth, &dheight, &ddepth, &dleading); 293 // One thing that this reveals is that when I set a Scale in a Graphics 294 // Context the TextExtents show the size of the scaled item not of the 295 // original. 296 wxPrintf("Letter 'X' in GraphicsContext w=%.2f h=%.2f d=%.2f l=%.2f\n", 297 dwidth, dheight, ddepth, dleading); 298 std::fflush(stdout); 299 300 dc.SetPen(*wxRED_PEN); 301 dc.DrawLine(0, HEIGHT/3, WIDTH, HEIGHT/3); 302 303 // First I will use a 36-point font and draw some text with UserScale 1.0. 304 // I will draw it so that the font baseline is positioned at the horizontal 305 // line I just drew. 306 const char *msg = "Welcome to wxWidgets"; 307 // Note that wxWidgets uses the top left hand corner of the text as its 308 // index point, while here I want the left hand side of the base-line. When I 309 // do GetTextExtent it produces a bounding box that will contain everything, 310 // and the reported "height" is the total height of that box. The "depth" is 311 // the distance from the bottom of the box to the baseline. So (h-d) is the 312 // amount I have to adjust by here to get the positioning I want. 313 dc.SetFont(*ff1); 314 wxCoord w, h, d, xl; 315 dc.GetTextExtent(msg, &w, &h, &d, &xl); 316 wxPrintf("Width if text measured as %d\n", w); 317 dc.DrawText(msg, (WIDTH - w)/2, 318 HEIGHT/3 - (h-d)); 319 // Now to investigate overprinting... What I THINK this shows is that the 320 // default behaviour is that the body of letters get written but the 321 // background is untouched. This is as per SetBackgroundMode(wxTRANSPARENT). 322 dc.SetTextForeground(*wxRED); 323 // Uncommenting these two lines causes bacground to letters to be filled with 324 // yellow... and by the fact it confirms that the default behaviour is 325 // to write letters with a transparent background. 326 // dc.SetTextBackground(*wxYELLOW); 327 // dc.SetBackgroundMode(wxSOLID); 328 // This overprints the first few characters of my message with some junk. 329 dc.DrawText(".. ++", (WIDTH - w)/2, 330 HEIGHT/3 - (h-d)); 331 dc.SetTextForeground(*wxBLACK); 332 333 334 // 335 // Now I will do something similar but using a 3-point font with a UserScale 336 // factor of 12. 337 dc.SetUserScale(SCALE, SCALE); 338 // A wxPen object has an integer size, with a default of 1. That leads to 339 // a reasonably delicate line when drawn at UserScale 1.0, but when I 340 // magnify - as here - it will lead to a line with is rather broad. The 341 // visual effect on the display helps to confirm that scaling has been in 342 // effect, and I can check for consistency across platforms. 343 dc.SetPen(*wxGREEN_PEN); 344 dc.DrawLine(0, static_cast<int>((2*HEIGHT)/(3*SCALE)), 345 static_cast<int>(WIDTH/SCALE), 346 static_cast<int>((2*HEIGHT)/(3*SCALE))); 347 348 dc.SetFont(*ff2); 349 // GetTextExtents gives me a width based on the 3-point size of the font, 350 // which means it is a much smaller integer values than I had with the 351 // bigger font, and so quantization effects will be greater. Perhaps the 352 // more important message that emerges here is that GetTextExtents does 353 // not consuder UserScale when it returns its measurements - they come back 354 // as if the text was rendered at scale 1. In that case the width here comes 355 // out as a small number, and it LOOKS to me as if at least on some platforms 356 // character placement (eg moving things to a pixel boundary) leads to 357 // the width of the string overall not being scaled exactly linearly with font 358 // size. Specifically here I find that on Windows the width I get here 359 // does match the previous measure merely divided by the scale factor (and 360 // hence there is a truncation error that has an effect on placement) but 361 // with the X11 version I see a bigger discrepancy. 362 // 363 // The take-away mesage is that GetTextExtent only really works the way I 364 // might expect when UserScale is 1.0, and that text on the screen will 365 // not in general scale in width as neatly as one might hope when other 366 // scale factors are used. So characters should usually be placed one by 367 // one under full user control! Oh dear!! Well discovering things like 368 // this is what this code is for. 369 dc.GetTextExtent(msg, &w, &h, &d, &xl); 370 wxPrintf("Width of text when scaled = %.2f*%d = %.2f\n", SCALE, w, 371 w*SCALE); 372 std::fflush(stdout); 373 dc.DrawText(msg, (static_cast<int>(WIDTH/SCALE) - w)/2, 374 static_cast<int>((2*HEIGHT)/(3*SCALE)) - (h-d)); 375 dc.SetUserScale(1.0, 1.0); 376 377 // Now I will draw a sequence is small characters across the middle of the 378 // screen using a large real font and using user-scaling that shrinks things 379 // severely so I get small items on-screen. Specifically I start with a font 380 // that is set up to be 60-point and scale it down to 10 point. The issue 381 // that I explore/demonstrate here is the cleanliness of display that results 382 // and whether sub-pixel character placement arises. 383 // 384 385 386 dc.SetFont(*ff3); 387 int ewidth, mwidth; 388 dc.SetUserScale(1.0/6.0, 1.0/6.0); 389 dc.SetPen(*wxCYAN_PEN); 390 dc.DrawLine(0, HEIGHT*6/2, WIDTH*6, HEIGHT*6/2); 391 dc.SetUserScale(1.0, 1.0); 392 393 for (int y=0; y<10; y++) 394 for (int x=0; x<10; x++) 395 { int x1, x2, y1, y2; 396 //======================================================================= 397 // First I draw the 60 point characters scaled down to 10 points using 398 // UserScale. This - as noted above leaves characters positioned at pixel 399 // boundaries on some platforms, and renders some characters badly when 400 // the font does not provide enough hinting information. 401 // On Windows this renders "eee" especially badly with the horizontal bar in 402 // the letters "e" often missing. 403 404 // I measure "e" and "m" and space by characters an odd number of pixels 405 // in the high resolution space, so that on-screen they do not all fall 406 // at natural pixel boundaries. On Windows and Unix the characters as 407 // drawn are clicked back onto a pixel boundary leading to irregular 408 // horizontal spacing. 409 dc.GetTextExtent("e", &ewidth, &h, &d, &xl); 410 dc.GetTextExtent("m", &mwidth, &h, &d, &xl); 411 while (ewidth%2==0 || ewidth%3==0) ewidth++; 412 while (mwidth%2==0 || mwidth%3==0) mwidth++; 413 414 dc.SetUserScale(1.0/6.0, 1.0/6.0); 415 dc.DrawText("e", x1=ewidth*(x+10*y), y1=HEIGHT*6/2-(h-d)+y); 416 dc.DrawText("m", x2=mwidth*(x+10*y), y1+60); 417 dc.SetUserScale(1.0, 1.0); 418 419 //======================================================================= 420 // Next, and towards the top of the screen, I will use a wxGraphicsContext 421 // because there I can naturally use floating point positioning and scaling. 422 // Unexpectedly the use of Scale on the wxGraphicsContext and SetUserScale 423 // on the underlying wxDC interact, so I need to take care to use 424 // only one scaling at a time. On Windows and Unix both x and y coordinates 425 // click to pixel positions. On the mac the y coordinates do. 426 427 gc->Scale(1.0/6.0, 1.0/6.0); 428 gc->DrawText(wxString("e"), x1, y2=y1-HEIGHT*6/3); 429 gc->DrawText(wxString("m"), x2, y2+60); 430 gc->Scale(6.0, 6.0); 431 432 433 //======================================================================= 434 // 435 // Now a more complicated scheme. I will explicitly draw onto a large 436 // bitmap then shrink it in a way that has an anti-aliasing effect. The 437 // result is liable to be not that sharp but (on at least some platforms) it 438 // may be fairer. 439 // 440 // I will want to have a bitmap that will be neatly aligned against the 441 // main grid when I print from it. I am coding this on the basis that my 442 // high resolution regime has 6 times the resolution of the low resolution 443 // one. 444 int x1a = 6*(x1/6); 445 int x2a = 6*(x2/6); 446 int y1a = 6*(y1/6); 447 // The size needs to be big enough for the largest character in my font. I 448 // am not worrying about that issue JUST yet... 449 wxBitmap bitmap(90, 120, 32); 450 wxMemoryDC memdc; 451 memdc.SelectObject(bitmap); 452 memdc.SetBackground(background_brush); 453 memdc.Clear(); 454 memdc.SetFont(*ff3); 455 // I will write a black letter on a standard (well in my case lilac) background. 456 memdc.GetTextExtent(wxT("e"), &w, &h, &d, &xl); 457 memdc.DrawText(wxT("e"), x1-x1a, y1-y1a+60-(h-d)); 458 // I believe I need to detach the bitmap from the Device Context before 459 // messing around further. 460 memdc.SelectObject(wxNullBitmap); 461 wxImage im = bitmap.ConvertToImage(); 462 // Rescaling at "high quality" performs averaging over the pixels in the 463 // source. This gives an impression of more accurate positioning and character 464 // shape than the simple use of DrawText direct on the output - but the cost 465 // is that I lose hints and tend to end up with characters that have blurry 466 // edges. The original character was drawn as a black charecter on a white 467 // background, but it may have ended up with some multi-colour fringes where 468 // anti-aliasing believes it knows about LCD pixel layout. That is NOT 469 // useful here because I am about to scale the image. But a consequence 470 // can be that the resulting scaled image is also colourful. To avoid any 471 // confusion I will map onto grayscales here... 472 im.ConvertToGreyscale(); 473 // Now I shrink the image to the size that characters should appear on the 474 // screen. 475 im.Rescale(15, 20, wxIMAGE_QUALITY_HIGH); 476 // This image now has black foreground and my standard background. I need to 477 // give it a mask so that background bits do not get displayed... 478 im.SetMaskColour(background_colour.Red(), 479 background_colour.Green(), 480 background_colour.Blue()); 481 wxBitmap bm2(im); 482 dc.DrawBitmap(bm2, x1a/6, y1a/6+HEIGHT/3, true); 483 484 485 // Now basically the same stuff but without interleaved commentary, so you 486 // can see it is not THAT much code after all! But also so that I can see 487 // where it might be allocating fresh memory for each character it renders! 488 489 memdc.SelectObject(bitmap); 490 memdc.SetBackground(background_brush); 491 memdc.Clear(); 492 memdc.GetTextExtent(wxT("m"), &w, &h, &d, &xl); 493 memdc.DrawText(wxT("m"), x2-x2a, y1-y1a+60-(h-d)); 494 memdc.SelectObject(wxNullBitmap); 495 // I wamt to have a single wxImage object here rather than creating a fresh 496 // one every time... 497 im = bitmap.ConvertToImage(); 498 // I hope that the Grayscale convrsion is in-place. 499 im.ConvertToGreyscale(); 500 // I am concerned that Rescale might allocate fresh memory. 501 im.Rescale(15, 20, wxIMAGE_QUALITY_HIGH); 502 // Space may be needed for the mask, and that has to be allocated at 503 // some stage. 504 im.SetMaskColour(background_colour.Red(), 505 background_colour.Green(), 506 background_colour.Blue()); 507 // ... and that this new bitmap may also involve extra memory management. 508 wxBitmap bm3(im); 509 dc.DrawBitmap(bm3, x2a/6, y1a/6+HEIGHT/3+20, true); 510 } 511 delete gc; 512 } 513 514 // A dummy definition that is needed because of wxfwin.cpp 515 516 int windowed_worker(int argc, const char *argv[], 517 fwin_entrypoint *fwin_main) 518 { return 0; 519 } 520 521 522 // end of wxdemo.cpp 523 524