1 /*
2  * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 
7 #include <cassert>
8 #include <cmath>
9 #include <fstream>
10 #include <string>
11 
12 #ifndef WT_TARGET_JAVA
13 #include "Wt/Render/WTextRenderer.h"
14 #endif
15 
16 #include "Wt/WApplication.h"
17 #include "Wt/WException.h"
18 #include "Wt/WLineF.h"
19 #include "Wt/WPainter.h"
20 #include "Wt/WPainterPath.h"
21 #include "Wt/WPaintDevice.h"
22 #include "Wt/WRectF.h"
23 #include "Wt/WStringStream.h"
24 #include "Wt/WTransform.h"
25 #include "Wt/WWebWidget.h"
26 
27 #include "Wt/WCanvasPaintDevice.h"
28 
29 #include "FileUtils.h"
30 #include "ImageUtils.h"
31 #include "UriUtils.h"
32 
33 #include <boost/algorithm/string.hpp>
34 
35 namespace Wt {
36 
37 namespace {
splitLabel(WString text)38   static std::vector<WString> splitLabel(WString text)
39   {
40     std::string s = text.toUTF8();
41     std::vector<std::string> splitText;
42     boost::split(splitText, s, boost::is_any_of("\n"));
43     std::vector<WString> result;
44     for (std::size_t i = 0; i < splitText.size(); ++i) {
45       result.push_back(splitText[i]);
46     }
47     return result;
48   }
49 
calcYOffset(int lineNb,int nbLines,double lineHeight,WFlags<AlignmentFlag> verticalAlign)50   static double calcYOffset(int lineNb,
51 			    int nbLines,
52 			    double lineHeight,
53 			    WFlags<AlignmentFlag> verticalAlign)
54   {
55     if (verticalAlign == AlignmentFlag::Middle) {
56       return - ((nbLines - 1) * lineHeight / 2.0) + lineNb * lineHeight;
57     } else if (verticalAlign == AlignmentFlag::Top) {
58       return lineNb * lineHeight;
59     } else if (verticalAlign == AlignmentFlag::Bottom) {
60       return - (nbLines - 1 - lineNb) * lineHeight;
61     } else {
62       return 0;
63     }
64   }
65 }
66 
67 #ifndef WT_TARGET_JAVA
68 
69 class MultiLineTextRenderer final : public Render::WTextRenderer
70 {
71 public:
MultiLineTextRenderer(WPainter & painter,const WRectF & rect)72   MultiLineTextRenderer(WPainter& painter, const WRectF& rect)
73     : painter_(painter),
74       rect_(rect)
75   { }
76 
pageWidth(int page)77   virtual double pageWidth(int page) const override
78   {
79     return rect_.right();
80   }
81 
pageHeight(int page)82   virtual double pageHeight(int page) const override
83   {
84     return 1E9;
85   }
86 
margin(Side side)87   virtual double margin(Side side) const override
88   {
89     switch (side) {
90     case Side::Top: return rect_.top(); break;
91     case Side::Left: return rect_.left(); break;
92     default:
93       return 0;
94     }
95   }
96 
startPage(int page)97   virtual WPaintDevice *startPage(int page) override
98   {
99     if (page > 0)
100       assert(false);
101 
102     return painter_.device();
103   }
104 
endPage(WPaintDevice * device)105   virtual void endPage(WPaintDevice *device) override
106   {
107   }
108 
getPainter(WPaintDevice * device)109   virtual WPainter *getPainter(WPaintDevice *device) override
110   {
111     return &painter_;
112   }
113 
114 private:
115   WPainter& painter_;
116   WRectF    rect_;
117 };
118 
119 #endif // WT_TARGET_JAVA
120 
State()121 WPainter::State::State()
122   : renderHints_(None),
123     clipping_(false)
124 {
125   currentFont_.setFamily(FontFamily::SansSerif);
126   currentFont_.setSize(WLength(10, LengthUnit::Point));
127 }
128 
129 #ifdef WT_TARGET_JAVA
130 
clone()131 WPainter::State WPainter::State::clone()
132 {
133   State result;
134 
135   result.worldTransform_ = worldTransform_;
136   result.currentBrush_ = currentBrush_;
137   result.currentFont_ = currentFont_;
138   result.currentPen_ = currentPen_;
139   result.currentShadow_ = currentShadow_;
140   result.renderHints_ = renderHints_;
141   result.clipPath_ = clipPath_;
142   result.clipPathTransform_ = clipPathTransform_;
143   result.clipping_ = clipping_;
144 
145   return result;
146 }
147 #endif // WT_TARGET_JAVA
148 
Image(const std::string & url,int width,int height)149 WPainter::Image::Image(const std::string& url, int width, int height)
150   : width_(width),
151     height_(height)
152 {
153   setUrl(url);
154 }
155 
Image(const std::string & url,const std::string & fileName)156 WPainter::Image::Image(const std::string& url, const std::string& fileName)
157 {
158   setUrl(url);
159 
160   if (DataUri::isDataUri(url)) {
161     DataUri uri(url);
162 
163     WPoint size = Wt::ImageUtils::getSize(uri.data);
164     if (size.x() == 0 || size.y() == 0)
165       throw WException("data url: (" + uri.mimeType
166 		       + "): could not determine image size");
167 
168     width_ = size.x();
169     height_ = size.y();
170   } else {
171     WPoint size = Wt::ImageUtils::getSize(fileName);
172 
173     if (size.x() == 0 || size.y() == 0)
174       throw WException("'" + fileName
175 		       + "': could not determine image size");
176 
177     width_ = size.x();
178     height_ = size.y();
179   }
180 }
181 
setUrl(const std::string & url)182 void WPainter::Image::setUrl(const std::string& url)
183 {
184   url_ = url; // url is resolved (to url or filesystem) in the paintdevice
185 }
186 
WPainter()187 WPainter::WPainter()
188   : device_(nullptr)
189 {
190   stateStack_.push_back(State());
191 }
192 
WPainter(WPaintDevice * device)193 WPainter::WPainter(WPaintDevice *device)
194   : device_(nullptr)
195 {
196   begin(device);
197 }
198 
~WPainter()199 WPainter::~WPainter()
200 {
201   end();
202 }
203 
setRenderHint(RenderHint hint,bool on)204 void WPainter::setRenderHint(RenderHint hint, bool on)
205 {
206   int old = s().renderHints_.value();
207 
208   if (on)
209     s().renderHints_ |= hint;
210   else
211     s().renderHints_.clear(hint);
212 
213   if (device_ && old != s().renderHints_.value())
214     device_->setChanged(PainterChangeFlag::Hints);
215 }
216 
begin(WPaintDevice * device)217 bool WPainter::begin(WPaintDevice *device)
218 {
219   if (device_)
220     return false;
221 
222   if (device->paintActive())
223     return false;
224 
225   stateStack_.clear();
226   stateStack_.push_back(State());
227 
228   device_ = device;
229   device_->setPainter(this);
230 
231   device_->init();
232 
233   viewPort_ = WRectF(0, 0, device_->width().value(), device_->height().value());
234 
235   window_ = viewPort_;
236 
237   recalculateViewTransform();
238 
239   return true;
240 }
241 
end()242 bool WPainter::end()
243 {
244   if (!device_)
245     return false;
246 
247   device_->done();
248 
249   device_->setPainter(nullptr);
250   device_ = nullptr;
251 
252   stateStack_.clear();
253 
254   return true;
255 }
256 
isActive()257 bool WPainter::isActive() const
258 {
259   return device_ != nullptr;
260 }
261 
save()262 void WPainter::save()
263 {
264   stateStack_.push_back(State(stateStack_.back()));
265 }
266 
restore()267 void WPainter::restore()
268 {
269   if (stateStack_.size() > 1) {
270     WFlags<PainterChangeFlag> flags = None;
271 
272     State& last = stateStack_.back();
273     State& next = stateStack_[stateStack_.size() - 2];
274 
275     if (last.worldTransform_ != next.worldTransform_)
276       flags |= PainterChangeFlag::Transform;
277     if (last.currentBrush_ != next.currentBrush_)
278       flags |= PainterChangeFlag::Brush;
279     if (last.currentFont_ != next.currentFont_)
280       flags |= PainterChangeFlag::Font;
281     if (last.currentPen_ != next.currentPen_)
282       flags |= PainterChangeFlag::Pen;
283     if (last.currentShadow_ != next.currentShadow_)
284       flags |= PainterChangeFlag::Shadow;
285     if (last.renderHints_ != next.renderHints_)
286       flags |= PainterChangeFlag::Hints;
287     if (last.clipPath_ != next.clipPath_)
288       flags |= PainterChangeFlag::Clipping;
289     if (last.clipping_ != next.clipping_)
290       flags |= PainterChangeFlag::Clipping;
291 
292     stateStack_.erase(stateStack_.begin() + stateStack_.size() - 1);
293 
294     if (!flags.empty() && device_)
295       device_->setChanged(flags);
296   }
297 }
298 
drawArc(const WRectF & rectangle,int startAngle,int spanAngle)299 void WPainter::drawArc(const WRectF& rectangle, int startAngle, int spanAngle)
300 {
301   device_->drawArc(rectangle.normalized(), startAngle / 16., spanAngle / 16.);
302 }
303 
drawArc(double x,double y,double width,double height,int startAngle,int spanAngle)304 void WPainter::drawArc(double x, double y, double width, double height,
305 		       int startAngle, int spanAngle)
306 {
307   drawArc(WRectF(x, y, width, height), startAngle, spanAngle);
308 }
309 
drawChord(const WRectF & rectangle,int startAngle,int spanAngle)310 void WPainter::drawChord(const WRectF& rectangle, int startAngle, int spanAngle)
311 {
312   WTransform oldTransform = WTransform(worldTransform());
313 
314   translate(rectangle.center().x(), rectangle.center().y());
315   scale(1., rectangle.height() / rectangle.width());
316 
317   double start = startAngle / 16.;
318   double span = spanAngle / 16.;
319 
320   WPainterPath path;
321   path.arcMoveTo(0, 0, rectangle.width()/2., start);
322   path.arcTo(0, 0, rectangle.width()/2., start, span);
323   path.closeSubPath();
324 
325   drawPath(path);
326 
327   setWorldTransform(oldTransform);
328 }
329 
drawChord(double x,double y,double width,double height,int startAngle,int spanAngle)330 void WPainter::drawChord(double x, double y, double width, double height,
331 			 int startAngle, int spanAngle)
332 {
333   drawChord(WRectF(x, y, width, height), startAngle, spanAngle);
334 }
335 
drawEllipse(const WRectF & rectangle)336 void WPainter::drawEllipse(const WRectF& rectangle)
337 {
338   device_->drawArc(rectangle.normalized(), 0, 360);
339 }
340 
drawEllipse(double x,double y,double width,double height)341 void WPainter::drawEllipse(double x, double y, double width, double height)
342 {
343   drawEllipse(WRectF(x, y, width, height));
344 }
345 
drawImage(const WPointF & point,const Image & image)346 void WPainter::drawImage(const WPointF& point, const Image& image)
347 {
348   drawImage(WRectF(point.x(), point.y(), image.width(), image.height()),
349 	    image, WRectF(0, 0, image.width(), image.height()));
350 }
351 
drawImage(const WPointF & point,const Image & image,const WRectF & sourceRect)352 void WPainter::drawImage(const WPointF& point, const Image& image,
353 			 const WRectF& sourceRect)
354 {
355   drawImage(WRectF(point.x(), point.y(),
356 		   sourceRect.width(), sourceRect.height()),
357 	    image, sourceRect);
358 }
359 
drawImage(const WRectF & rect,const Image & image)360 void WPainter::drawImage(const WRectF& rect, const Image& image)
361 {
362   drawImage(rect, image, WRectF(0, 0, image.width(), image.height()));
363 }
364 
drawImage(const WRectF & rect,const Image & image,const WRectF & sourceRect)365 void WPainter::drawImage(const WRectF& rect, const Image& image,
366 			 const WRectF& sourceRect)
367 {
368   device_->drawImage(rect.normalized(), image.uri(),
369 		     image.width(), image.height(),
370 		     sourceRect.normalized());
371 }
372 
drawImage(double x,double y,const Image & image,double sx,double sy,double sw,double sh)373 void WPainter::drawImage(double x, double y, const Image& image,
374 			 double sx, double sy, double sw, double sh)
375 {
376   if (sw <= 0)
377     sw = image.width() - sx;
378   if (sh <= 0)
379     sh = image.height() - sy;
380 
381   device_->drawImage(WRectF(x, y, sw, sh),
382 		     image.uri(), image.width(), image.height(),
383 		     WRectF(sx, sy, sw, sh));
384 }
385 
drawLine(const WLineF & line)386 void WPainter::drawLine(const WLineF& line)
387 {
388   drawLine(line.x1(), line.y1(), line.x2(), line.y2());
389 }
390 
drawLine(const WPointF & p1,const WPointF & p2)391 void WPainter::drawLine(const WPointF& p1, const WPointF& p2)
392 {
393   drawLine(p1.x(), p1.y(), p2.x(), p2.y());
394 }
395 
drawLine(double x1,double y1,double x2,double y2)396 void WPainter::drawLine(double x1, double y1, double x2, double y2)
397 {
398   device_->drawLine(x1, y1, x2, y2);
399 }
400 
drawLines(const WT_ARRAY WLineF * lines,int lineCount)401 void WPainter::drawLines(const WT_ARRAY WLineF *lines, int lineCount)
402 {
403   for (int i = 0; i < lineCount; ++i)
404     drawLine(lines[i]);
405 }
406 
drawLines(const WT_ARRAY WPointF * pointPairs,int lineCount)407 void WPainter::drawLines(const WT_ARRAY WPointF *pointPairs, int lineCount)
408 {
409   for (int i = 0; i < lineCount; ++i)
410     drawLine(pointPairs[i*2], pointPairs[i*2 + 1]);
411 }
412 
drawLines(const std::vector<WLineF> & lines)413 void WPainter::drawLines(const std::vector<WLineF>& lines)
414 {
415   for (unsigned i = 0; i < lines.size(); ++i)
416     drawLine(lines[i]);
417 }
418 
drawLines(const std::vector<WPointF> & pointPairs)419 void WPainter::drawLines(const std::vector<WPointF>& pointPairs)
420 {
421   for (unsigned i = 0; i < pointPairs.size()/2; ++i)
422     drawLine(pointPairs[i*2], pointPairs[i*2 + 1]);
423 }
424 
drawPath(const WPainterPath & path)425 void WPainter::drawPath(const WPainterPath& path)
426 {
427   device_->drawPath(path);
428 }
429 
drawStencilAlongPath(const WPainterPath & stencil,const WPainterPath & path,bool softClipping)430 void WPainter::drawStencilAlongPath(const WPainterPath &stencil,
431 				    const WPainterPath &path,
432 				    bool softClipping)
433 {
434   WCanvasPaintDevice *cDevice = dynamic_cast<WCanvasPaintDevice*>(device_);
435   if (cDevice) {
436     cDevice->drawStencilAlongPath(stencil, path, softClipping);
437   } else {
438     for (std::size_t i = 0; i < path.segments().size(); ++i) {
439       const WPainterPath::Segment &seg = path.segments()[i];
440       if (softClipping && !clipPath().isEmpty() &&
441 	  !clipPathTransform().map(clipPath())
442 	    .isPointInPath(worldTransform().map(WPointF(seg.x(),seg.y())))) {
443 	continue;
444       }
445       if (seg.type() == LineTo ||
446 	  seg.type() == MoveTo ||
447 	  seg.type() == CubicEnd ||
448 	  seg.type() == QuadEnd) {
449 	WPointF p = WPointF(seg.x(), seg.y());
450 	drawPath((WTransform().translate(p)).map(stencil));
451       }
452     }
453   }
454 }
455 
drawPie(const WRectF & rectangle,int startAngle,int spanAngle)456 void WPainter::drawPie(const WRectF& rectangle, int startAngle, int spanAngle)
457 {
458   WTransform oldTransform = WTransform(worldTransform());
459 
460   translate(rectangle.center().x(), rectangle.center().y());
461   scale(1., rectangle.height() / rectangle.width());
462 
463   WPainterPath path(WPointF(0.0, 0.0));
464   path.arcTo(0.0, 0.0, rectangle.width() / 2.0,
465 	     startAngle / 16., spanAngle / 16.);
466   path.closeSubPath();
467 
468   drawPath(path);
469 
470   setWorldTransform(oldTransform);
471 }
472 
drawPie(double x,double y,double width,double height,int startAngle,int spanAngle)473 void WPainter::drawPie(double x, double y, double width, double height,
474 		       int startAngle, int spanAngle)
475 {
476   drawPie(WRectF(x, y, width, height), startAngle, spanAngle);
477 }
478 
drawPoint(double x,double y)479 void WPainter::drawPoint(double x, double y)
480 {
481   drawLine(x - 0.05, y - 0.05, x + 0.05, y + 0.05);
482 }
483 
drawPoint(const WPointF & point)484 void WPainter::drawPoint(const WPointF& point)
485 {
486   drawPoint(point.x(), point.y());
487 }
488 
drawPoints(const WT_ARRAY WPointF * points,int pointCount)489 void WPainter::drawPoints(const WT_ARRAY WPointF *points, int pointCount)
490 {
491   for (int i = 0; i < pointCount; ++i)
492     drawPoint(points[i]);
493 }
494 
drawPolygon(const WT_ARRAY WPointF * points,int pointCount)495 void WPainter::drawPolygon(const WT_ARRAY WPointF *points, int pointCount
496 			   /*, FillRule fillRule */)
497 {
498   if (pointCount < 2)
499     return;
500 
501   WPainterPath path;
502 
503   path.moveTo(points[0]);
504   for (int i = 1; i < pointCount; ++i)
505     path.lineTo(points[i]);
506 
507   path.closeSubPath();
508 
509   drawPath(path);
510 }
511 
drawPolyline(const WT_ARRAY WPointF * points,int pointCount)512 void WPainter::drawPolyline(const WT_ARRAY WPointF *points, int pointCount)
513 {
514   if (pointCount < 2)
515     return;
516 
517   WPainterPath path;
518 
519   path.moveTo(points[0]);
520   for (int i = 1; i < pointCount; ++i)
521     path.lineTo(points[i]);
522 
523   WBrush oldBrush = WBrush(brush());
524   setBrush(WBrush());
525   drawPath(path);
526   setBrush(oldBrush);
527 }
528 
drawRect(const WRectF & rectangle)529 void WPainter::drawRect(const WRectF& rectangle)
530 {
531   device_->drawRect(rectangle);
532 }
533 
drawRect(double x,double y,double width,double height)534 void WPainter::drawRect(double x, double y, double width, double height)
535 {
536   drawRect(WRectF(x, y, width, height));
537 }
538 
drawRects(const WT_ARRAY WRectF * rectangles,int rectCount)539 void WPainter::drawRects(const WT_ARRAY WRectF *rectangles, int rectCount)
540 {
541   for (int i = 0; i < rectCount; ++i)
542     drawRect(rectangles[i]);
543 }
544 
drawRects(const std::vector<WRectF> & rectangles)545 void WPainter::drawRects(const std::vector<WRectF>& rectangles)
546 {
547   for (unsigned i = 0; i < rectangles.size(); ++i)
548     drawRect(rectangles[i]);
549 }
550 
drawText(const WRectF & rectangle,WFlags<AlignmentFlag> flags,const WString & text)551 void WPainter::drawText(const WRectF& rectangle, WFlags<AlignmentFlag> flags,
552 			const WString& text)
553 {
554   if (!(flags & AlignVerticalMask))
555     flags |= AlignmentFlag::Top;
556   if (!(flags & AlignHorizontalMask))
557     flags |= AlignmentFlag::Left;
558 
559   device_->drawText(rectangle.normalized(), flags, TextFlag::SingleLine,
560 		    text, nullptr);
561 }
562 
drawText(const WRectF & rectangle,WFlags<AlignmentFlag> alignmentFlags,TextFlag textFlag,const WString & text,const WPointF * clipPoint)563 void WPainter::drawText(const WRectF& rectangle,
564 			WFlags<AlignmentFlag> alignmentFlags,
565 			TextFlag textFlag,
566 			const WString& text,
567 			const WPointF *clipPoint)
568 {
569   if (textFlag == TextFlag::SingleLine) {
570     if (!(alignmentFlags & AlignVerticalMask))
571       alignmentFlags |= AlignmentFlag::Top;
572     if (!(alignmentFlags & AlignHorizontalMask))
573       alignmentFlags |= AlignmentFlag::Left;
574 
575     device_->drawText(rectangle.normalized(), alignmentFlags,
576 		      TextFlag::SingleLine, text, clipPoint);
577   } else {
578     if (!(alignmentFlags & AlignVerticalMask))
579       alignmentFlags |= AlignmentFlag::Top;
580     if (!(alignmentFlags & AlignHorizontalMask))
581       alignmentFlags |= AlignmentFlag::Left;
582 
583     if (device_->features().test(PaintDeviceFeatureFlag::WordWrap))
584       device_->drawText(rectangle.normalized(), alignmentFlags, textFlag,
585 			text, clipPoint);
586     else if (device_->features().test(PaintDeviceFeatureFlag::FontMetrics)) {
587 #ifndef WT_TARGET_JAVA
588       MultiLineTextRenderer renderer(*this, rectangle);
589 
590       AlignmentFlag horizontalAlign = alignmentFlags & AlignHorizontalMask;
591       AlignmentFlag verticalAlign = alignmentFlags & AlignVerticalMask;
592 
593       /*
594        * Oh irony: after all these years of hating CSS, we now
595        * implemented an XHTML renderer for which we need to use the
596        * same silly workarounds to render the text with all possible
597        * alignment options
598        */
599       WStringStream s;
600       s << "<table style=\"width:" << (int)rectangle.width() << "px;\""
601 	          "cellspacing=\"0\"><tr>"
602 	     "<td style=\"padding:0px;height:" << (int)rectangle.height() <<
603 	                 "px;color:" << pen().color().cssText()
604 	              << ";text-align:";
605 
606       switch (horizontalAlign) {
607       case AlignmentFlag::Left: s << "left"; break;
608       case AlignmentFlag::Right: s << "right"; break;
609       case AlignmentFlag::Center: s << "center"; break;
610       default: break;
611       }
612 
613       s << ";vertical-align:";
614 
615       switch (verticalAlign) {
616       case AlignmentFlag::Top: s << "top"; break;
617       case AlignmentFlag::Bottom: s << "bottom"; break;
618       case AlignmentFlag::Middle: s << "middle"; break;
619       default: break;
620       }
621 
622       s << ";" << font().cssText(false);
623 
624       s << "\">"
625 	 << WWebWidget::escapeText(text, true).toUTF8()
626 	 << "</td></tr></table>";
627 
628       save();
629 
630       /*
631        * FIXME: what if there was already a clip path? We need to combine
632        * them ...
633        */
634       WPainterPath p;
635       p.addRect(rectangle.x() + 1, rectangle.y() + 1,
636 		rectangle.width() - 2, rectangle.height() - 2);
637       setClipPath(p);
638       setClipping(true);
639       renderer.render(WString::fromUTF8(s.str()));
640       restore();
641 #endif // WT_TARGET_JAVA
642     } else
643       throw WException("WPainter::drawText(): device does not support "
644 		       "WordWrap or FontMetrics");
645   }
646 }
647 
drawText(double x,double y,double width,double height,WFlags<AlignmentFlag> alignmentFlags,TextFlag textFlag,const WString & text)648 void WPainter::drawText(double x, double y, double width, double height,
649 			WFlags<AlignmentFlag> alignmentFlags,
650 			TextFlag textFlag,
651 			const WString& text)
652 {
653   drawText(WRectF(x, y, width, height), alignmentFlags, textFlag, text);
654 }
655 
drawText(double x,double y,double width,double height,WFlags<AlignmentFlag> flags,const WString & text)656 void WPainter::drawText(double x, double y, double width, double height,
657 			WFlags<AlignmentFlag> flags, const WString& text)
658 {
659   drawText(WRectF(x, y, width, height), flags, text);
660 }
661 
drawTextOnPath(const WRectF & rect,WFlags<AlignmentFlag> alignmentFlags,const std::vector<WString> & text,const WTransform & transform,const WPainterPath & path,double angle,double lineHeight,bool softClipping)662 void WPainter::drawTextOnPath(const WRectF &rect,
663 			      WFlags<AlignmentFlag> alignmentFlags,
664 			      const std::vector<WString> &text,
665 			      const WTransform &transform,
666 			      const WPainterPath &path,
667 			      double angle, double lineHeight,
668 			      bool softClipping)
669 {
670   if (!(alignmentFlags & AlignVerticalMask))
671     alignmentFlags |= AlignmentFlag::Top;
672   if (!(alignmentFlags & AlignHorizontalMask))
673     alignmentFlags |= AlignmentFlag::Left;
674   WCanvasPaintDevice *cDevice = dynamic_cast<WCanvasPaintDevice*>(device_);
675   if (cDevice) {
676     cDevice->drawTextOnPath(rect, alignmentFlags, text, transform, path, angle, lineHeight, softClipping);
677   } else {
678     WPainterPath tpath = transform.map(path);
679     for (std::size_t i = 0; i < path.segments().size(); ++i) {
680       if (i >= text.size())
681 	break;
682       const WPainterPath::Segment &seg = path.segments()[i];
683       const WPainterPath::Segment &tseg = tpath.segments()[i];
684       std::vector<WString> splitText = splitLabel(text[i]);
685       if (seg.type() == MoveTo ||
686 	  seg.type() == LineTo ||
687 	  seg.type() == QuadEnd ||
688 	  seg.type() == CubicEnd) {
689 	save();
690 	setClipping(false);
691 	translate(tseg.x(), tseg.y());
692 	rotate(-angle);
693 	for (std::size_t j = 0; j < splitText.size(); ++j) {
694 	  double yOffset = calcYOffset(j, splitText.size(), lineHeight, alignmentFlags & AlignVerticalMask);
695 	  WPointF p(tseg.x(), tseg.y());
696 	  drawText(WRectF(rect.left(), rect.top() + yOffset, rect.width(), rect.height()),
697 			      alignmentFlags, TextFlag::SingleLine, splitText[j], softClipping ? &p : 0);
698 	}
699 	restore();
700       }
701     }
702   }
703 }
704 
fillPath(const WPainterPath & path,const WBrush & b)705 void WPainter::fillPath(const WPainterPath& path, const WBrush& b)
706 {
707   WBrush oldBrush = WBrush(brush());
708   WPen   oldPen = WPen(pen());
709 
710   setBrush(b);
711   setPen(PenStyle::None);
712 
713   drawPath(path);
714 
715   setBrush(oldBrush);
716   setPen(oldPen);
717 }
718 
fillRect(const WRectF & rectangle,const WBrush & b)719 void WPainter::fillRect(const WRectF& rectangle, const WBrush& b)
720 {
721   WBrush oldBrush = WBrush(brush());
722   WPen   oldPen = WPen(pen());
723 
724   setBrush(b);
725   setPen(PenStyle::None);
726 
727   drawRect(rectangle);
728 
729   setBrush(oldBrush);
730   setPen(oldPen);
731 }
732 
fillRect(double x,double y,double width,double height,const WBrush & brush)733 void WPainter::fillRect(double x, double y, double width, double height,
734 			const WBrush& brush)
735 {
736   fillRect(WRectF(x, y, width, height), brush);
737 }
738 
strokePath(const WPainterPath & path,const WPen & p)739 void WPainter::strokePath(const WPainterPath& path, const WPen& p)
740 {
741   WBrush oldBrush = WBrush(brush());
742   WPen   oldPen = WPen(pen());
743 
744   setBrush(WBrush());
745   setPen(p);
746 
747   drawPath(path);
748 
749   setBrush(oldBrush);
750   setPen(oldPen);
751 }
752 
setBrush(const WBrush & b)753 void WPainter::setBrush(const WBrush& b)
754 {
755   if (brush() != b) {
756     s().currentBrush_ = b;
757     device_->setChanged(PainterChangeFlag::Brush);
758   }
759 }
760 
setFont(const WFont & f)761 void WPainter::setFont(const WFont& f)
762 {
763   if (font() != f) {
764     s().currentFont_ = f;
765     device_->setChanged(PainterChangeFlag::Font);
766   }
767 }
768 
setPen(const WPen & p)769 void WPainter::setPen(const WPen& p)
770 {
771   if (pen() != p) {
772     s().currentPen_ = p;
773     device_->setChanged(PainterChangeFlag::Pen);
774   }
775 }
776 
setShadow(const WShadow & shadow)777 void WPainter::setShadow(const WShadow& shadow)
778 {
779   if (this->shadow() != shadow) {
780     s().currentShadow_ = shadow;
781     device_->setChanged(PainterChangeFlag::Shadow);
782   }
783 }
784 
resetTransform()785 void WPainter::resetTransform()
786 {
787   s().worldTransform_.reset();
788 
789   if (device_)
790     device_->setChanged(PainterChangeFlag::Transform);
791 }
792 
rotate(double angle)793 void WPainter::rotate(double angle)
794 {
795   s().worldTransform_.rotate(angle);
796 
797   if (device_)
798     device_->setChanged(PainterChangeFlag::Transform);
799 }
800 
scale(double sx,double sy)801 void WPainter::scale(double sx, double sy)
802 {
803   s().worldTransform_.scale(sx, sy);
804 
805   if (device_)
806     device_->setChanged(PainterChangeFlag::Transform);
807 }
808 
translate(double dx,double dy)809 void WPainter::translate(double dx, double dy)
810 {
811   translate(WPointF(dx, dy));
812 }
813 
translate(const WPointF & p)814 void WPainter::translate(const WPointF& p)
815 {
816   s().worldTransform_.translate(p);
817 
818   if (device_)
819     device_->setChanged(PainterChangeFlag::Transform);
820 }
821 
setWorldTransform(const WTransform & matrix,bool combine)822 void WPainter::setWorldTransform(const WTransform& matrix, bool combine)
823 {
824   if (combine)
825     s().worldTransform_ *= matrix;
826   else
827     s().worldTransform_ = matrix;
828 
829   if (device_)
830     device_->setChanged(PainterChangeFlag::Transform);
831 }
832 
setViewPort(const WRectF & viewPort)833 void WPainter::setViewPort(const WRectF& viewPort)
834 {
835   viewPort_ = viewPort;
836 
837   recalculateViewTransform();
838 }
839 
setViewPort(double x,double y,double width,double height)840 void WPainter::setViewPort(double x, double y, double width, double height)
841 {
842   setViewPort(WRectF(x, y, width, height));
843 }
844 
setWindow(const WRectF & window)845 void WPainter::setWindow(const WRectF& window)
846 {
847   window_ = window;
848 
849   recalculateViewTransform();
850 }
851 
setWindow(double x,double y,double width,double height)852 void WPainter::setWindow(double x, double y, double width, double height)
853 {
854   setWindow(WRectF(x, y, width, height));
855 }
856 
recalculateViewTransform()857 void WPainter::recalculateViewTransform()
858 {
859   viewTransform_ = WTransform();
860 
861   double scaleX = viewPort_.width() / window_.width();
862   double scaleY = viewPort_.height() / window_.height();
863 
864   viewTransform_.translate(viewPort_.x() - window_.x() * scaleX,
865 			   viewPort_.y() - window_.y() * scaleY);
866   viewTransform_.scale(scaleX, scaleY);
867 
868   if (device_)
869     device_->setChanged(PainterChangeFlag::Transform);
870 }
871 
combinedTransform()872 WTransform WPainter::combinedTransform() const
873 {
874   return viewTransform_ * s().worldTransform_;
875 }
876 
clipPathTransform()877 const WTransform& WPainter::clipPathTransform() const
878 {
879   return s().clipPathTransform_;
880 }
881 
setClipping(bool enable)882 void WPainter::setClipping(bool enable)
883 {
884   if (s().clipping_ != enable) {
885     s().clipping_ = enable;
886     if (device_)
887       device_->setChanged(PainterChangeFlag::Clipping);
888   }
889 }
890 
setClipPath(const WPainterPath & clipPath)891 void WPainter::setClipPath(const WPainterPath& clipPath)
892 {
893   s().clipPath_ = clipPath;
894   s().clipPathTransform_ = combinedTransform();
895 
896   if (s().clipping_ && device_)
897     device_->setChanged(PainterChangeFlag::Clipping);
898 }
899 
normalizedPenWidth(const WLength & penWidth,bool correctCosmetic)900 WLength WPainter::normalizedPenWidth(const WLength& penWidth,
901 				     bool correctCosmetic) const
902 {
903   double w = penWidth.value();
904 
905   if (w == 0 && correctCosmetic) {
906     // cosmetic width -- must be untransformed 1 pixel
907     const WTransform& t = combinedTransform();
908     if (!t.isIdentity()) {
909       WTransform::TRSRDecomposition d;
910       t.decomposeTranslateRotateScaleRotate(d);
911 
912       w = 2.0/(std::fabs(d.sx) + std::fabs(d.sy));
913     } else
914       w = 1.0;
915 
916     return WLength(w, LengthUnit::Pixel);
917   } else if (w != 0 && !correctCosmetic) {
918     // non-cosmetic width -- must be transformed
919     const WTransform& t = combinedTransform();
920     if (!t.isIdentity()) {
921       WTransform::TRSRDecomposition d;
922       t.decomposeTranslateRotateScaleRotate(d);
923 
924       w *= (std::fabs(d.sx) + std::fabs(d.sy))/2.0;
925     }
926 
927     return WLength(w, LengthUnit::Pixel);
928   } else
929     return penWidth;
930 }
931 
932 
933 }
934