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 if (m_graphics_changed
) NewGraphics();
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 if (m_graphics_changed
) NewGraphics();
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 if (m_graphics_changed
) NewGraphics();
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 x
+= (wxCoord
)(h
*sin(rad
));
257 y
+= (wxCoord
)(h
*cos(rad
));
258 CalcBoundingBox(x
, y
);
259 CalcBoundingBox((wxCoord
)(x
+ h
*sin(rad
)), (wxCoord
)(y
+ h
*cos(rad
)));
261 if (m_backgroundMode
== wxBRUSHSTYLE_SOLID
)
263 // draw background first
264 // just like DoDrawRectangle except we pass the text color to it and set the border to a 1 pixel wide text background
266 sTmp
.Printf ( wxT(" <rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" "), x
,y
+desc
-h
, w
, h
);
267 s
= sTmp
+ wxT("style=\"") + wxBrushString(m_textBackgroundColour
);
268 s
+= wxT("stroke-width:1; ") + wxPenString(m_textBackgroundColour
);
269 sTmp
.Printf ( wxT("\" transform=\"rotate( %s %d %d ) \" />"), NumStr(-angle
), x
,y
);
270 s
+= sTmp
+ wxT("\n");
273 //now do the text itself
274 s
.Printf (wxT(" <text x=\"%d\" y=\"%d\" "),x
,y
);
276 sTmp
= m_font
.GetFaceName();
277 if (sTmp
.Len() > 0) s
+= wxT("style=\"font-family:") + sTmp
+ wxT("; ");
278 else s
+= wxT("style=\" ");
280 wxString fontweights
[3] = { wxT("normal"), wxT("lighter"), wxT("bold") };
281 s
+= wxT("font-weight:") + fontweights
[m_font
.GetWeight() - wxNORMAL
] + wxT("; ");
283 wxString fontstyles
[5] = { wxT("normal"), wxT("style error"), wxT("style error"), wxT("italic"), wxT("oblique") };
284 s
+= wxT("font-style:") + fontstyles
[m_font
.GetStyle() - wxNORMAL
] + wxT("; ");
286 sTmp
.Printf (wxT("font-size:%dpt; "), m_font
.GetPointSize() );
288 //text will be solid, unless alpha value isn't opaque in the foreground colour
289 s
+= wxBrushString(m_textForegroundColour
) + wxPenString(m_textForegroundColour
);
290 sTmp
.Printf ( wxT("stroke-width:0;\" transform=\"rotate( %s %d %d ) \" >"), NumStr(-angle
), x
,y
);
291 s
+= sTmp
+ sText
+ wxT("</text> ") + wxT("\n");
298 void wxSVGFileDCImpl::DoDrawRectangle(wxCoord x
, wxCoord y
, wxCoord width
, wxCoord height
)
300 DoDrawRoundedRectangle(x
, y
, width
, height
, 0);
303 void wxSVGFileDCImpl::DoDrawRoundedRectangle(wxCoord x
, wxCoord y
, wxCoord width
, wxCoord height
, double radius
)
306 if (m_graphics_changed
) NewGraphics();
309 s
.Printf ( wxT(" <rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" rx=\"%s\" "),
310 x
, y
, width
, height
, NumStr(radius
) );
315 CalcBoundingBox(x
, y
);
316 CalcBoundingBox(x
+ width
, y
+ height
);
319 void wxSVGFileDCImpl::DoDrawPolygon(int n
, wxPoint points
[],
320 wxCoord xoffset
, wxCoord yoffset
,
321 wxPolygonFillMode fillStyle
)
323 if (m_graphics_changed
) NewGraphics();
325 s
= wxT("<polygon style=\"");
326 if ( fillStyle
== wxODDEVEN_RULE
)
327 s
+= wxT("fill-rule:evenodd; ");
329 s
+= wxT("fill-rule:nonzero; ");
331 s
+= wxT("\" \npoints=\"");
333 for (int i
= 0; i
< n
; i
++)
335 sTmp
.Printf ( wxT("%d,%d"), points
[i
].x
+xoffset
, points
[i
].y
+yoffset
);
336 s
+= sTmp
+ wxT("\n");
337 CalcBoundingBox ( points
[i
].x
+xoffset
, points
[i
].y
+yoffset
);
339 s
+= wxT("\" /> \n");
343 void wxSVGFileDCImpl::DoDrawEllipse (wxCoord x
, wxCoord y
, wxCoord width
, wxCoord height
)
346 if (m_graphics_changed
) NewGraphics();
352 s
.Printf ( wxT("<ellipse cx=\"%d\" cy=\"%d\" rx=\"%d\" ry=\"%d\" "), x
+rw
,y
+rh
, rw
, rh
);
357 CalcBoundingBox(x
, y
);
358 CalcBoundingBox(x
+ width
, y
+ height
);
361 void wxSVGFileDCImpl::DoDrawArc(wxCoord x1
, wxCoord y1
, wxCoord x2
, wxCoord y2
, wxCoord xc
, wxCoord yc
)
363 /* Draws an arc of a circle, centred on (xc, yc), with starting point
364 (x1, y1) and ending at (x2, y2). The current pen is used for the outline
365 and the current brush for filling the shape.
367 The arc is drawn in an anticlockwise direction from the start point to
370 Might be better described as Pie drawing */
372 if (m_graphics_changed
) NewGraphics();
375 // we need the radius of the circle which has two estimates
376 double r1
= sqrt ( double( (x1
-xc
)*(x1
-xc
) ) + double( (y1
-yc
)*(y1
-yc
) ) );
377 double r2
= sqrt ( double( (x2
-xc
)*(x2
-xc
) ) + double( (y2
-yc
)*(y2
-yc
) ) );
379 wxASSERT_MSG( (fabs ( r2
-r1
) <= 3), wxT("wxSVGFileDC::DoDrawArc Error in getting radii of circle"));
380 if ( fabs ( r2
-r1
) > 3 ) //pixels
382 s
= wxT("<!--- wxSVGFileDC::DoDrawArc Error in getting radii of circle --> \n");
386 double theta1
= atan2((double)(yc
-y1
),(double)(x1
-xc
));
387 if ( theta1
< 0 ) theta1
= theta1
+ M_PI
* 2;
388 double theta2
= atan2((double)(yc
-y2
), (double)(x2
-xc
));
389 if ( theta2
< 0 ) theta2
= theta2
+ M_PI
* 2;
390 if ( theta2
< theta1
) theta2
= theta2
+ M_PI
*2;
392 int fArc
; // flag for large or small arc 0 means less than 180 degrees
393 if ( fabs(theta2
- theta1
) > M_PI
) fArc
= 1; else fArc
= 0;
395 int fSweep
= 0; // flag for sweep always 0
397 s
.Printf ( wxT("<path d=\"M%d %d A%s %s 0.0 %d %d %d %d L%d %d z "),
398 x1
,y1
, NumStr(r1
), NumStr(r2
), fArc
, fSweep
, x2
, y2
, xc
, yc
);
400 // the z means close the path and fill
401 s
+= wxT(" \" /> \n");
410 void wxSVGFileDCImpl::DoDrawEllipticArc(wxCoord x
,wxCoord y
,wxCoord w
,wxCoord h
,double sa
,double ea
)
413 Draws an arc of an ellipse. The current pen is used for drawing the arc
414 and the current brush is used for drawing the pie. This function is
415 currently only available for X window and PostScript device contexts.
417 x and y specify the x and y coordinates of the upper-left corner of the
418 rectangle that contains the ellipse.
420 width and height specify the width and height of the rectangle that
421 contains the ellipse.
423 start and end specify the start and end of the arc relative to the
424 three-o'clock position from the center of the rectangle. Angles are
425 specified in degrees (360 is a complete circle). Positive values mean
426 counter-clockwise motion. If start is equal to end, a complete ellipse
429 //known bug: SVG draws with the current pen along the radii, but this does not happen in wxMSW
431 if (m_graphics_changed
) NewGraphics();
441 double xs
, ys
, xe
, ye
;
442 xs
= xc
+ rx
* cos (DegToRad(sa
));
443 xe
= xc
+ rx
* cos (DegToRad(ea
));
444 ys
= yc
- ry
* sin (DegToRad(sa
));
445 ye
= yc
- ry
* sin (DegToRad(ea
));
447 ///now same as circle arc...
449 double theta1
= atan2(ys
-yc
, xs
-xc
);
450 double theta2
= atan2(ye
-yc
, xe
-xc
);
452 int fArc
; // flag for large or small arc 0 means less than 180 degrees
453 if ( (theta2
- theta1
) > 0 ) fArc
= 1; else fArc
= 0;
456 if ( fabs(theta2
- theta1
) > M_PI
) fSweep
= 1; else fSweep
= 0;
458 s
.Printf ( wxT("<path d=\"M%d %d A%d %d 0.0 %d %d %d %d L %d %d z "),
459 int(xs
), int(ys
), int(rx
), int(ry
),
460 fArc
, fSweep
, int(xe
), int(ye
), int(xc
), int(yc
) );
462 s
+= wxT(" \" /> \n");
470 void wxSVGFileDCImpl::DoGetTextExtent(const wxString
& string
, wxCoord
*w
, wxCoord
*h
, wxCoord
*descent
, wxCoord
*externalLeading
, const wxFont
*font
) const
475 sDC
.SetFont (m_font
);
476 if ( font
!= NULL
) sDC
.SetFont ( *font
);
477 sDC
.GetTextExtent(string
, w
, h
, descent
, externalLeading
);
480 wxCoord
wxSVGFileDCImpl::GetCharHeight() const
483 sDC
.SetFont (m_font
);
485 return sDC
.GetCharHeight();
489 wxCoord
wxSVGFileDCImpl::GetCharWidth() const
492 sDC
.SetFont (m_font
);
494 return sDC
.GetCharWidth();
498 // ----------------------------------------------------------
499 // wxSVGFileDCImpl - set functions
500 // ----------------------------------------------------------
502 void wxSVGFileDCImpl::SetBackground( const wxBrush
&brush
)
504 m_backgroundBrush
= brush
;
508 void wxSVGFileDCImpl::SetBackgroundMode( int mode
)
510 m_backgroundMode
= mode
;
514 void wxSVGFileDCImpl::SetBrush(const wxBrush
& brush
)
519 m_graphics_changed
= true;
523 void wxSVGFileDCImpl::SetPen(const wxPen
& pen
)
525 // width, color, ends, joins : currently implemented
526 // dashes, stipple : not implemented
529 m_graphics_changed
= true;
532 void wxSVGFileDCImpl::NewGraphics()
534 wxString s
, sBrush
, sPenCap
, sPenJoin
, sPenStyle
, sLast
, sWarn
;
536 sBrush
= wxT("</g>\n<g style=\"") + wxBrushString ( m_brush
.GetColour(), m_brush
.GetStyle() )
537 + wxPenString(m_pen
.GetColour(), m_pen
.GetStyle());
539 switch ( m_pen
.GetCap() )
541 case wxCAP_PROJECTING
:
542 sPenCap
= wxT("stroke-linecap:square; ");
545 sPenCap
= wxT("stroke-linecap:butt; ");
549 sPenCap
= wxT("stroke-linecap:round; ");
552 switch ( m_pen
.GetJoin() )
555 sPenJoin
= wxT("stroke-linejoin:bevel; ");
558 sPenJoin
= wxT("stroke-linejoin:miter; ");
562 sPenJoin
= wxT("stroke-linejoin:round; ");
565 sLast
.Printf( wxT("stroke-width:%d\" \n transform=\"translate(%s %s) scale(%s %s)\">"),
566 m_pen
.GetWidth(), NumStr(m_logicalOriginX
), NumStr(m_logicalOriginY
), NumStr(m_scaleX
), NumStr(m_scaleY
) );
568 s
= sBrush
+ sPenCap
+ sPenJoin
+ sPenStyle
+ sLast
+ wxT("\n") + sWarn
;
570 m_graphics_changed
= false;
574 void wxSVGFileDCImpl::SetFont(const wxFont
& font
)
580 // export a bitmap as a raster image in png
581 bool wxSVGFileDCImpl::DoBlit(wxCoord xdest
, wxCoord ydest
, wxCoord width
, wxCoord height
,
582 wxDC
* source
, wxCoord xsrc
, wxCoord ysrc
,
583 wxRasterOperationMode logicalFunc
/*= wxCOPY*/, bool useMask
/*= false*/,
584 wxCoord
/*xsrcMask = -1*/, wxCoord
/*ysrcMask = -1*/)
586 if (logicalFunc
!= wxCOPY
)
588 wxASSERT_MSG(false, wxT("wxSVGFileDC::DoBlit Call requested nonCopy mode; this is not possible"));
591 if (useMask
!= false)
593 wxASSERT_MSG(false, wxT("wxSVGFileDC::DoBlit Call requested false mask; this is not possible"));
596 wxBitmap
myBitmap (width
, height
);
598 memDC
.SelectObject( myBitmap
);
599 memDC
.Blit(0, 0, width
, height
, source
, xsrc
, ysrc
);
600 memDC
.SelectObject( wxNullBitmap
);
601 DoDrawBitmap(myBitmap
, xdest
, ydest
);
605 void wxSVGFileDCImpl::DoDrawIcon(const class wxIcon
& myIcon
, wxCoord x
, wxCoord y
)
607 wxBitmap
myBitmap (myIcon
.GetWidth(), myIcon
.GetHeight() );
609 memDC
.SelectObject( myBitmap
);
610 memDC
.DrawIcon(myIcon
,0,0);
611 memDC
.SelectObject( wxNullBitmap
);
612 DoDrawBitmap(myBitmap
, x
, y
);
615 void wxSVGFileDCImpl::DoDrawBitmap(const class wxBitmap
& bmp
, wxCoord x
, wxCoord y
, bool WXUNUSED(bTransparent
) /*=0*/ )
617 if (m_graphics_changed
) NewGraphics();
619 wxString sTmp
, s
, sPNG
;
620 if ( wxImage::FindHandler(wxBITMAP_TYPE_PNG
) == NULL
)
621 wxImage::AddHandler(new wxPNGHandler
);
623 // create suitable file name
624 sTmp
.Printf ( wxT("_image%d.png"), m_sub_images
);
625 sPNG
= m_filename
.BeforeLast(wxT('.')) + sTmp
;
626 while (wxFile::Exists(sPNG
) )
629 sTmp
.Printf ( wxT("_image%d.png"), m_sub_images
);
630 sPNG
= m_filename
.BeforeLast(wxT('.')) + sTmp
;
633 //create copy of bitmap (wxGTK doesn't like saving a constant bitmap)
634 wxBitmap myBitmap
= bmp
;
636 bool bPNG_OK
= myBitmap
.SaveFile(sPNG
,wxBITMAP_TYPE_PNG
);
638 // reference the bitmap from the SVG doc
639 // only use filename & ext
640 sPNG
= sPNG
.AfterLast(wxFileName::GetPathSeparator());
642 // reference the bitmap from the SVG doc
643 int w
= myBitmap
.GetWidth();
644 int h
= myBitmap
.GetHeight();
645 sTmp
.Printf ( wxT(" <image x=\"%d\" y=\"%d\" width=\"%dpx\" height=\"%dpx\" "), x
,y
,w
,h
);
647 sTmp
.Printf ( wxT(" xlink:href=\"%s\"> \n"), sPNG
.c_str() );
648 s
+= sTmp
+ wxT("<title>Image from wxSVG</title> </image>") + wxT("\n");
654 m_OK
= m_outfile
->IsOk() && bPNG_OK
;
657 void wxSVGFileDCImpl::write(const wxString
&s
)
659 const wxCharBuffer buf
= s
.utf8_str();
660 m_outfile
->Write(buf
, strlen((const char *)buf
));
661 m_OK
= m_outfile
->IsOk();