File indexing completed on 2024-06-23 04:27:02
0001 /* This file is part of the KDE project 0002 * SPDX-FileCopyrightText: 2007 Jan Hambrecht <jaham@gmx.net> 0003 * SPDX-FileCopyrightText: 2010 Thomas Zander <zander@kde.org> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "EnhancedPathCommand.h" 0009 #include "EnhancedPathParameter.h" 0010 #include "EnhancedPathShape.h" 0011 #include <KoPathPoint.h> 0012 #include <math.h> 0013 #include <QDebug> 0014 0015 // radian to degree factor 0016 const qreal rad2deg = 180.0 / M_PI; 0017 0018 EnhancedPathCommand::EnhancedPathCommand(const QChar &command, EnhancedPathShape *parent) 0019 : m_command(command) 0020 , m_parent(parent) 0021 { 0022 Q_ASSERT(m_parent); 0023 } 0024 0025 EnhancedPathCommand::~EnhancedPathCommand() 0026 { 0027 } 0028 0029 bool EnhancedPathCommand::execute() 0030 { 0031 /* 0032 * The parameters of the commands are in viewbox coordinates, which have 0033 * to be converted to the shapes coordinate system by calling viewboxToShape 0034 * on the enhanced path the command works on. 0035 * Parameters which resemble angles are angles corresponding to the viewbox 0036 * coordinate system. Those have to be transformed into angles corresponding 0037 * to the normal mathematically coordinate system to be used for the arcTo 0038 * drawing routine. This is done by computing (2*M_PI - angle). 0039 */ 0040 QList<QPointF> points = pointsFromParameters(); 0041 const int pointsCount = points.size(); 0042 0043 switch (m_command.unicode()) { 0044 // starts new subpath at given position (x y) + 0045 case 'M': 0046 if (!pointsCount) { 0047 return false; 0048 } 0049 m_parent->moveTo(points[0]); 0050 if (pointsCount > 1) 0051 for (int i = 1; i < pointsCount; i++) { 0052 m_parent->lineTo(points[i]); 0053 } 0054 break; 0055 // line from current point (x y) + 0056 case 'L': 0057 Q_FOREACH (const QPointF &point, points) { 0058 m_parent->lineTo(point); 0059 } 0060 break; 0061 // cubic bezier curve from current point (x1 y1 x2 y2 x y) + 0062 case 'C': 0063 for (int i = 0; i < pointsCount; i += 3) { 0064 m_parent->curveTo(points[i], points[i + 1], points[i + 2]); 0065 } 0066 break; 0067 // closes the current subpath 0068 case 'Z': 0069 m_parent->close(); 0070 break; 0071 // ends the current set of subpaths 0072 case 'N': 0073 // N just ends the complete path 0074 break; 0075 // no fill for current set of subpaths 0076 case 'F': 0077 // TODO implement me 0078 break; 0079 // no stroke for current set of subpaths 0080 case 'S': 0081 // TODO implement me 0082 break; 0083 // segment of an ellipse (x y w h t0 t1) + 0084 case 'T': 0085 // same like T but with implied movement to starting point (x y w h t0 t1) + 0086 case 'U': { 0087 bool lineTo = m_command.unicode() == 'T'; 0088 0089 for (int i = 0; i < pointsCount; i += 3) { 0090 const QPointF &radii = points[i + 1]; 0091 const QPointF &angles = points[i + 2] / rad2deg; 0092 // compute the ellipses starting point 0093 QPointF start(radii.x() * cos(angles.x()), -1 * radii.y() * sin(angles.x())); 0094 qreal sweepAngle = degSweepAngle(points[i + 2].x(), points[i + 2].y(), false); 0095 0096 if (lineTo) { 0097 m_parent->lineTo(points[i] + start); 0098 } else { 0099 m_parent->moveTo(points[i] + start); 0100 } 0101 0102 m_parent->arcTo(radii.x(), radii.y(), points[i + 2].x(), sweepAngle); 0103 } 0104 break; 0105 } 0106 // counter-clockwise arc (x1 y1 x2 y2 x3 y3 x y) + 0107 case 'A': 0108 // the same as A, with implied moveto to the starting point (x1 y1 x2 y2 x3 y3 x y) + 0109 case 'B': 0110 // clockwise arc (x1 y1 x2 y2 x3 y3 x y) + 0111 case 'W': 0112 // the same as W, but implied moveto (x1 y1 x2 y2 x3 y3 x y) + 0113 case 'V': { 0114 bool lineTo = ((m_command.unicode() == 'A') || (m_command.unicode() == 'W')); 0115 bool clockwise = ((m_command.unicode() == 'W') || (m_command.unicode() == 'V')); 0116 for (int i = 0; i < pointsCount; i += 4) { 0117 QRectF bbox = rectFromPoints(points[i], points[i + 1]); 0118 QPointF center = bbox.center(); 0119 qreal rx = 0.5 * bbox.width(); 0120 qreal ry = 0.5 * bbox.height(); 0121 0122 if (rx == 0) { 0123 rx = 1; 0124 } 0125 0126 if (ry == 0) { 0127 ry = 1; 0128 } 0129 0130 QPointF startRadialVector = points[i + 2] - center; 0131 QPointF endRadialVector = points[i + 3] - center; 0132 0133 // convert from ellipse space to unit-circle space 0134 qreal x0 = startRadialVector.x() / rx; 0135 qreal y0 = startRadialVector.y() / ry; 0136 0137 qreal x1 = endRadialVector.x() / rx; 0138 qreal y1 = endRadialVector.y() / ry; 0139 0140 qreal startAngle = angleFromPoint(QPointF(x0, y0)); 0141 qreal stopAngle = angleFromPoint(QPointF(x1, y1)); 0142 0143 // we are moving counter-clockwise to the end angle 0144 qreal sweepAngle = radSweepAngle(startAngle, stopAngle, clockwise); 0145 // compute the starting point to draw the line to 0146 // as the point x3 y3 is not on the ellipse, spec says the point define radial vector 0147 QPointF startPoint(rx * cos(startAngle), ry * sin(2 * M_PI - startAngle)); 0148 0149 // if A or W is first command in enhanced path 0150 // move to the starting point 0151 bool isFirstCommandInPath = (m_parent->subpathCount() == 0); 0152 bool isFirstCommandInSubpath = m_parent->isClosedSubpath(m_parent->subpathCount() - 1); 0153 0154 if (lineTo && !isFirstCommandInPath && !isFirstCommandInSubpath) { 0155 m_parent->lineTo(center + startPoint); 0156 } else { 0157 m_parent->moveTo(center + startPoint); 0158 } 0159 0160 m_parent->arcTo(rx, ry, startAngle * rad2deg, sweepAngle * rad2deg); 0161 } 0162 break; 0163 } 0164 // elliptical quadrant (initial segment tangential to x-axis) (x y) + 0165 case 'X': { 0166 KoPathPoint *lastPoint = lastPathPoint(); 0167 bool xDir = true; 0168 foreach (const QPointF &point, points) { 0169 qreal rx = point.x() - lastPoint->point().x(); 0170 qreal ry = point.y() - lastPoint->point().y(); 0171 qreal startAngle = xDir ? (ry > 0.0 ? 90.0 : 270.0) : (rx < 0.0 ? 0.0 : 180.0); 0172 qreal sweepAngle = xDir ? (rx * ry < 0.0 ? 90.0 : -90.0) : (rx * ry > 0.0 ? 90.0 : -90.0); 0173 lastPoint = m_parent->arcTo(fabs(rx), fabs(ry), startAngle, sweepAngle); 0174 xDir = !xDir; 0175 } 0176 break; 0177 } 0178 // elliptical quadrant (initial segment tangential to y-axis) (x y) + 0179 case 'Y': { 0180 KoPathPoint *lastPoint = lastPathPoint(); 0181 bool xDir = false; 0182 foreach (const QPointF &point, points) { 0183 qreal rx = point.x() - lastPoint->point().x(); 0184 qreal ry = point.y() - lastPoint->point().y(); 0185 qreal startAngle = xDir ? (ry > 0.0 ? 90.0 : 270.0) : (rx < 0.0 ? 0.0 : 180.0); 0186 qreal sweepAngle = xDir ? (rx * ry < 0.0 ? 90.0 : -90.0) : (rx * ry > 0.0 ? 90.0 : -90.0); 0187 lastPoint = m_parent->arcTo(fabs(rx), fabs(ry), startAngle, sweepAngle); 0188 xDir = !xDir; 0189 } 0190 break; 0191 } 0192 // quadratic bezier curve (x1 y1 x y)+ 0193 case 'Q': 0194 for (int i = 0; i < pointsCount; i += 2) { 0195 m_parent->curveTo(points[i], points[i + 1]); 0196 } 0197 break; 0198 default: 0199 break; 0200 } 0201 return true; 0202 } 0203 0204 QList<QPointF> EnhancedPathCommand::pointsFromParameters() 0205 { 0206 QList<QPointF> points; 0207 QPointF p; 0208 0209 int paramCount = m_parameters.count(); 0210 for (int i = 0; i < paramCount - 1; i += 2) { 0211 p.setX(m_parameters[i]->evaluate()); 0212 p.setY(m_parameters[i + 1]->evaluate()); 0213 points.append(p); 0214 } 0215 0216 int mod = 1; 0217 if (m_command.unicode() == 'C' || m_command.unicode() == 'U' 0218 || m_command.unicode() == 'T') { 0219 mod = 3; 0220 } else if (m_command.unicode() == 'A' || m_command.unicode() == 'B' 0221 || m_command.unicode() == 'W' || m_command.unicode() == 'V') { 0222 mod = 4; 0223 } else if (m_command.unicode() == 'Q') { 0224 mod = 2; 0225 } 0226 if ((points.count() % mod) != 0) { // invalid command 0227 qWarning() << "Invalid point count for command" << m_command << "ignoring" << "count:" << points.count() << "mod:" << mod; 0228 return QList<QPointF>(); 0229 } 0230 0231 return points; 0232 } 0233 0234 void EnhancedPathCommand::addParameter(EnhancedPathParameter *parameter) 0235 { 0236 if (parameter) { 0237 m_parameters.append(parameter); 0238 } 0239 } 0240 0241 qreal EnhancedPathCommand::angleFromPoint(const QPointF &point) const 0242 { 0243 qreal angle = atan2(point.y(), point.x()); 0244 if (angle < 0.0) { 0245 angle += 2 * M_PI; 0246 } 0247 0248 return 2 * M_PI - angle; 0249 } 0250 0251 qreal EnhancedPathCommand::radSweepAngle(qreal start, qreal stop, bool clockwise) const 0252 { 0253 qreal sweepAngle = stop - start; 0254 if (fabs(sweepAngle) < 0.1) { 0255 return 2 * M_PI; 0256 } 0257 if (clockwise) { 0258 // we are moving clockwise to the end angle 0259 if (stop > start) { 0260 sweepAngle = (stop - start) - 2 * M_PI; 0261 } 0262 } else { 0263 // we are moving counter-clockwise to the stop angle 0264 if (start > stop) { 0265 sweepAngle = 2 * M_PI - (start - stop); 0266 } 0267 } 0268 0269 return sweepAngle; 0270 } 0271 0272 qreal EnhancedPathCommand::degSweepAngle(qreal start, qreal stop, bool clockwise) const 0273 { 0274 qreal sweepAngle = stop - start; 0275 if (fabs(sweepAngle) < 0.1) { 0276 return 360.0; 0277 } 0278 if (clockwise) { 0279 // we are moving clockwise to the end angle 0280 if (stop > start) { 0281 sweepAngle = (stop - start) - 360.0; 0282 } 0283 } else { 0284 // we are moving counter-clockwise to the stop angle 0285 if (start > stop) { 0286 sweepAngle = 360.0 - (start - stop); 0287 } 0288 } 0289 0290 return sweepAngle; 0291 } 0292 0293 KoPathPoint *EnhancedPathCommand::lastPathPoint() const 0294 { 0295 KoPathPoint *lastPoint = 0; 0296 int subpathCount = m_parent->subpathCount(); 0297 if (subpathCount) { 0298 int subpathPointCount = m_parent->subpathPointCount(subpathCount - 1); 0299 lastPoint = m_parent->pointByIndex(KoPathPointIndex(subpathCount - 1, subpathPointCount - 1)); 0300 } 0301 return lastPoint; 0302 } 0303 0304 QRectF EnhancedPathCommand::rectFromPoints(const QPointF &tl, const QPointF &br) const 0305 { 0306 return QRectF(tl, QSizeF(br.x() - tl.x(), br.y() - tl.y())).normalized(); 0307 } 0308 0309 QString EnhancedPathCommand::toString() const 0310 { 0311 QString cmd = m_command; 0312 0313 Q_FOREACH (EnhancedPathParameter *p, m_parameters) { 0314 cmd += p->toString() + ' '; 0315 } 0316 0317 return cmd.trimmed(); 0318 }