Пример #1
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="shape">The parent shape. If not null, it will automatically add the created loop
        /// to the shape.
        /// </param>
        /// <param name="initialInfo">Information on initial nodes to create.</param>
        public BLoop(BShape shape, params BNode.BezierInfo [] initialInfo)
        {
            if (shape != null)
            {
                shape.AddLoop(this);
            }

            foreach (BNode.BezierInfo bi in initialInfo)
            {
                BNode bn = new BNode(this, bi);
                bn.FlagDirty();
                nodes.Add(bn);
            }

            if (initialInfo.Length == 0)
            {
            }   // Do nothing
            else if (initialInfo.Length == 1)
            {
            }   // Also do nothing
            if (initialInfo.Length == 2)
            {
                this.nodes[0].next = this.nodes[1];
                this.nodes[1].prev = this.nodes[0];
            }
            else
            {
                int lastIdx = this.nodes.Count - 1;

                for (int i = 0; i < lastIdx; ++i)
                {
                    BNode bnprv = this.nodes[i];
                    BNode bnnxt = this.nodes[i + 1];

                    bnprv.next = bnnxt;
                    bnnxt.prev = bnprv;
                }

                // Close the shape.
                if (this.nodes.Count > 0)
                {
                    BNode bnfirst = this.nodes[0];
                    BNode bnlast  = this.nodes[lastIdx];
                    bnfirst.prev = bnlast;
                    bnlast.next  = bnfirst;
                }
            }

#if DEVELOPMENT_BUILD || UNITY_EDITOR
            this.debugCounter = Utils.RegisterCounter();
#endif
        }
Пример #2
0
        /// <summary>
        /// Given a string and a typeface, create the vector shapes for them.
        /// </summary>
        /// <param name="l">The layer to create the glyph shapes in.</param>
        /// <param name="offset">The location where the glyphs will be created. Represents the
        /// baseline position for the start of the string.</param>
        /// <param name="font">The font to create.</param>
        /// <param name="scale">A multiplier scale on the created geometry.</param>
        /// <param name="strToGen">The string to generate.</param>
        /// <returns>A 1-1 mapping between the string chars and the generated shapes. For missing glyphs, the entry will
        /// either be an empty glyph or null.</returns>
        public static List <BShape> GenerateString(
            Layer l,
            Vector2 offset,
            Font.Typeface font,
            float scale,
            string strToGen)
        {
            Vector2       pos = offset;
            List <BShape> ret = new List <BShape>();

            const float normLineHeight = 1.0f;

            // For each shape, generate the geometry.
            for (int i = 0; i < strToGen.Length; ++i)
            {
                char       c = strToGen[i];
                Font.Glyph g;

                if (c == '\n')
                {
                    pos.x  = offset.x;
                    pos.y += 1.0f * scale * normLineHeight;
                    ret.Add(null);
                    continue;
                }

                if (font.glyphLookup.TryGetValue(c, out g) == false)
                {
                    ret.Add(null);
                    continue;
                }

                BShape glyphShape = GenerateGlyph(g, l, pos, scale);
                ret.Add(glyphShape);

                pos.x += g.advance * scale;
            }

            return(ret);
        }
Пример #3
0
        /// <summary>
        /// Create a deep copy of the shape.
        /// </summary>
        /// <param name="layer">The layer to insert the shape into.</param>
        /// <returns>The duplicate layer.</returns>
        public BShape Clone(Layer layer)
        {
            BShape ret = new BShape(this.docPos, this.rotation);

            ret.layer = layer;
            if (layer != null)
            {
                layer.shapes.Add(ret);
            }

            Dictionary <BNode, BNode> conversion = new Dictionary <BNode, BNode>();

            foreach (BLoop loop in this.loops)
            {
                BLoop newLoop = new BLoop(ret);
                foreach (BNode bn in loop.nodes)
                {
                    BNode newNode = bn.Clone(newLoop);
                    newLoop.nodes.Add(newNode);

                    conversion.Add(bn, newNode);
                }

                foreach (BNode bn in newLoop.nodes)
                {
                    if (bn.prev != null)
                    {
                        bn.prev = conversion[bn.prev];
                    }

                    if (bn.next != null)
                    {
                        bn.next = conversion[bn.next];
                    }
                }
            }

            return(ret);
        }
Пример #4
0
        /// <summary>
        /// Remove a loop from its parent shape, and optionally remove
        /// the shape from the layer if it's null.
        /// </summary>
        /// <param name="loop">The loop to remove from its parent.</param>
        /// <param name="rmShapeIfEmpty">If true, the parent shape will be removed from
        /// its parent layer if the operation leaves that shape empty. </param>
        public static void RemoveLoop(BLoop loop, bool rmShapeIfEmpty)
        {
            if (loop.shape == null)
            {
                return;
            }

            BShape shape = loop.shape;

            shape.loops.Remove(loop);
            loop.shape = null;

            if (rmShapeIfEmpty == false || shape.loops.Count > 0)
            {
                return;
            }

            if (shape.layer != null)
            {
                shape.layer.shapes.Remove(shape);
                shape.layer = null;
            }
        }
Пример #5
0
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="shape">The parent shape for the generator to modify.</param>
 public BShapeGen(BShape shape)
 {
     this.shape = shape;
 }
Пример #6
0
        /// <summary>
        /// Line loop constructor.
        ///
        /// Creates a shape constructed of straight lines, defined by an ordered array
        /// of 2D points.
        /// </summary>
        /// <param name="shape">The parent shape.</param>
        /// <param name="closed">
        /// If true, the shape is created as a closed path..
        /// Else if false, the shape is created as an open path</param>
        /// <param name="linePoints">The ordered list of points used to define the initial path.</param>
        public BLoop(BShape shape, bool closed, params Vector2 [] linePoints)
        {
            // Arguably, we could have made the style of creation between this and the
            // "params BNode.BezierInfo []" constructor the same style of either
            // storing in an array and connecting afterwards (the other) or making
            // connections as we go and storing the last (this one).
            if (shape != null)
            {
                shape.AddLoop(this);
            }

            if (linePoints.Length == 0)
            {
            }
            else if (linePoints.Length == 1)
            {
                BNode bn = new BNode(this, linePoints[0]);
                this.nodes.Add(bn);
            }
            else if (linePoints.Length == 2)
            {
                BNode bnA = new BNode(this, linePoints[0]);
                this.nodes.Add(bnA);

                BNode bnB = new BNode(this, linePoints[1]);
                this.nodes.Add(bnB);

                bnA.next = bnB;
                bnB.prev = bnA;
            }
            else
            {
                BNode first = null;
                BNode prev  = null;

                foreach (Vector2 v2 in linePoints)
                {
                    BNode bn = new BNode(this, v2);
                    this.nodes.Add(bn);

                    if (prev == null)
                    {
                        first = bn;
                    }
                    else
                    {
                        prev.next = bn;
                        bn.prev   = prev;
                    }
                    prev = bn;
                }

                if (closed == true)
                {
                    // At this point, prev will point to the last item.
                    first.prev = prev;
                    prev.next  = first;
                }
            }

#if DEVELOPMENT_BUILD || UNITY_EDITOR
            this.debugCounter = Utils.RegisterCounter();
#endif
        }
Пример #7
0
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="shape">The shape to attach to.</param>
 /// <param name="point1">An endpoint of the line.</param>
 /// <param name="point2">The other endpoint of the line.</param>
 public BShapeGenLine(BShape shape, Vector2 point1, Vector2 point2)
     : base(shape)
 {
     this.pt0 = point1;
     this.pt1 = point2;
 }
Пример #8
0
        /// <summary>
        /// Given a shape, fill the session with its closed islands.
        /// </summary>
        /// <param name="shape">The shape to analyze.</param>
        /// <returns>The number of islands extracted.</returns>
        public int ExtractFillLoops(BShape shape)
        {
            int ret = 0;

            List <BNode> islandNodes = new List <BNode>();

            // For all loops in the shape, extract a single peice of each
            // unique circular island.
            foreach (BLoop loop in shape.loops)
            {
                HashSet <BNode> toScan = new HashSet <BNode>(loop.nodes);

                while (toScan.Count > 0)
                {
                    BNode bn = Utils.GetFirstInHash(toScan);

                    BNode.EndpointQuery eq = bn.GetPathLeftmost();

                    // If cyclical, save a single node to scan the island later
                    if (eq.result == BNode.EndpointResult.Cyclical)
                    {
                        islandNodes.Add(bn);
                    }

                    // And remove every part of it from what we're going to
                    //scan in the future.
                    BNode it = bn;
                    while (true)
                    {
                        toScan.Remove(it);

                        it = it.next;
                        if (it == null || it == eq.node)
                        {
                            break;
                        }
                    }
                }
            }

            // Extra islands from the looped nodes we've collected.
            foreach (BNode bisln in islandNodes)
            {
                BSample bstart = bisln.sample;
                BSample bit    = bstart;

                FillIsland island = new FillIsland();
                this.islands.Add(island);
                ++ret;

                FillSegment firstSeg = null;
                FillSegment lastSeg  = null;

                // For now we're going to assume the samples are well formed.
                while (true)
                {
                    FillSegment fs = new FillSegment();

                    // Transfer positions. We keep a local copy of positions, but by convention,
                    // the prevPos will match the prev's nextPos, and the nextPos will match
                    // the next's prevPos.
                    fs.pos = bit.pos;

                    island.segments.Add(fs);

                    if (firstSeg == null)
                    {
                        firstSeg = fs;
                    }
                    else
                    {
                        lastSeg.next = fs;
                        fs.prev      = lastSeg;
                    }

                    lastSeg = fs;

                    bit = bit.next;
                    if (bit == bstart)
                    {
                        break;
                    }
                }
                lastSeg.next  = firstSeg;
                firstSeg.prev = lastSeg;

                // Delme
                if (island.TestValidity() == false)
                {
                    throw new System.Exception("FillSession.ExtractFillLoops produced invalid island.");
                }
            }


            return(ret);
        }
Пример #9
0
        /// <summary>
        /// Given a font glyph, generate a formal path for it.
        /// </summary>
        /// <param name="glyph">The glyph to turn into a path.</param>
        /// <param name="l">The layer to create the shape in.</param>
        /// <param name="offset">The offset to translate the glyph.</param>
        /// <param name="scale">The scale of the glyph, with 1.0 being the
        /// default size.</param>
        /// <returns>The created path. If the glyph has multiple
        /// contours, they will be created as individual loops.</returns>
        public static BShape GenerateGlyph(
            Font.Glyph glyph,
            Layer l,
            Vector2 offset,
            float scale)
        {
            BShape shapeLetter = new BShape(Vector2.zero, 0.0f);

            shapeLetter.layer = l;

            if (l != null)
            {
                l.shapes.Add(shapeLetter);
            }

            // Generate each contour in the glyph. When we're iterating through the glyph points,
            // we need to remember we're dealing with two possible conventions at once - TTF/OTF and
            // CFF.
            //
            // Remember TTF/OTF uses quadratic Beziers and the control flags.
            //
            // While CCF uses cubic Beziers and the point tangents and tangent flags.
            for (int j = 0; j < glyph.contours.Count; ++j)
            {
                BLoop loopCont = new BLoop(shapeLetter);

                //https://stackoverflow.com/questions/20733790/truetype-fonts-glyph-are-made-of-quadratic-bezier-why-do-more-than-one-consecu
                Font.Contour cont = glyph.contours[j];

                for (int k = 0; k < cont.points.Count; ++k)
                {
                    int nextId = (k + 1) % cont.points.Count;
                    // If two control points are next to each other, there's an implied
                    // point in between them at their average. The easiest way to handle
                    // that is to make a representation where we manually inserted them
                    // to define them explicitly.
                    //
                    // NOTE: We should probably just directly "sanitize" this information
                    // when it's first loaded.
                    if (cont.points[k].isControl == true && cont.points[nextId].isControl == true)
                    {
                        Font.Point pt = new Font.Point();
                        pt.isControl = false;
                        pt.position  = (cont.points[k].position + cont.points[nextId].position) * 0.5f;

                        // Things that process this data may want to know it's implied, especially
                        // diagnostic tools.
                        pt.implied = true;

                        cont.points.Insert(nextId, pt);
                        ++k;
                    }
                }

                BNode   firstNode = null;   // Used to know what to link the last node to when we're done looping.
                BNode   prevNode  = null;   // Used to have a record of the last node when we're done looping.
                Vector2?lastTan   = null;   // The last used tangent when dealing with control points.

                // Point are now either points, or curve controls surrounded by points -
                // or it's a CFF and we don't actually care about control points since we have
                // explicitly defined tangents.
                //
                // The code is written to handle both without explicitly knowing which system is being used.
                for (int k = 0; k < cont.points.Count; ++k)
                {
                    Vector2 ptpos = cont.points[k].position * scale + offset;

                    if (cont.points[k].isControl == false)
                    {
                        BNode node = new BNode(loopCont, ptpos);
                        loopCont.nodes.Add(node);

                        if (lastTan.HasValue == true)
                        {
                            node.UseTanIn = true;
                            node.TanIn    = (lastTan.Value - ptpos) * (2.0f / 3.0f) * scale;
                        }

                        lastTan = null;
                        if (prevNode != null)
                        {
                            node.prev     = prevNode;
                            prevNode.next = node;
                        }

                        if (firstNode == null)
                        {
                            firstNode = node;
                        }

                        int kPrev = (k - 1 + cont.points.Count) % cont.points.Count;

                        if (k != 0 && cont.points[kPrev].isControl == false && cont.points[kPrev].useTangentOut == false)
                        {
                            prevNode.UseTanOut = false;
                            node.UseTanIn      = false;
                        }

                        if (cont.points[k].useTangentIn == true)
                        {
                            node.UseTanIn = true;
                            node.TanIn    = cont.points[k].tangentIn;
                        }

                        if (cont.points[k].useTangentOut == true)
                        {
                            node.UseTanOut = true;
                            node.TanOut    = cont.points[k].tangentOut;
                        }

                        node.FlagDirty();
                        prevNode = node;
                    }
                    else // if (cont.points[k].control == true)
                    {
                        lastTan = ptpos;

                        if (prevNode != null)
                        {
                            prevNode.UseTanOut = true;
                            prevNode.TanOut    = (ptpos - prevNode.Pos) * (2.0f / 3.0f) * scale;
                        }
                    }
                }

                if (firstNode != null)
                {
                    prevNode.next  = firstNode;
                    firstNode.prev = prevNode;

                    if (
                        cont.points[0].isControl == false &&
                        cont.points[0].useTangentIn == false &&
                        cont.points[cont.points.Count - 1].isControl == false &&
                        cont.points[cont.points.Count - 1].useTangentOut == false)
                    {
                        firstNode.UseTanIn = false;
                        prevNode.UseTanOut = false;
                    }

                    if (lastTan.HasValue == true)
                    {
                        firstNode.UseTanIn = true;
                        firstNode.TanIn    = (lastTan.Value - firstNode.Pos) * (2.0f / 3.0f) * scale;
                    }
                }
            }

            return(shapeLetter);
        }
Пример #10
0
        /// <summary>
        /// Given a BShape that was created from a font glyph, modify as needed so that
        /// it can be immediately filled properly.
        /// </summary>
        /// <param name="shape"></param>
        public static void BridgeGlyph(BShape shape)
        {
            // Find the outer-most point
            BNode   maxXNode;
            float   maxXT;
            Vector2 maxV;

            BNode.GetMaxPoint(shape.EnumerateNodes(), out maxXNode, out maxV, out maxXT, 0);

            if (maxXNode == null)
            {
                return;
            }

            float wind = BNode.CalculateWinding(maxXNode.Travel());

            // Separate positive from negative islands.
            List <BNode> positiveIslands = new List <BNode>();
            List <BNode> negativeIslands = new List <BNode>();

            foreach (BLoop bl in shape.loops)
            {
                List <BNode> islands = bl.GetIslands(IslandTypeRequest.Closed);
                for (int i = 0; i < islands.Count; ++i)
                {
                    float islW = BNode.CalculateWinding(islands[i].Travel());
                    if (Mathf.Sign(islW) == Mathf.Sign(wind))
                    {
                        positiveIslands.Add(islands[i]);
                    }
                    else
                    {
                        negativeIslands.Add(islands[i]);
                    }
                }
            }

            // We're going to do a non-generic kludge that should handle how most fonts are
            // created. For every negative shape, we're going to use it to hollow the next
            // largest positive shape. And then just leave positive (bridged) shapes left.

            foreach (BNode negIsl in negativeIslands)
            {
                List <BNode> positiveIslandOrder = new List <BNode>();

                List <BNode> negIslSegs = new List <BNode>(negIsl.Travel());
                BNode        negMaxX;
                Vector2      pos;
                float        t;
                BNode.GetMaxPoint(negIslSegs, out negMaxX, out pos, out t, 0);
                Vector2 control = pos + new Vector2(1.0f, 0.0f);

                // Add all nodes in all positive islands
                int   closest    = -1;
                float closestDst = 0.0f;
                Dictionary <BNode, int> indexFrom  = new Dictionary <BNode, int>();
                List <float>            interCurve = new List <float>();
                List <float>            interLine  = new List <float>();
                List <BNode>            interNode  = new List <BNode>();

                for (int i = 0; i < positiveIslands.Count; ++i)
                {
                    foreach (BNode bn in positiveIslands[i].Travel())
                    {
                        indexFrom.Add(bn, i);
                        int inters = bn.ProjectSegment(pos, control, interCurve, interLine, true);

                        for (int j = 0; j < inters; ++j)
                        {
                            interNode.Add(bn);
                        }
                    }
                }

                const float eps = 0.000001f;
                for (int i = 0; i < interCurve.Count;)
                {
                    if (interCurve[i] < -eps || interCurve[i] >= 1.0f + eps || interLine[i] < 0.0f)
                    {
                        interCurve.RemoveAt(i);
                        interLine.RemoveAt(i);
                        interNode.RemoveAt(i);
                    }
                    else
                    {
                        ++i;
                    }
                }

                // We now have all the value intersections going to the right,
                // and we have their distances (interLine). Time to go through
                // the entries from the nearest to the farthest islands, making
                // sure every island is only visited once.
                HashSet <BNode> usedNodes = new HashSet <BNode>();
                do
                {
                    if (interLine.Count == 0)
                    {
                        break;
                    }

                    int   idx = 0;
                    float dst = interLine[0];

                    for (int i = 0; i < interCurve.Count; ++i)
                    {
                        if (interLine[i] < dst)
                        {
                            idx = i;
                            dst = interLine[i];
                        }
                    }

                    List <BNode>         posIslandSegs = new List <BNode>(interNode[idx].Travel());
                    BNode                posRepl;
                    Boolean.BoundingMode bm = Boolean.Difference(posIslandSegs[0].parent, posIslandSegs, negIslSegs, out posRepl, false);
                    if (bm == Boolean.BoundingMode.Collision)
                    {
                        break;
                    }

                    if (bm == Boolean.BoundingMode.Degenerate)
                    {
                        bm = Boolean.GetLoopBoundingMode(posIslandSegs, negIslSegs);
                    }

                    if (bm == Boolean.BoundingMode.LeftSurroundsRight)
                    {
                        foreach (BNode bn in negMaxX.Travel())
                        {
                            bn.SetParent(interNode[idx].parent);
                        }


                        BNode.MakeBridge(negMaxX, t, interNode[idx], interCurve[idx]);
                    }
                    else if (bm == Boolean.BoundingMode.RightSurroundsLeft)
                    {
                        for (int i = 0; i < interCurve.Count;)
                        {
                            if (usedNodes.Contains(interNode[i]) == true)
                            {
                                interCurve.RemoveAt(i);
                                interLine.RemoveAt(i);
                                interNode.RemoveAt(i);
                            }
                            else
                            {
                                ++i;
                            }
                        }
                        continue;
                    }

                    //positiveIslands.RemoveAt(indexFrom[posIslandSegs[0]]);
                }while (false);
            }

            // The old implementation.
            // It was an attempt to make it generic without any
            // assumptions - but the current version takes advantage
            // of a property of how fonts are authored to be well-formed
            // and shows less edge cases.
            //
            //for(int p = 0; p < posIslands.Count; ++p)
            //{
            //    for(int n = 0; n < negIslands.Count; ++n)
            //    {
            //        List<BNode> posIslSegs = new List<BNode>(posIslands[p].Travel());
            //        List<BNode> negIslSegs = new List<BNode>(negIslands[n].Travel());
            //
            //        Boolean.BoundingMode bm = Boolean.Difference(posIslSegs[0].parent, posIslSegs, negIslSegs, false);
            //        if( bm == Boolean.BoundingMode.Collision)
            //        {
            //            // If there's a collision, the positive loops is modified and items will be clipped.
            //            //
            //            // This solution isn't completely robust because it's possible every node
            //            // of the original island is clipped.
            //            for(int i = 0; i < posIslSegs.Count; ++i)
            //            {
            //                if(posIslSegs[i].next != null && posIslSegs[i].prev != null)
            //                {
            //                    posIslands[p] = posIslSegs[i];
            //                    break;
            //                }
            //            }
            //        }
            //        else if( bm == Boolean.BoundingMode.LeftSurroundsRight)
            //        {
            //            // If the positive fully wraps the negative, bridge it.
            //            Dictionary<BNode, BNode> dmap = BNode.CloneNodes(negIslSegs, false);
            //            List<BNode> negClone = new List<BNode>();
            //            foreach(BNode b in negIslSegs)
            //            {
            //                BNode cl = dmap[b];
            //                cl.SetParent(posIslSegs[0].parent);
            //                negClone.Add(cl);
            //            }
            //
            //            BNode outer;
            //            BNode inner;
            //            float outT;
            //            float inT;
            //            BNode.FindBridge(posIslSegs, negClone, out inner, out outer, out inT, out outT);
            //            if(outer != null)
            //                BNode.MakeBridge(inner, inT, outer, outT);
            //        }
            //    }
            //}
            //
            //// Subtract negative nodes from any positive nodes it affects.
            //
            //// Get rid of negative nodes, we made sure their "cavity-ness"
            //// was applied in a way it will show when we tessellate the
            //// shape.
            //foreach(BNode bn in negIslands)
            //    bn.RemoveIsland(false);

            shape.CleanEmptyLoops();
        }
Пример #11
0
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="shape">The shape to attach to.</param>
 /// <param name="points">The ordered point of the shape.</param>
 public BShapeGenPolyline(BShape shape, params Vector2 [] points)
     : base(shape)
 {
     this.polyPoints = new List <Vector2>(points);
 }