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 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 NewGraphicsIfNeeded();
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 NewGraphicsIfNeeded();
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 NewGraphicsIfNeeded();
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 NewGraphicsIfNeeded();
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 NewGraphicsIfNeeded();
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
)
518 m_graphics_changed
= true;
522 void wxSVGFileDCImpl::SetPen(const wxPen
& pen
)
524 // width, color, ends, joins : currently implemented
525 // dashes, stipple : not implemented
528 m_graphics_changed
= true;
531 void wxSVGFileDCImpl::NewGraphicsIfNeeded()
533 if ( !m_graphics_changed
)
536 m_graphics_changed
= false;
538 wxString s
, sBrush
, sPenCap
, sPenJoin
, sPenStyle
, sLast
, sWarn
;
540 sBrush
= wxT("</g>\n<g style=\"") + wxBrushString ( m_brush
.GetColour(), m_brush
.GetStyle() )
541 + wxPenString(m_pen
.GetColour(), m_pen
.GetStyle());
543 switch ( m_pen
.GetCap() )
545 case wxCAP_PROJECTING
:
546 sPenCap
= wxT("stroke-linecap:square; ");
549 sPenCap
= wxT("stroke-linecap:butt; ");
553 sPenCap
= wxT("stroke-linecap:round; ");
556 switch ( m_pen
.GetJoin() )
559 sPenJoin
= wxT("stroke-linejoin:bevel; ");
562 sPenJoin
= wxT("stroke-linejoin:miter; ");
566 sPenJoin
= wxT("stroke-linejoin:round; ");
569 sLast
.Printf( wxT("stroke-width:%d\" \n transform=\"translate(%s %s) scale(%s %s)\">"),
570 m_pen
.GetWidth(), NumStr(m_logicalOriginX
), NumStr(m_logicalOriginY
), NumStr(m_scaleX
), NumStr(m_scaleY
) );
572 s
= sBrush
+ sPenCap
+ sPenJoin
+ sPenStyle
+ sLast
+ wxT("\n") + sWarn
;
577 void wxSVGFileDCImpl::SetFont(const wxFont
& font
)
583 // export a bitmap as a raster image in png
584 bool wxSVGFileDCImpl::DoBlit(wxCoord xdest
, wxCoord ydest
, wxCoord width
, wxCoord height
,
585 wxDC
* source
, wxCoord xsrc
, wxCoord ysrc
,
586 wxRasterOperationMode logicalFunc
/*= wxCOPY*/, bool useMask
/*= false*/,
587 wxCoord
/*xsrcMask = -1*/, wxCoord
/*ysrcMask = -1*/)
589 if (logicalFunc
!= wxCOPY
)
591 wxASSERT_MSG(false, wxT("wxSVGFileDC::DoBlit Call requested nonCopy mode; this is not possible"));
594 if (useMask
!= false)
596 wxASSERT_MSG(false, wxT("wxSVGFileDC::DoBlit Call requested false mask; this is not possible"));
599 wxBitmap
myBitmap (width
, height
);
601 memDC
.SelectObject( myBitmap
);
602 memDC
.Blit(0, 0, width
, height
, source
, xsrc
, ysrc
);
603 memDC
.SelectObject( wxNullBitmap
);
604 DoDrawBitmap(myBitmap
, xdest
, ydest
);
608 void wxSVGFileDCImpl::DoDrawIcon(const class wxIcon
& myIcon
, wxCoord x
, wxCoord y
)
610 wxBitmap
myBitmap (myIcon
.GetWidth(), myIcon
.GetHeight() );
612 memDC
.SelectObject( myBitmap
);
613 memDC
.DrawIcon(myIcon
,0,0);
614 memDC
.SelectObject( wxNullBitmap
);
615 DoDrawBitmap(myBitmap
, x
, y
);
618 void wxSVGFileDCImpl::DoDrawBitmap(const class wxBitmap
& bmp
, wxCoord x
, wxCoord y
, bool WXUNUSED(bTransparent
) /*=0*/ )
620 NewGraphicsIfNeeded();
622 wxString sTmp
, s
, sPNG
;
623 if ( wxImage::FindHandler(wxBITMAP_TYPE_PNG
) == NULL
)
624 wxImage::AddHandler(new wxPNGHandler
);
626 // create suitable file name
627 sTmp
.Printf ( wxT("_image%d.png"), m_sub_images
);
628 sPNG
= m_filename
.BeforeLast(wxT('.')) + sTmp
;
629 while (wxFile::Exists(sPNG
) )
632 sTmp
.Printf ( wxT("_image%d.png"), m_sub_images
);
633 sPNG
= m_filename
.BeforeLast(wxT('.')) + sTmp
;
636 //create copy of bitmap (wxGTK doesn't like saving a constant bitmap)
637 wxBitmap myBitmap
= bmp
;
639 bool bPNG_OK
= myBitmap
.SaveFile(sPNG
,wxBITMAP_TYPE_PNG
);
641 // reference the bitmap from the SVG doc
642 // only use filename & ext
643 sPNG
= sPNG
.AfterLast(wxFileName::GetPathSeparator());
645 // reference the bitmap from the SVG doc
646 int w
= myBitmap
.GetWidth();
647 int h
= myBitmap
.GetHeight();
648 sTmp
.Printf ( wxT(" <image x=\"%d\" y=\"%d\" width=\"%dpx\" height=\"%dpx\" "), x
,y
,w
,h
);
650 sTmp
.Printf ( wxT(" xlink:href=\"%s\"> \n"), sPNG
.c_str() );
651 s
+= sTmp
+ wxT("<title>Image from wxSVG</title> </image>") + wxT("\n");
657 m_OK
= m_outfile
->IsOk() && bPNG_OK
;
660 void wxSVGFileDCImpl::write(const wxString
&s
)
662 const wxCharBuffer buf
= s
.utf8_str();
663 m_outfile
->Write(buf
, strlen((const char *)buf
));
664 m_OK
= m_outfile
->IsOk();