1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/svg.cpp
4 // Author: Chris Elliott
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
11 // For compilers that support precompilation, includes "wx/wx.h".
12 #include "wx/wxprec.h"
21 #include "wx/dcmemory.h"
22 #include "wx/dcscreen.h"
28 #include "wx/wfstream.h"
29 #include "wx/filename.h"
31 // ----------------------------------------------------------
33 // ----------------------------------------------------------
38 inline double DegToRad(double deg
) { return (deg
* M_PI
) / 180.0; }
40 // This function returns a string representation of a floating point number in
41 // C locale (i.e. always using "." for the decimal separator) and with the
42 // fixed precision (which is 2 for some unknown reason but this is what it was
43 // in this code originally).
44 inline wxString
NumStr(double f
)
46 return wxString::FromCDouble(f
, 2);
49 // Return the colour representation as HTML-like "#rrggbb" string and also
50 // returns its alpha as opacity number in 0..1 range.
51 wxString
Col2SVG(wxColour c
, float *opacity
)
53 if ( c
.Alpha() != wxALPHA_OPAQUE
)
55 *opacity
= c
.Alpha()/255.;
57 // Remove the alpha before using GetAsString(wxC2S_HTML_SYNTAX) as it
58 // doesn't support colours with alpha channel.
59 c
= wxColour(c
.GetRGB());
66 return c
.GetAsString(wxC2S_HTML_SYNTAX
);
69 wxString
wxPenString(wxColour c
, int style
= wxPENSTYLE_SOLID
)
72 wxString s
= wxT("stroke:") + Col2SVG(c
, &opacity
) + wxT("; ");
76 case wxPENSTYLE_SOLID
:
77 s
+= wxString::Format(wxT("stroke-opacity:%s; "), NumStr(opacity
));
79 case wxPENSTYLE_TRANSPARENT
:
80 s
+= wxT("stroke-opacity:0.0; ");
83 wxASSERT_MSG(false, wxT("wxSVGFileDC::Requested Pen Style not available"));
89 wxString
wxBrushString(wxColour c
, int style
= wxBRUSHSTYLE_SOLID
)
92 wxString s
= wxT("fill:") + Col2SVG(c
, &opacity
) + wxT("; ");
96 case wxBRUSHSTYLE_SOLID
:
97 s
+= wxString::Format(wxT("fill-opacity:%s; "), NumStr(opacity
));
99 case wxBRUSHSTYLE_TRANSPARENT
:
100 s
+= wxT("fill-opacity:0.0; ");
103 wxASSERT_MSG(false, wxT("wxSVGFileDC::Requested Brush Style not available"));
109 } // anonymous namespace
111 // ----------------------------------------------------------
113 // ----------------------------------------------------------
115 IMPLEMENT_ABSTRACT_CLASS(wxSVGFileDCImpl
, wxDC
)
117 wxSVGFileDCImpl::wxSVGFileDCImpl( wxSVGFileDC
*owner
, const wxString
&filename
,
118 int width
, int height
, double dpi
) :
121 Init( filename
, width
, height
, dpi
);
124 void wxSVGFileDCImpl::Init (const wxString
&filename
, int Width
, int Height
, double dpi
)
133 m_mm_to_pix_x
= dpi
/25.4;
134 m_mm_to_pix_y
= dpi
/25.4;
136 m_backgroundBrush
= *wxTRANSPARENT_BRUSH
;
137 m_textForegroundColour
= *wxBLACK
;
138 m_textBackgroundColour
= *wxWHITE
;
139 m_colour
= wxColourDisplay();
141 m_pen
= *wxBLACK_PEN
;
142 m_font
= *wxNORMAL_FONT
;
143 m_brush
= *wxWHITE_BRUSH
;
145 m_graphics_changed
= true;
147 ////////////////////code here
149 m_outfile
= new wxFileOutputStream(filename
);
150 m_OK
= m_outfile
->IsOk();
153 m_filename
= filename
;
156 s
= wxT("<?xml version=\"1.0\" standalone=\"no\"?>") + wxString(wxT("\n"));
158 s
= wxT("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\" ") + wxString(wxT("\n"));
160 s
= wxT("\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\"> ") + wxString(wxT("\n"));
162 s
= wxT("<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" ") + wxString(wxT("\n"));
164 s
.Printf( wxT(" width=\"%scm\" height=\"%scm\" viewBox=\"0 0 %d %d \"> \n"), NumStr(float(Width
)/dpi
*2.54), NumStr(float(Height
)/dpi
*2.54), Width
, Height
);
166 s
= wxT("<title>SVG Picture created as ") + wxFileName(filename
).GetFullName() + wxT(" </title>") + wxT("\n");
168 s
= wxString (wxT("<desc>Picture generated by wxSVG ")) + wxSVGVersion
+ wxT(" </desc>")+ wxT("\n");
170 s
= wxT("<g style=\"fill:black; stroke:black; stroke-width:1\">") + wxString(wxT("\n"));
175 wxSVGFileDCImpl::~wxSVGFileDCImpl()
177 wxString s
= wxT("</g> \n</svg> \n");
182 void wxSVGFileDCImpl::DoGetSizeMM( int *width
, int *height
) const
185 *width
= wxRound( (double)m_width
/ m_mm_to_pix_x
);
188 *height
= wxRound( (double)m_height
/ m_mm_to_pix_y
);
191 wxSize
wxSVGFileDCImpl::GetPPI() const
193 return wxSize( wxRound(m_dpi
), wxRound(m_dpi
) );
196 void wxSVGFileDCImpl::DoDrawLine (wxCoord x1
, wxCoord y1
, wxCoord x2
, wxCoord y2
)
198 NewGraphicsIfNeeded();
200 s
.Printf ( wxT("<path d=\"M%d %d L%d %d\" /> \n"), x1
,y1
,x2
,y2
);
205 CalcBoundingBox(x1
, y1
);
206 CalcBoundingBox(x2
, y2
);
209 void wxSVGFileDCImpl::DoDrawLines(int n
, wxPoint points
[], wxCoord xoffset
, wxCoord yoffset
)
211 for ( int i
= 1; i
< n
; i
++ )
213 DoDrawLine ( points
[i
-1].x
+ xoffset
, points
[i
-1].y
+ yoffset
,
214 points
[ i
].x
+ xoffset
, points
[ i
].y
+ yoffset
);
218 void wxSVGFileDCImpl::DoDrawPoint (wxCoord x1
, wxCoord y1
)
221 NewGraphicsIfNeeded();
222 s
= wxT("<g style = \"stroke-linecap:round;\" > ") + wxString(wxT("\n"));
224 DoDrawLine ( x1
,y1
,x1
,y1
);
229 void wxSVGFileDCImpl::DoDrawCheckMark(wxCoord x1
, wxCoord y1
, wxCoord width
, wxCoord height
)
231 wxDCImpl::DoDrawCheckMark (x1
,y1
,width
,height
);
234 void wxSVGFileDCImpl::DoDrawText(const wxString
& text
, wxCoord x1
, wxCoord y1
)
236 DoDrawRotatedText(text
, x1
,y1
,0.0);
239 void wxSVGFileDCImpl::DoDrawRotatedText(const wxString
& sText
, wxCoord x
, wxCoord y
, double angle
)
241 //known bug; if the font is drawn in a scaled DC, it will not behave exactly as wxMSW
242 NewGraphicsIfNeeded();
245 // calculate bounding box
247 DoGetTextExtent(sText
, &w
, &h
, &desc
);
249 double rad
= DegToRad(angle
);
251 // wxT("upper left") and wxT("upper right")
252 CalcBoundingBox(x
, y
);
253 CalcBoundingBox((wxCoord
)(x
+ w
*cos(rad
)), (wxCoord
)(y
- h
*sin(rad
)));
255 // wxT("bottom left") and wxT("bottom right")
256 CalcBoundingBox((wxCoord
)(x
+ h
*sin(rad
)), (wxCoord
)(y
+ h
*cos(rad
)));
257 CalcBoundingBox((wxCoord
)(x
+ h
*sin(rad
) + w
*cos(rad
)), (wxCoord
)(y
+ h
*cos(rad
) - w
*sin(rad
)));
259 if (m_backgroundMode
== wxBRUSHSTYLE_SOLID
)
261 // draw background first
262 // just like DoDrawRectangle except we pass the text color to it and set the border to a 1 pixel wide text background
264 sTmp
.Printf ( wxT(" <rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" "), x
, y
, w
, h
);
265 s
= sTmp
+ wxT("style=\"") + wxBrushString(m_textBackgroundColour
);
266 s
+= wxT("stroke-width:1; ") + wxPenString(m_textBackgroundColour
);
267 sTmp
.Printf ( wxT("\" transform=\"rotate( %s %d %d ) \" />"), NumStr(-angle
), x
,y
);
268 s
+= sTmp
+ wxT("\n");
272 // convert x,y to SVG text x,y (the coordinates of the text baseline)
273 x
= (wxCoord
)(x
+ (h
-desc
)*sin(rad
));
274 y
= (wxCoord
)(y
+ (h
-desc
)*cos(rad
));
276 //now do the text itself
277 s
.Printf (wxT(" <text x=\"%d\" y=\"%d\" "),x
,y
);
279 sTmp
= m_font
.GetFaceName();
280 if (sTmp
.Len() > 0) s
+= wxT("style=\"font-family:") + sTmp
+ wxT("; ");
281 else s
+= wxT("style=\" ");
283 wxString fontweights
[3] = { wxT("normal"), wxT("lighter"), wxT("bold") };
284 s
+= wxT("font-weight:") + fontweights
[m_font
.GetWeight() - wxNORMAL
] + wxT("; ");
286 wxString fontstyles
[5] = { wxT("normal"), wxT("style error"), wxT("style error"), wxT("italic"), wxT("oblique") };
287 s
+= wxT("font-style:") + fontstyles
[m_font
.GetStyle() - wxNORMAL
] + wxT("; ");
289 sTmp
.Printf (wxT("font-size:%dpt; "), m_font
.GetPointSize() );
291 //text will be solid, unless alpha value isn't opaque in the foreground colour
292 s
+= wxBrushString(m_textForegroundColour
) + wxPenString(m_textForegroundColour
);
293 sTmp
.Printf ( wxT("stroke-width:0;\" transform=\"rotate( %s %d %d ) \" >"), NumStr(-angle
), x
,y
);
294 s
+= sTmp
+ sText
+ wxT("</text> ") + wxT("\n");
301 void wxSVGFileDCImpl::DoDrawRectangle(wxCoord x
, wxCoord y
, wxCoord width
, wxCoord height
)
303 DoDrawRoundedRectangle(x
, y
, width
, height
, 0);
306 void wxSVGFileDCImpl::DoDrawRoundedRectangle(wxCoord x
, wxCoord y
, wxCoord width
, wxCoord height
, double radius
)
309 NewGraphicsIfNeeded();
312 s
.Printf ( wxT(" <rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" rx=\"%s\" "),
313 x
, y
, width
, height
, NumStr(radius
) );
318 CalcBoundingBox(x
, y
);
319 CalcBoundingBox(x
+ width
, y
+ height
);
322 void wxSVGFileDCImpl::DoDrawPolygon(int n
, wxPoint points
[],
323 wxCoord xoffset
, wxCoord yoffset
,
324 wxPolygonFillMode fillStyle
)
326 NewGraphicsIfNeeded();
328 s
= wxT("<polygon style=\"");
329 if ( fillStyle
== wxODDEVEN_RULE
)
330 s
+= wxT("fill-rule:evenodd; ");
332 s
+= wxT("fill-rule:nonzero; ");
334 s
+= wxT("\" \npoints=\"");
336 for (int i
= 0; i
< n
; i
++)
338 sTmp
.Printf ( wxT("%d,%d"), points
[i
].x
+xoffset
, points
[i
].y
+yoffset
);
339 s
+= sTmp
+ wxT("\n");
340 CalcBoundingBox ( points
[i
].x
+xoffset
, points
[i
].y
+yoffset
);
342 s
+= wxT("\" /> \n");
346 void wxSVGFileDCImpl::DoDrawEllipse (wxCoord x
, wxCoord y
, wxCoord width
, wxCoord height
)
349 NewGraphicsIfNeeded();
355 s
.Printf ( wxT("<ellipse cx=\"%d\" cy=\"%d\" rx=\"%d\" ry=\"%d\" "), x
+rw
,y
+rh
, rw
, rh
);
360 CalcBoundingBox(x
, y
);
361 CalcBoundingBox(x
+ width
, y
+ height
);
364 void wxSVGFileDCImpl::DoDrawArc(wxCoord x1
, wxCoord y1
, wxCoord x2
, wxCoord y2
, wxCoord xc
, wxCoord yc
)
366 /* Draws an arc of a circle, centred on (xc, yc), with starting point
367 (x1, y1) and ending at (x2, y2). The current pen is used for the outline
368 and the current brush for filling the shape.
370 The arc is drawn in an anticlockwise direction from the start point to
373 Might be better described as Pie drawing */
375 NewGraphicsIfNeeded();
378 // we need the radius of the circle which has two estimates
379 double r1
= sqrt ( double( (x1
-xc
)*(x1
-xc
) ) + double( (y1
-yc
)*(y1
-yc
) ) );
380 double r2
= sqrt ( double( (x2
-xc
)*(x2
-xc
) ) + double( (y2
-yc
)*(y2
-yc
) ) );
382 wxASSERT_MSG( (fabs ( r2
-r1
) <= 3), wxT("wxSVGFileDC::DoDrawArc Error in getting radii of circle"));
383 if ( fabs ( r2
-r1
) > 3 ) //pixels
385 s
= wxT("<!--- wxSVGFileDC::DoDrawArc Error in getting radii of circle --> \n");
389 double theta1
= atan2((double)(yc
-y1
),(double)(x1
-xc
));
390 if ( theta1
< 0 ) theta1
= theta1
+ M_PI
* 2;
391 double theta2
= atan2((double)(yc
-y2
), (double)(x2
-xc
));
392 if ( theta2
< 0 ) theta2
= theta2
+ M_PI
* 2;
393 if ( theta2
< theta1
) theta2
= theta2
+ M_PI
*2;
395 int fArc
; // flag for large or small arc 0 means less than 180 degrees
396 if ( fabs(theta2
- theta1
) > M_PI
) fArc
= 1; else fArc
= 0;
398 int fSweep
= 0; // flag for sweep always 0
400 s
.Printf ( wxT("<path d=\"M%d %d A%s %s 0.0 %d %d %d %d L%d %d z "),
401 x1
,y1
, NumStr(r1
), NumStr(r2
), fArc
, fSweep
, x2
, y2
, xc
, yc
);
403 // the z means close the path and fill
404 s
+= wxT(" \" /> \n");
413 void wxSVGFileDCImpl::DoDrawEllipticArc(wxCoord x
,wxCoord y
,wxCoord w
,wxCoord h
,double sa
,double ea
)
416 Draws an arc of an ellipse. The current pen is used for drawing the arc
417 and the current brush is used for drawing the pie. This function is
418 currently only available for X window and PostScript device contexts.
420 x and y specify the x and y coordinates of the upper-left corner of the
421 rectangle that contains the ellipse.
423 width and height specify the width and height of the rectangle that
424 contains the ellipse.
426 start and end specify the start and end of the arc relative to the
427 three-o'clock position from the center of the rectangle. Angles are
428 specified in degrees (360 is a complete circle). Positive values mean
429 counter-clockwise motion. If start is equal to end, a complete ellipse
432 //known bug: SVG draws with the current pen along the radii, but this does not happen in wxMSW
434 NewGraphicsIfNeeded();
444 double xs
, ys
, xe
, ye
;
445 xs
= xc
+ rx
* cos (DegToRad(sa
));
446 xe
= xc
+ rx
* cos (DegToRad(ea
));
447 ys
= yc
- ry
* sin (DegToRad(sa
));
448 ye
= yc
- ry
* sin (DegToRad(ea
));
450 ///now same as circle arc...
452 double theta1
= atan2(ys
-yc
, xs
-xc
);
453 double theta2
= atan2(ye
-yc
, xe
-xc
);
455 int fArc
; // flag for large or small arc 0 means less than 180 degrees
456 if ( (theta2
- theta1
) > 0 ) fArc
= 1; else fArc
= 0;
459 if ( fabs(theta2
- theta1
) > M_PI
) fSweep
= 1; else fSweep
= 0;
461 s
.Printf ( wxT("<path d=\"M%d %d A%d %d 0.0 %d %d %d %d L %d %d z "),
462 int(xs
), int(ys
), int(rx
), int(ry
),
463 fArc
, fSweep
, int(xe
), int(ye
), int(xc
), int(yc
) );
465 s
+= wxT(" \" /> \n");
473 void wxSVGFileDCImpl::DoGetTextExtent(const wxString
& string
, wxCoord
*w
, wxCoord
*h
, wxCoord
*descent
, wxCoord
*externalLeading
, const wxFont
*font
) const
478 sDC
.SetFont (m_font
);
479 if ( font
!= NULL
) sDC
.SetFont ( *font
);
480 sDC
.GetTextExtent(string
, w
, h
, descent
, externalLeading
);
483 wxCoord
wxSVGFileDCImpl::GetCharHeight() const
486 sDC
.SetFont (m_font
);
488 return sDC
.GetCharHeight();
492 wxCoord
wxSVGFileDCImpl::GetCharWidth() const
495 sDC
.SetFont (m_font
);
497 return sDC
.GetCharWidth();
501 // ----------------------------------------------------------
502 // wxSVGFileDCImpl - set functions
503 // ----------------------------------------------------------
505 void wxSVGFileDCImpl::SetBackground( const wxBrush
&brush
)
507 m_backgroundBrush
= brush
;
511 void wxSVGFileDCImpl::SetBackgroundMode( int mode
)
513 m_backgroundMode
= mode
;
517 void wxSVGFileDCImpl::SetBrush(const wxBrush
& brush
)
521 m_graphics_changed
= true;
525 void wxSVGFileDCImpl::SetPen(const wxPen
& pen
)
527 // width, color, ends, joins : currently implemented
528 // dashes, stipple : not implemented
531 m_graphics_changed
= true;
534 void wxSVGFileDCImpl::NewGraphicsIfNeeded()
536 if ( !m_graphics_changed
)
539 m_graphics_changed
= false;
541 wxString s
, sBrush
, sPenCap
, sPenJoin
, sPenStyle
, sLast
, sWarn
;
543 sBrush
= wxT("</g>\n<g style=\"") + wxBrushString ( m_brush
.GetColour(), m_brush
.GetStyle() )
544 + wxPenString(m_pen
.GetColour(), m_pen
.GetStyle());
546 switch ( m_pen
.GetCap() )
548 case wxCAP_PROJECTING
:
549 sPenCap
= wxT("stroke-linecap:square; ");
552 sPenCap
= wxT("stroke-linecap:butt; ");
556 sPenCap
= wxT("stroke-linecap:round; ");
559 switch ( m_pen
.GetJoin() )
562 sPenJoin
= wxT("stroke-linejoin:bevel; ");
565 sPenJoin
= wxT("stroke-linejoin:miter; ");
569 sPenJoin
= wxT("stroke-linejoin:round; ");
572 sLast
.Printf( wxT("stroke-width:%d\" \n transform=\"translate(%s %s) scale(%s %s)\">"),
573 m_pen
.GetWidth(), NumStr(m_logicalOriginX
), NumStr(m_logicalOriginY
), NumStr(m_scaleX
), NumStr(m_scaleY
) );
575 s
= sBrush
+ sPenCap
+ sPenJoin
+ sPenStyle
+ sLast
+ wxT("\n") + sWarn
;
580 void wxSVGFileDCImpl::SetFont(const wxFont
& font
)
586 // export a bitmap as a raster image in png
587 bool wxSVGFileDCImpl::DoBlit(wxCoord xdest
, wxCoord ydest
, wxCoord width
, wxCoord height
,
588 wxDC
* source
, wxCoord xsrc
, wxCoord ysrc
,
589 wxRasterOperationMode logicalFunc
/*= wxCOPY*/, bool useMask
/*= false*/,
590 wxCoord
/*xsrcMask = -1*/, wxCoord
/*ysrcMask = -1*/)
592 if (logicalFunc
!= wxCOPY
)
594 wxASSERT_MSG(false, wxT("wxSVGFileDC::DoBlit Call requested nonCopy mode; this is not possible"));
597 if (useMask
!= false)
599 wxASSERT_MSG(false, wxT("wxSVGFileDC::DoBlit Call requested false mask; this is not possible"));
602 wxBitmap
myBitmap (width
, height
);
604 memDC
.SelectObject( myBitmap
);
605 memDC
.Blit(0, 0, width
, height
, source
, xsrc
, ysrc
);
606 memDC
.SelectObject( wxNullBitmap
);
607 DoDrawBitmap(myBitmap
, xdest
, ydest
);
611 void wxSVGFileDCImpl::DoDrawIcon(const class wxIcon
& myIcon
, wxCoord x
, wxCoord y
)
613 wxBitmap
myBitmap (myIcon
.GetWidth(), myIcon
.GetHeight() );
615 memDC
.SelectObject( myBitmap
);
616 memDC
.DrawIcon(myIcon
,0,0);
617 memDC
.SelectObject( wxNullBitmap
);
618 DoDrawBitmap(myBitmap
, x
, y
);
621 void wxSVGFileDCImpl::DoDrawBitmap(const class wxBitmap
& bmp
, wxCoord x
, wxCoord y
, bool WXUNUSED(bTransparent
) /*=0*/ )
623 NewGraphicsIfNeeded();
625 wxString sTmp
, s
, sPNG
;
626 if ( wxImage::FindHandler(wxBITMAP_TYPE_PNG
) == NULL
)
627 wxImage::AddHandler(new wxPNGHandler
);
629 // create suitable file name
630 sTmp
.Printf ( wxT("_image%d.png"), m_sub_images
);
631 sPNG
= m_filename
.BeforeLast(wxT('.')) + sTmp
;
632 while (wxFile::Exists(sPNG
) )
635 sTmp
.Printf ( wxT("_image%d.png"), m_sub_images
);
636 sPNG
= m_filename
.BeforeLast(wxT('.')) + sTmp
;
639 //create copy of bitmap (wxGTK doesn't like saving a constant bitmap)
640 wxBitmap myBitmap
= bmp
;
642 bool bPNG_OK
= myBitmap
.SaveFile(sPNG
,wxBITMAP_TYPE_PNG
);
644 // reference the bitmap from the SVG doc
645 // only use filename & ext
646 sPNG
= sPNG
.AfterLast(wxFileName::GetPathSeparator());
648 // reference the bitmap from the SVG doc
649 int w
= myBitmap
.GetWidth();
650 int h
= myBitmap
.GetHeight();
651 sTmp
.Printf ( wxT(" <image x=\"%d\" y=\"%d\" width=\"%dpx\" height=\"%dpx\" "), x
,y
,w
,h
);
653 sTmp
.Printf ( wxT(" xlink:href=\"%s\"> \n"), sPNG
.c_str() );
654 s
+= sTmp
+ wxT("<title>Image from wxSVG</title> </image>") + wxT("\n");
660 m_OK
= m_outfile
->IsOk() && bPNG_OK
;
663 void wxSVGFileDCImpl::write(const wxString
&s
)
665 const wxCharBuffer buf
= s
.utf8_str();
666 m_outfile
->Write(buf
, strlen((const char *)buf
));
667 m_OK
= m_outfile
->IsOk();