SvgMergedShapes.cs
//
// This code is part of GrapeCity Documents for Imaging samples.
// Copyright (c) GrapeCity, Inc. All rights reserved.
//
using System;
using System.IO;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using GrapeCity.Documents.Drawing;
using GrapeCity.Documents.Text;
using GrapeCity.Documents.Imaging;
using GrapeCity.Documents.Svg;
using GcImagingWeb.Samples.Common;
using System.Xml;

namespace GcImagingWeb.Samples
{
    // This example shows how to combine SVG elements to create clipping paths
    // that are used to effectively merge geometric figures.
    // Parts of the image are associated with SVG title elements which show
    // as tooltips if the SVG is displayed in a browser.
    public class SvgMergedShapes
    {
        public string DefaultMime { get => Common.Util.MimeTypes.SVG; }

        public Stream GenerateImageStream(string targetMime, Size pixelSize, float dpi, bool opaque, string[] sampleParams = null)
        {
            if (targetMime != Common.Util.MimeTypes.SVG)
                throw new Exception("This sample only supports SVG output format.");

            using var svgDoc = new GcSvgDocument();
            var svg = svgDoc.RootSvg;
            var items = svg.Children;

            svg.Width = new SvgLength(pixelSize.Width);
            svg.Height = new SvgLength(pixelSize.Height);

            // Root title:
            var title = new SvgTitleElement();
            items.Add(title);
            title.Children.Add(new SvgContentElement(XmlNodeType.Text)
            {
                Content = "Root SVG Title"
            });
            // Root bounds:
            var bounds = new SvgRectElement()
            {
                X = new SvgLength(0),
                Y = new SvgLength(0),
                Width = new SvgLength(100, SvgLengthUnits.Percentage),
                Height = new SvgLength(100, SvgLengthUnits.Percentage),
                Stroke = new SvgPaint(new SvgColor(Color.DarkBlue)),
                Fill = new SvgPaint(new SvgColor(Color.DarkSeaGreen)),
            };
            items.Add(bounds);

            // Geometry:
            float
                centerX = 200,
                centerY = 200,
                radius = 190,
                polyWidth = 180,
                polyHeight = 320,
                polyD = 30,
                pad = 10;

            // We add an outer group to hold everything else,
            // so that it can be easily positioned within the overall bounds:
            var outerGroup = new SvgGroupElement();
            items.Add(outerGroup);
            items = outerGroup.Children;
            // Center the group:
            outerGroup.Transform = new List<SvgTransform>()
            {
                new SvgTranslateTransform() { TranslateX = new SvgLength(pixelSize.Width / 2 - centerX), TranslateY = new SvgLength(pixelSize.Height / 2 - centerY - polyHeight / 2) },
            };

            // Add a 'defs' element containing clipPath elements
            // that will be used to merge various geometric figures
            // to provide a complex clipping path:
            var defs = new SvgDefsElement();
            items.Add(defs);

            // Create the outer clip built by adding a circle and a polygon
            // resembling an inverted keystone, so that the union of the two figures
            // looks like a keyhole:
            var clipPathOuter = new SvgClipPathElement() { ID = "outerClip" };
            defs.Children.Add(clipPathOuter);
            clipPathOuter.Children.Add(new SvgCircleElement()
            {
                CenterX = new SvgLength(centerX),
                CenterY = new SvgLength(centerY),
                Radius = new SvgLength(radius)
            });
            clipPathOuter.Children.Add(new SvgPolygonElement()
            {
                Points = new List<SvgPoint>()
                {
                    new SvgPoint(new SvgLength(centerX - polyWidth / 2), new SvgLength(centerY + radius * 0.8f)),
                    new SvgPoint(new SvgLength(centerX - polyWidth / 2 - polyD), new SvgLength(centerY + radius + polyHeight)),
                    new SvgPoint(new SvgLength(centerX + polyWidth / 2 + polyD), new SvgLength(centerY + radius + polyHeight)),
                    new SvgPoint(new SvgLength(centerX + polyWidth / 2), new SvgLength(centerY + radius * 0.8f)),
                }
            });

            // Create the inner clip by adding a smaller circle and a rectangle
            // that is smaller than the keystone polygon:
            var clipPathInner = new SvgClipPathElement() { ID = "innerClip" };
            defs.Children.Add(clipPathInner);
            clipPathInner.Children.Add(new SvgCircleElement()
            {
                CenterX = new SvgLength(centerX),
                CenterY = new SvgLength(centerY),
                Radius = new SvgLength(radius - pad)
            });
            clipPathInner.Children.Add(new SvgRectElement()
            {
                X = new SvgLength(centerX - polyWidth / 2 + pad),
                Y = new SvgLength(centerY + radius * 0.8f),
                Width = new SvgLength(polyWidth - pad * 2),
                Height = new SvgLength(polyHeight - pad * 2)
            });

            // A filled rectangle taking up the whole SVG area (100%x100%)
            // but clipped by the "outerClip" figure:
            var rcOuter = new SvgRectElement()
            {
                Width = SvgLength.Percent100,
                Height = SvgLength.Percent100,
                Fill = new SvgPaint(Color.DarkBlue),
                ClipPath = new SvgReference("outerClip")
            };
            items.Add(rcOuter);
            var descR = new SvgTitleElement();
            descR.Children.Add(new SvgContentElement(XmlNodeType.Text)
            {
                Content = "Title of the dark blue rectangle"
            });
            rcOuter.Children.Add(descR);

            // Group clipped by the "innerClip", this will contain
            // a yellow fill and a rotated semi-transparent raster image:
            var group = new SvgGroupElement()
            {
                ClipPath = new SvgReference("innerClip")
            };
            items.Add(group);

            // We add to the group clipped by "innerClip"
            // a filled rectangle taking up the whole area (100%x100%):
            var rcInner = new SvgRectElement()
            {
                Width = SvgLength.Percent100,
                Height = SvgLength.Percent100,
                Fill = new SvgPaint(Color.DarkOrange),
            };
            group.Children.Add(rcInner);

            var desc = new SvgTitleElement();
            rcInner.Children.Add(desc);
            desc.Children.Add(new SvgContentElement(XmlNodeType.Text)
            {
                Content = "Title of the dark orange rectangle"
            });

            // We also add to the group an image that is rotated
            // and drawn semi-transparently:
            var imagePath = Path.Combine("Resources", "Images", "colosseum.jpg");
            var bmp = new GcBitmap(imagePath);

            var img = new SvgImageElement()
            {
                Href = new SvgReference(bmp, true) { InJpegFormat = true },
                Width = new SvgLength(532),
                Height = new SvgLength(800),
                Transform = new List<SvgTransform>()
                {
                    new SvgTranslateTransform() { TranslateX = new SvgLength(320), TranslateY = new SvgLength(-330) },
                    new SvgRotateTransform() { Angle = new SvgAngle(30) }
                },
                Opacity = 0.8f,
            };
            group.Children.Add(img);

            var descImg = new SvgTitleElement();
            img.Children.Add(descImg);
            descImg.Children.Add(new SvgContentElement(XmlNodeType.Text)
            {
                Content = "Title of the image"
            });

            // Save SVG to the output Stream
            var ms = new MemoryStream();
            svgDoc.Save(ms, new XmlWriterSettings() { Indent = true });
            ms.Seek(0, SeekOrigin.Begin);
            return ms;
        }
    }
}