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