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