Coverage Report

Created: 2026-05-30 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}