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 wxString
wxPenString(wxColour c
, int style
= wxPENSTYLE_SOLID
)
51 wxString s
= wxT("stroke:") + c
.GetAsString(wxC2S_HTML_SYNTAX
) + wxT("; ");
52 // Use the color's alpha value (if not opaque) for the opacity.
53 // Note that a transparent pen will override the alpha value.
54 if (c
.Alpha() != wxALPHA_OPAQUE
&& style
!= wxPENSTYLE_TRANSPARENT
)
56 s
+= wxString::Format(wxT("stroke-opacity:%s; "), NumStr(c
.Alpha()/255.));
62 case wxPENSTYLE_SOLID
:
63 s
+= wxT("stroke-opacity:1.0; ");
65 case wxPENSTYLE_TRANSPARENT
:
66 s
+= wxT("stroke-opacity:0.0; ");
69 wxASSERT_MSG(false, wxT("wxSVGFileDC::Requested Pen Style not available"));
75 wxString
wxBrushString(wxColour c
, int style
= wxBRUSHSTYLE_SOLID
)
77 wxString s
= wxT("fill:") + c
.GetAsString(wxC2S_HTML_SYNTAX
) + wxT("; ");
78 // Use the color's alpha value (if not opaque) for the opacity.
79 // Note that a transparent brush will override the alpha value.
80 if (c
.Alpha() != wxALPHA_OPAQUE
&& style
!= wxBRUSHSTYLE_TRANSPARENT
)
82 s
+= wxString::Format(wxT("fill-opacity:%s; "), NumStr(c
.Alpha()/255.));
88 case wxBRUSHSTYLE_SOLID
:
89 s
+= wxT("fill-opacity:1.0; ");
91 case wxBRUSHSTYLE_TRANSPARENT
:
92 s
+= wxT("fill-opacity:0.0; ");
95 wxASSERT_MSG(false, wxT("wxSVGFileDC::Requested Brush Style not available"));
101 } // anonymous namespace
103 // ----------------------------------------------------------
105 // ----------------------------------------------------------
107 IMPLEMENT_ABSTRACT_CLASS(wxSVGFileDCImpl
, wxDC
)
109 wxSVGFileDCImpl::wxSVGFileDCImpl( wxSVGFileDC
*owner
, const wxString
&filename
,
110 int width
, int height
, double dpi
) :
113 Init( filename
, width
, height
, dpi
);
116 void wxSVGFileDCImpl::Init (const wxString
&filename
, int Width
, int Height
, double dpi
)
125 m_mm_to_pix_x
= dpi
/25.4;
126 m_mm_to_pix_y
= dpi
/25.4;
128 m_backgroundBrush
= *wxTRANSPARENT_BRUSH
;
129 m_textForegroundColour
= *wxBLACK
;
130 m_textBackgroundColour
= *wxWHITE
;
131 m_colour
= wxColourDisplay();
133 m_pen
= *wxBLACK_PEN
;
134 m_font
= *wxNORMAL_FONT
;
135 m_brush
= *wxWHITE_BRUSH
;
137 m_graphics_changed
= true;
139 ////////////////////code here
141 m_outfile
= new wxFileOutputStream(filename
);
142 m_OK
= m_outfile
->IsOk();
145 m_filename
= filename
;
148 s
= wxT("<?xml version=\"1.0\" standalone=\"no\"?>") + wxString(wxT("\n"));
150 s
= wxT("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\" ") + wxString(wxT("\n"));
152 s
= wxT("\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\"> ") + wxString(wxT("\n"));
154 s
= wxT("<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" ") + wxString(wxT("\n"));
156 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
);
158 s
= wxT("<title>SVG Picture created as ") + wxFileName(filename
).GetFullName() + wxT(" </title>") + wxT("\n");
160 s
= wxString (wxT("<desc>Picture generated by wxSVG ")) + wxSVGVersion
+ wxT(" </desc>")+ wxT("\n");
162 s
= wxT("<g style=\"fill:black; stroke:black; stroke-width:1\">") + wxString(wxT("\n"));
167 wxSVGFileDCImpl::~wxSVGFileDCImpl()
169 wxString s
= wxT("</g> \n</svg> \n");
174 void wxSVGFileDCImpl::DoGetSizeMM( int *width
, int *height
) const
177 *width
= wxRound( (double)m_width
/ m_mm_to_pix_x
);
180 *height
= wxRound( (double)m_height
/ m_mm_to_pix_y
);
183 wxSize
wxSVGFileDCImpl::GetPPI() const
185 return wxSize( wxRound(m_dpi
), wxRound(m_dpi
) );
188 void wxSVGFileDCImpl::DoDrawLine (wxCoord x1
, wxCoord y1
, wxCoord x2
, wxCoord y2
)
190 if (m_graphics_changed
) NewGraphics();
192 s
.Printf ( wxT("<path d=\"M%d %d L%d %d\" /> \n"), x1
,y1
,x2
,y2
);
197 CalcBoundingBox(x1
, y1
);
198 CalcBoundingBox(x2
, y2
);
201 void wxSVGFileDCImpl::DoDrawLines(int n
, wxPoint points
[], wxCoord xoffset
, wxCoord yoffset
)
203 for ( int i
= 1; i
< n
; i
++ )
205 DoDrawLine ( points
[i
-1].x
+ xoffset
, points
[i
-1].y
+ yoffset
,
206 points
[ i
].x
+ xoffset
, points
[ i
].y
+ yoffset
);
210 void wxSVGFileDCImpl::DoDrawPoint (wxCoord x1
, wxCoord y1
)
213 if (m_graphics_changed
) NewGraphics();
214 s
= wxT("<g style = \"stroke-linecap:round;\" > ") + wxString(wxT("\n"));
216 DoDrawLine ( x1
,y1
,x1
,y1
);
221 void wxSVGFileDCImpl::DoDrawCheckMark(wxCoord x1
, wxCoord y1
, wxCoord width
, wxCoord height
)
223 wxDCImpl::DoDrawCheckMark (x1
,y1
,width
,height
);
226 void wxSVGFileDCImpl::DoDrawText(const wxString
& text
, wxCoord x1
, wxCoord y1
)
228 DoDrawRotatedText(text
, x1
,y1
,0.0);
231 void wxSVGFileDCImpl::DoDrawRotatedText(const wxString
& sText
, wxCoord x
, wxCoord y
, double angle
)
233 //known bug; if the font is drawn in a scaled DC, it will not behave exactly as wxMSW
234 if (m_graphics_changed
) NewGraphics();
237 // calculate bounding box
239 DoGetTextExtent(sText
, &w
, &h
, &desc
);
241 double rad
= DegToRad(angle
);
243 // wxT("upper left") and wxT("upper right")
244 CalcBoundingBox(x
, y
);
245 CalcBoundingBox((wxCoord
)(x
+ w
*cos(rad
)), (wxCoord
)(y
- h
*sin(rad
)));
247 // wxT("bottom left") and wxT("bottom right")
248 x
+= (wxCoord
)(h
*sin(rad
));
249 y
+= (wxCoord
)(h
*cos(rad
));
250 CalcBoundingBox(x
, y
);
251 CalcBoundingBox((wxCoord
)(x
+ h
*sin(rad
)), (wxCoord
)(y
+ h
*cos(rad
)));
253 if (m_backgroundMode
== wxBRUSHSTYLE_SOLID
)
255 // draw background first
256 // just like DoDrawRectangle except we pass the text color to it and set the border to a 1 pixel wide text background
258 sTmp
.Printf ( wxT(" <rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" "), x
,y
+desc
-h
, w
, h
);
259 s
= sTmp
+ wxT("style=\"") + wxBrushString(m_textBackgroundColour
);
260 s
+= wxT("stroke-width:1; ") + wxPenString(m_textBackgroundColour
);
261 sTmp
.Printf ( wxT("\" transform=\"rotate( %s %d %d ) \" />"), NumStr(-angle
), x
,y
);
262 s
+= sTmp
+ wxT("\n");
265 //now do the text itself
266 s
.Printf (wxT(" <text x=\"%d\" y=\"%d\" "),x
,y
);
268 sTmp
= m_font
.GetFaceName();
269 if (sTmp
.Len() > 0) s
+= wxT("style=\"font-family:") + sTmp
+ wxT("; ");
270 else s
+= wxT("style=\" ");
272 wxString fontweights
[3] = { wxT("normal"), wxT("lighter"), wxT("bold") };
273 s
+= wxT("font-weight:") + fontweights
[m_font
.GetWeight() - wxNORMAL
] + wxT("; ");
275 wxString fontstyles
[5] = { wxT("normal"), wxT("style error"), wxT("style error"), wxT("italic"), wxT("oblique") };
276 s
+= wxT("font-style:") + fontstyles
[m_font
.GetStyle() - wxNORMAL
] + wxT("; ");
278 sTmp
.Printf (wxT("font-size:%dpt; "), m_font
.GetPointSize() );
280 //text will be solid, unless alpha value isn't opaque in the foreground colour
281 s
+= wxBrushString(m_textForegroundColour
) + wxPenString(m_textForegroundColour
);
282 sTmp
.Printf ( wxT("stroke-width:0;\" transform=\"rotate( %s %d %d ) \" >"), NumStr(-angle
), x
,y
);
283 s
+= sTmp
+ sText
+ wxT("</text> ") + wxT("\n");
290 void wxSVGFileDCImpl::DoDrawRectangle(wxCoord x
, wxCoord y
, wxCoord width
, wxCoord height
)
292 DoDrawRoundedRectangle(x
, y
, width
, height
, 0);
295 void wxSVGFileDCImpl::DoDrawRoundedRectangle(wxCoord x
, wxCoord y
, wxCoord width
, wxCoord height
, double radius
)
298 if (m_graphics_changed
) NewGraphics();
301 s
.Printf ( wxT(" <rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" rx=\"%s\" "),
302 x
, y
, width
, height
, NumStr(radius
) );
307 CalcBoundingBox(x
, y
);
308 CalcBoundingBox(x
+ width
, y
+ height
);
311 void wxSVGFileDCImpl::DoDrawPolygon(int n
, wxPoint points
[],
312 wxCoord xoffset
, wxCoord yoffset
,
313 wxPolygonFillMode fillStyle
)
315 if (m_graphics_changed
) NewGraphics();
317 s
= wxT("<polygon style=\"");
318 if ( fillStyle
== wxODDEVEN_RULE
)
319 s
+= wxT("fill-rule:evenodd; ");
321 s
+= wxT("fill-rule:nonzero; ");
323 s
+= wxT("\" \npoints=\"");
325 for (int i
= 0; i
< n
; i
++)
327 sTmp
.Printf ( wxT("%d,%d"), points
[i
].x
+xoffset
, points
[i
].y
+yoffset
);
328 s
+= sTmp
+ wxT("\n");
329 CalcBoundingBox ( points
[i
].x
+xoffset
, points
[i
].y
+yoffset
);
331 s
+= wxT("\" /> \n");
335 void wxSVGFileDCImpl::DoDrawEllipse (wxCoord x
, wxCoord y
, wxCoord width
, wxCoord height
)
338 if (m_graphics_changed
) NewGraphics();
344 s
.Printf ( wxT("<ellipse cx=\"%d\" cy=\"%d\" rx=\"%d\" ry=\"%d\" "), x
+rw
,y
+rh
, rw
, rh
);
349 CalcBoundingBox(x
, y
);
350 CalcBoundingBox(x
+ width
, y
+ height
);
353 void wxSVGFileDCImpl::DoDrawArc(wxCoord x1
, wxCoord y1
, wxCoord x2
, wxCoord y2
, wxCoord xc
, wxCoord yc
)
355 /* Draws an arc of a circle, centred on (xc, yc), with starting point
356 (x1, y1) and ending at (x2, y2). The current pen is used for the outline
357 and the current brush for filling the shape.
359 The arc is drawn in an anticlockwise direction from the start point to
362 Might be better described as Pie drawing */
364 if (m_graphics_changed
) NewGraphics();
367 // we need the radius of the circle which has two estimates
368 double r1
= sqrt ( double( (x1
-xc
)*(x1
-xc
) ) + double( (y1
-yc
)*(y1
-yc
) ) );
369 double r2
= sqrt ( double( (x2
-xc
)*(x2
-xc
) ) + double( (y2
-yc
)*(y2
-yc
) ) );
371 wxASSERT_MSG( (fabs ( r2
-r1
) <= 3), wxT("wxSVGFileDC::DoDrawArc Error in getting radii of circle"));
372 if ( fabs ( r2
-r1
) > 3 ) //pixels
374 s
= wxT("<!--- wxSVGFileDC::DoDrawArc Error in getting radii of circle --> \n");
378 double theta1
= atan2((double)(yc
-y1
),(double)(x1
-xc
));
379 if ( theta1
< 0 ) theta1
= theta1
+ M_PI
* 2;
380 double theta2
= atan2((double)(yc
-y2
), (double)(x2
-xc
));
381 if ( theta2
< 0 ) theta2
= theta2
+ M_PI
* 2;
382 if ( theta2
< theta1
) theta2
= theta2
+ M_PI
*2;
384 int fArc
; // flag for large or small arc 0 means less than 180 degrees
385 if ( fabs(theta2
- theta1
) > M_PI
) fArc
= 1; else fArc
= 0;
387 int fSweep
= 0; // flag for sweep always 0
389 s
.Printf ( wxT("<path d=\"M%d %d A%s %s 0.0 %d %d %d %d L%d %d z "),
390 x1
,y1
, NumStr(r1
), NumStr(r2
), fArc
, fSweep
, x2
, y2
, xc
, yc
);
392 // the z means close the path and fill
393 s
+= wxT(" \" /> \n");
402 void wxSVGFileDCImpl::DoDrawEllipticArc(wxCoord x
,wxCoord y
,wxCoord w
,wxCoord h
,double sa
,double ea
)
405 Draws an arc of an ellipse. The current pen is used for drawing the arc
406 and the current brush is used for drawing the pie. This function is
407 currently only available for X window and PostScript device contexts.
409 x and y specify the x and y coordinates of the upper-left corner of the
410 rectangle that contains the ellipse.
412 width and height specify the width and height of the rectangle that
413 contains the ellipse.
415 start and end specify the start and end of the arc relative to the
416 three-o'clock position from the center of the rectangle. Angles are
417 specified in degrees (360 is a complete circle). Positive values mean
418 counter-clockwise motion. If start is equal to end, a complete ellipse
421 //known bug: SVG draws with the current pen along the radii, but this does not happen in wxMSW
423 if (m_graphics_changed
) NewGraphics();
433 double xs
, ys
, xe
, ye
;
434 xs
= xc
+ rx
* cos (DegToRad(sa
));
435 xe
= xc
+ rx
* cos (DegToRad(ea
));
436 ys
= yc
- ry
* sin (DegToRad(sa
));
437 ye
= yc
- ry
* sin (DegToRad(ea
));
439 ///now same as circle arc...
441 double theta1
= atan2(ys
-yc
, xs
-xc
);
442 double theta2
= atan2(ye
-yc
, xe
-xc
);
444 int fArc
; // flag for large or small arc 0 means less than 180 degrees
445 if ( (theta2
- theta1
) > 0 ) fArc
= 1; else fArc
= 0;
448 if ( fabs(theta2
- theta1
) > M_PI
) fSweep
= 1; else fSweep
= 0;
450 s
.Printf ( wxT("<path d=\"M%d %d A%d %d 0.0 %d %d %d %d L %d %d z "),
451 int(xs
), int(ys
), int(rx
), int(ry
),
452 fArc
, fSweep
, int(xe
), int(ye
), int(xc
), int(yc
) );
454 s
+= wxT(" \" /> \n");
462 void wxSVGFileDCImpl::DoGetTextExtent(const wxString
& string
, wxCoord
*w
, wxCoord
*h
, wxCoord
*descent
, wxCoord
*externalLeading
, const wxFont
*font
) const
467 sDC
.SetFont (m_font
);
468 if ( font
!= NULL
) sDC
.SetFont ( *font
);
469 sDC
.GetTextExtent(string
, w
, h
, descent
, externalLeading
);
472 wxCoord
wxSVGFileDCImpl::GetCharHeight() const
475 sDC
.SetFont (m_font
);
477 return sDC
.GetCharHeight();
481 wxCoord
wxSVGFileDCImpl::GetCharWidth() const
484 sDC
.SetFont (m_font
);
486 return sDC
.GetCharWidth();
490 // ----------------------------------------------------------
491 // wxSVGFileDCImpl - set functions
492 // ----------------------------------------------------------
494 void wxSVGFileDCImpl::SetBackground( const wxBrush
&brush
)
496 m_backgroundBrush
= brush
;
500 void wxSVGFileDCImpl::SetBackgroundMode( int mode
)
502 m_backgroundMode
= mode
;
506 void wxSVGFileDCImpl::SetBrush(const wxBrush
& brush
)
511 m_graphics_changed
= true;
515 void wxSVGFileDCImpl::SetPen(const wxPen
& pen
)
517 // width, color, ends, joins : currently implemented
518 // dashes, stipple : not implemented
521 m_graphics_changed
= true;
524 void wxSVGFileDCImpl::NewGraphics()
526 wxString s
, sBrush
, sPenCap
, sPenJoin
, sPenStyle
, sLast
, sWarn
;
528 sBrush
= wxT("</g>\n<g style=\"") + wxBrushString ( m_brush
.GetColour(), m_brush
.GetStyle() )
529 + wxPenString(m_pen
.GetColour(), m_pen
.GetStyle());
531 switch ( m_pen
.GetCap() )
533 case wxCAP_PROJECTING
:
534 sPenCap
= wxT("stroke-linecap:square; ");
537 sPenCap
= wxT("stroke-linecap:butt; ");
541 sPenCap
= wxT("stroke-linecap:round; ");
544 switch ( m_pen
.GetJoin() )
547 sPenJoin
= wxT("stroke-linejoin:bevel; ");
550 sPenJoin
= wxT("stroke-linejoin:miter; ");
554 sPenJoin
= wxT("stroke-linejoin:round; ");
557 sLast
.Printf( wxT("stroke-width:%d\" \n transform=\"translate(%s %s) scale(%s %s)\">"),
558 m_pen
.GetWidth(), NumStr(m_logicalOriginX
), NumStr(m_logicalOriginY
), NumStr(m_scaleX
), NumStr(m_scaleY
) );
560 s
= sBrush
+ sPenCap
+ sPenJoin
+ sPenStyle
+ sLast
+ wxT("\n") + sWarn
;
562 m_graphics_changed
= false;
566 void wxSVGFileDCImpl::SetFont(const wxFont
& font
)
572 // export a bitmap as a raster image in png
573 bool wxSVGFileDCImpl::DoBlit(wxCoord xdest
, wxCoord ydest
, wxCoord width
, wxCoord height
,
574 wxDC
* source
, wxCoord xsrc
, wxCoord ysrc
,
575 wxRasterOperationMode logicalFunc
/*= wxCOPY*/, bool useMask
/*= false*/,
576 wxCoord
/*xsrcMask = -1*/, wxCoord
/*ysrcMask = -1*/)
578 if (logicalFunc
!= wxCOPY
)
580 wxASSERT_MSG(false, wxT("wxSVGFileDC::DoBlit Call requested nonCopy mode; this is not possible"));
583 if (useMask
!= false)
585 wxASSERT_MSG(false, wxT("wxSVGFileDC::DoBlit Call requested false mask; this is not possible"));
588 wxBitmap
myBitmap (width
, height
);
590 memDC
.SelectObject( myBitmap
);
591 memDC
.Blit(0, 0, width
, height
, source
, xsrc
, ysrc
);
592 memDC
.SelectObject( wxNullBitmap
);
593 DoDrawBitmap(myBitmap
, xdest
, ydest
);
597 void wxSVGFileDCImpl::DoDrawIcon(const class wxIcon
& myIcon
, wxCoord x
, wxCoord y
)
599 wxBitmap
myBitmap (myIcon
.GetWidth(), myIcon
.GetHeight() );
601 memDC
.SelectObject( myBitmap
);
602 memDC
.DrawIcon(myIcon
,0,0);
603 memDC
.SelectObject( wxNullBitmap
);
604 DoDrawBitmap(myBitmap
, x
, y
);
607 void wxSVGFileDCImpl::DoDrawBitmap(const class wxBitmap
& bmp
, wxCoord x
, wxCoord y
, bool WXUNUSED(bTransparent
) /*=0*/ )
609 if (m_graphics_changed
) NewGraphics();
611 wxString sTmp
, s
, sPNG
;
612 if ( wxImage::FindHandler(wxBITMAP_TYPE_PNG
) == NULL
)
613 wxImage::AddHandler(new wxPNGHandler
);
615 // create suitable file name
616 sTmp
.Printf ( wxT("_image%d.png"), m_sub_images
);
617 sPNG
= m_filename
.BeforeLast(wxT('.')) + sTmp
;
618 while (wxFile::Exists(sPNG
) )
621 sTmp
.Printf ( wxT("_image%d.png"), m_sub_images
);
622 sPNG
= m_filename
.BeforeLast(wxT('.')) + sTmp
;
625 //create copy of bitmap (wxGTK doesn't like saving a constant bitmap)
626 wxBitmap myBitmap
= bmp
;
628 bool bPNG_OK
= myBitmap
.SaveFile(sPNG
,wxBITMAP_TYPE_PNG
);
630 // reference the bitmap from the SVG doc
631 // only use filename & ext
632 sPNG
= sPNG
.AfterLast(wxFileName::GetPathSeparator());
634 // reference the bitmap from the SVG doc
635 int w
= myBitmap
.GetWidth();
636 int h
= myBitmap
.GetHeight();
637 sTmp
.Printf ( wxT(" <image x=\"%d\" y=\"%d\" width=\"%dpx\" height=\"%dpx\" "), x
,y
,w
,h
);
639 sTmp
.Printf ( wxT(" xlink:href=\"%s\"> \n"), sPNG
.c_str() );
640 s
+= sTmp
+ wxT("<title>Image from wxSVG</title> </image>") + wxT("\n");
646 m_OK
= m_outfile
->IsOk() && bPNG_OK
;
649 void wxSVGFileDCImpl::write(const wxString
&s
)
651 const wxCharBuffer buf
= s
.utf8_str();
652 m_outfile
->Write(buf
, strlen((const char *)buf
));
653 m_OK
= m_outfile
->IsOk();