/// <summary>Filter a PathRenderInfo object</summary>
        /// <param name="path">the PathRenderInfo object to be filtered</param>
        public virtual Path FilterStrokePath(PathRenderInfo path)
        {
            PdfArray        dashPattern     = path.GetLineDashPattern();
            LineDashPattern lineDashPattern = new LineDashPattern(dashPattern.GetAsArray(0), dashPattern.GetAsNumber(1
                                                                                                                     ).FloatValue());

            return(FilterStrokePath(path.GetPath(), path.GetCtm(), path.GetLineWidth(), path.GetLineCapStyle(), path.GetLineJoinStyle
                                        (), path.GetMiterLimit(), lineDashPattern));
        }
        /// <summary>Apply a LineDashPattern along a Path</summary>
        /// <param name="path">input path</param>
        /// <param name="lineDashPattern">input LineDashPattern</param>
        /// <returns>a dashed Path</returns>
        public static Path ApplyDashPattern(Path path, LineDashPattern lineDashPattern)
        {
            ICollection <int> modifiedSubpaths = new HashSet <int>(path.ReplaceCloseWithLine());
            Path dashedPath     = new Path();
            int  currentSubpath = 0;

            foreach (Subpath subpath in path.GetSubpaths())
            {
                IList <Point> subpathApprox = subpath.GetPiecewiseLinearApproximation();
                if (subpathApprox.Count > 1)
                {
                    dashedPath.MoveTo((float)subpathApprox[0].GetX(), (float)subpathApprox[0].GetY());
                    float remainingDist  = 0;
                    bool  remainingIsGap = false;
                    for (int i = 1; i < subpathApprox.Count; ++i)
                    {
                        Point nextPoint = null;
                        if (remainingDist != 0)
                        {
                            nextPoint     = GetNextPoint(subpathApprox[i - 1], subpathApprox[i], remainingDist);
                            remainingDist = ApplyDash(dashedPath, subpathApprox[i - 1], subpathApprox[i], nextPoint, remainingIsGap);
                        }
                        while (JavaUtil.FloatCompare(remainingDist, 0) == 0 && !dashedPath.GetCurrentPoint().Equals(subpathApprox[
                                                                                                                        i]))
                        {
                            LineDashPattern.DashArrayElem currentElem = lineDashPattern.Next();
                            nextPoint = GetNextPoint(nextPoint != null ? nextPoint : subpathApprox[i - 1], subpathApprox[i], currentElem
                                                     .GetVal());
                            remainingDist = ApplyDash(dashedPath, subpathApprox[i - 1], subpathApprox[i], nextPoint, currentElem.IsGap
                                                          ());
                            remainingIsGap = currentElem.IsGap();
                        }
                    }
                    // If true, then the line closing the subpath was explicitly added (see Path.ReplaceCloseWithLine).
                    // This causes a loss of a visual effect of line join style parameter, so in this clause
                    // we simply add overlapping dash (or gap, no matter), which continues the last dash and equals to
                    // the first dash (or gap) of the path.
                    if (modifiedSubpaths.Contains(currentSubpath))
                    {
                        lineDashPattern.Reset();
                        LineDashPattern.DashArrayElem currentElem = lineDashPattern.Next();
                        Point nextPoint = GetNextPoint(subpathApprox[0], subpathApprox[1], currentElem.GetVal());
                        ApplyDash(dashedPath, subpathApprox[0], subpathApprox[1], nextPoint, currentElem.IsGap());
                    }
                }
                // According to PDF spec. line dash pattern should be restarted for each new subpath.
                lineDashPattern.Reset();
                ++currentSubpath;
            }
            return(dashedPath);
        }
        private Path FilterStrokePath(Path sourcePath, Matrix ctm, float lineWidth, int lineCapStyle, int lineJoinStyle
                                      , float miterLimit, LineDashPattern lineDashPattern)
        {
            Path     path     = sourcePath;
            JoinType joinType = ClipperBridge.GetJoinType(lineJoinStyle);
            EndType  endType  = ClipperBridge.GetEndType(lineCapStyle);

            if (lineDashPattern != null)
            {
                if (!lineDashPattern.IsSolid())
                {
                    path = LineDashPattern.ApplyDashPattern(path, lineDashPattern);
                }
            }
            ClipperOffset offset = new ClipperOffset(miterLimit, PdfCleanUpTool.arcTolerance * PdfCleanUpTool.floatMultiplier
                                                     );
            IList <Subpath> degenerateSubpaths = ClipperBridge.AddPath(offset, path, joinType, endType);
            PolyTree        resultTree         = new PolyTree();

            offset.Execute(ref resultTree, lineWidth * PdfCleanUpTool.floatMultiplier / 2);
            Path offsetedPath = ClipperBridge.ConvertToPath(resultTree);

            if (degenerateSubpaths.Count > 0)
            {
                if (endType == EndType.OPEN_ROUND)
                {
                    IList <Subpath> circles = ConvertToCircles(degenerateSubpaths, lineWidth / 2);
                    offsetedPath.AddSubpaths(circles);
                }
                else
                {
                    if (endType == EndType.OPEN_SQUARE && lineDashPattern != null)
                    {
                        IList <Subpath> squares = ConvertToSquares(degenerateSubpaths, lineWidth, sourcePath);
                        offsetedPath.AddSubpaths(squares);
                    }
                }
            }
            return(FilterFillPath(offsetedPath, ctm, PdfCanvasConstants.FillingRule.NONZERO_WINDING));
        }
 /// <summary>Construct a new DashArrayElem object</summary>
 /// <param name="val">the length of the dash array element</param>
 /// <param name="isGap">whether this element indicates a gap, or a stroke</param>
 internal DashArrayElem(LineDashPattern _enclosing, float val, bool isGap)
 {
     this._enclosing = _enclosing;
     this.val        = val;
     this.isGap      = isGap;
 }