1 // Copyright 2017 PDFium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7 #include "xfa/fxfa/parser/cxfa_rectangle.h"
8
9 #include <utility>
10
11 #include "core/fxge/render_defines.h"
12 #include "fxjs/xfa/cjx_node.h"
13 #include "third_party/base/ptr_util.h"
14 #include "xfa/fxfa/parser/cxfa_corner.h"
15 #include "xfa/fxfa/parser/cxfa_stroke.h"
16
17 namespace {
18
19 const CXFA_Node::PropertyData kRectanglePropertyData[] = {
20 {XFA_Element::Edge, 4, 0},
21 {XFA_Element::Corner, 4, 0},
22 {XFA_Element::Fill, 1, 0},
23 };
24
25 const CXFA_Node::AttributeData kRectangleAttributeData[] = {
26 {XFA_Attribute::Id, XFA_AttributeType::CData, nullptr},
27 {XFA_Attribute::Use, XFA_AttributeType::CData, nullptr},
28 {XFA_Attribute::Usehref, XFA_AttributeType::CData, nullptr},
29 {XFA_Attribute::Hand, XFA_AttributeType::Enum,
30 (void*)XFA_AttributeValue::Even},
31 };
32
33 } // namespace
34
CXFA_Rectangle(CXFA_Document * doc,XFA_PacketType packet)35 CXFA_Rectangle::CXFA_Rectangle(CXFA_Document* doc, XFA_PacketType packet)
36 : CXFA_Box(doc,
37 packet,
38 (XFA_XDPPACKET_Template | XFA_XDPPACKET_Form),
39 XFA_ObjectType::Node,
40 XFA_Element::Rectangle,
41 kRectanglePropertyData,
42 kRectangleAttributeData,
43 pdfium::MakeUnique<CJX_Node>(this)) {}
44
CXFA_Rectangle(CXFA_Document * pDoc,XFA_PacketType ePacket,uint32_t validPackets,XFA_ObjectType oType,XFA_Element eType,pdfium::span<const PropertyData> properties,pdfium::span<const AttributeData> attributes,std::unique_ptr<CJX_Object> js_node)45 CXFA_Rectangle::CXFA_Rectangle(CXFA_Document* pDoc,
46 XFA_PacketType ePacket,
47 uint32_t validPackets,
48 XFA_ObjectType oType,
49 XFA_Element eType,
50 pdfium::span<const PropertyData> properties,
51 pdfium::span<const AttributeData> attributes,
52 std::unique_ptr<CJX_Object> js_node)
53 : CXFA_Box(pDoc,
54 ePacket,
55 validPackets,
56 oType,
57 eType,
58 properties,
59 attributes,
60 std::move(js_node)) {}
61
~CXFA_Rectangle()62 CXFA_Rectangle::~CXFA_Rectangle() {}
63
GetFillPath(const std::vector<CXFA_Stroke * > & strokes,const CFX_RectF & rtWidget,CXFA_GEPath * fillPath)64 void CXFA_Rectangle::GetFillPath(const std::vector<CXFA_Stroke*>& strokes,
65 const CFX_RectF& rtWidget,
66 CXFA_GEPath* fillPath) {
67 bool bSameStyles = true;
68 CXFA_Stroke* stroke1 = strokes[0];
69 for (int32_t i = 1; i < 8; i++) {
70 CXFA_Stroke* stroke2 = strokes[i];
71 if (!stroke1->SameStyles(stroke2, 0)) {
72 bSameStyles = false;
73 break;
74 }
75 stroke1 = stroke2;
76 }
77
78 if (bSameStyles) {
79 stroke1 = strokes[0];
80 for (int32_t i = 2; i < 8; i += 2) {
81 CXFA_Stroke* stroke2 = strokes[i];
82 if (!stroke1->SameStyles(stroke2, XFA_STROKE_SAMESTYLE_NoPresence |
83 XFA_STROKE_SAMESTYLE_Corner)) {
84 bSameStyles = false;
85 break;
86 }
87 stroke1 = stroke2;
88 }
89 if (bSameStyles) {
90 stroke1 = strokes[0];
91 if (stroke1->IsInverted())
92 bSameStyles = false;
93 if (stroke1->GetJoinType() != XFA_AttributeValue::Square)
94 bSameStyles = false;
95 }
96 }
97 if (bSameStyles) {
98 fillPath->AddRectangle(rtWidget.left, rtWidget.top, rtWidget.width,
99 rtWidget.height);
100 return;
101 }
102
103 for (int32_t i = 0; i < 8; i += 2) {
104 float sx = 0.0f;
105 float sy = 0.0f;
106 float vx = 1.0f;
107 float vy = 1.0f;
108 float nx = 1.0f;
109 float ny = 1.0f;
110 CFX_PointF cp1, cp2;
111 CXFA_Stroke* corner1 = strokes[i];
112 CXFA_Stroke* corner2 = strokes[(i + 2) % 8];
113 float fRadius1 = corner1->GetRadius();
114 float fRadius2 = corner2->GetRadius();
115 bool bInverted = corner1->IsInverted();
116 bool bRound = corner1->GetJoinType() == XFA_AttributeValue::Round;
117 if (bRound) {
118 sy = FX_PI / 2;
119 }
120 switch (i) {
121 case 0:
122 cp1 = rtWidget.TopLeft();
123 cp2 = rtWidget.TopRight();
124 vx = 1, vy = 1;
125 nx = -1, ny = 0;
126 if (bRound) {
127 sx = bInverted ? FX_PI / 2 : FX_PI;
128 } else {
129 sx = 1, sy = 0;
130 }
131 break;
132 case 2:
133 cp1 = rtWidget.TopRight();
134 cp2 = rtWidget.BottomRight();
135 vx = -1, vy = 1;
136 nx = 0, ny = -1;
137 if (bRound) {
138 sx = bInverted ? FX_PI : FX_PI * 3 / 2;
139 } else {
140 sx = 0, sy = 1;
141 }
142 break;
143 case 4:
144 cp1 = rtWidget.BottomRight();
145 cp2 = rtWidget.BottomLeft();
146 vx = -1, vy = -1;
147 nx = 1, ny = 0;
148 if (bRound) {
149 sx = bInverted ? FX_PI * 3 / 2 : 0;
150 } else {
151 sx = -1, sy = 0;
152 }
153 break;
154 case 6:
155 cp1 = rtWidget.BottomLeft();
156 cp2 = rtWidget.TopLeft();
157 vx = 1, vy = -1;
158 nx = 0, ny = 1;
159 if (bRound) {
160 sx = bInverted ? 0 : FX_PI / 2;
161 } else {
162 sx = 0;
163 sy = -1;
164 }
165 break;
166 }
167 if (i == 0)
168 fillPath->MoveTo(CFX_PointF(cp1.x, cp1.y + fRadius1));
169
170 if (bRound) {
171 if (fRadius1 < 0)
172 sx -= FX_PI;
173 if (bInverted)
174 sy *= -1;
175
176 CFX_RectF rtRadius(cp1.x, cp1.y, fRadius1 * 2 * vx, fRadius1 * 2 * vy);
177 rtRadius.Normalize();
178 if (bInverted)
179 rtRadius.Offset(-fRadius1 * vx, -fRadius1 * vy);
180
181 fillPath->ArcTo(rtRadius.TopLeft(), rtRadius.Size(), sx, sy);
182 } else {
183 CFX_PointF cp;
184 if (bInverted) {
185 cp.x = cp1.x + fRadius1 * vx;
186 cp.y = cp1.y + fRadius1 * vy;
187 } else {
188 cp = cp1;
189 }
190 fillPath->LineTo(cp);
191 fillPath->LineTo(
192 CFX_PointF(cp1.x + fRadius1 * sx, cp1.y + fRadius1 * sy));
193 }
194 fillPath->LineTo(CFX_PointF(cp2.x + fRadius2 * nx, cp2.y + fRadius2 * ny));
195 }
196 }
197
Draw(const std::vector<CXFA_Stroke * > & strokes,CXFA_Graphics * pGS,CFX_RectF rtWidget,const CFX_Matrix & matrix)198 void CXFA_Rectangle::Draw(const std::vector<CXFA_Stroke*>& strokes,
199 CXFA_Graphics* pGS,
200 CFX_RectF rtWidget,
201 const CFX_Matrix& matrix) {
202 bool bVisible = false;
203 for (int32_t j = 0; j < 4; j++) {
204 if (strokes[j * 2 + 1]->IsVisible()) {
205 bVisible = true;
206 break;
207 }
208 }
209 if (!bVisible)
210 return;
211
212 for (int32_t i = 1; i < 8; i += 2) {
213 float fThickness = std::fmax(0.0, strokes[i]->GetThickness());
214 float fHalf = fThickness / 2;
215 XFA_AttributeValue iHand = GetHand();
216 switch (i) {
217 case 1:
218 if (iHand == XFA_AttributeValue::Left) {
219 rtWidget.top -= fHalf;
220 rtWidget.height += fHalf;
221 } else if (iHand == XFA_AttributeValue::Right) {
222 rtWidget.top += fHalf;
223 rtWidget.height -= fHalf;
224 }
225 break;
226 case 3:
227 if (iHand == XFA_AttributeValue::Left) {
228 rtWidget.width += fHalf;
229 } else if (iHand == XFA_AttributeValue::Right) {
230 rtWidget.width -= fHalf;
231 }
232 break;
233 case 5:
234 if (iHand == XFA_AttributeValue::Left) {
235 rtWidget.height += fHalf;
236 } else if (iHand == XFA_AttributeValue::Right) {
237 rtWidget.height -= fHalf;
238 }
239 break;
240 case 7:
241 if (iHand == XFA_AttributeValue::Left) {
242 rtWidget.left -= fHalf;
243 rtWidget.width += fHalf;
244 } else if (iHand == XFA_AttributeValue::Right) {
245 rtWidget.left += fHalf;
246 rtWidget.width -= fHalf;
247 }
248 break;
249 }
250 }
251 Stroke(strokes, pGS, rtWidget, matrix);
252 }
253
Stroke(const std::vector<CXFA_Stroke * > & strokes,CXFA_Graphics * pGS,CFX_RectF rtWidget,const CFX_Matrix & matrix)254 void CXFA_Rectangle::Stroke(const std::vector<CXFA_Stroke*>& strokes,
255 CXFA_Graphics* pGS,
256 CFX_RectF rtWidget,
257 const CFX_Matrix& matrix) {
258 bool bVisible;
259 float fThickness;
260 XFA_AttributeValue i3DType;
261 std::tie(i3DType, bVisible, fThickness) = Get3DStyle();
262 if (i3DType != XFA_AttributeValue::Unknown) {
263 if (!bVisible || fThickness < 0.001f)
264 return;
265
266 switch (i3DType) {
267 case XFA_AttributeValue::Lowered:
268 StrokeLowered(pGS, rtWidget, fThickness, matrix);
269 break;
270 case XFA_AttributeValue::Raised:
271 StrokeRaised(pGS, rtWidget, fThickness, matrix);
272 break;
273 case XFA_AttributeValue::Etched:
274 StrokeEtched(pGS, rtWidget, fThickness, matrix);
275 break;
276 case XFA_AttributeValue::Embossed:
277 StrokeEmbossed(pGS, rtWidget, fThickness, matrix);
278 break;
279 default:
280 NOTREACHED();
281 break;
282 }
283 return;
284 }
285
286 bool bClose = false;
287 bool bSameStyles = true;
288 CXFA_Stroke* stroke1 = strokes[0];
289 for (int32_t i = 1; i < 8; i++) {
290 CXFA_Stroke* stroke2 = strokes[i];
291 if (!stroke1->SameStyles(stroke2, 0)) {
292 bSameStyles = false;
293 break;
294 }
295 stroke1 = stroke2;
296 }
297 if (bSameStyles) {
298 stroke1 = strokes[0];
299 bClose = true;
300 for (int32_t i = 2; i < 8; i += 2) {
301 CXFA_Stroke* stroke2 = strokes[i];
302 if (!stroke1->SameStyles(stroke2, XFA_STROKE_SAMESTYLE_NoPresence |
303 XFA_STROKE_SAMESTYLE_Corner)) {
304 bSameStyles = false;
305 break;
306 }
307 stroke1 = stroke2;
308 }
309 if (bSameStyles) {
310 stroke1 = strokes[0];
311 if (stroke1->IsInverted())
312 bSameStyles = false;
313 if (stroke1->GetJoinType() != XFA_AttributeValue::Square)
314 bSameStyles = false;
315 }
316 }
317
318 bool bStart = true;
319 CXFA_GEPath path;
320 for (int32_t i = 0; i < 8; i++) {
321 CXFA_Stroke* stroke = strokes[i];
322 if ((i % 1) == 0 && stroke->GetRadius() < 0) {
323 bool bEmpty = path.IsEmpty();
324 if (!bEmpty) {
325 if (stroke)
326 stroke->Stroke(&path, pGS, matrix);
327 path.Clear();
328 }
329 bStart = true;
330 continue;
331 }
332 GetPath(strokes, rtWidget, path, i, bStart, !bSameStyles);
333
334 bStart = !stroke->SameStyles(strokes[(i + 1) % 8], 0);
335 if (bStart) {
336 if (stroke)
337 stroke->Stroke(&path, pGS, matrix);
338 path.Clear();
339 }
340 }
341 bool bEmpty = path.IsEmpty();
342 if (!bEmpty) {
343 if (bClose) {
344 path.Close();
345 }
346 if (strokes[7])
347 strokes[7]->Stroke(&path, pGS, matrix);
348 }
349 }
350
StrokeRect(CXFA_Graphics * pGraphic,const CFX_RectF & rt,float fLineWidth,const CFX_Matrix & matrix,FX_ARGB argbTopLeft,FX_ARGB argbBottomRight)351 void CXFA_Rectangle::StrokeRect(CXFA_Graphics* pGraphic,
352 const CFX_RectF& rt,
353 float fLineWidth,
354 const CFX_Matrix& matrix,
355 FX_ARGB argbTopLeft,
356 FX_ARGB argbBottomRight) {
357 float fBottom = rt.bottom();
358 float fRight = rt.right();
359 CXFA_GEPath pathLT;
360 pathLT.MoveTo(CFX_PointF(rt.left, fBottom));
361 pathLT.LineTo(CFX_PointF(rt.left, rt.top));
362 pathLT.LineTo(CFX_PointF(fRight, rt.top));
363 pathLT.LineTo(CFX_PointF(fRight - fLineWidth, rt.top + fLineWidth));
364 pathLT.LineTo(CFX_PointF(rt.left + fLineWidth, rt.top + fLineWidth));
365 pathLT.LineTo(CFX_PointF(rt.left + fLineWidth, fBottom - fLineWidth));
366 pathLT.LineTo(CFX_PointF(rt.left, fBottom));
367 pGraphic->SetFillColor(CXFA_GEColor(argbTopLeft));
368 pGraphic->FillPath(&pathLT, FXFILL_WINDING, &matrix);
369
370 CXFA_GEPath pathRB;
371 pathRB.MoveTo(CFX_PointF(fRight, rt.top));
372 pathRB.LineTo(CFX_PointF(fRight, fBottom));
373 pathRB.LineTo(CFX_PointF(rt.left, fBottom));
374 pathRB.LineTo(CFX_PointF(rt.left + fLineWidth, fBottom - fLineWidth));
375 pathRB.LineTo(CFX_PointF(fRight - fLineWidth, fBottom - fLineWidth));
376 pathRB.LineTo(CFX_PointF(fRight - fLineWidth, rt.top + fLineWidth));
377 pathRB.LineTo(CFX_PointF(fRight, rt.top));
378 pGraphic->SetFillColor(CXFA_GEColor(argbBottomRight));
379 pGraphic->FillPath(&pathRB, FXFILL_WINDING, &matrix);
380 }
381
StrokeLowered(CXFA_Graphics * pGS,CFX_RectF rt,float fThickness,const CFX_Matrix & matrix)382 void CXFA_Rectangle::StrokeLowered(CXFA_Graphics* pGS,
383 CFX_RectF rt,
384 float fThickness,
385 const CFX_Matrix& matrix) {
386 float fHalfWidth = fThickness / 2.0f;
387 CFX_RectF rtInner(rt);
388 rtInner.Deflate(fHalfWidth, fHalfWidth);
389
390 CXFA_GEPath path;
391 path.AddRectangle(rt.left, rt.top, rt.width, rt.height);
392 path.AddRectangle(rtInner.left, rtInner.top, rtInner.width, rtInner.height);
393 pGS->SetFillColor(CXFA_GEColor(0xFF000000));
394 pGS->FillPath(&path, FXFILL_ALTERNATE, &matrix);
395
396 StrokeRect(pGS, rtInner, fHalfWidth, matrix, 0xFF808080, 0xFFC0C0C0);
397 }
398
StrokeRaised(CXFA_Graphics * pGS,CFX_RectF rt,float fThickness,const CFX_Matrix & matrix)399 void CXFA_Rectangle::StrokeRaised(CXFA_Graphics* pGS,
400 CFX_RectF rt,
401 float fThickness,
402 const CFX_Matrix& matrix) {
403 float fHalfWidth = fThickness / 2.0f;
404 CFX_RectF rtInner(rt);
405 rtInner.Deflate(fHalfWidth, fHalfWidth);
406
407 CXFA_GEPath path;
408 path.AddRectangle(rt.left, rt.top, rt.width, rt.height);
409 path.AddRectangle(rtInner.left, rtInner.top, rtInner.width, rtInner.height);
410 pGS->SetFillColor(CXFA_GEColor(0xFF000000));
411 pGS->FillPath(&path, FXFILL_ALTERNATE, &matrix);
412
413 StrokeRect(pGS, rtInner, fHalfWidth, matrix, 0xFFFFFFFF, 0xFF808080);
414 }
415
StrokeEtched(CXFA_Graphics * pGS,CFX_RectF rt,float fThickness,const CFX_Matrix & matrix)416 void CXFA_Rectangle::StrokeEtched(CXFA_Graphics* pGS,
417 CFX_RectF rt,
418 float fThickness,
419 const CFX_Matrix& matrix) {
420 float fHalfWidth = fThickness / 2.0f;
421 StrokeRect(pGS, rt, fThickness, matrix, 0xFF808080, 0xFFFFFFFF);
422
423 CFX_RectF rtInner(rt);
424 rtInner.Deflate(fHalfWidth, fHalfWidth);
425 StrokeRect(pGS, rtInner, fHalfWidth, matrix, 0xFFFFFFFF, 0xFF808080);
426 }
427
StrokeEmbossed(CXFA_Graphics * pGS,CFX_RectF rt,float fThickness,const CFX_Matrix & matrix)428 void CXFA_Rectangle::StrokeEmbossed(CXFA_Graphics* pGS,
429 CFX_RectF rt,
430 float fThickness,
431 const CFX_Matrix& matrix) {
432 float fHalfWidth = fThickness / 2.0f;
433 StrokeRect(pGS, rt, fThickness, matrix, 0xFF808080, 0xFF000000);
434
435 CFX_RectF rtInner(rt);
436 rtInner.Deflate(fHalfWidth, fHalfWidth);
437 StrokeRect(pGS, rtInner, fHalfWidth, matrix, 0xFF000000, 0xFF808080);
438 }
439
GetPath(const std::vector<CXFA_Stroke * > & strokes,CFX_RectF rtWidget,CXFA_GEPath & path,int32_t nIndex,bool bStart,bool bCorner)440 void CXFA_Rectangle::GetPath(const std::vector<CXFA_Stroke*>& strokes,
441 CFX_RectF rtWidget,
442 CXFA_GEPath& path,
443 int32_t nIndex,
444 bool bStart,
445 bool bCorner) {
446 ASSERT(nIndex >= 0);
447 ASSERT(nIndex < 8);
448
449 int32_t n = (nIndex & 1) ? nIndex - 1 : nIndex;
450 CXFA_Stroke* corner1 = strokes[n];
451 CXFA_Stroke* corner2 = strokes[(n + 2) % 8];
452 float fRadius1 = bCorner ? corner1->GetRadius() : 0.0f;
453 float fRadius2 = bCorner ? corner2->GetRadius() : 0.0f;
454 bool bInverted = corner1->IsInverted();
455 float offsetY = 0.0f;
456 float offsetX = 0.0f;
457 bool bRound = corner1->GetJoinType() == XFA_AttributeValue::Round;
458 float halfAfter = 0.0f;
459 float halfBefore = 0.0f;
460
461 CXFA_Stroke* stroke = strokes[nIndex];
462 if (stroke->IsCorner()) {
463 CXFA_Stroke* strokeBefore = strokes[(nIndex + 1 * 8 - 1) % 8];
464 CXFA_Stroke* strokeAfter = strokes[nIndex + 1];
465 if (stroke->IsInverted()) {
466 if (!stroke->SameStyles(strokeBefore, 0))
467 halfBefore = strokeBefore->GetThickness() / 2;
468 if (!stroke->SameStyles(strokeAfter, 0))
469 halfAfter = strokeAfter->GetThickness() / 2;
470 }
471 } else {
472 CXFA_Stroke* strokeBefore = strokes[(nIndex + 8 - 2) % 8];
473 CXFA_Stroke* strokeAfter = strokes[(nIndex + 2) % 8];
474 if (!bRound && !bInverted) {
475 halfBefore = strokeBefore->GetThickness() / 2;
476 halfAfter = strokeAfter->GetThickness() / 2;
477 }
478 }
479
480 float offsetEX = 0.0f;
481 float offsetEY = 0.0f;
482 float sx = 0.0f;
483 float sy = 0.0f;
484 float vx = 1.0f;
485 float vy = 1.0f;
486 float nx = 1.0f;
487 float ny = 1.0f;
488 CFX_PointF cpStart;
489 CFX_PointF cp1;
490 CFX_PointF cp2;
491 if (bRound)
492 sy = FX_PI / 2;
493
494 switch (nIndex) {
495 case 0:
496 case 1:
497 cp1 = rtWidget.TopLeft();
498 cp2 = rtWidget.TopRight();
499 if (nIndex == 0) {
500 cpStart.x = cp1.x - halfBefore;
501 cpStart.y = cp1.y + fRadius1, offsetY = -halfAfter;
502 } else {
503 cpStart.x = cp1.x + fRadius1 - halfBefore, cpStart.y = cp1.y,
504 offsetEX = halfAfter;
505 }
506 vx = 1, vy = 1;
507 nx = -1, ny = 0;
508 if (bRound) {
509 sx = bInverted ? FX_PI / 2 : FX_PI;
510 } else {
511 sx = 1, sy = 0;
512 }
513 break;
514 case 2:
515 case 3:
516 cp1 = rtWidget.TopRight();
517 cp2 = rtWidget.BottomRight();
518 if (nIndex == 2) {
519 cpStart.x = cp1.x - fRadius1, cpStart.y = cp1.y - halfBefore,
520 offsetX = halfAfter;
521 } else {
522 cpStart.x = cp1.x, cpStart.y = cp1.y + fRadius1 - halfBefore,
523 offsetEY = halfAfter;
524 }
525 vx = -1, vy = 1;
526 nx = 0, ny = -1;
527 if (bRound) {
528 sx = bInverted ? FX_PI : FX_PI * 3 / 2;
529 } else {
530 sx = 0, sy = 1;
531 }
532 break;
533 case 4:
534 case 5:
535 cp1 = rtWidget.BottomRight();
536 cp2 = rtWidget.BottomLeft();
537 if (nIndex == 4) {
538 cpStart.x = cp1.x + halfBefore, cpStart.y = cp1.y - fRadius1,
539 offsetY = halfAfter;
540 } else {
541 cpStart.x = cp1.x - fRadius1 + halfBefore, cpStart.y = cp1.y,
542 offsetEX = -halfAfter;
543 }
544 vx = -1, vy = -1;
545 nx = 1, ny = 0;
546 if (bRound) {
547 sx = bInverted ? FX_PI * 3 / 2 : 0;
548 } else {
549 sx = -1, sy = 0;
550 }
551 break;
552 case 6:
553 case 7:
554 cp1 = rtWidget.BottomLeft();
555 cp2 = rtWidget.TopLeft();
556 if (nIndex == 6) {
557 cpStart.x = cp1.x + fRadius1, cpStart.y = cp1.y + halfBefore,
558 offsetX = -halfAfter;
559 } else {
560 cpStart.x = cp1.x, cpStart.y = cp1.y - fRadius1 + halfBefore,
561 offsetEY = -halfAfter;
562 }
563 vx = 1;
564 vy = -1;
565 nx = 0;
566 ny = 1;
567 if (bRound) {
568 sx = bInverted ? 0 : FX_PI / 2;
569 } else {
570 sx = 0;
571 sy = -1;
572 }
573 break;
574 }
575 if (bStart) {
576 path.MoveTo(cpStart);
577 }
578 if (nIndex & 1) {
579 path.LineTo(CFX_PointF(cp2.x + fRadius2 * nx + offsetEX,
580 cp2.y + fRadius2 * ny + offsetEY));
581 return;
582 }
583 if (bRound) {
584 if (fRadius1 < 0)
585 sx -= FX_PI;
586 if (bInverted)
587 sy *= -1;
588
589 CFX_RectF rtRadius(cp1.x + offsetX * 2, cp1.y + offsetY * 2,
590 fRadius1 * 2 * vx - offsetX * 2,
591 fRadius1 * 2 * vy - offsetY * 2);
592 rtRadius.Normalize();
593 if (bInverted)
594 rtRadius.Offset(-fRadius1 * vx, -fRadius1 * vy);
595
596 path.ArcTo(rtRadius.TopLeft(), rtRadius.Size(), sx, sy);
597 } else {
598 CFX_PointF cp;
599 if (bInverted) {
600 cp.x = cp1.x + fRadius1 * vx;
601 cp.y = cp1.y + fRadius1 * vy;
602 } else {
603 cp = cp1;
604 }
605 path.LineTo(cp);
606 path.LineTo(CFX_PointF(cp1.x + fRadius1 * sx + offsetX,
607 cp1.y + fRadius1 * sy + offsetY));
608 }
609 }
610