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