| // 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)); |
| } |
| } |