Files
wxWidgets/samples/svg/svgtest.cpp
T
Maarten Bent 700defddcc Save SVG files with the actual drawing size
First render to a temporary wxSVGFileDC, then use the size of the bounding box as input for the actual SVG size.
Use a slightly larger size to account for lines drawn with a bigger wxPen width.
2025-09-14 14:23:21 +02:00

502 lines
19 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: svgtest.cpp
// Purpose: SVG sample
// Author: Chris Elliott
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// ===========================================================================
// declarations
// ===========================================================================
// ---------------------------------------------------------------------------
// headers
// ---------------------------------------------------------------------------
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include "wx/dcsvg.h"
#include "wx/notebook.h"
#include "SVGlogo24.xpm"
#ifndef wxHAS_IMAGES_IN_RESOURCES
#include "../sample.xpm"
#endif
#include <math.h>
// ---------------------------------------------------------------------------
// classes
// ---------------------------------------------------------------------------
class MyApp : public wxApp
{
public:
bool OnInit() override;
};
// Existing pages:
enum Page
{
Page_Lines,
Page_Polygons,
Page_Text,
Page_Arcs,
Page_Checkmarks,
Page_ScaledText,
Page_Bitmaps,
Page_Clipping,
Page_TextPos,
Page_Max
};
static const char* pageNames[] =
{
"Lines",
"Polygons",
"Text",
"Arcs",
"Checkmarks",
"Scaled text",
"Bitmaps",
"Clipping",
"Text position",
};
wxCOMPILE_TIME_ASSERT( WXSIZEOF(pageNames) == Page_Max, PageNamesMismatch );
static const char* pageDescriptions[] =
{
"Green Cross, Cyan Line and spline",
"Blue rectangle, red edge, clear rounded rectangle, gold ellipse, gold and clear stars",
"Swiss, Times text; red text, rotated and colored orange",
"This is an arc test page",
"Two check marks",
"Scaling test page",
"Icon and Bitmap ",
"Clipping region",
"Text position test page",
};
wxCOMPILE_TIME_ASSERT( WXSIZEOF(pageDescriptions) == Page_Max, PageDescriptionsMismatch );
class MyPage : public wxScrolledWindow
{
public:
MyPage(wxNotebook *parent, int index);
virtual void OnDraw(wxDC& dc) override;
bool OnSave(const wxString& filename);
private:
int m_index;
};
class MyFrame : public wxFrame
{
public:
MyFrame(wxWindow *parent, const wxWindowID id, const wxString& title);
void FileSavePicture(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
void OnQuit(wxCommandEvent& event);
private:
wxNotebook *m_notebook;
wxDECLARE_EVENT_TABLE();
};
// ---------------------------------------------------------------------------
// event tables
// ---------------------------------------------------------------------------
wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
EVT_MENU(wxID_EXIT, MyFrame::OnQuit)
EVT_MENU(wxID_SAVE, MyFrame::FileSavePicture)
wxEND_EVENT_TABLE()
// ===========================================================================
// implementation
// ===========================================================================
// ---------------------------------------------------------------------------
// MyApp
// ---------------------------------------------------------------------------
wxIMPLEMENT_APP(MyApp);
bool MyApp::OnInit()
{
// Create the main frame window
MyFrame* frame = new MyFrame(nullptr, -1, "SVG Demo");
frame->SetSize(frame->FromDIP(wxSize(600, 500)));
frame->Show(true);
return true;
}
// ---------------------------------------------------------------------------
// MyFrame
// ---------------------------------------------------------------------------
// Define my frame constructor
MyFrame::MyFrame(wxWindow *parent, const wxWindowID id, const wxString& title)
: wxFrame(parent, id, title)
{
SetIcon(wxICON(sample));
#if wxUSE_STATUSBAR
CreateStatusBar();
#endif // wxUSE_STATUSBAR
// Make a menubar
wxMenu *file_menu = new wxMenu;
file_menu->Append(wxID_SAVE);
file_menu->Append(wxID_EXIT);
wxMenu *help_menu = new wxMenu;
help_menu->Append(wxID_ABOUT);
wxMenuBar *menu_bar = new wxMenuBar;
menu_bar->Append(file_menu, "&File");
menu_bar->Append(help_menu, "&Help");
// Associate the menu bar with the frame
SetMenuBar(menu_bar);
// Create a notebook
m_notebook = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_TOP);
//Add SVG Windows to a notebook
for (int i = 0; i < Page_Max; ++i)
{
m_notebook->AddPage(new MyPage(m_notebook, i), pageNames[i]);
}
}
void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event))
{
Close();
}
void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
{
(void)wxMessageBox("wxWidgets SVG sample\n"
"Authors:\n"
" Chris Elliott (c) 2002-2009\n"
" Prashant Kumar Nirmal (c) 2017\n"
"Usage: click File|Save to Save the Selected SVG Test",
"About SVG Test");
}
void MyFrame::FileSavePicture(wxCommandEvent& WXUNUSED(event))
{
#if wxUSE_FILEDLG
MyPage * const page = (MyPage *) m_notebook->GetCurrentPage();
wxFileDialog dialog(this, "Save Picture as", wxEmptyString,
m_notebook->GetPageText(m_notebook->GetSelection()),
"SVG vector picture files (*.svg)|*.svg",
wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
if (dialog.ShowModal() == wxID_OK)
{
if (!page->OnSave ( dialog.GetPath() ))
{
return;
}
}
return;
#endif // wxUSE_FILEDLG
}
// ---------------------------------------------------------------------------
// MyPage
// ---------------------------------------------------------------------------
// Define a constructor for my page
MyPage::MyPage(wxNotebook *parent, int index)
: wxScrolledWindow(parent)
{
SetBackgroundColour(*wxWHITE);
SetScrollbars(20, 20, 50, 50);
m_index = index;
}
bool MyPage::OnSave(const wxString& filename)
{
wxSize svgSize;
wxSVGFileDC tempSvgDC("", svgSize.x, svgSize.y, 72, pageNames[m_index]);
OnDraw(tempSvgDC);
svgSize = wxSize(tempSvgDC.MaxX(), tempSvgDC.MaxY());
svgSize.IncBy(15); // account for wxPen width exceeding bounds
wxSVGFileDC svgDC(filename, svgSize.x, svgSize.y, 72, pageNames[m_index]);
svgDC.SetBitmapHandler(new wxSVGBitmapEmbedHandler());
OnDraw(svgDC);
return svgDC.IsOk();
}
// Define the repainting behaviour
void MyPage::OnDraw(wxDC& dc)
{
// vars to use ...
wxPen wP;
wxBrush wB;
wxPoint points[6];
wxColour wC;
dc.SetFont(wxFontInfo(9).FaceName("Arial").Family(wxFONTFAMILY_SWISS));
dc.SetPen(*wxGREEN_PEN);
switch (m_index)
{
case Page_Lines:
// draw lines to make a cross
dc.DrawLine(dc.FromDIP(0), dc.FromDIP(0), dc.FromDIP(200), dc.FromDIP(200));
dc.DrawLine(dc.FromDIP(200), dc.FromDIP(0), dc.FromDIP(0), dc.FromDIP(200));
// draw point colored line and spline
wP = *wxCYAN_PEN;
wP.SetWidth(3);
dc.SetPen(wP);
dc.DrawPoint(dc.FromDIP(25), dc.FromDIP(15));
dc.DrawLine(dc.FromDIP(50), dc.FromDIP(30), dc.FromDIP(200), dc.FromDIP(30));
dc.DrawSpline(dc.FromDIP(50), dc.FromDIP(200), dc.FromDIP(50), dc.FromDIP(100), dc.FromDIP(200), dc.FromDIP(10));
break;
case Page_Polygons:
// draw standard shapes
dc.SetBrush(*wxCYAN_BRUSH);
dc.SetPen(*wxRED_PEN);
dc.DrawRectangle(dc.FromDIP(10), dc.FromDIP(10), dc.FromDIP(100), dc.FromDIP(70));
wB = wxBrush ("DARK ORCHID", wxBRUSHSTYLE_TRANSPARENT);
dc.SetBrush (wB);
dc.DrawRoundedRectangle(dc.FromDIP(50), dc.FromDIP(50), dc.FromDIP(100), dc.FromDIP(70), dc.FromDIP(20));
dc.SetBrush (wxBrush("GOLDENROD") );
dc.DrawEllipse(dc.FromDIP(100), dc.FromDIP(100), dc.FromDIP(100), dc.FromDIP(50));
points[0].x = dc.FromDIP(100); points[0].y = dc.FromDIP(200);
points[1].x = dc.FromDIP(70); points[1].y = dc.FromDIP(260);
points[2].x = dc.FromDIP(160); points[2].y = dc.FromDIP(230);
points[3].x = dc.FromDIP(40); points[3].y = dc.FromDIP(230);
points[4].x = dc.FromDIP(130); points[4].y = dc.FromDIP(260);
points[5].x = dc.FromDIP(100); points[5].y = dc.FromDIP(200);
dc.DrawPolygon(5, points);
dc.DrawLines(6, points, dc.FromDIP(160));
break;
case Page_Text:
// draw text in Arial or similar font
dc.DrawLine(dc.FromDIP(50), dc.FromDIP(25), dc.FromDIP(50), dc.FromDIP(35));
dc.DrawLine(dc.FromDIP(45), dc.FromDIP(30), dc.FromDIP(55), dc.FromDIP(30));
dc.DrawText("This is a Swiss-style string", dc.FromDIP(50), dc.FromDIP(30));
wC = dc.GetTextForeground();
dc.SetTextForeground ("FIREBRICK");
// no effect in msw ??
dc.SetTextBackground ("WHEAT");
dc.DrawText("This is a Red string", dc.FromDIP(50), dc.FromDIP(200));
dc.DrawRotatedText("This is a 45 deg string", dc.FromDIP(50), dc.FromDIP(200), 45);
dc.DrawRotatedText("This is a 90 deg string", dc.FromDIP(50), dc.FromDIP(200), 90);
dc.SetFont(wxFontInfo(18)
.FaceName("Times New Roman")
.Family(wxFONTFAMILY_ROMAN)
.Italic().Bold());
dc.SetTextForeground (wC);
dc.DrawText("This is a Times-style string", dc.FromDIP(50), dc.FromDIP(60));
break;
case Page_Arcs:
// four arcs start and end points, center
dc.SetBrush(*wxGREEN_BRUSH);
dc.DrawArc(dc.FromDIP(200), dc.FromDIP(300), dc.FromDIP(370), dc.FromDIP(230), dc.FromDIP(300), dc.FromDIP(300));
dc.SetBrush(*wxBLUE_BRUSH);
dc.DrawArc(dc.FromDIP(270 - 50), dc.FromDIP(270 - 86), dc.FromDIP(270 - 86), dc.FromDIP(270 - 50), dc.FromDIP(270), dc.FromDIP(270));
dc.SetDeviceOrigin(-10,-10);
dc.DrawArc(dc.FromDIP(270 - 50), dc.FromDIP(270 - 86), dc.FromDIP(270 - 86), dc.FromDIP(270 - 50), dc.FromDIP(270), dc.FromDIP(270));
dc.SetDeviceOrigin(0,0);
wP.SetColour ("CADET BLUE");
dc.SetPen(wP);
dc.DrawArc(dc.FromDIP(75), dc.FromDIP(125), dc.FromDIP(110), dc.FromDIP(40), dc.FromDIP(75), dc.FromDIP(75));
wP.SetColour ("SALMON");
dc.SetPen(wP);
dc.SetBrush(*wxRED_BRUSH);
//top left corner, width and height, start and end angle
// 315 same center and x-radius as last pie-arc, half Y radius
dc.DrawEllipticArc(dc.FromDIP(25), dc.FromDIP(50), dc.FromDIP(100), dc.FromDIP(50), 180.0, 45.0);
wP = *wxCYAN_PEN;
wP.SetWidth(3);
dc.SetPen(wP);
//wxBRUSHSTYLE_TRANSPARENT));
dc.SetBrush (wxBrush ("SALMON"));
dc.DrawEllipticArc(dc.FromDIP(300), dc.FromDIP(0), dc.FromDIP(200), dc.FromDIP(100), 0.0, 145.0);
//same end point
dc.DrawEllipticArc(dc.FromDIP(300), dc.FromDIP(50), dc.FromDIP(200), dc.FromDIP(100), 90.0, 145.0);
dc.DrawEllipticArc(dc.FromDIP(300), dc.FromDIP(100), dc.FromDIP(200), dc.FromDIP(100), 90.0, 345.0);
break;
case Page_Checkmarks:
dc.DrawCheckMark(dc.FromDIP(30), dc.FromDIP(30), dc.FromDIP(25), dc.FromDIP(25));
dc.SetBrush (wxBrush ("SALMON",wxBRUSHSTYLE_TRANSPARENT));
dc.DrawCheckMark(dc.FromDIP(80), dc.FromDIP(50), dc.FromDIP(75), dc.FromDIP(75));
dc.DrawRectangle(dc.FromDIP(80), dc.FromDIP(50), dc.FromDIP(75), dc.FromDIP(75));
break;
case Page_ScaledText:
dc.SetFont(wxFontInfo(18)
.FaceName("Times New Roman")
.Family(wxFONTFAMILY_ROMAN)
.Italic().Bold());
dc.DrawLine(dc.FromDIP(0), dc.FromDIP(0), dc.FromDIP(200), dc.FromDIP(200));
dc.DrawLine(dc.FromDIP(200), dc.FromDIP(0), dc.FromDIP(0), dc.FromDIP(200));
dc.DrawText("This is an 18pt string", dc.FromDIP(50), dc.FromDIP(60));
// rescale and draw in blue
wP = *wxCYAN_PEN;
dc.SetPen(wP);
dc.SetUserScale (2.0,0.5);
dc.SetDeviceOrigin(dc.FromDIP(200), dc.FromDIP(0));
dc.DrawLine(dc.FromDIP(0), dc.FromDIP(0), dc.FromDIP(200), dc.FromDIP(200));
dc.DrawLine(dc.FromDIP(200), dc.FromDIP(0), dc.FromDIP(0), dc.FromDIP(200));
dc.DrawText("This is an 18pt string 2 x 0.5 UserScaled", dc.FromDIP(50), dc.FromDIP(60));
dc.SetUserScale (2.0,2.0);
dc.SetDeviceOrigin(dc.FromDIP(200), dc.FromDIP(200));
dc.DrawText("This is an 18pt string 2 x 2 UserScaled", dc.FromDIP(50), dc.FromDIP(60));
wP = *wxRED_PEN;
dc.SetPen(wP);
dc.SetUserScale (1.0,1.0);
dc.SetDeviceOrigin(dc.FromDIP(0), dc.FromDIP(10));
dc.SetMapMode (wxMM_METRIC); //svg ignores this
dc.DrawLine(dc.FromDIP(0), dc.FromDIP(0), dc.FromDIP(200), dc.FromDIP(200));
dc.DrawLine(dc.FromDIP(200), dc.FromDIP(0), dc.FromDIP(0), dc.FromDIP(200));
dc.DrawText("This is an 18pt string in MapMode", dc.FromDIP(50), dc.FromDIP(60));
break;
case Page_Bitmaps:
dc.DrawIcon(wxICON(sample), dc.FromDIP(10), dc.FromDIP(10));
dc.DrawBitmap(wxBitmap(svgbitmap_xpm), dc.FromDIP(50), dc.FromDIP(15));
break;
case Page_Clipping:
dc.SetTextForeground("RED");
dc.DrawText("Red = Clipping Off", dc.FromDIP(30), dc.FromDIP(5));
dc.SetTextForeground("GREEN");
dc.DrawText("Green = Clipping On", dc.FromDIP(30), dc.FromDIP(25));
dc.SetTextForeground("BLACK");
dc.SetPen(*wxRED_PEN);
dc.SetBrush (wxBrush ("SALMON",wxBRUSHSTYLE_TRANSPARENT));
dc.DrawCheckMark(dc.FromDIP(80), dc.FromDIP(50), dc.FromDIP(75), dc.FromDIP(75));
dc.DrawRectangle(dc.FromDIP(80), dc.FromDIP(50), dc.FromDIP(75), dc.FromDIP(75));
dc.SetPen(*wxGREEN_PEN);
// Clipped checkmarks
dc.DrawRectangle(dc.FromDIP(180), dc.FromDIP(50), dc.FromDIP(75), dc.FromDIP(75));
dc.SetClippingRegion(dc.FromDIP(180), dc.FromDIP(50), dc.FromDIP(75), dc.FromDIP(75)); // x,y,width,height version
dc.DrawCheckMark(dc.FromDIP(180), dc.FromDIP(50), dc.FromDIP(75), dc.FromDIP(75));
dc.DestroyClippingRegion();
dc.DrawRectangle(wxRect(dc.FromDIP(wxPoint(80, 150)), dc.FromDIP(wxSize(75, 75))));
dc.SetClippingRegion(dc.FromDIP(wxPoint(80, 150)), dc.FromDIP(wxSize(75, 75))); // pt,size version
dc.DrawCheckMark(dc.FromDIP(80), dc.FromDIP(150), dc.FromDIP(75), dc.FromDIP(75));
dc.DestroyClippingRegion();
dc.DrawRectangle(wxRect(dc.FromDIP(wxPoint(180, 150)), dc.FromDIP(wxSize(75, 75))));
dc.SetClippingRegion(wxRect(dc.FromDIP(wxPoint(180, 150)), dc.FromDIP(wxSize(75, 75)))); // rect version
dc.DrawCheckMark(dc.FromDIP(180), dc.FromDIP(150), dc.FromDIP(75), dc.FromDIP(75));
dc.DestroyClippingRegion();
dc.DrawRectangle(wxRect(dc.FromDIP(wxPoint(80, 250)), dc.FromDIP(wxSize(50, 65))));
dc.DrawRectangle(wxRect(dc.FromDIP(wxPoint(105, 260)), dc.FromDIP(wxSize(50, 65))));
dc.SetClippingRegion(wxRect(dc.FromDIP(wxPoint(80, 250)), dc.FromDIP(wxSize(50, 65)))); // second call to SetClippingRegion
dc.SetClippingRegion(wxRect(dc.FromDIP(wxPoint(105, 260)), dc.FromDIP(wxSize(50, 65)))); // forms intersection with previous
dc.DrawCheckMark(dc.FromDIP(80), dc.FromDIP(250), dc.FromDIP(75), dc.FromDIP(75));
dc.DestroyClippingRegion(); // only one call to destroy (there's no stack)
/*
** Clipping by wxRegion not implemented for SVG. Should be
** possible, but need to access points that define the wxRegion
** from inside DoSetDeviceClippingRegion() and wxRegion does not
** implement anything like getPoints().
points[0].x = 180; points[0].y = 250;
points[1].x = 255; points[1].y = 250;
points[2].x = 180; points[2].y = 325;
points[3].x = 255; points[3].y = 325;
points[4].x = 180; points[4].y = 250;
dc.DrawLines (5, points);
wxRegion reg = wxRegion(5,points);
dc.SetClippingRegion(reg);
dc.DrawCheckMark ( 180,250,75,75);
dc.DestroyClippingRegion();
*/
break;
case Page_TextPos:
wxString txtStr;
wxCoord txtX, txtY, txtW, txtH, txtDescent, txtEL;
wxCoord txtPad = 0;
wP = *wxRED_PEN;
dc.SetPen(wP);
//dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID);
//dc.SetTextBackground(*wxBLUE);
// Horizontal text
txtStr = "Horizontal string";
dc.GetTextExtent(txtStr, &txtW, &txtH, &txtDescent, &txtEL);
txtX = dc.FromDIP(50);
txtY = dc.FromDIP(200);
dc.DrawRectangle(txtX, txtY, txtW + 2*txtPad, txtH + 2*txtPad);
dc.DrawText(txtStr, txtX + txtPad, txtY + txtPad);
// Vertical text
txtStr = "Vertical string";
dc.GetTextExtent(txtStr, &txtW, &txtH, &txtDescent, &txtEL);
txtX = dc.FromDIP(50);
txtY = dc.FromDIP(150);
dc.DrawRectangle(txtX, txtY - (txtW + 2*txtPad), txtH + 2*txtPad, txtW + 2*txtPad);
dc.DrawRotatedText(txtStr, txtX + txtPad, txtY - txtPad, 90);
// 45 degree text
txtStr = "45 deg string";
dc.GetTextExtent(txtStr, &txtW, &txtH, &txtDescent, &txtEL);
double lenW = (double)(txtW + 2*txtPad) / sqrt(2.0);
double lenH = (double)(txtH + 2*txtPad) / sqrt(2.0);
double padding = (double)txtPad / sqrt(2.0);
txtX = dc.FromDIP(150);
txtY = dc.FromDIP(100);
dc.DrawLine(txtX - int(padding), txtY, txtX + int(lenW), txtY - int(lenW)); // top
dc.DrawLine(txtX + int(lenW), txtY - int(lenW), txtX - int(padding + lenH + lenW), txtY + int(lenH - lenW));
dc.DrawLine(txtX - int(padding), txtY, txtX - int(padding + lenH), txtY + int(lenH));
dc.DrawLine(txtX - int(padding + lenH), txtY + int(lenH), txtX - int(padding + lenH + lenW), txtY + int(lenH - lenW)); // bottom
dc.DrawRotatedText(txtStr, txtX, txtY, 45);
break;
}
wxLogStatus(pageDescriptions[m_index]);
}