1#!/usr/bin/env perl -w
2#**************************************************************
3#
4#  Licensed to the Apache Software Foundation (ASF) under one
5#  or more contributor license agreements.  See the NOTICE file
6#  distributed with this work for additional information
7#  regarding copyright ownership.  The ASF licenses this file
8#  to you under the Apache License, Version 2.0 (the
9#  "License"); you may not use this file except in compliance
10#  with the License.  You may obtain a copy of the License at
11#
12#    http://www.apache.org/licenses/LICENSE-2.0
13#
14#  Unless required by applicable law or agreed to in writing,
15#  software distributed under the License is distributed on an
16#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17#  KIND, either express or implied.  See the License for the
18#  specific language governing permissions and limitations
19#  under the License.
20#
21#**************************************************************
22
23
24
25use warnings;
26
27sub usage() {
28    print STDERR <<EOF;
29Usage: preset-definitions-to-shape-types.pl <shapes> <text>
30
31Converts presetShapeDefinitions.xml and presetTextWarpDefinitions.xml to a
32.cxx that contains VML with the definitions of the shapes.  The result is
33written to stdout.
34
35<shapes> presetShapeDefinitions.xml (including the path to it)
36<text>   presetTextWarpDefinitions.xml (including the path to it)
37EOF
38    exit 1;
39}
40
41sub show_call_stack
42{
43    my ( $path, $line, $subr );
44    my $max_depth = 30;
45    my $i = 1;
46    print STDERR "--- Begin stack trace ---\n";
47    while ( (my @call_details = (caller($i++))) && ($i<$max_depth) ) {
48        print STDERR "$call_details[1] line $call_details[2] in function $call_details[3]\n";
49    }
50    print STDERR "--- End stack trace ---\n";
51}
52
53$src_shapes = shift;
54$src_text = shift;
55
56usage() if ( !defined( $src_shapes ) || !defined( $src_text ) ||
57             $src_shapes eq "-h" || $src_shapes eq "--help" ||
58             !-f $src_shapes || !-f $src_text );
59
60# Global variables
61@levels = ();
62$shape_name = "";
63$state = "";
64$path = "";
65$adjust = "";
66$max_adj_no = 0;
67@formulas = ();
68%variables = ();
69$ignore_this_shape = 0;
70$handles = "";
71$textboxrect = "";
72$last_pos_x = "";
73$last_pos_y = "";
74$no_stroke = 0;
75$no_fill = 0;
76$path_w = 1;
77$path_h = 1;
78@quadratic_bezier = ();
79
80%result_shapes = ();
81
82%shapes_ids = (
83    0 => 'notPrimitive',
84    1 => 'rectangle',
85    2 => 'roundRectangle',
86    3 => 'ellipse',
87    4 => 'diamond',
88    5 => 'triangle',
89    6 => 'rtTriangle',
90    7 => 'parallelogram',
91    8 => 'trapezoid',
92    9 => 'hexagon',
93    10 => 'octagon',
94    11 => 'plus',
95    12 => 'star5',
96    13 => 'rightArrow',
97    14 => 'thickArrow', # should not be used
98    15 => 'homePlate',
99    16 => 'cube',
100    17 => 'wedgeRoundRectCallout', # balloon
101    18 => 'star16', # seal
102    19 => 'arc',
103    20 => 'line',
104    21 => 'plaque',
105    22 => 'can',
106    23 => 'donut',
107    24 => 'textPlain', # textSimple - FIXME MS Office 2007 converts these to textboxes with unstyled text, so is it actually correct to map it to a real style?
108    25 => 'textStop', # textOctagon FIXME see 24
109    26 => 'textTriangle', # textHexagon FIXMME see 24
110    27 => 'textCanDown', # textCurve FIXMME see 24
111    28 => 'textWave1', # textWave FIXMME see 24
112    29 => 'textArchUpPour', # textRing FIXMME see 24
113    30 => 'textCanDown', # textOnCurve FIXMME see 24
114    31 => 'textArchUp', # textOnRing FIXMME see 24
115    32 => 'straightConnector1',
116    33 => 'bentConnector2',
117    34 => 'bentConnector3',
118    35 => 'bentConnector4',
119    36 => 'bentConnector5',
120    37 => 'curvedConnector2',
121    38 => 'curvedConnector3',
122    39 => 'curvedConnector4',
123    40 => 'curvedConnector5',
124    41 => 'callout1',
125    42 => 'callout2',
126    43 => 'callout3',
127    44 => 'accentCallout1',
128    45 => 'accentCallout2',
129    46 => 'accentCallout3',
130    47 => 'borderCallout1',
131    48 => 'borderCallout2',
132    49 => 'borderCallout3',
133    50 => 'accentBorderCallout1',
134    51 => 'accentBorderCallout2',
135    52 => 'accentBorderCallout3',
136    53 => 'ribbon',
137    54 => 'ribbon2',
138    55 => 'chevron',
139    56 => 'pentagon',
140    57 => 'noSmoking',
141    58 => 'star8', # seal8
142    59 => 'star16', # seal16
143    60 => 'star32', # seal32
144    61 => 'wedgeRectCallout',
145    62 => 'wedgeRoundRectCallout', # wedgeRRectCallout
146    63 => 'wedgeEllipseCallout',
147    64 => 'wave',
148    65 => 'foldedCorner',
149    66 => 'leftArrow',
150    67 => 'downArrow',
151    68 => 'upArrow',
152    69 => 'leftRightArrow',
153    70 => 'upDownArrow',
154    71 => 'irregularSeal1',
155    72 => 'irregularSeal2',
156    73 => 'lightningBolt',
157    74 => 'heart',
158    75 => 'frame', # pictureFrame
159    76 => 'quadArrow',
160    77 => 'leftArrowCallout',
161    78 => 'rightArrowCallout',
162    79 => 'upArrowCallout',
163    80 => 'downArrowCallout',
164    81 => 'leftRightArrowCallout',
165    82 => 'upDownArrowCallout',
166    83 => 'quadArrowCallout',
167    84 => 'bevel',
168    85 => 'leftBracket',
169    86 => 'rightBracket',
170    87 => 'leftBrace',
171    88 => 'rightBrace',
172    89 => 'leftUpArrow',
173    90 => 'bentUpArrow',
174    91 => 'bentArrow',
175    92 => 'star24', # seal24
176    93 => 'stripedRightArrow',
177    94 => 'notchedRightArrow',
178    95 => 'blockArc',
179    96 => 'smileyFace',
180    97 => 'verticalScroll',
181    98 => 'horizontalScroll',
182    99 => 'circularArrow',
183    100 => 'notchedCircularArrow', # should not be used
184    101 => 'uturnArrow',
185    102 => 'curvedRightArrow',
186    103 => 'curvedLeftArrow',
187    104 => 'curvedUpArrow',
188    105 => 'curvedDownArrow',
189    106 => 'cloudCallout',
190    107 => 'ellipseRibbon',
191    108 => 'ellipseRibbon2',
192    109 => 'flowChartProcess',
193    110 => 'flowChartDecision',
194    111 => 'flowChartInputOutput',
195    112 => 'flowChartPredefinedProcess',
196    113 => 'flowChartInternalStorage',
197    114 => 'flowChartDocument',
198    115 => 'flowChartMultidocument',
199    116 => 'flowChartTerminator',
200    117 => 'flowChartPreparation',
201    118 => 'flowChartManualInput',
202    119 => 'flowChartManualOperation',
203    120 => 'flowChartConnector',
204    121 => 'flowChartPunchedCard',
205    122 => 'flowChartPunchedTape',
206    123 => 'flowChartSummingJunction',
207    124 => 'flowChartOr',
208    125 => 'flowChartCollate',
209    126 => 'flowChartSort',
210    127 => 'flowChartExtract',
211    128 => 'flowChartMerge',
212    129 => 'flowChartOfflineStorage',
213    130 => 'flowChartOnlineStorage',
214    131 => 'flowChartMagneticTape',
215    132 => 'flowChartMagneticDisk',
216    133 => 'flowChartMagneticDrum',
217    134 => 'flowChartDisplay',
218    135 => 'flowChartDelay',
219    136 => 'textPlain', # textPlainText
220    137 => 'textStop',
221    138 => 'textTriangle',
222    139 => 'textTriangleInverted',
223    140 => 'textChevron',
224    141 => 'textChevronInverted',
225    142 => 'textRingInside',
226    143 => 'textRingOutside',
227    144 => 'textArchUp', # textArchUpCurve
228    145 => 'textArchDown', # textArchDownCurve
229    146 => 'textCircle', # textCircleCurve
230    147 => 'textButton', # textButtonCurve
231    148 => 'textArchUpPour',
232    149 => 'textArchDownPour',
233    150 => 'textCirclePour',
234    151 => 'textButtonPour',
235    152 => 'textCurveUp',
236    153 => 'textCurveDown',
237    154 => 'textCascadeUp',
238    155 => 'textCascadeDown',
239    156 => 'textWave1',
240    157 => 'textWave2',
241    158 => 'textWave3',
242    159 => 'textWave4',
243    160 => 'textInflate',
244    161 => 'textDeflate',
245    162 => 'textInflateBottom',
246    163 => 'textDeflateBottom',
247    164 => 'textInflateTop',
248    165 => 'textDeflateTop',
249    166 => 'textDeflateInflate',
250    167 => 'textDeflateInflateDeflate',
251    168 => 'textFadeRight',
252    169 => 'textFadeLeft',
253    170 => 'textFadeUp',
254    171 => 'textFadeDown',
255    172 => 'textSlantUp',
256    173 => 'textSlantDown',
257    174 => 'textCanUp',
258    175 => 'textCanDown',
259    176 => 'flowChartAlternateProcess',
260    177 => 'flowChartOffpageConnector',
261    178 => 'callout1', # callout90
262    179 => 'accentCallout1', # accentCallout90
263    180 => 'borderCallout1', # borderCallout90
264    181 => 'accentBorderCallout1', # accentBorderCallout90
265    182 => 'leftRightUpArrow',
266    183 => 'sun',
267    184 => 'moon',
268    185 => 'bracketPair',
269    186 => 'bracePair',
270    187 => 'star4', # seal4
271    188 => 'doubleWave',
272    189 => 'actionButtonBlank',
273    190 => 'actionButtonHome',
274    191 => 'actionButtonHelp',
275    192 => 'actionButtonInformation',
276    193 => 'actionButtonForwardNext',
277    194 => 'actionButtonBackPrevious',
278    195 => 'actionButtonEnd',
279    196 => 'actionButtonBeginning',
280    197 => 'actionButtonReturn',
281    198 => 'actionButtonDocument',
282    199 => 'actionButtonSound',
283    200 => 'actionButtonMovie',
284    201 => 'hostControl', # should not be used
285    202 => 'textBox'
286);
287# An error occurred, we have to ignore this shape
288sub error( $ )
289{
290    my ( $msg ) = @_;
291
292    $ignore_this_shape = 1;
293    print STDERR "ERROR (in $shape_name ): $msg\n";
294}
295
296# Check that we are in the correct level
297sub is_level( $$ )
298{
299    my ( $level, $value ) = @_;
300
301    if ( $level > 0 ) {
302        error( "Error in is_level(), \$level should be <= 0." );
303    }
304    return ( $#levels + $level > 0 ) && ( $levels[$#levels + $level] eq $value );
305}
306
307# Setup the %variables map with predefined values
308sub setup_variables()
309{
310    %variables = (
311        'l'        => 0,
312        't'        => 0,
313        'r'        => 21600,
314        'b'        => 21600,
315
316        'w'        => 21600,
317        'h'        => 21600,
318        'ss'       => 21600,
319        'ls'       => 21600,
320
321        'ssd2'     => 10800, # 1/2
322        'ssd4'     => 5400,  # 1/4
323        'ssd6'     => 3600,  # 1/6
324        'ssd8'     => 2700,  # 1/8
325        'ssd16'    => 1350,  # 1/16
326        'ssd32'    => 675,   # 1/32
327
328        'hc'       => 10800, # horizontal center
329        'vc'       => 10800, # vertical center
330
331        'wd2'      => 10800, # 1/2
332        'wd3'      => 7200,  # 1/3
333        'wd4'      => 5400,  # 1/4
334        'wd5'      => 4320,  # 1/5
335        'wd6'      => 3600,  # 1/6
336        'wd8'      => 2700,  # 1/8
337        'wd10'     => 2160,  # 1/10
338        'wd12'     => 1800,  # 1/12
339        'wd32'     => 675,   # 1/32
340
341        'hd2'      => 10800, # 1/2
342        'hd3'      => 7200,  # 1/3
343        'hd4'      => 5400,  # 1/4
344        'hd5'      => 4320,  # 1/5
345        'hd6'      => 3600,  # 1/6
346        'hd8'      => 2700,  # 1/8
347        'hd10'     => 2160,  # 1/10
348        'hd12'     => 1800,  # 1/12
349        'hd32'     => 675,   # 1/32
350
351        '25000'    => 5400,
352        '12500'    => 2700,
353
354        'cd4'      => 90,    # 1/4 of a circle
355        'cd2'      => 180,   # 1/2 of a circle
356        '3cd4'     => 270,   # 3/4 of a circle
357
358        'cd8'      => 45,    # 1/8 of a circle
359        '3cd8'     => 135,   # 3/8 of a circle
360        '5cd8'     => 225,   # 5/8 of a circle
361        '7cd8'     => 315,   # 7/8 of a circle
362
363        '-5400000' => -90,
364        '-10800000'=> -180,
365        '-16200000'=> -270,
366        '-21600000'=> -360,
367        '-21599999'=> -360,
368
369        '5400000'  => 90,
370        '10800000' => 180,
371        '16200000' => 270,
372        '21600000' => 360,
373        '21599999' => 360
374#
375#        '21600000' => 360,   # angle conversions
376#        '27000000' => 450,
377#        '32400000' => 540,
378#        '37800000' => 630
379    );
380}
381
382# Convert the (predefiend) value to a number
383sub value( $ )
384{
385    my ( $val ) = @_;
386
387    my $result = $variables{$val};
388    return $result if ( defined( $result ) );
389
390    return $val if ( $val =~ /^[0-9-]+$/ );
391
392    error( "Unknown variable '$val'." );
393
394    show_call_stack();
395    return $val;
396}
397
398# Convert the DrawingML formula to a VML one
399%command_variables = (
400    'w' => 'width',
401    'h' => 'height',
402    'r' => 'width',
403    'b' => 'height'
404);
405
406# The same as value(), but some of the hardcoded values can have a name
407sub command_value( $ )
408{
409    my ( $value ) = @_;
410
411    return "" if ( $value eq "" );
412
413    return $value if ( $value =~ /^@/ );
414
415    my $command_val = $command_variables{$value};
416    if ( defined( $command_val ) ) {
417        return $command_val;
418    }
419
420    return value( $value );
421}
422
423# Insert the new formula to the list of formulas
424# Creates the name if it's empty...
425sub insert_formula( $$ )
426{
427    my ( $name, $fmla ) = @_;
428
429    my $i = 0;
430    foreach $f ( @formulas ) {
431        if ( $f eq $fmla ) {
432            if ( $name ne "" ) {
433                $variables{$name} = "@" . $i;
434            }
435            return "@" . $i;
436        }
437        ++$i;
438    }
439
440    if ( $name eq "" ) {
441        $name = "@" . ( $#formulas + 1 );
442    }
443
444    $variables{$name} = "@" . ( $#formulas + 1 );
445    push @formulas, $fmla;
446
447    if ( $#formulas > 127 ) {
448        error( "Reached the maximum amount of formulas, have to ignore the shape '$shape_name'" );
449    }
450
451    return $variables{$name};
452}
453
454# The same as insert_formula(), but converts the params
455sub insert_formula_params( $$$$$ )
456{
457    my ( $name, $command, $p1, $p2, $p3 ) = @_;
458
459    my $result = $command;
460    if ( $p1 ne "" ) {
461        $result .= " " . command_value( $p1 );
462        if ( $p2 ne "" ) {
463            $result .= " " . command_value( $p2 );
464            if ( $p3 ne "" ) {
465                $result .= " " . command_value( $p3 );
466            }
467        }
468    }
469
470    return insert_formula( $name, $result );
471}
472
473# Convert the formula from DrawingML to VML
474sub convert_formula( $$ )
475{
476    my ( $name, $fmla ) = @_;
477
478    if ( $fmla =~ /^([^ ]+)/ ) {
479        my $command = $1;
480
481        # parse the parameters
482        ( my $values = $fmla ) =~ s/^([^ ]+) *//;
483        my $p1 = "", $p2 = "", $p3 = "";
484        if ( $values =~ /^([^ ]+)/ ) {
485            $p1 = $1;
486            $values =~ s/^([^ ]+) *//;
487            if ( $values =~ /^([^ ]+)/ ) {
488                $p2 = $1;
489                $values =~ s/^([^ ]+) *//;
490                if ( $values =~ /^([^ ]+)/ ) {
491                    $p3 = $1;
492                }
493            }
494        }
495
496        # now convert the formula
497        if ( $command eq "+-" ) {
498            if ( $p1 eq "100000" ) {
499                $p1 = value( 'w' );
500            }
501            insert_formula_params( $name, "sum", $p1, $p2, $p3 );
502            return;
503        }
504        elsif ( $command eq "*/" ) {
505            if ( ( $p2 =~ /^(w|h|ss|hd2|wd2|vc)$/ ) && defined( $variables{$p1} ) ) {
506                # switch it ;-) - presetTextWarpDefinitions.xml has it in other order
507                my $tmp = $p1;
508                $p1 = $p2;
509                $p2 = $tmp;
510            }
511
512            if ( ( $p1 =~ /^(w|h|ss|hd2|wd2|vc)$/ ) && defined( $variables{$p2} ) ) {
513                my $val3 = $p3;
514                if ( $val3 =~ /^[0-9-]+$/ ) {
515                    $val3 *= ( value( 'w' ) / value( $p1 ) );
516
517                    # Oh yes, I'm too lazy to implement the full GCD here ;-)
518                    if ( ( $val3 % 100000 ) == 0 ) {
519                        $p1 = 1;
520                        $p3 = sprintf( "%.0f", ( $val3 / 100000 ) );
521                    }
522                    elsif ( $val3 < 100000 ) {
523                        $p3 = 1;
524                        while ( ( ( $p3 * 100000 ) % $val3 ) != 0 ) {
525                            ++$p3
526                        }
527                        $p1 = ( $p3 * 100000 ) / $val3;
528                    }
529                    else {
530                        error( "Need to count the greatest common divisor." );
531                    }
532                }
533            }
534            elsif ( $p3 eq "100000" && $p2 =~ /^[0-9-]+$/ ) {
535                # prevent overflows in some shapes
536                $p2 = sprintf( "%.0f", ( $p2 / 10 ) );
537                $p3 /= 10;
538            }
539            elsif ( $p3 eq "32768" && $p2 =~ /^[0-9-]+$/ ) {
540                # prevent overflows in some shapes
541                $p2 = sprintf( "%.0f", ( $p2 / 8 ) );
542                $p3 /= 8;
543            }
544            elsif ( $p3 eq "50000" ) {
545                $p3 = 10800;
546            }
547            elsif ( $name =~ /^maxAdj/ ) {
548                my $val = value( $p1 );
549                if ( $val =~ /^[0-9-]+$/ ) {
550                    $p1 = sprintf( "%.0f", ( value( 'w' ) * $val / 100000 ) );
551                }
552            }
553
554            if ( ( value( $p1 ) eq value( $p3 ) ) || ( value( $p2 ) eq value( $p3 ) ) ) {
555                my $val = value( ( value( $p1 ) eq value( $p3 ) )? $p2: $p1 );
556                if ( $val =~ /^@([0-9]+)$/ ) {
557                    insert_formula( $name, $formulas[$1] );
558                }
559                else {
560                    insert_formula( $name, "val $val" );
561                }
562            }
563            else {
564                insert_formula_params( $name, "prod", $p1, $p2, $p3 );
565            }
566            return;
567        }
568        elsif ( $command eq "+/" ) {
569            # we have to split this into 2 formulas - 'sum' and 'prod'
570            my $constructed = insert_formula_params( "", "sum", $p1, $p2, "0" );
571            insert_formula_params( $name, "prod", 1, $constructed, $p3); # references the 'sum' formula
572            return;
573        }
574        elsif ( $command eq "?:" ) {
575            insert_formula_params( $name, "if", $p1, $p2, $p3 );
576            return;
577        }
578        elsif ( $command eq "sin" || $command eq "cos" ) {
579            if ( $p2 =~ /^[0-9-]+$/ && ( ( $p2 % 60000 ) == 0 ) ) {
580                $p2 /= 60000;
581            }
582            else {
583                $p2 = insert_formula_params( "", "prod", "1", $p2, "60000" );
584            }
585            # we have to use 'sumangle' even for the case when $p2 is const
586            # and theoretically could be written as such; but Word does not
587            # accept it :-(
588            my $conv = insert_formula_params( "", "sumangle", "0", $p2, "0" );
589
590            $p2 = $conv;
591
592            insert_formula_params( $name, $command, $p1, $p2, "" );
593            return;
594        }
595        elsif ( $command eq "abs" ) {
596            insert_formula_params( $name, $command, $p1, "", "" );
597            return;
598        }
599        elsif ( $command eq "max" || $command eq "min" ) {
600            insert_formula_params( $name, $command, $p1, $p2, "" );
601            return;
602        }
603        elsif ( $command eq "at2" ) {
604            insert_formula_params( $name, "atan2", $p1, $p2, "" );
605            return;
606        }
607        elsif ( $command eq "cat2" ) {
608            insert_formula_params( $name, "cosatan2", $p1, $p2, $p3 );
609            return;
610        }
611        elsif ( $command eq "sat2" ) {
612            insert_formula_params( $name, "sinatan2", $p1, $p2, $p3 );
613            return;
614        }
615        elsif ( $command eq "sqrt" ) {
616            insert_formula_params( $name, "sqrt", $p1, "", "" );
617            return;
618        }
619        elsif ( $command eq "mod" ) {
620            insert_formula_params( $name, "mod", $p1, $p2, $p3 );
621            return;
622        }
623        elsif ( $command eq "val" ) {
624            insert_formula_params( $name, "val", value( $p1 ), "", "" );
625            return;
626        }
627        else {
628            error( "Unknown formula '$name', '$fmla'." );
629        }
630    }
631    else {
632        error( "Cannot convert formula's command '$name', '$fmla'." );
633    }
634}
635
636# There's no exact equivalent of 'arcTo' in VML, we have to do some special casing...
637%convert_arcTo = (
638    '0' => {
639        '90' => {
640            'path' => 'qy',
641            'op' => [ 'sum 0 __last_x__ __wR__', 'sum __hR__ __last_y__ 0' ],
642        },
643        '-90' => {
644            'path' => 'qy',
645            'op' => [ 'sum 0 __last_x__ __wR__', 'sum 0 __last_y__ __hR__' ],
646        },
647    },
648    '90' => {
649        '90' => {
650            'path' => 'qx',
651            'op' => [ 'sum 0 __last_x__ __wR__', 'sum 0 __last_y__ __hR__' ],
652        },
653        '-90' => {
654            'path' => 'qx',
655            'op' => [ 'sum __wR__ __last_x__ 0', 'sum 0 __last_y__ __hR__' ],
656        },
657    },
658    '180' => {
659        '90' => {
660            'path' => 'qy',
661            'op' => [ 'sum __wR__ __last_x__ 0', 'sum 0 __last_y__ __hR__' ],
662        },
663        '-90' => {
664            'path' => 'qy',
665            'op' => [ 'sum __wR__ __last_x__ 0', 'sum __hR__ __last_y__ 0' ],
666        },
667    },
668    '270' => {
669        '90' => {
670            'path' => 'qx',
671            'op' => [ 'sum __wR__ __last_x__ 0', 'sum __hR__ __last_y__ 0' ],
672        },
673        '-90' => {
674            'path' => 'qx',
675            'op' => [ 'sum 0 __last_x__ __wR__', 'sum __hR__ __last_y__ 0' ],
676        },
677    },
678);
679
680# Elliptic quadrant
681# FIXME optimize so that we compute the const values when possible
682sub elliptic_quadrant( $$$$ )
683{
684    my ( $wR, $hR, $stAng, $swAng ) = @_;
685
686    if ( defined( $convert_arcTo{$stAng} ) && defined( $convert_arcTo{$stAng}{$swAng} ) ) {
687        my $conv_path = $convert_arcTo{$stAng}{$swAng}{'path'};
688        my $conv_op_ref = $convert_arcTo{$stAng}{$swAng}{'op'};
689
690        $path .= "$conv_path";
691
692        my $pos_x = $last_pos_x;
693        my $pos_y = $last_pos_y;
694        for ( my $i = 0; $i <= $#{$conv_op_ref}; ++$i ) {
695            my $op = $conv_op_ref->[$i];
696
697            $op =~ s/__last_x__/$last_pos_x/g;
698            $op =~ s/__last_y__/$last_pos_y/g;
699            $op =~ s/__wR__/$wR/g;
700            $op =~ s/__hR__/$hR/g;
701
702            my $fmla = insert_formula( "", $op );
703
704            $path .= $fmla;
705
706            # so far it's sufficient just to rotate the positions
707            # FIXME if not ;-)
708            $pos_x = $pos_y;
709            $pos_y = $fmla;
710        }
711        $last_pos_x = $pos_x;
712        $last_pos_y = $pos_y;
713    }
714    else {
715        error( "Unhandled elliptic_quadrant(), input is ($wR, $hR, $stAng, $swAng)." );
716    }
717}
718
719# Convert the quadratic bezier to cubic (exact)
720# No idea why, but the 'qb' did not work for me :-(
721sub quadratic_to_cubic_bezier( $ )
722{
723    my ( $axis ) = @_;
724
725    my $a0 = $quadratic_bezier[0]->{$axis};
726    my $a1 = $quadratic_bezier[1]->{$axis};
727    my $a2 = $quadratic_bezier[2]->{$axis};
728
729    my $b0 = $a0;
730
731    # $b1 = $a0 + 2/3 * ( $a1 - $a0 ), but in VML
732    # FIXME optimize for constants - compute directly
733    my $b1_1 = insert_formula_params( "", "sum", "0", $a1, $a0 );
734    my $b1_2 = insert_formula_params( "", "prod", "2", $b1_1, "3" );
735    my $b1   = insert_formula_params( "", "sum", $a0, $b1_2, "0" );
736
737    # $b2 = $b1 + 1/3 * ( $a2 - $a0 );
738    # FIXME optimize for constants - compute directly
739    my $b2_1 = insert_formula_params( "", "sum", "0", $a2, $a0 );
740    my $b2_2 = insert_formula_params( "", "prod", "1", $b2_1, "3" );
741    my $b2   = insert_formula_params( "", "sum", $b1, $b2_2, "0" );
742
743    my $b3 = $a2;
744
745    return ( $b0, $b1, $b2, $b3 );
746}
747
748# Extend $path by one more point
749sub add_point_to_path( $$ )
750{
751    my ( $x, $y ) = @_;
752
753    if ( $path =~ /[0-9]$/ && $x =~ /^[0-9-]/ ) {
754        $path .= ",";
755    }
756    $path .= $x;
757
758    if ( $path =~ /[0-9]$/ && $y =~ /^[0-9-]/ ) {
759        $path .= ",";
760    }
761    $path .= $y;
762}
763
764# Start of an element
765sub start_element( $% )
766{
767    my ( $element, %attr ) = @_;
768
769    push @levels, $element;
770
771    #print "element: $element\n";
772
773    if ( is_level( -1, "presetShapeDefinitons" ) || is_level( -1, "presetTextWarpDefinitions" ) ) {
774        $shape_name = $element;
775
776        $state = "";
777        $ignore_this_shape = 0;
778        $path = "";
779        $adjust = "";
780        $max_adj_no = 0;
781        @formulas = ();
782        $handles = "";
783        $textboxrect = "";
784        $last_pos_x = "";
785        $last_pos_y = "";
786        $no_stroke = 0;
787        $no_fill = 0;
788        @quadratic_bezier = ();
789
790        setup_variables();
791
792        if ( $shape_name eq "sun" ) {
793            # hack for this shape
794            $variables{'100000'} = "21600";
795            $variables{'50000'} = "10800";
796            $variables{'25000'} = "5400";
797            $variables{'12500'} = "2700";
798            $variables{'3662'} = "791";
799        }
800
801        my $found = 0;
802        foreach my $name ( values( %shapes_ids ) ) {
803            if ( $name eq $shape_name ) {
804                $found = 1;
805                last;
806            }
807        }
808        if ( !$found ) {
809            error( "Unknown shape '$shape_name'." );
810        }
811    }
812    elsif ( $element eq "pathLst" ) {
813        $state = "path";
814    }
815    elsif ( $element eq "avLst" ) {
816        $state = "adjust";
817    }
818    elsif ( $element eq "gdLst" ) {
819        $state = "formulas";
820    }
821    elsif ( $element eq "ahLst" ) {
822        $state = "handles";
823    }
824    elsif ( $element eq "rect" ) {
825        $textboxrect = value( $attr{'l'} ) . "," . value( $attr{'t'} ) . "," .
826                       value( $attr{'r'} ) . "," . value( $attr{'b'} );
827    }
828    elsif ( $state eq "path" ) {
829        if ( $element eq "path" ) {
830            $no_stroke = ( defined( $attr{'stroke'} ) && $attr{'stroke'} eq 'false' );
831            $no_fill = ( defined( $attr{'fill'} ) && $attr{'fill'} eq 'none' );
832            $path_w = $attr{'w'};
833            $path_h = $attr{'h'};
834        }
835        elsif ( $element eq "moveTo" ) {
836            $path .= "m";
837        }
838        elsif ( $element eq "lnTo" ) {
839            $path .= "l";
840        }
841        elsif ( $element eq "cubicBezTo" ) {
842            $path .= "c";
843        }
844        elsif ( $element eq "quadBezTo" ) {
845            my %points = ( 'x' => $last_pos_x, 'y' => $last_pos_y );
846            @quadratic_bezier = ( \%points );
847        }
848        elsif ( $element eq "close" ) {
849            $path .= "x";
850        }
851        elsif ( $element eq "pt" ) {
852            # rememeber the last position for the arcTo
853            $last_pos_x = value( $attr{'x'} );
854            $last_pos_y = value( $attr{'y'} );
855
856            $last_pos_x *= ( value( 'w' ) / $path_w ) if ( defined( $path_w ) );
857            $last_pos_y *= ( value( 'h' ) / $path_h ) if ( defined( $path_h ) );
858
859            if ( $#quadratic_bezier >= 0 ) {
860                my %points = ( 'x' => $last_pos_x, 'y' => $last_pos_y );
861                push( @quadratic_bezier, \%points );
862            }
863            else {
864                add_point_to_path( $last_pos_x, $last_pos_y );
865            }
866        }
867        elsif ( ( $element eq "arcTo" ) && ( $last_pos_x ne "" ) && ( $last_pos_y ne "" ) ) {
868            # there's no exact equivalent of arcTo in VML, so we have to
869            # compute here a bit...
870            my $stAng = value( $attr{'stAng'} );
871            my $swAng = value( $attr{'swAng'} );
872            my $wR = value( $attr{'wR'} );
873            my $hR = value( $attr{'hR'} );
874
875            $wR *= ( value( 'w' ) / $path_w ) if ( defined( $path_w ) );
876            $hR *= ( value( 'h' ) / $path_h ) if ( defined( $path_h ) );
877
878            if ( ( $stAng =~ /^[0-9-]+$/ ) && ( $swAng =~ /^[0-9-]+$/ ) ) {
879                if ( ( ( $stAng % 90 ) == 0 ) && ( ( $swAng % 90 ) == 0 ) && ( $swAng != 0 ) ) {
880                    my $end = $stAng + $swAng;
881                    my $step = ( $swAng > 0 )? 90: -90;
882
883                    for ( my $cur = $stAng; $cur != $end; $cur += $step ) {
884                        elliptic_quadrant( $wR, $hR, ( $cur % 360 ), $step );
885                    }
886                }
887                else {
888                    error( "Unsupported numeric 'arcTo' ($attr{'wR'}, $attr{'hR'}, $stAng, $swAng)." );
889                }
890            }
891            else {
892                error( "Unsupported 'arcTo' conversion ($attr{'wR'}, $attr{'hR'}, $stAng, $swAng)." );
893            }
894        }
895        else {
896            error( "Unhandled path element '$element'." );
897        }
898    }
899    elsif ( $state eq "adjust" ) {
900        if ( $element eq "gd" ) {
901            my $adj_no = $attr{'name'};
902            my $is_const = 0;
903
904            $adj_no =~ s/^adj//;
905            if ( $adj_no eq "" ) {
906                $max_adj_no = 0;
907            }
908            elsif ( !( $adj_no =~ /^[0-9]*$/ ) ) {
909                ++$max_adj_no;
910                $is_const = 1;
911            }
912            elsif ( $adj_no != $max_adj_no + 1 ) {
913                error( "Wrong order of adj values." );
914                ++$max_adj_no;
915            }
916            else {
917                $max_adj_no = $adj_no;
918            }
919
920            if ( $attr{'fmla'} =~ /^val ([0-9-]*)$/ ) {
921                my $val = sprintf( "%.0f", ( 21600 * $1 ) / 100000 );
922                if ( $is_const ) {
923                    $variables{$adj_no} = $val;
924                }
925                elsif ( $adjust eq "" ) {
926                    $adjust = $val;
927                }
928                else {
929                    $adjust = "$val,$adjust";
930                }
931            }
932            else {
933                error( "Wrong fmla '$attr{'fmla'}'." );
934            }
935        }
936        else {
937            error( "Unhandled adjust element '$element'." );
938        }
939    }
940    elsif ( $state eq "formulas" ) {
941        if ( $element eq "gd" ) {
942            if ( $attr{'fmla'} =~ /^\*\/ (h|w|ss) adj([0-9]+) 100000$/ ) {
943                insert_formula( $attr{'name'}, "val #" . ( $max_adj_no - $2 ) );
944            }
945            elsif ( $attr{'fmla'} =~ /^pin [^ ]+ ([^ ]+) / ) {
946                print STDERR "TODO Map 'pin' to VML as xrange for handles.\n";
947                my $pin_val = $1;
948                if ( $pin_val eq "adj" ) {
949                    insert_formula( $attr{'name'}, "val #0" );
950                }
951                elsif ( $pin_val =~ /^adj([0-9]+)/ ) {
952                    insert_formula( $attr{'name'}, "val #" . ( $max_adj_no - $1 ) );
953                }
954                else {
955                    insert_formula( $attr{'name'}, "val " . value( $pin_val ) );
956                }
957            }
958            elsif ( $attr{'fmla'} =~ /adj/ ) {
959                error( "Non-standard usage of adj in '$attr{'fmla'}'." );
960            }
961            else {
962                convert_formula( $attr{'name'}, $attr{'fmla'} );
963            }
964        }
965    }
966    elsif ( $state eq "handles" ) {
967        if ( $element eq "pos" ) {
968            $handles .= "<v:h position=\"" . value( $attr{'x'} ) . "," . value( $attr{'y'} ) . "\"/>\n";
969        }
970    }
971}
972
973# End of an element
974sub end_element( $ )
975{
976    my ( $element ) = @_;
977
978    pop @levels;
979
980    if ( $element eq $shape_name ) {
981        if ( !$ignore_this_shape ) {
982            # we have all the info, generate the shape now
983            $state = "";
984
985            # shape path
986            my $out = "<v:shapetype id=\"shapetype___ID__\" coordsize=\"21600,21600\" o:spt=\"__ID__\" ";
987            if ( $adjust ne "" ) {
988                $out .= "adj=\"$adjust\" ";
989            }
990
991            # optimize it [yes, we need this twice ;-)]
992            $path =~ s/([^0-9-@])0([^0-9-@])/$1$2/g;
993            $path =~ s/([^0-9-@])0([^0-9-@])/$1$2/g;
994
995            $out .= "path=\"$path\">\n";
996
997            # stroke
998            $out .= "<v:stroke joinstyle=\"miter\"/>\n";
999
1000            # formulas
1001            if ( $#formulas >= 0 )
1002            {
1003                $out .= "<v:formulas>\n";
1004                foreach $fmla ( @formulas ) {
1005                    $out .= "<v:f eqn=\"$fmla\"/>\n"
1006                }
1007                $out .= "</v:formulas>\n";
1008            }
1009
1010            # path
1011            if ( $textboxrect ne "" ) { # TODO connectlocs, connectangles
1012                $out .= "<v:path gradientshapeok=\"t\" o:connecttype=\"rect\" textboxrect=\"$textboxrect\"/>\n";
1013            }
1014
1015            # handles
1016            if ( $handles ne "" ) {
1017                $out .= "<v:handles>\n$handles</v:handles>\n";
1018            }
1019
1020            $out .="</v:shapetype>";
1021
1022            # hooray! :-)
1023            $result_shapes{$shape_name} = $out;
1024        }
1025        else {
1026            print STDERR "Shape '$shape_name' ignored; see the above error(s) for the reason.\n";
1027        }
1028        $shape_name = "";
1029    }
1030    elsif ( $state eq "path" ) {
1031        if ( $element eq "path" ) {
1032            $path .= "ns" if ( $no_stroke );
1033            $path .= "nf" if ( $no_fill );
1034            $path .= "e";
1035        }
1036        elsif ( $element eq "quadBezTo" ) {
1037            # we have to convert the quadratic bezier to cubic
1038            if ( $#quadratic_bezier == 2 ) {
1039                my @points_x = quadratic_to_cubic_bezier( 'x' );
1040                my @points_y = quadratic_to_cubic_bezier( 'y' );
1041
1042                $path .= "c";
1043                # ignore the starting point
1044                for ( my $i = 1; $i < 4; ++$i ) {
1045                    add_point_to_path( $points_x[$i], $points_y[$i] );
1046                }
1047            }
1048            else {
1049                error( "Wrong number of points of the quadratic bezier." );
1050            }
1051            @quadratic_bezier = ();
1052        }
1053    }
1054    elsif ( $element eq "avLst" ) {
1055        $state = "";
1056    }
1057    elsif ( $element eq "gdLst" ) {
1058        $state = "";
1059    }
1060    elsif ( $element eq "ahLst" ) {
1061        $state = "";
1062    }
1063}
1064
1065# Text inside an element
1066sub characters( $ )
1067{
1068    #my ( $text ) = @_;
1069}
1070
1071#################### A trivial XML parser ####################
1072
1073# Parse the attributes
1074sub parse_start_element( $ )
1075{
1076    # split the string containing both the elements and attributes
1077    my ( $element_tmp ) = @_;
1078
1079    $element_tmp =~ s/\s*$//;
1080    $element_tmp =~ s/^\s*//;
1081
1082    ( my $element = $element_tmp ) =~ s/\s.*$//;
1083    if ( $element_tmp =~ /\s/ ) {
1084        $element_tmp =~ s/^[^\s]*\s//;
1085    }
1086    else {
1087        $element_tmp = "";
1088    }
1089
1090    # we have the element, now the attributes
1091    my %attr;
1092    my $is_key = 1;
1093    my $key = "";
1094    foreach my $tmp ( split( /"/, $element_tmp ) ) {
1095        if ( $is_key ) {
1096            $key = $tmp;
1097            $key =~ s/^\s*//;
1098            $key =~ s/\s*=\s*$//;
1099        }
1100        else {
1101            $attr{$key} = $tmp;
1102        }
1103        $is_key = !$is_key;
1104    }
1105
1106    if ( $element ne "" ) {
1107        start_element( $element, %attr );
1108    }
1109}
1110
1111# Parse the file
1112sub parse( $ )
1113{
1114    my ( $file ) = @_;
1115
1116    my $in_comment = 0;
1117    my $line = "";
1118    while (<$file>) {
1119        # ignore comments
1120        s/<\?[^>]*\?>//g;
1121        s/<!--[^>]*-->//g;
1122        if ( /<!--/ ) {
1123            $in_comment = 1;
1124            s/<!--.*//;
1125        }
1126        elsif ( /-->/ && $in_comment ) {
1127            $in_comment = 0;
1128            s/.*-->//;
1129        }
1130        elsif ( $in_comment ) {
1131            next;
1132        }
1133        # ignore empty lines
1134        chomp;
1135        s/^\s*//;
1136        s/\s*$//;
1137        next if ( $_ eq "" );
1138
1139        # take care of lines where element continues
1140        if ( $line ne "" ) {
1141            $line .= " " . $_;
1142        }
1143        else {
1144            $line = $_;
1145        }
1146        next if ( !/>$/ );
1147
1148        # the actual parsing
1149        my @starts = split( /</, $line );
1150        $line = "";
1151        foreach $start ( @starts ) {
1152            next if ( $start eq "" );
1153
1154            @ends = split( />/, $start );
1155            my $element = $ends[0];
1156            my $data = $ends[1];
1157
1158            # start or end element
1159            if ( $element =~ /^\/(.*)/ ) {
1160                end_element( $1 );
1161            }
1162            elsif ( $element =~ /^(.*)\/$/ ) {
1163                parse_start_element( $1 );
1164                ( my $end = $1 ) =~ s/\s.*$//;
1165                end_element( $end );
1166            }
1167            else {
1168                parse_start_element( $element );
1169            }
1170
1171            # the data
1172            characters( $data ) if ( defined( $data ) && $data ne "" );
1173        }
1174    }
1175}
1176
1177# Do the real work
1178open( IN, "<$src_shapes" ) || die "Cannot open $src_shapes.";
1179parse( IN );
1180close( IN );
1181
1182open( IN, "<$src_text" ) || die "Cannot open $src_text.";
1183parse( IN );
1184close( IN );
1185
1186if ( !defined( $result_shapes{'textBox'} ) ) {
1187    $result_shapes{'textBox'} =
1188        "<v:shapetype id=\"shapetype___ID__\" coordsize=\"21600,21600\" " .
1189        "o:spt=\"__ID__\" path=\"m,l,21600l21600,21600l21600,xe\">\n" .
1190        "<v:stroke joinstyle=\"miter\"/>\n" .
1191        "<v:path gradientshapeok=\"t\" o:connecttype=\"rect\"/>\n" .
1192        "</v:shapetype>";
1193}
1194
1195# Generate the code
1196print <<EOF;
1197// Shape types generated from
1198//   '$src_shapes'
1199// and
1200//   '$src_text'
1201// which are part of the OOXML documentation
1202
1203#include <svx/escherex.hxx>
1204
1205const char* pShapeTypes[ ESCHER_ShpInst_COUNT ] =
1206{
1207EOF
1208
1209for ( $i = 0; $i < 203; ++$i ) {
1210    if ( $i < 4 ) {
1211        print "    /* $i - $shapes_ids{$i} - handled separately */\n    NULL,\n";
1212    }
1213    else {
1214        print "    /* $i - $shapes_ids{$i} */\n";
1215        my $out = $result_shapes{$shapes_ids{$i}};
1216        if ( defined( $out ) ) {
1217            # set the id
1218            $out =~ s/__ID__/$i/g;
1219
1220            # escape the '"'s
1221            $out =~ s/"/\\"/g;
1222
1223            # output as string
1224            $out =~ s/^/    "/;
1225            $out =~ s/\n/"\n    "/g;
1226            $out =~ s/$/"/;
1227
1228            print "$out,\n";
1229        }
1230        else {
1231            print "    NULL,\n";
1232        }
1233    }
1234}
1235
1236print <<EOF;
1237};
1238EOF
1239