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