프로젝트

일반

사용자정보

통계
| 브랜치(Branch): | 개정판:

markus / MarkupToPDF / Controls / Common / StringToPathConverter.cs @ 24c5e56c

이력 | 보기 | 이력해설 | 다운로드 (23.7 KB)

1
using System;
2
using System.Net;
3
using System.Windows;
4
using System.Windows.Controls;
5
using System.Windows.Documents;
6
using System.Windows.Ink;
7
using System.Windows.Input;
8
using System.Windows.Media;
9
using System.Windows.Media.Animation;
10
using System.Windows.Shapes;
11
using System.Windows.Data;
12
using System.Globalization;
13

    
14
namespace MarkupToPDF.Controls.Common
15
{
16
    public class StringToPathConverter : IValueConverter, IDisposable
17
    {
18
        #region Const & Private Variables
19
        const bool AllowSign = true;
20
        const bool AllowComma = true;
21
        const bool IsFilled = true;
22
        const bool IsClosed = true;
23

    
24
        IFormatProvider _formatProvider;
25

    
26
        PathFigure _figure = null;     // Figure object, which will accept parsed segments
27
        string _pathString;        // Input string to be parsed
28
        int _pathLength;
29
        int _curIndex;          // Location to read next character from
30
        bool _figureStarted;     // StartFigure is effective 
31

    
32
        Point _lastStart;         // Last figure starting point
33
        Point _lastPoint;         // Last point 
34
        Point _secondLastPoint;   // The point before last point
35

    
36
        char _token;             // Non whitespace character returned by ReadToken
37
        #endregion
38

    
39
        #region Public Functionality
40
        /// <summary>
41
        /// Main conversion routine - converts string path data definition to PathGeometry object
42
        /// </summary>
43
        /// <param name="path">String with path data definition</param>
44
        /// <returns>PathGeometry object created from string definition</returns>
45
        public PathGeometry Convert(string path)
46
        {
47
            if (null == path)
48
                throw new ArgumentException("Path string cannot be null!");
49

    
50
            if (path.Length == 0)
51
                throw new ArgumentException("Path string cannot be empty!");
52

    
53
            return parse(path);
54
        }
55

    
56
        /// <summary>
57
        /// Main back conversion routine - converts PathGeometry object to its string equivalent
58
        /// </summary>
59
        /// <param name="geometry">Path Geometry object</param>
60
        /// <returns>String equivalent to PathGeometry contents</returns>
61
        public string ConvertBack(PathGeometry geometry)
62
        {
63
            if (null == geometry)
64
                throw new ArgumentException("Path Geometry cannot be null!");
65

    
66
            return parseBack(geometry);
67
        }
68
        #endregion
69

    
70
        #region Private Functionality
71
        /// <summary>
72
        /// Main parser routine, which loops over each char in received string, and performs actions according to command/parameter being passed
73
        /// </summary>
74
        /// <param name="path">String with path data definition</param>
75
        /// <returns>PathGeometry object created from string definition</returns>
76
        private PathGeometry parse(string path)
77
        {
78
            PathGeometry _pathGeometry = null;
79

    
80

    
81
            _formatProvider = CultureInfo.InvariantCulture;
82
            _pathString = path;
83
            _pathLength = path.Length;
84
            _curIndex = 0;
85

    
86
            _secondLastPoint = new Point(0, 0);
87
            _lastPoint = new Point(0, 0);
88
            _lastStart = new Point(0, 0);
89

    
90
            _figureStarted = false;
91

    
92
            bool first = true;
93

    
94
            char last_cmd = ' ';
95

    
96
            while (ReadToken()) // Empty path is allowed in XAML
97
            {
98
                char cmd = _token;
99

    
100
                if (first)
101
                {
102
                    if ((cmd != 'M') && (cmd != 'm'))  // Path starts with M|m 
103
                    {
104
                        ThrowBadToken();
105
                    }
106

    
107
                    first = false;
108
                }
109

    
110
                switch (cmd)
111
                {
112
                    case 'm':
113
                    case 'M':
114
                        // XAML allows multiple points after M/m
115
                        _lastPoint = ReadPoint(cmd, !AllowComma);
116

    
117
                        _figure = new PathFigure();
118
                        _figure.StartPoint = _lastPoint;
119
                        _figure.IsFilled = IsFilled;
120
                        _figure.IsClosed = !IsClosed;
121
                        //context.BeginFigure(_lastPoint, IsFilled, !IsClosed);
122
                        _figureStarted = true;
123
                        _lastStart = _lastPoint;
124
                        last_cmd = 'M';
125

    
126
                        while (IsNumber(AllowComma))
127
                        {
128
                            _lastPoint = ReadPoint(cmd, !AllowComma);
129

    
130
                            LineSegment _lineSegment = new LineSegment();
131
                            _lineSegment.Point = _lastPoint;
132
                            _figure.Segments.Add(_lineSegment);
133
                            //context.LineTo(_lastPoint, IsStroked, !IsSmoothJoin);
134
                            last_cmd = 'L';
135
                        }
136
                        break;
137

    
138
                    case 'l':
139
                    case 'L':
140
                    case 'h':
141
                    case 'H':
142
                    case 'v':
143
                    case 'V':
144
                        EnsureFigure();
145

    
146
                        do
147
                        {
148
                            switch (cmd)
149
                            {
150
                                case 'l': _lastPoint = ReadPoint(cmd, !AllowComma); break;
151
                                case 'L': _lastPoint = ReadPoint(cmd, !AllowComma); break;
152
                                case 'h': _lastPoint.X += ReadNumber(!AllowComma); break;
153
                                case 'H': _lastPoint.X = ReadNumber(!AllowComma); break;
154
                                case 'v': _lastPoint.Y += ReadNumber(!AllowComma); break;
155
                                case 'V': _lastPoint.Y = ReadNumber(!AllowComma); break;
156
                            }
157

    
158
                            LineSegment _lineSegment = new LineSegment();
159
                            _lineSegment.Point = _lastPoint;
160
                            _figure.Segments.Add(_lineSegment);
161
                            //context.LineTo(_lastPoint, IsStroked, !IsSmoothJoin);
162
                        }
163
                        while (IsNumber(AllowComma));
164

    
165
                        last_cmd = 'L';
166
                        break;
167

    
168
                    case 'c':
169
                    case 'C': // cubic Bezier 
170
                    case 's':
171
                    case 'S': // smooth cublic Bezier
172
                        EnsureFigure();
173

    
174
                        do
175
                        {
176
                            Point p;
177

    
178
                            if ((cmd == 's') || (cmd == 'S'))
179
                            {
180
                                if (last_cmd == 'C')
181
                                {
182
                                    p = Reflect();
183
                                }
184
                                else
185
                                {
186
                                    p = _lastPoint;
187
                                }
188

    
189
                                _secondLastPoint = ReadPoint(cmd, !AllowComma);
190
                            }
191
                            else
192
                            {
193
                                p = ReadPoint(cmd, !AllowComma);
194

    
195
                                _secondLastPoint = ReadPoint(cmd, AllowComma);
196
                            }
197

    
198
                            _lastPoint = ReadPoint(cmd, AllowComma);
199

    
200
                            BezierSegment _bizierSegment = new BezierSegment();
201
                            _bizierSegment.Point1 = p;
202
                            _bizierSegment.Point2 = _secondLastPoint;
203
                            _bizierSegment.Point3 = _lastPoint;
204
                            _figure.Segments.Add(_bizierSegment);
205
                            //context.BezierTo(p, _secondLastPoint, _lastPoint, IsStroked, !IsSmoothJoin);
206

    
207
                            last_cmd = 'C';
208
                        }
209
                        while (IsNumber(AllowComma));
210

    
211
                        break;
212

    
213
                    case 'q':
214
                    case 'Q': // quadratic Bezier 
215
                    case 't':
216
                    case 'T': // smooth quadratic Bezier
217
                        EnsureFigure();
218

    
219
                        do
220
                        {
221
                            if ((cmd == 't') || (cmd == 'T'))
222
                            {
223
                                if (last_cmd == 'Q')
224
                                {
225
                                    _secondLastPoint = Reflect();
226
                                }
227
                                else
228
                                {
229
                                    _secondLastPoint = _lastPoint;
230
                                }
231

    
232
                                _lastPoint = ReadPoint(cmd, !AllowComma);
233
                            }
234
                            else
235
                            {
236
                                _secondLastPoint = ReadPoint(cmd, !AllowComma);
237
                                _lastPoint = ReadPoint(cmd, AllowComma);
238
                            }
239

    
240
                            QuadraticBezierSegment _quadraticBezierSegment = new QuadraticBezierSegment();
241
                            _quadraticBezierSegment.Point1 = _secondLastPoint;
242
                            _quadraticBezierSegment.Point2 = _lastPoint;
243
                            _figure.Segments.Add(_quadraticBezierSegment);
244
                            //context.QuadraticBezierTo(_secondLastPoint, _lastPoint, IsStroked, !IsSmoothJoin);
245

    
246
                            last_cmd = 'Q';
247
                        }
248
                        while (IsNumber(AllowComma));
249

    
250
                        break;
251

    
252
                    case 'a':
253
                    case 'A':
254
                        EnsureFigure();
255

    
256
                        do
257
                        {
258
                            // A 3,4 5, 0, 0, 6,7
259
                            double w = ReadNumber(!AllowComma);
260
                            double h = ReadNumber(AllowComma);
261
                            double rotation = ReadNumber(AllowComma);
262
                            bool large = ReadBool();
263
                            bool sweep = ReadBool();
264

    
265
                            _lastPoint = ReadPoint(cmd, AllowComma);
266

    
267
                            ArcSegment _arcSegment = new ArcSegment();
268
                            _arcSegment.Point = _lastPoint;
269
                            _arcSegment.Size = new Size(w, h);
270
                            _arcSegment.RotationAngle = rotation;
271
                            _arcSegment.IsLargeArc = large;
272
                            _arcSegment.SweepDirection = sweep ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;
273
                            _figure.Segments.Add(_arcSegment);
274
                            //context.ArcTo(
275
                            //    _lastPoint,
276
                            //    new Size(w, h),
277
                            //    rotation,
278
                            //    large,
279
                            //    sweep ? SweepDirection.Clockwise : SweepDirection.Counterclockwise,
280
                            //    IsStroked,
281
                            //    !IsSmoothJoin
282
                            //    );
283
                        }
284
                        while (IsNumber(AllowComma));
285

    
286
                        last_cmd = 'A';
287
                        break;
288

    
289
                    case 'z':
290
                    case 'Z':
291
                        EnsureFigure();
292
                        _figure.IsClosed = IsClosed;
293
                        //context.SetClosedState(IsClosed);
294

    
295
                        _figureStarted = false;
296
                        last_cmd = 'Z';
297

    
298
                        _lastPoint = _lastStart; // Set reference point to be first point of current figure
299
                        break;
300

    
301
                    default:
302
                        ThrowBadToken();
303
                        break;
304
                }
305
            }
306

    
307
            if (null != _figure)
308
            {
309
                _pathGeometry = new PathGeometry();
310
                _pathGeometry.Figures.Add(_figure);
311

    
312
            }
313
            return _pathGeometry;
314
        }
315

    
316
        void SkipDigits(bool signAllowed)
317
        {
318
            // Allow for a sign 
319
            if (signAllowed && More() && ((_pathString[_curIndex] == '-') || _pathString[_curIndex] == '+'))
320
            {
321
                _curIndex++;
322
            }
323

    
324
            while (More() && (_pathString[_curIndex] >= '0') && (_pathString[_curIndex] <= '9'))
325
            {
326
                _curIndex++;
327
            }
328
        }
329

    
330
        bool ReadBool()
331
        {
332
            SkipWhiteSpace(AllowComma);
333

    
334
            if (More())
335
            {
336
                _token = _pathString[_curIndex++];
337

    
338
                if (_token == '0')
339
                {
340
                    return false;
341
                }
342
                else if (_token == '1')
343
                {
344
                    return true;
345
                }
346
            }
347

    
348
            ThrowBadToken();
349

    
350
            return false;
351
        }
352

    
353
        private Point Reflect()
354
        {
355
            return new Point(2 * _lastPoint.X - _secondLastPoint.X,
356
                             2 * _lastPoint.Y - _secondLastPoint.Y);
357
        }
358

    
359
        private void EnsureFigure()
360
        {
361
            if (!_figureStarted)
362
            {
363
                _figure = new PathFigure();
364
                _figure.StartPoint = _lastStart;
365

    
366
                //_context.BeginFigure(_lastStart, IsFilled, !IsClosed);
367
                _figureStarted = true;
368
            }
369
        }
370

    
371
        double ReadNumber(bool allowComma)
372
        {
373
            if (!IsNumber(allowComma))
374
            {
375
                ThrowBadToken();
376
            }
377

    
378
            bool simple = true;
379
            int start = _curIndex;
380

    
381
            //
382
            // Allow for a sign
383
            //
384
            // There are numbers that cannot be preceded with a sign, for instance, -NaN, but it's 
385
            // fine to ignore that at this point, since the CLR parser will catch this later.
386
            // 
387
            if (More() && ((_pathString[_curIndex] == '-') || _pathString[_curIndex] == '+'))
388
            {
389
                _curIndex++;
390
            }
391

    
392
            // Check for Infinity (or -Infinity).
393
            if (More() && (_pathString[_curIndex] == 'I'))
394
            {
395
                // 
396
                // Don't bother reading the characters, as the CLR parser will 
397
                // do this for us later.
398
                // 
399
                _curIndex = Math.Min(_curIndex + 8, _pathLength); // "Infinity" has 8 characters
400
                simple = false;
401
            }
402
            // Check for NaN 
403
            else if (More() && (_pathString[_curIndex] == 'N'))
404
            {
405
                // 
406
                // Don't bother reading the characters, as the CLR parser will
407
                // do this for us later. 
408
                //
409
                _curIndex = Math.Min(_curIndex + 3, _pathLength); // "NaN" has 3 characters
410
                simple = false;
411
            }
412
            else
413
            {
414
                SkipDigits(!AllowSign);
415

    
416
                // Optional period, followed by more digits 
417
                if (More() && (_pathString[_curIndex] == '.'))
418
                {
419
                    simple = false;
420
                    _curIndex++;
421
                    SkipDigits(!AllowSign);
422
                }
423

    
424
                // Exponent
425
                if (More() && ((_pathString[_curIndex] == 'E') || (_pathString[_curIndex] == 'e')))
426
                {
427
                    simple = false;
428
                    _curIndex++;
429
                    SkipDigits(AllowSign);
430
                }
431
            }
432

    
433
            if (simple && (_curIndex <= (start + 8))) // 32-bit integer
434
            {
435
                int sign = 1;
436

    
437
                if (_pathString[start] == '+')
438
                {
439
                    start++;
440
                }
441
                else if (_pathString[start] == '-')
442
                {
443
                    start++;
444
                    sign = -1;
445
                }
446

    
447
                int value = 0;
448

    
449
                while (start < _curIndex)
450
                {
451
                    value = value * 10 + (_pathString[start] - '0');
452
                    start++;
453
                }
454

    
455
                return value * sign;
456
            }
457
            else
458
            {
459
                string subString = _pathString.Substring(start, _curIndex - start);
460

    
461
                try
462
                {
463
                    return System.Convert.ToDouble(subString, _formatProvider);
464
                }
465
                catch (FormatException except)
466
                {
467
                    throw new FormatException(string.Format("Unexpected character in path '{0}' at position {1}", _pathString, _curIndex - 1), except);
468
                }
469
            }
470
        }
471

    
472
        private bool IsNumber(bool allowComma)
473
        {
474
            bool commaMet = SkipWhiteSpace(allowComma);
475

    
476
            if (More())
477
            {
478
                _token = _pathString[_curIndex];
479

    
480
                // Valid start of a number
481
                if ((_token == '.') || (_token == '-') || (_token == '+') || ((_token >= '0') && (_token <= '9'))
482
                    || (_token == 'I')  // Infinity
483
                    || (_token == 'N')) // NaN 
484
                {
485
                    return true;
486
                }
487
            }
488

    
489
            if (commaMet) // Only allowed between numbers
490
            {
491
                ThrowBadToken();
492
            }
493

    
494
            return false;
495
        }
496

    
497
        private Point ReadPoint(char cmd, bool allowcomma)
498
        {
499
            double x = ReadNumber(allowcomma);
500
            double y = ReadNumber(AllowComma);
501

    
502
            if (cmd >= 'a') // 'A' < 'a'. lower case for relative
503
            {
504
                x += _lastPoint.X;
505
                y += _lastPoint.Y;
506
            }
507

    
508
            return new Point(x, y);
509
        }
510

    
511
        private bool ReadToken()
512
        {
513
            SkipWhiteSpace(!AllowComma);
514

    
515
            // Check for end of string 
516
            if (More())
517
            {
518
                _token = _pathString[_curIndex++];
519

    
520
                return true;
521
            }
522
            else
523
            {
524
                return false;
525
            }
526
        }
527

    
528
        bool More()
529
        {
530
            return _curIndex < _pathLength;
531
        }
532

    
533
        // Skip white space, one comma if allowed
534
        private bool SkipWhiteSpace(bool allowComma)
535
        {
536
            bool commaMet = false;
537

    
538
            while (More())
539
            {
540
                char ch = _pathString[_curIndex];
541

    
542
                switch (ch)
543
                {
544
                    case ' ':
545
                    case '\n':
546
                    case '\r':
547
                    case '\t': // SVG whitespace 
548
                        break;
549
                    case ';':
550
                    case ',':
551
                        if (allowComma)
552
                        {
553
                            commaMet = true;
554
                            allowComma = false; // one comma only
555
                        }
556
                        else
557
                        {
558
                            ThrowBadToken();
559
                        }
560
                        break;
561

    
562
                    default:
563
                        // Avoid calling IsWhiteSpace for ch in (' ' .. 'z']
564
                        if (((ch > ' ') && (ch <= 'z')) || !Char.IsWhiteSpace(ch))
565
                        {
566
                            return commaMet;
567
                        }
568
                        break;
569
                }
570

    
571
                _curIndex++;
572
            }
573

    
574
            return commaMet;
575
        }
576

    
577
        private void ThrowBadToken()
578
        {
579
            throw new FormatException(string.Format("Unexpected character in path '{0}' at position {1}", _pathString, _curIndex - 1));
580
        }
581

    
582
        static internal char GetNumericListSeparator(IFormatProvider provider)
583
        {
584
            char numericSeparator = ',';
585

    
586
            // Get the NumberFormatInfo out of the provider, if possible
587
            // If the IFormatProvider doesn't not contain a NumberFormatInfo, then 
588
            // this method returns the current culture's NumberFormatInfo. 
589
            NumberFormatInfo numberFormat = NumberFormatInfo.GetInstance(provider);
590

    
591
            // Is the decimal separator is the same as the list separator?
592
            // If so, we use the ";". 
593
            if ((numberFormat.NumberDecimalSeparator.Length > 0) && (numericSeparator == numberFormat.NumberDecimalSeparator[0]))
594
            {
595
                numericSeparator = ';';
596
            }
597

    
598
            return numericSeparator;
599
        }
600

    
601
        private string parseBack(PathGeometry geometry)
602
        {
603
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
604
            IFormatProvider provider = new System.Globalization.CultureInfo("en-us");
605
            string format = null;
606

    
607
            foreach (PathFigure figure in geometry.Figures)
608
            {
609
                sb.Append("M " + ((IFormattable)figure.StartPoint).ToString(format, provider) + " ");
610

    
611
                foreach (PathSegment segment in figure.Segments)
612
                {
613
                    char separator = GetNumericListSeparator(provider);
614

    
615
                    if (segment.GetType() == typeof(LineSegment))
616
                    {
617
                        LineSegment _lineSegment = segment as LineSegment;
618

    
619
                        sb.Append("L " + ((IFormattable)_lineSegment.Point).ToString(format, provider) + " ");
620
                    }
621
                    else if (segment.GetType() == typeof(BezierSegment))
622
                    {
623
                        BezierSegment _bezierSegment = segment as BezierSegment;
624

    
625
                        sb.Append(String.Format(provider,
626
                             "C{1:" + format + "}{0}{2:" + format + "}{0}{3:" + format + "} ",
627
                             separator,
628
                             _bezierSegment.Point1,
629
                             _bezierSegment.Point2,
630
                             _bezierSegment.Point3
631
                             ));
632
                    }
633
                    else if (segment.GetType() == typeof(QuadraticBezierSegment))
634
                    {
635
                        QuadraticBezierSegment _quadraticBezierSegment = segment as QuadraticBezierSegment;
636

    
637
                        sb.Append(String.Format(provider,
638
                             "Q{1:" + format + "}{0}{2:" + format + "} ",
639
                             separator,
640
                             _quadraticBezierSegment.Point1,
641
                             _quadraticBezierSegment.Point2));
642
                    }
643
                    else if (segment.GetType() == typeof(ArcSegment))
644
                    {
645
                        ArcSegment _arcSegment = segment as ArcSegment;
646

    
647
                        sb.Append(String.Format(provider,
648
                             "A{1:" + format + "}{0}{2:" + format + "}{0}{3}{0}{4}{0}{5:" + format + "} ",
649
                             separator,
650
                             _arcSegment.Size,
651
                             _arcSegment.RotationAngle,
652
                             _arcSegment.IsLargeArc ? "1" : "0",
653
                             _arcSegment.SweepDirection == SweepDirection.Clockwise ? "1" : "0",
654
                             _arcSegment.Point));
655
                    }
656
                }
657

    
658
                if (figure.IsClosed)
659
                    sb.Append("Z");
660
            }
661

    
662
            return sb.ToString();
663
        }
664
        #endregion
665

    
666
        #region IValueConverter Members
667

    
668
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
669
        {
670
            string path = value as string;
671
            if (null != path)
672
                return Convert(path);
673
            else
674
                return null;
675
        }
676

    
677
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
678
        {
679
            PathGeometry geometry = value as PathGeometry;
680

    
681
            if (null != geometry)
682
                return ConvertBack(geometry);
683
            else
684
                return default(string);
685
        }
686

    
687
        #endregion
688

    
689
        public void Dispose()
690
        {
691
            //GC.Collect();
692
            ////GC.SuppressFinalize(this);            
693
        }
694
    }
695
}
클립보드 이미지 추가 (최대 크기: 500 MB)