| // Copyright 2017 PDFium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com | |
| #include "xfa/fxfa/parser/cxfa_rectangle.h" | |
| #include <cmath> | |
| #include <utility> | |
| #include "fxjs/xfa/cjx_node.h" | |
| #include "third_party/base/check.h" | |
| #include "third_party/base/notreached.h" | |
| #include "xfa/fxfa/parser/cxfa_corner.h" | |
| #include "xfa/fxfa/parser/cxfa_document.h" | |
| #include "xfa/fxfa/parser/cxfa_stroke.h" | |
| namespace { | |
| const CXFA_Node::PropertyData kRectanglePropertyData[] = { | |
| {XFA_Element::Edge, 4, 0}, | |
| {XFA_Element::Corner, 4, 0}, | |
| {XFA_Element::Fill, 1, 0}, | |
| }; | |
| const CXFA_Node::AttributeData kRectangleAttributeData[] = { | |
| {XFA_Attribute::Id, XFA_AttributeType::CData, nullptr}, | |
| {XFA_Attribute::Use, XFA_AttributeType::CData, nullptr}, | |
| {XFA_Attribute::Usehref, XFA_AttributeType::CData, nullptr}, | |
| {XFA_Attribute::Hand, XFA_AttributeType::Enum, | |
| (void*)XFA_AttributeValue::Even}, | |
| }; | |
| } // namespace | |
| CXFA_Rectangle::CXFA_Rectangle(CXFA_Document* doc, XFA_PacketType packet) | |
| : CXFA_Box(doc, | |
| packet, | |
| (XFA_XDPPACKET_Template | XFA_XDPPACKET_Form), | |
| XFA_ObjectType::Node, | |
| XFA_Element::Rectangle, | |
| kRectanglePropertyData, | |
| kRectangleAttributeData, | |
| cppgc::MakeGarbageCollected<CJX_Node>( | |
| doc->GetHeap()->GetAllocationHandle(), | |
| this)) {} | |
| CXFA_Rectangle::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, | |
| CJX_Object* js_node) | |
| : CXFA_Box(pDoc, | |
| ePacket, | |
| validPackets, | |
| oType, | |
| eType, | |
| properties, | |
| attributes, | |
| js_node) {} | |
| CXFA_Rectangle::~CXFA_Rectangle() = default; | |
| void CXFA_Rectangle::GetFillPath(const std::vector<CXFA_Stroke*>& strokes, | |
| const CFX_RectF& rtWidget, | |
| CFGAS_GEPath* fillPath) { | |
| bool bSameStyles = true; | |
| CXFA_Stroke* stroke1 = strokes[0]; | |
| for (int32_t i = 1; i < 8; i++) { | |
| CXFA_Stroke* stroke2 = strokes[i]; | |
| if (!stroke1->SameStyles(stroke2, 0)) { | |
| bSameStyles = false; | |
| break; | |
| } | |
| stroke1 = stroke2; | |
| } | |
| if (bSameStyles) { | |
| stroke1 = strokes[0]; | |
| for (int32_t i = 2; i < 8; i += 2) { | |
| CXFA_Stroke* stroke2 = strokes[i]; | |
| if (!stroke1->SameStyles(stroke2, XFA_STROKE_SAMESTYLE_NoPresence | | |
| XFA_STROKE_SAMESTYLE_Corner)) { | |
| bSameStyles = false; | |
| break; | |
| } | |
| stroke1 = stroke2; | |
| } | |
| if (bSameStyles) { | |
| stroke1 = strokes[0]; | |
| if (stroke1->IsInverted()) | |
| bSameStyles = false; | |
| if (stroke1->GetJoinType() != XFA_AttributeValue::Square) | |
| bSameStyles = false; | |
| } | |
| } | |
| if (bSameStyles) { | |
| fillPath->AddRectangle(rtWidget.left, rtWidget.top, rtWidget.width, | |
| rtWidget.height); | |
| return; | |
| } | |
| for (int32_t i = 0; i < 8; i += 2) { | |
| float sx = 0.0f; | |
| float sy = 0.0f; | |
| float vx = 1.0f; | |
| float vy = 1.0f; | |
| float nx = 1.0f; | |
| float ny = 1.0f; | |
| CFX_PointF cp1; | |
| CFX_PointF cp2; | |
| CXFA_Stroke* corner1 = strokes[i]; | |
| CXFA_Stroke* corner2 = strokes[(i + 2) % 8]; | |
| float fRadius1 = corner1->GetRadius(); | |
| float fRadius2 = corner2->GetRadius(); | |
| bool bInverted = corner1->IsInverted(); | |
| bool bRound = corner1->GetJoinType() == XFA_AttributeValue::Round; | |
| if (bRound) { | |
| sy = FX_PI / 2; | |
| } | |
| switch (i) { | |
| case 0: | |
| cp1 = rtWidget.TopLeft(); | |
| cp2 = rtWidget.TopRight(); | |
| vx = 1; | |
| vy = 1; | |
| nx = -1; | |
| ny = 0; | |
| if (bRound) { | |
| sx = bInverted ? FX_PI / 2 : FX_PI; | |
| } else { | |
| sx = 1; | |
| sy = 0; | |
| } | |
| break; | |
| case 2: | |
| cp1 = rtWidget.TopRight(); | |
| cp2 = rtWidget.BottomRight(); | |
| vx = -1; | |
| vy = 1; | |
| nx = 0; | |
| ny = -1; | |
| if (bRound) { | |
| sx = bInverted ? FX_PI : FX_PI * 3 / 2; | |
| } else { | |
| sx = 0; | |
| sy = 1; | |
| } | |
| break; | |
| case 4: | |
| cp1 = rtWidget.BottomRight(); | |
| cp2 = rtWidget.BottomLeft(); | |
| vx = -1; | |
| vy = -1; | |
| nx = 1; | |
| ny = 0; | |
| if (bRound) { | |
| sx = bInverted ? FX_PI * 3 / 2 : 0; | |
| } else { | |
| sx = -1; | |
| sy = 0; | |
| } | |
| break; | |
| case 6: | |
| cp1 = rtWidget.BottomLeft(); | |
| cp2 = rtWidget.TopLeft(); | |
| vx = 1; | |
| vy = -1; | |
| nx = 0; | |
| ny = 1; | |
| if (bRound) { | |
| sx = bInverted ? 0 : FX_PI / 2; | |
| } else { | |
| sx = 0; | |
| sy = -1; | |
| } | |
| break; | |
| } | |
| if (i == 0) | |
| fillPath->MoveTo(CFX_PointF(cp1.x, cp1.y + fRadius1)); | |
| if (bRound) { | |
| if (fRadius1 < 0) | |
| sx -= FX_PI; | |
| if (bInverted) | |
| sy *= -1; | |
| CFX_RectF rtRadius(cp1.x, cp1.y, fRadius1 * 2 * vx, fRadius1 * 2 * vy); | |
| rtRadius.Normalize(); | |
| if (bInverted) | |
| rtRadius.Offset(-fRadius1 * vx, -fRadius1 * vy); | |
| fillPath->ArcTo(rtRadius.TopLeft(), rtRadius.Size(), sx, sy); | |
| } else { | |
| CFX_PointF cp; | |
| if (bInverted) { | |
| cp.x = cp1.x + fRadius1 * vx; | |
| cp.y = cp1.y + fRadius1 * vy; | |
| } else { | |
| cp = cp1; | |
| } | |
| fillPath->LineTo(cp); | |
| fillPath->LineTo( | |
| CFX_PointF(cp1.x + fRadius1 * sx, cp1.y + fRadius1 * sy)); | |
| } | |
| fillPath->LineTo(CFX_PointF(cp2.x + fRadius2 * nx, cp2.y + fRadius2 * ny)); | |
| } | |
| } | |
| void CXFA_Rectangle::Draw(const std::vector<CXFA_Stroke*>& strokes, | |
| CFGAS_GEGraphics* pGS, | |
| CFX_RectF rtWidget, | |
| const CFX_Matrix& matrix) { | |
| bool bVisible = false; | |
| for (int32_t j = 0; j < 4; j++) { | |
| if (strokes[j * 2 + 1]->IsVisible()) { | |
| bVisible = true; | |
| break; | |
| } | |
| } | |
| if (!bVisible) | |
| return; | |
| for (int32_t i = 1; i < 8; i += 2) { | |
| float fThickness = std::fmax(0.0, strokes[i]->GetThickness()); | |
| float fHalf = fThickness / 2; | |
| XFA_AttributeValue iHand = GetHand(); | |
| switch (i) { | |
| case 1: | |
| if (iHand == XFA_AttributeValue::Left) { | |
| rtWidget.top -= fHalf; | |
| rtWidget.height += fHalf; | |
| } else if (iHand == XFA_AttributeValue::Right) { | |
| rtWidget.top += fHalf; | |
| rtWidget.height -= fHalf; | |
| } | |
| break; | |
| case 3: | |
| if (iHand == XFA_AttributeValue::Left) { | |
| rtWidget.width += fHalf; | |
| } else if (iHand == XFA_AttributeValue::Right) { | |
| rtWidget.width -= fHalf; | |
| } | |
| break; | |
| case 5: | |
| if (iHand == XFA_AttributeValue::Left) { | |
| rtWidget.height += fHalf; | |
| } else if (iHand == XFA_AttributeValue::Right) { | |
| rtWidget.height -= fHalf; | |
| } | |
| break; | |
| case 7: | |
| if (iHand == XFA_AttributeValue::Left) { | |
| rtWidget.left -= fHalf; | |
| rtWidget.width += fHalf; | |
| } else if (iHand == XFA_AttributeValue::Right) { | |
| rtWidget.left += fHalf; | |
| rtWidget.width -= fHalf; | |
| } | |
| break; | |
| } | |
| } | |
| Stroke(strokes, pGS, rtWidget, matrix); | |
| } | |
| void CXFA_Rectangle::Stroke(const std::vector<CXFA_Stroke*>& strokes, | |
| CFGAS_GEGraphics* pGS, | |
| CFX_RectF rtWidget, | |
| const CFX_Matrix& matrix) { | |
| bool bVisible; | |
| float fThickness; | |
| XFA_AttributeValue i3DType; | |
| std::tie(i3DType, bVisible, fThickness) = Get3DStyle(); | |
| if (i3DType != XFA_AttributeValue::Unknown) { | |
| if (!bVisible || fThickness < 0.001f) | |
| return; | |
| switch (i3DType) { | |
| case XFA_AttributeValue::Lowered: | |
| StrokeLowered(pGS, rtWidget, fThickness, matrix); | |
| break; | |
| case XFA_AttributeValue::Raised: | |
| StrokeRaised(pGS, rtWidget, fThickness, matrix); | |
| break; | |
| case XFA_AttributeValue::Etched: | |
| StrokeEtched(pGS, rtWidget, fThickness, matrix); | |
| break; | |
| case XFA_AttributeValue::Embossed: | |
| StrokeEmbossed(pGS, rtWidget, fThickness, matrix); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| break; | |
| } | |
| return; | |
| } | |
| bool bClose = false; | |
| bool bSameStyles = true; | |
| CXFA_Stroke* stroke1 = strokes[0]; | |
| for (int32_t i = 1; i < 8; i++) { | |
| CXFA_Stroke* stroke2 = strokes[i]; | |
| if (!stroke1->SameStyles(stroke2, 0)) { | |
| bSameStyles = false; | |
| break; | |
| } | |
| stroke1 = stroke2; | |
| } | |
| if (bSameStyles) { | |
| stroke1 = strokes[0]; | |
| bClose = true; | |
| for (int32_t i = 2; i < 8; i += 2) { | |
| CXFA_Stroke* stroke2 = strokes[i]; | |
| if (!stroke1->SameStyles(stroke2, XFA_STROKE_SAMESTYLE_NoPresence | | |
| XFA_STROKE_SAMESTYLE_Corner)) { | |
| bSameStyles = false; | |
| break; | |
| } | |
| stroke1 = stroke2; | |
| } | |
| if (bSameStyles) { | |
| stroke1 = strokes[0]; | |
| if (stroke1->IsInverted()) | |
| bSameStyles = false; | |
| if (stroke1->GetJoinType() != XFA_AttributeValue::Square) | |
| bSameStyles = false; | |
| } | |
| } | |
| bool bStart = true; | |
| CFGAS_GEPath path; | |
| for (int32_t i = 0; i < 8; i++) { | |
| CXFA_Stroke* stroke = strokes[i]; | |
| if ((i % 1) == 0 && stroke->GetRadius() < 0) { | |
| bool bEmpty = path.IsEmpty(); | |
| if (!bEmpty) { | |
| if (stroke) | |
| stroke->Stroke(&path, pGS, matrix); | |
| path.Clear(); | |
| } | |
| bStart = true; | |
| continue; | |
| } | |
| GetPath(strokes, rtWidget, path, i, bStart, !bSameStyles); | |
| bStart = !stroke->SameStyles(strokes[(i + 1) % 8], 0); | |
| if (bStart) { | |
| if (stroke) | |
| stroke->Stroke(&path, pGS, matrix); | |
| path.Clear(); | |
| } | |
| } | |
| bool bEmpty = path.IsEmpty(); | |
| if (!bEmpty) { | |
| if (bClose) { | |
| path.Close(); | |
| } | |
| if (strokes[7]) | |
| strokes[7]->Stroke(&path, pGS, matrix); | |
| } | |
| } | |
| void CXFA_Rectangle::StrokeRect(CFGAS_GEGraphics* pGraphic, | |
| const CFX_RectF& rt, | |
| float fLineWidth, | |
| const CFX_Matrix& matrix, | |
| FX_ARGB argbTopLeft, | |
| FX_ARGB argbBottomRight) { | |
| float fBottom = rt.bottom(); | |
| float fRight = rt.right(); | |
| CFGAS_GEPath pathLT; | |
| pathLT.MoveTo(CFX_PointF(rt.left, fBottom)); | |
| pathLT.LineTo(CFX_PointF(rt.left, rt.top)); | |
| pathLT.LineTo(CFX_PointF(fRight, rt.top)); | |
| pathLT.LineTo(CFX_PointF(fRight - fLineWidth, rt.top + fLineWidth)); | |
| pathLT.LineTo(CFX_PointF(rt.left + fLineWidth, rt.top + fLineWidth)); | |
| pathLT.LineTo(CFX_PointF(rt.left + fLineWidth, fBottom - fLineWidth)); | |
| pathLT.LineTo(CFX_PointF(rt.left, fBottom)); | |
| pGraphic->SetFillColor(CFGAS_GEColor(argbTopLeft)); | |
| pGraphic->FillPath(&pathLT, CFX_FillRenderOptions::FillType::kWinding, | |
| matrix); | |
| CFGAS_GEPath pathRB; | |
| pathRB.MoveTo(CFX_PointF(fRight, rt.top)); | |
| pathRB.LineTo(CFX_PointF(fRight, fBottom)); | |
| pathRB.LineTo(CFX_PointF(rt.left, fBottom)); | |
| pathRB.LineTo(CFX_PointF(rt.left + fLineWidth, fBottom - fLineWidth)); | |
| pathRB.LineTo(CFX_PointF(fRight - fLineWidth, fBottom - fLineWidth)); | |
| pathRB.LineTo(CFX_PointF(fRight - fLineWidth, rt.top + fLineWidth)); | |
| pathRB.LineTo(CFX_PointF(fRight, rt.top)); | |
| pGraphic->SetFillColor(CFGAS_GEColor(argbBottomRight)); | |
| pGraphic->FillPath(&pathRB, CFX_FillRenderOptions::FillType::kWinding, | |
| matrix); | |
| } | |
| void CXFA_Rectangle::StrokeLowered(CFGAS_GEGraphics* pGS, | |
| CFX_RectF rt, | |
| float fThickness, | |
| const CFX_Matrix& matrix) { | |
| float fHalfWidth = fThickness / 2.0f; | |
| CFX_RectF rtInner(rt); | |
| rtInner.Deflate(fHalfWidth, fHalfWidth); | |
| CFGAS_GEPath path; | |
| path.AddRectangle(rt.left, rt.top, rt.width, rt.height); | |
| path.AddRectangle(rtInner.left, rtInner.top, rtInner.width, rtInner.height); | |
| pGS->SetFillColor(CFGAS_GEColor(0xFF000000)); | |
| pGS->FillPath(&path, CFX_FillRenderOptions::FillType::kEvenOdd, matrix); | |
| StrokeRect(pGS, rtInner, fHalfWidth, matrix, 0xFF808080, 0xFFC0C0C0); | |
| } | |
| void CXFA_Rectangle::StrokeRaised(CFGAS_GEGraphics* pGS, | |
| CFX_RectF rt, | |
| float fThickness, | |
| const CFX_Matrix& matrix) { | |
| float fHalfWidth = fThickness / 2.0f; | |
| CFX_RectF rtInner(rt); | |
| rtInner.Deflate(fHalfWidth, fHalfWidth); | |
| CFGAS_GEPath path; | |
| path.AddRectangle(rt.left, rt.top, rt.width, rt.height); | |
| path.AddRectangle(rtInner.left, rtInner.top, rtInner.width, rtInner.height); | |
| pGS->SetFillColor(CFGAS_GEColor(0xFF000000)); | |
| pGS->FillPath(&path, CFX_FillRenderOptions::FillType::kEvenOdd, matrix); | |
| StrokeRect(pGS, rtInner, fHalfWidth, matrix, 0xFFFFFFFF, 0xFF808080); | |
| } | |
| void CXFA_Rectangle::StrokeEtched(CFGAS_GEGraphics* pGS, | |
| CFX_RectF rt, | |
| float fThickness, | |
| const CFX_Matrix& matrix) { | |
| float fHalfWidth = fThickness / 2.0f; | |
| StrokeRect(pGS, rt, fThickness, matrix, 0xFF808080, 0xFFFFFFFF); | |
| CFX_RectF rtInner(rt); | |
| rtInner.Deflate(fHalfWidth, fHalfWidth); | |
| StrokeRect(pGS, rtInner, fHalfWidth, matrix, 0xFFFFFFFF, 0xFF808080); | |
| } | |
| void CXFA_Rectangle::StrokeEmbossed(CFGAS_GEGraphics* pGS, | |
| CFX_RectF rt, | |
| float fThickness, | |
| const CFX_Matrix& matrix) { | |
| float fHalfWidth = fThickness / 2.0f; | |
| StrokeRect(pGS, rt, fThickness, matrix, 0xFF808080, 0xFF000000); | |
| CFX_RectF rtInner(rt); | |
| rtInner.Deflate(fHalfWidth, fHalfWidth); | |
| StrokeRect(pGS, rtInner, fHalfWidth, matrix, 0xFF000000, 0xFF808080); | |
| } | |
| void CXFA_Rectangle::GetPath(const std::vector<CXFA_Stroke*>& strokes, | |
| CFX_RectF rtWidget, | |
| CFGAS_GEPath& path, | |
| int32_t nIndex, | |
| bool bStart, | |
| bool bCorner) { | |
| DCHECK(nIndex >= 0); | |
| DCHECK(nIndex < 8); | |
| int32_t n = (nIndex & 1) ? nIndex - 1 : nIndex; | |
| CXFA_Stroke* corner1 = strokes[n]; | |
| CXFA_Stroke* corner2 = strokes[(n + 2) % 8]; | |
| float fRadius1 = bCorner ? corner1->GetRadius() : 0.0f; | |
| float fRadius2 = bCorner ? corner2->GetRadius() : 0.0f; | |
| bool bInverted = corner1->IsInverted(); | |
| float offsetY = 0.0f; | |
| float offsetX = 0.0f; | |
| bool bRound = corner1->GetJoinType() == XFA_AttributeValue::Round; | |
| float halfAfter = 0.0f; | |
| float halfBefore = 0.0f; | |
| CXFA_Stroke* stroke = strokes[nIndex]; | |
| if (stroke->IsCorner()) { | |
| CXFA_Stroke* strokeBefore = strokes[(nIndex + 1 * 8 - 1) % 8]; | |
| CXFA_Stroke* strokeAfter = strokes[nIndex + 1]; | |
| if (stroke->IsInverted()) { | |
| if (!stroke->SameStyles(strokeBefore, 0)) | |
| halfBefore = strokeBefore->GetThickness() / 2; | |
| if (!stroke->SameStyles(strokeAfter, 0)) | |
| halfAfter = strokeAfter->GetThickness() / 2; | |
| } | |
| } else { | |
| CXFA_Stroke* strokeBefore = strokes[(nIndex + 8 - 2) % 8]; | |
| CXFA_Stroke* strokeAfter = strokes[(nIndex + 2) % 8]; | |
| if (!bRound && !bInverted) { | |
| halfBefore = strokeBefore->GetThickness() / 2; | |
| halfAfter = strokeAfter->GetThickness() / 2; | |
| } | |
| } | |
| float offsetEX = 0.0f; | |
| float offsetEY = 0.0f; | |
| float sx = 0.0f; | |
| float sy = 0.0f; | |
| float vx = 1.0f; | |
| float vy = 1.0f; | |
| float nx = 1.0f; | |
| float ny = 1.0f; | |
| CFX_PointF cpStart; | |
| CFX_PointF cp1; | |
| CFX_PointF cp2; | |
| if (bRound) | |
| sy = FX_PI / 2; | |
| switch (nIndex) { | |
| case 0: | |
| case 1: | |
| cp1 = rtWidget.TopLeft(); | |
| cp2 = rtWidget.TopRight(); | |
| if (nIndex == 0) { | |
| cpStart.x = cp1.x - halfBefore; | |
| cpStart.y = cp1.y + fRadius1; | |
| offsetY = -halfAfter; | |
| } else { | |
| cpStart.x = cp1.x + fRadius1 - halfBefore; | |
| cpStart.y = cp1.y; | |
| offsetEX = halfAfter; | |
| } | |
| vx = 1; | |
| vy = 1; | |
| nx = -1; | |
| ny = 0; | |
| if (bRound) { | |
| sx = bInverted ? FX_PI / 2 : FX_PI; | |
| } else { | |
| sx = 1; | |
| sy = 0; | |
| } | |
| break; | |
| case 2: | |
| case 3: | |
| cp1 = rtWidget.TopRight(); | |
| cp2 = rtWidget.BottomRight(); | |
| if (nIndex == 2) { | |
| cpStart.x = cp1.x - fRadius1; | |
| cpStart.y = cp1.y - halfBefore; | |
| offsetX = halfAfter; | |
| } else { | |
| cpStart.x = cp1.x; | |
| cpStart.y = cp1.y + fRadius1 - halfBefore; | |
| offsetEY = halfAfter; | |
| } | |
| vx = -1; | |
| vy = 1; | |
| nx = 0; | |
| ny = -1; | |
| if (bRound) { | |
| sx = bInverted ? FX_PI : FX_PI * 3 / 2; | |
| } else { | |
| sx = 0; | |
| sy = 1; | |
| } | |
| break; | |
| case 4: | |
| case 5: | |
| cp1 = rtWidget.BottomRight(); | |
| cp2 = rtWidget.BottomLeft(); | |
| if (nIndex == 4) { | |
| cpStart.x = cp1.x + halfBefore; | |
| cpStart.y = cp1.y - fRadius1; | |
| offsetY = halfAfter; | |
| } else { | |
| cpStart.x = cp1.x - fRadius1 + halfBefore; | |
| cpStart.y = cp1.y; | |
| offsetEX = -halfAfter; | |
| } | |
| vx = -1; | |
| vy = -1; | |
| nx = 1; | |
| ny = 0; | |
| if (bRound) { | |
| sx = bInverted ? FX_PI * 3 / 2 : 0; | |
| } else { | |
| sx = -1; | |
| sy = 0; | |
| } | |
| break; | |
| case 6: | |
| case 7: | |
| cp1 = rtWidget.BottomLeft(); | |
| cp2 = rtWidget.TopLeft(); | |
| if (nIndex == 6) { | |
| cpStart.x = cp1.x + fRadius1; | |
| cpStart.y = cp1.y + halfBefore; | |
| offsetX = -halfAfter; | |
| } else { | |
| cpStart.x = cp1.x; | |
| cpStart.y = cp1.y - fRadius1 + halfBefore; | |
| offsetEY = -halfAfter; | |
| } | |
| vx = 1; | |
| vy = -1; | |
| nx = 0; | |
| ny = 1; | |
| if (bRound) { | |
| sx = bInverted ? 0 : FX_PI / 2; | |
| } else { | |
| sx = 0; | |
| sy = -1; | |
| } | |
| break; | |
| } | |
| if (bStart) { | |
| path.MoveTo(cpStart); | |
| } | |
| if (nIndex & 1) { | |
| path.LineTo(CFX_PointF(cp2.x + fRadius2 * nx + offsetEX, | |
| cp2.y + fRadius2 * ny + offsetEY)); | |
| return; | |
| } | |
| if (bRound) { | |
| if (fRadius1 < 0) | |
| sx -= FX_PI; | |
| if (bInverted) | |
| sy *= -1; | |
| CFX_RectF rtRadius(cp1.x + offsetX * 2, cp1.y + offsetY * 2, | |
| fRadius1 * 2 * vx - offsetX * 2, | |
| fRadius1 * 2 * vy - offsetY * 2); | |
| rtRadius.Normalize(); | |
| if (bInverted) | |
| rtRadius.Offset(-fRadius1 * vx, -fRadius1 * vy); | |
| path.ArcTo(rtRadius.TopLeft(), rtRadius.Size(), sx, sy); | |
| } else { | |
| CFX_PointF cp; | |
| if (bInverted) { | |
| cp.x = cp1.x + fRadius1 * vx; | |
| cp.y = cp1.y + fRadius1 * vy; | |
| } else { | |
| cp = cp1; | |
| } | |
| path.LineTo(cp); | |
| path.LineTo(CFX_PointF(cp1.x + fRadius1 * sx + offsetX, | |
| cp1.y + fRadius1 * sy + offsetY)); | |
| } | |
| } |