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