1 {
2 Reads an ODG Document
3 
4 License: The same modified LGPL as the Free Pascal RTL
5          See the file COPYING.modifiedLGPL for more details
6 
7 An OpenDocument document is a compressed ZIP file with the following files inside:
8 
9 content.xml     - Actual contents
10 meta.xml        - Authoring data
11 settings.xml    - User persistent viewing information, such as zoom, cursor position, etc.
12 styles.xml      - Styles, which are the only way to do formatting
13 mimetype        - application/vnd.oasis.opendocument.spreadsheet
14 META-INF\manifest.xml  - Describes the other files in the archive
15 
16 Specifications obtained from:
17 
18 http://docs.oasis-open.org/office/v1.1/OS/OpenDocument-v1.1.pdf
19 
20 Example of content.xml structure:
21 
22 <?xml version="1.0" encoding="UTF-8"?>
23 <office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" .....>
24 <office:scripts/>
25 <office:automatic-styles>
26   <style:style style:name="dp1" style:family="drawing-page"/>
27   <style:style style:name="gr1" style:family="graphic" style:parent-style-name="standard">
28   ....
29 </office:automatic-styles>
30 <office:body>
31 <office:drawing>
32   <draw:page draw:name="page1" draw:style-name="dp1" draw:master-page-name="Oletus">
33     <draw:ellipse draw:style-name="gr2" draw:text-style-name="P1" draw:layer="layout" svg:width="11cm" svg:height="3cm" svg:x="5.5cm" svg:y="6.5cm">
34       <text:p/>
35     </draw:ellipse>
36     ... other elements in the page...
37   </draw:page>
38 </office:drawing>
39 </office:body>
40 </office:document-content>
41 
42 AUTHORS: Felipe Monteiro de Carvalho
43 }
44 unit odgvectorialreader;
45 
46 {$mode objfpc}{$H+}
47 
48 interface
49 
50 uses
51   Classes, SysUtils, math,
52   zipper, {NOTE: might require zipper from FPC 2.6.2+ }
53   xmlread, DOM, AVL_Tree,
54   fpimage, fpcanvas, fgl,
55   fpvectorial, fpvutils, lazutf8;
56 
57 type
58   TDoubleArray = array of Double;
59 
60   TSVGTokenType = (
61     // moves
62     sttMoveTo, sttRelativeMoveTo,
63     // Close Path
64     sttClosePath,
65     // lines
66     sttLineTo, sttRelativeLineTo,
67     sttHorzLineTo, sttRelativeHorzLineTo, sttVertLineTo, sttRelativeVertLineTo,
68     // cubic beziers
69     sttBezierTo, sttRelativeBezierTo,
70     // quadratic beziers
71     sttQuadraticBezierTo, sttRelativeQuadraticBezierTo,
72     // Elliptic curves
73     sttEllipticArcTo, sttRelativeEllipticArcTo,
74     sttEllipticArcToWithAngle,
75     // ToDo: Find out what these are
76     sttUnknown,
77     // numbers
78     sttFloatValue,
79     // text
80     sttConstantValue, sttNamedEquation);
81 
82   TSVGToken = class
83     TokenType: TSVGTokenType;
84     Value: Float;
85     StrValue: string;
86   end;
87 
88   TSVGTokenList = specialize TFPGList<TSVGToken>;
89 
90   { TSVGPathTokenizer }
91 
92   TSVGPathTokenizer = class
93   public
94     FPointSeparator, FCommaSeparator: TFormatSettings;
95     Tokens: TSVGTokenList;
96     constructor Create;
97     Destructor Destroy; override;
98     procedure AddToken(AStr: string);
99     procedure TokenizePathString(AStr: string);
100     procedure TokenizeFunctions(AStr: string);
101   end;
102 
103   TODGStyle = class(TvStyle)
104     //
105   end;
106 
107   TODGMasterPage = class
108   public
109     Name: string;
110     PageLayoutName: string;
111     StyleName: string;
112   end;
113 
114   TODGPageLayout = class
115   public
116     Name: string;
117     MarginTop, MarginBottom, MarginLeft, MarginRight: Double;
118     PageWidth, PageHeight: Double;
119   end;
120 
121   TCustomShapeInfo = class
122   public
123     Left, Top, Width, Height: Double; // in milimiters
124     ViewBox_Left, ViewBox_Top, ViewBox_Width, ViewBox_Height: Double; // unitless
125     Formulas: array of TvFormula;
126   end;
127 
128   { TvODGVectorialReader }
129 
130   TvODGVectorialReader = class(TvCustomVectorialReader)
131   private
132     FPointSeparator, FCommaSeparator: TFormatSettings;
133     FStyles: TFPList; // of TODGStyle;
134     FAutomaticStyles: TFPList; // of TODGStyle;
135     FPageLayouts: TFPList; // of TODGPageLayout;
136     FMasterPages: TFPList; // of TODGMasterPage;
137     //FSVGPathTokenizer: TSVGPathTokenizer;
138     //
ReadSpaceSeparatedFloatsnull139     function ReadSpaceSeparatedFloats(AInput: string; AOtherSeparators: string): TDoubleArray;
ReadSpaceSeparatedStringsnull140     function ReadSpaceSeparatedStrings(AInput: string; AOtherSeparators: string): TStringList;
141     //
142     procedure DeleteStyle(data,arg:pointer);
143     procedure ApplyGraphicAttributeToPenAndBrush(ANodeName, ANodeValue: string; var APen: TvPen; var ABrush: TvBrush);
144     procedure ApplyGraphicAttributeToEntity(ANodeName, ANodeValue: string; ADest: TvEntityWithPen);
145     procedure ApplyStyleByNameToEntity(AStyleName: string; ADest: TvEntityWithPen);
146     procedure ApplyTextStyleByNameToEntity(AStyleName: string; ADest: TvEntityWithPen);
147     procedure ApplyMasterPageToPage(AMasterPageName: string; ADest: TvVectorialPage);
148     //
149     procedure ReadStyleStyleNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
150     procedure ReadEnhancedGeometryNodeToTPath(ANode: TDOMNode; AData: TvVectorialPage; ADest: TPath; ADeltaX, ADeltaY: Double; var AInfo: TCustomShapeInfo);
151     procedure ConvertPathStringToTPath(AStr: string; AData: TvVectorialPage; ADest: TPath; ADeltaX, ADeltaY: Double; AInfo: TCustomShapeInfo);
ResolveEnhancedGeometryFormulanull152     function  ResolveEnhancedGeometryFormula(AInput: TSVGToken; AInfo: TCustomShapeInfo): Double;
153     //
154     procedure ReadElement(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
155     procedure ReadCustomShapeNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
156     procedure ReadEllipseNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
157     procedure ReadFrameNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
158     procedure ReadLineNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
159     procedure ReadPathNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
ReadTextPSequenceNodenull160     function  ReadTextPSequenceNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvRichText;
ReadTextPNodenull161     function  ReadTextPNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvParagraph;
162     //
163     procedure ReadStylesMasterPage(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
164     procedure ReadStylesPageLayout(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
165     //
166     procedure ParsePathString(AInputStr: string; ADest: TPath);
167     procedure GetDrawTransforms(AInputStr: string; out ASkewX, ASkewY, ARotate, ATranslateX, ATranslateY: Double);
ReadSVGColornull168     function ReadSVGColor(AValue: string): TFPColor;
GetAttrValuenull169     function GetAttrValue(ANode : TDOMNode; AAttrName : string) : string;
StringWithUnitToFloatnull170     function  StringWithUnitToFloat(AStr: string): Double;
171     procedure ConvertODGCoordinatesToFPVCoordinates(
172       const AData: TvVectorialPage;
173       const ASrcX, ASrcY: Double; var ADestX, ADestY: Double);
174     procedure ConvertODGDeltaToFPVDelta(
175       const AData: TvVectorialPage;
176       const ASrcX, ASrcY: Double; var ADestX, ADestY: Double);
177     procedure ConvertViewBoxCoordinatesToFPVCoordinates(
178       const AData: TvVectorialPage; const AInfo: TCustomShapeInfo;
179       const ASrcX, ASrcY: Double; var ADestX, ADestY: Double);
180     procedure ConvertViewBoxDeltaToFPVDelta(
181       const AInfo: TCustomShapeInfo;
182       const ASrcX, ASrcY: Double; var ADestX, ADestY: Double);
183     procedure ConvertMilimiterCoordinatesToFPVCoordinates(
184       const AData: TvVectorialPage;
185       const ASrcX, ASrcY: Double; var ADestX, ADestY: Double);
186   public
187     { General reading methods }
188     constructor Create; override;
189     Destructor Destroy; override;
190     procedure ReadFromStream(AStream: TStream; AData: TvVectorialDocument); override;
191     procedure ReadFromFile(AFileName: string; AData: TvVectorialDocument); override;
192     procedure ReadFromContentXMLDocument(AXMLDocument: TXMLDocument; AData: TvVectorialDocument);
193     procedure ReadFromStylesXMLDocument(AXMLDocument: TXMLDocument; AData: TvVectorialDocument);
194   end;
195 
196 implementation
197 
198 const
199   { OpenDocument general XML constants }
200   XML_HEADER           = '<?xml version="1.0" encoding="utf-8" ?>';
201 
202   { OpenDocument Directory structure constants }
203   OPENDOC_PATH_CONTENT   = 'content.xml';
204   OPENDOC_PATH_META      = 'meta.xml';
205   OPENDOC_PATH_SETTINGS  = 'settings.xml';
206   OPENDOC_PATH_STYLES    = 'styles.xml';
207   OPENDOC_PATH_MIMETYPE  = 'mimetype';
208   OPENDOC_PATH_METAINF = 'META-INF' + '/';
209   OPENDOC_PATH_METAINF_MANIFEST = 'META-INF' + '/' + 'manifest.xml';
210 
211   { OpenDocument schemas constants }
212   SCHEMAS_XMLNS_OFFICE   = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0';
213   SCHEMAS_XMLNS_DCTERMS  = 'http://purl.org/dc/terms/';
214   SCHEMAS_XMLNS_META     = 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0';
215   SCHEMAS_XMLNS          = 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties';
216   SCHEMAS_XMLNS_CONFIG   = 'urn:oasis:names:tc:opendocument:xmlns:config:1.0';
217   SCHEMAS_XMLNS_OOO      = 'http://openoffice.org/2004/office';
218   SCHEMAS_XMLNS_MANIFEST = 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0';
219   SCHEMAS_XMLNS_FO       = 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0';
220   SCHEMAS_XMLNS_STYLE    = 'urn:oasis:names:tc:opendocument:xmlns:style:1.0';
221   SCHEMAS_XMLNS_SVG      = 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0';
222   SCHEMAS_XMLNS_TABLE    = 'urn:oasis:names:tc:opendocument:xmlns:table:1.0';
223   SCHEMAS_XMLNS_TEXT     = 'urn:oasis:names:tc:opendocument:xmlns:text:1.0';
224   SCHEMAS_XMLNS_V        = 'urn:schemas-microsoft-com:vml';
225   SCHEMAS_XMLNS_NUMBER   = 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0';
226   SCHEMAS_XMLNS_CHART    = 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0';
227   SCHEMAS_XMLNS_DR3D     = 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0';
228   SCHEMAS_XMLNS_MATH     = 'http://www.w3.org/1998/Math/MathML';
229   SCHEMAS_XMLNS_FORM     = 'urn:oasis:names:tc:opendocument:xmlns:form:1.0';
230   SCHEMAS_XMLNS_SCRIPT   = 'urn:oasis:names:tc:opendocument:xmlns:script:1.0';
231   SCHEMAS_XMLNS_OOOW     = 'http://openoffice.org/2004/writer';
232   SCHEMAS_XMLNS_OOOC     = 'http://openoffice.org/2004/calc';
233   SCHEMAS_XMLNS_DOM      = 'http://www.w3.org/2001/xml-events';
234   SCHEMAS_XMLNS_XFORMS   = 'http://www.w3.org/2002/xforms';
235   SCHEMAS_XMLNS_XSD      = 'http://www.w3.org/2001/XMLSchema';
236   SCHEMAS_XMLNS_XSI      = 'http://www.w3.org/2001/XMLSchema-instance';
237 
238   // SVG requires hardcoding a DPI value
239 
240   // The Opera Browser and Inkscape use 90 DPI, so we follow that
241 
242   // 1 Inch = 25.4 milimiters
243   // 90 inches per pixel = (1 / 90) * 25.4 = 0.2822
244   // FLOAT_MILIMETERS_PER_PIXEL = 0.3528; // DPI 72 = 1 / 72 inches per pixel
245 
246   FLOAT_MILIMETERS_PER_PIXEL = 0.2822; // DPI 90 = 1 / 90 inches per pixel
247   FLOAT_PIXELS_PER_MILIMETER = 3.5433; // DPI 90 = 1 / 90 inches per pixel
248 
249 { TSVGPathTokenizer }
250 
251 constructor TSVGPathTokenizer.Create;
252 begin
253   inherited Create;
254 
255   FPointSeparator := DefaultFormatSettings;
256   FPointSeparator.DecimalSeparator := '.';
257   FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
258 
259   Tokens := TSVGTokenList.Create;
260 end;
261 
262 destructor TSVGPathTokenizer.Destroy;
263 begin
264   Tokens.Free;
265 
266   inherited Destroy;
267 end;
268 
269 {
270 <draw:enhanced-geometry
271  svg:viewBox="0 0 1000 1000"
272  draw:modifiers="0 0"
273  draw:enhanced-path="M 0 1000 L $0 $1 1000 1000 Z N
274  M 0 1000 L ?f0 ?f1 F N">
275   <draw:equation draw:name="f0" draw:formula="($0 + right) / 2"/>
276   <draw:equation draw:name="f1" draw:formula="($1 + bottom) / 2"/>
277   <draw:equation draw:name="f2" draw:formula="2*(bottom - top) / 3"/>
278   <draw:handle draw:handle-position="$0 $1"
279    draw:handle-range-x-minimum="0"
280    draw:handle-range-x-maximum="right"
281    draw:handle-range-y-minimum="0"
282    draw:handle-range-y-maximum="?f2"/>
283 </draw:enhanced-geometry>
284 }
285 procedure TSVGPathTokenizer.AddToken(AStr: string);
286 var
287   lToken: TSVGToken;
288   lStr: string;
289 begin
290   lToken := TSVGToken.Create;
291 
292   lStr := Trim(AStr);
293   if lStr = '' then Exit;
294 
295   // Moves
296   case lStr[1] of
297   'M': lToken.TokenType := sttMoveTo;
298   'm': lToken.TokenType := sttRelativeMoveTo;
299   // Close Path
300   'Z': lToken.TokenType := sttClosePath;
301   'z': lToken.TokenType := sttClosePath;
302   // Lines
303   'L': lToken.TokenType := sttLineTo;
304   'l': lToken.TokenType := sttRelativeLineTo;
305   'H': lToken.TokenType := sttHorzLineTo;
306   'h': lToken.TokenType := sttRelativeHorzLineTo;
307   'V': lToken.TokenType := sttVertLineTo;
308   'v': lToken.TokenType := sttRelativeVertLineTo;
309   // cubic Bézier curve commands
310   'C': lToken.TokenType := sttBezierTo;
311   'c': lToken.TokenType := sttRelativeBezierTo;
312   // quadratic beziers
313   'Q': lToken.TokenType := sttQuadraticBezierTo;
314   'q': lToken.TokenType := sttRelativeQuadraticBezierTo;
315   // Elliptic curves
316   'A': lToken.TokenType := sttEllipticArcTo;
317   'a': lToken.TokenType := sttRelativeEllipticArcTo;
318   // U -> angle- ellipse
319   // (x y w h t0 t1) +
320   // The same as the “T” command, except that a implied moveto
321   // to the starting point is done.
322   // T -> angle- ellipseto
323   // (x y w h t0 t1) +
324   // Draws a segment of an ellipse. The ellipse is specified by
325   // the center(x, y), the size(w, h) and the start-angle t0 and end-angle t1.
326   'U', 'T': lToken.TokenType := sttEllipticArcToWithAngle;
327   // End path
328   // Ends the current set of sub-paths. The sub- paths will be filled
329   // by using the “even-odd” filling rule. Other following subpaths will
330   // be filled independently.
331   'N': lToken.TokenType := sttUnknown;
332   // Types that I don't know what they do
333   'e': lToken.TokenType := sttUnknown;
334   // Constants
335   '$':
336   begin
337     lToken.TokenType := sttConstantValue;
338     lToken.Value := 0;
339     lToken.StrValue := lStr;
340   end;
341   // Named Equations
342   '?':
343   begin
344     lToken.TokenType := sttNamedEquation;
345     lToken.Value := 0;
346     lToken.StrValue := lStr;
347   end;
348   else
349     lToken.TokenType := sttFloatValue;
350     lToken.Value := StrToFloat(AStr, FPointSeparator);
351   end;
352 
353   // Sometimes we get a command glued to a value, for example M150
354   if (not (lToken.TokenType in [sttFloatValue, sttConstantValue, sttNamedEquation]))
355     and (Length(lStr) > 1) then
356   begin
357     Tokens.Add(lToken);
358     lToken.TokenType := sttFloatValue;
359     lStr := Copy(AStr, 2, Length(AStr));
360     lToken.Value := StrToFloat(lStr, FPointSeparator);
361   end;
362 
363   Tokens.Add(lToken);
364 end;
365 
366 procedure TSVGPathTokenizer.TokenizePathString(AStr: string);
367 const
368   Str_Space: Char = ' ';
369   Str_Comma: Char = ',';
370 var
371   i: Integer;
372   lTmpStr: string = '';
373   lState: Integer;
374   lFirstTmpStrChar, lCurChar: Char;
375 begin
376   lState := 0;
377 
378   i := 1;
379   while i <= Length(AStr) do
380   begin
381     case lState of
382     0: // Adding to the tmp string
383     begin
384       lCurChar := AStr[i];
385       if lCurChar = Str_Space then
386       begin
387         lState := 1;
388         AddToken(lTmpStr);
389         lTmpStr := '';
390       end
391       else if lCurChar = Str_Comma then
392       begin
393         AddToken(lTmpStr);
394         lTmpStr := '';
395       end
396       else if lCurChar in ['?', '$'] then
397       begin
398         if lTmpStr <> '' then AddToken(lTmpStr);
399         lState := 2;
400         lTmpStr := lCurChar;
401       end
402       else
403       begin
404         // Check for a break, from letter to number
405         if (Length(lTmpStr) >= 1) then
406         begin
407           lFirstTmpStrChar := lTmpStr[1];
408           if ((lFirstTmpStrChar in ['a'..'z', 'A'..'Z']) and not (lCurChar  in ['a'..'z', 'A'..'Z'])) or
409              (not (lFirstTmpStrChar in ['a'..'z', 'A'..'Z']) and (lCurChar  in ['a'..'z', 'A'..'Z'])) then
410           begin
411             AddToken(lTmpStr);
412             lTmpStr := '';
413             Continue;
414           end;
415         end;
416 
417         lTmpStr := lTmpStr + lCurChar;
418       end;
419 
420       Inc(i);
421     end;
422     1: // Removing spaces
423     begin
424       if AStr[i] <> Str_Space then lState := 0
425       else Inc(i);
426     end;
427     2: // Adding a special group, such as "$2" or "?f3", which stop only at a space
428     begin
429       lCurChar := AStr[i];
430       if lCurChar = Str_Space then
431       begin
432         lState := 1;
433         AddToken(lTmpStr);
434         lTmpStr := '';
435       end
436       else
437         lTmpStr := lTmpStr + lCurChar;
438 
439       Inc(i);
440     end;
441     end;
442   end;
443 
444   // If there is a token still to be added, add it now
445   if (lState = 0) and (lTmpStr <> '') then AddToken(lTmpStr);
446 end;
447 
448 procedure TSVGPathTokenizer.TokenizeFunctions(AStr: string);
449 const
450   Str_Space: Char = ' ';
451   Str_Start_Params: Char = '(';
452   Str_End_Params: Char = ')';
453   ListOfCommandLetters: set of Char = ['a'..'d', 'f'..'z', 'A'..'D', 'F'..'Z'];
454 var
455   i: Integer;
456   lTmpStr: string = '';
457   lState: Integer;
458   lFirstTmpStrChar, lCurChar: Char;
459   lToken: TSVGToken;
460 begin
461   lState := 0;
462 
463   i := 1;
464   while i <= Length(AStr) do
465   begin
466     case lState of
467     0: // Adding to the tmp string
468     begin
469       lCurChar := AStr[i];
470       if lCurChar in [Str_Start_Params, Str_End_Params] then
471       begin
472         lState := 1;
473         // Add the token
474         lToken := TSVGToken.Create;
475         lToken.StrValue := lTmpStr;
476         Tokens.Add(lToken);
477         //
478         lTmpStr := '';
479       end
480       else
481       begin
482         lTmpStr := lTmpStr + lCurChar;
483       end;
484 
485       Inc(i);
486     end;
487     1: // Removing spaces
488     begin
489       if AStr[i] <> Str_Space then lState := 0
490       else Inc(i);
491     end;
492     end;
493   end;
494 
495   // If there is a token still to be added, add it now
496   if (lState = 0) and (lTmpStr <> '') then AddToken(lTmpStr);
497 end;
498 
499 
ReadSpaceSeparatedFloatsnull500 function TvODGVectorialReader.ReadSpaceSeparatedFloats(AInput: string;
501   AOtherSeparators: string): TDoubleArray;
502 var
503   lStrings: TStringList;
504   lInputStr: string;
505   lMatrixElements: array of Double;
506   i: Integer;
507 begin
508   lStrings := TStringList.Create;
509   try
510     lStrings.Delimiter := ' ';
511     // now other separator too
512     lInputStr := AInput;
513     for i := 1 to Length(AOtherSeparators) do
514     begin
515       lInputStr := StringReplace(lInputStr, AOtherSeparators[i], ' ', [rfReplaceAll]);
516     end;
517     //
518     lStrings.DelimitedText := lInputStr;
519     SetLength(lMatrixElements, lStrings.Count);
520     for i := 0 to lStrings.Count-1 do
521     begin
522       lMatrixElements[i] := StringWithUnitToFloat(lStrings.Strings[i]);
523     end;
524 
525     Result := lMatrixElements;
526   finally
527     lStrings.Free;
528   end;
529 end;
530 
ReadSpaceSeparatedStringsnull531 function TvODGVectorialReader.ReadSpaceSeparatedStrings(AInput: string;
532   AOtherSeparators: string): TStringList;
533 var
534   i: Integer;
535   lInputStr: String;
536 begin
537   Result := TStringList.Create;
538   Result.Delimiter := ' ';
539   // now other separator too
540   lInputStr := AInput;
541   for i := 1 to Length(AOtherSeparators) do
542   begin
543     lInputStr := StringReplace(lInputStr, AOtherSeparators[i], ' ', [rfReplaceAll]);
544   end;
545   //
546   Result.DelimitedText := lInputStr;
547 end;
548 
549 procedure TvODGVectorialReader.DeleteStyle(data, arg: pointer);
550 begin
551   TObject(data).Free;
552 end;
553 
554 procedure TvODGVectorialReader.ApplyGraphicAttributeToPenAndBrush(ANodeName,
555   ANodeValue: string; var APen: TvPen; var ABrush: TvBrush);
556 var
557   lColor: TFPColor;
558 begin
559   case ANodeName of
560   // "none", "solid"
561   'draw:fill':
562   begin
563     case ANodeValue of
564     'none': ABrush.Style := bsClear;
565     'solid': ABrush.Style := bsSolid;
566     end;
567   end;
568   // "#ffffff"
569   'draw:fill-color':
570   begin
571     lColor := ReadSVGColor(ANodeValue);
572     ABrush.Color := lColor;
573   end;
574   // Values: "justify", "center", "left"
575   'draw:textarea-horizontal-align':
576   begin
577   end;
578   // Values: "middle"
579   'draw:textarea-vertical-align':
580   begin
581   end;
582   // true-false
583   'draw:auto-grow-height':
584   begin
585   end;
586   // true-false
587   'draw:auto-grow-width':
588   begin
589   end;
590   // "none", "dash"
591   'draw:stroke':
592   begin
593     case ANodeValue of
594     'none': APen.Style := psClear;
595     'dash': APen.Style := psDash;
596     end;
597   end;
598   // "Fine_20_Dashed_20__28_var_29_"
599   'draw:stroke-dash':
600   begin
601   end;
602   // "Arrow"
603   'draw:marker-start':
604   begin
605   end;
606   // "0.45cm"
607   'draw:marker-start-width':
608   begin
609   end;
610   // "Circle"
611   'draw:marker-end':
612   begin
613   end;
614   // "0.45cm"
615   'draw:marker-end-width':
616   begin
617   end;
618   // "Transparency_20_1"
619   'draw:opacity-name':
620   begin
621   end;
622   // "0.1cm"
623   'svg:stroke-width': APen.Width := Round(StringWithUnitToFloat(ANodeValue));
624   // "#000000"
625   'svg:stroke-color': APen.Color := ReadSVGColor(ANodeValue);
626   // "0cm"
627   'fo:min-height':
628   begin
629   end;
630   // "0cm"
631   'fo:min-width':
632   begin
633   end;
634   // "wrap"
635   'fo:wrap-option':
636   begin
637   end;
638   // "0.175cm"
639   'fo:padding-top':
640   begin
641   end;
642   // "0.175cm"
643   'fo:padding-bottom':
644   begin
645   end;
646   // "0.3cm"
647   'fo:padding-left':
648   begin
649   end;
650   // "0.3cm"
651   'fo:padding-right':
652   begin
653   end;
654   end;
655 end;
656 
657 procedure TvODGVectorialReader.ApplyGraphicAttributeToEntity(ANodeName,
658   ANodeValue: string; ADest: TvEntityWithPen);
659 var
660   DummyBrush: TvBrush;
661 begin
662   if (ADest is TvEntityWithPenAndBrush) then
663   begin
664     ApplyGraphicAttributeToPenAndBrush(ANodeName, ANodeValue, ADest.Pen,
665       (ADest as TvEntityWithPenAndBrush).Brush);
666   end
667   else
668     ApplyGraphicAttributeToPenAndBrush(ANodeName, ANodeValue, ADest.Pen, DummyBrush);
669 end;
670 
671 // Don't apply font properties here, because there is a separate method for this
672 procedure TvODGVectorialReader.ApplyStyleByNameToEntity(AStyleName: string;
673   ADest: TvEntityWithPen);
674 var
675   i: Integer;
676   lCurStyle: TODGStyle;
677 begin
678   for i := 0 to FStyles.Count-1 do
679   begin
680     lCurStyle := TODGStyle(FStyles.Items[i]);
681     if lCurStyle.Name = AStyleName then
682     begin
683       ADest.AssignPen(lCurStyle.Pen);
684       if ADest is TvEntityWithPenAndBrush then
685         TvEntityWithPenAndBrush(ADest).AssignBrush(@lCurStyle.Brush);
686 
687       Exit;
688     end;
689   end;
690 end;
691 
692 procedure TvODGVectorialReader.ApplyTextStyleByNameToEntity(AStyleName: string;
693   ADest: TvEntityWithPen);
694 var
695   i: Integer;
696   lCurStyle: TODGStyle;
697 begin
698   for i := 0 to FStyles.Count-1 do
699   begin
700     lCurStyle := TODGStyle(FStyles.Items[i]);
701     if lCurStyle.Name = AStyleName then
702     begin
703       if ADest is TvEntityWithPenBrushAndFont then
704         TvEntityWithPenBrushAndFont(ADest).AssignFont(lCurStyle.Font);
705 
706       Exit;
707     end;
708   end;
709 end;
710 
711 procedure TvODGVectorialReader.ApplyMasterPageToPage(AMasterPageName: string;
712   ADest: TvVectorialPage);
713 var
714   i: Integer;
715   lMasterPage: TODGMasterPage;
716   lMasterPageLayout: TODGPageLayout;
717 begin
718   // Find the Master Page
719   for i := 0 to FMasterPages.Count-1 do
720   begin
721     lMasterPage := TODGMasterPage(FMasterPages.Items[i]);
722     if lMasterPage.Name = AMasterPageName then Break
723     else lMasterPage := nil;
724   end;
725 
726   if lMasterPage = nil then
727     raise Exception.Create(Format('[TvODGVectorialReader.ApplyMasterPageToPage] Master page not found: %s', [AMasterPageName]));
728 
729   // Find the Master Page Properties
730   for i := 0 to FPageLayouts.Count-1 do
731   begin
732     lMasterPageLayout := TODGPageLayout(FPageLayouts.Items[i]);
733     if lMasterPageLayout.Name = lMasterPage.PageLayoutName then Break
734     else lMasterPageLayout := nil;
735   end;
736 
737   if lMasterPageLayout = nil then
738     raise Exception.Create(Format('[TvODGVectorialReader.ApplyMasterPageToPage] Master page layout not found: %s', [lMasterPage.PageLayoutName]));
739 
740   ADest.Width := lMasterPageLayout.PageWidth;
741   ADest.Height := lMasterPageLayout.PageHeight;
742 end;
743 
744 {
745 <style:style style:name="gr2" style:family="graphic" style:parent-style-name="standard">
746   <style:graphic-properties draw:fill="solid" draw:fill-color="#ffff99"
747    draw:textarea-horizontal-align="center" draw:textarea-vertical-align="middle"/>
748 </style:style>
749 }
750 procedure TvODGVectorialReader.ReadStyleStyleNode(ANode: TDOMNode;
751   AData: TvVectorialPage; ADoc: TvVectorialDocument);
752 var
753   lStyle: TvStyle;
754   i: Integer;
755   lGraphicPropertiesNode: TDOMNode;
756   lNodeName, lNodeValue: DOMString;
757 begin
758   lStyle := TODGStyle.Create();
759 
760   // Read attributes of the main style tag
761   // <style:style style:name="gr4" style:family="graphic" style:parent-style-name="standard">;
762   for i := 0 to ANode.Attributes.Length - 1 do
763   begin
764     lNodeName := LowerCase(ANode.Attributes.Item[i].NodeName);
765     lNodeValue := ANode.Attributes.Item[i].NodeValue;
766     case lNodeName of
767     'style:name': lStyle.Name := lNodeValue;
768     //'style:family'
769     'style:parent-style-name':
770     begin
771       lNodeValue := LowerCase(lNodeValue);
772       case lNodeValue of
773       // "standard"
774       'standard': Continue;
775       // "objectwithoutfill"
776       'objectwithoutfill':
777       begin
778         lStyle.Brush.Style := bsClear;
779       end;
780       end;
781     end;
782     end;
783   end;
784 
785   // Read graphic properties
786   lGraphicPropertiesNode := ANode.FindNode('style:graphic-properties');
787   if lGraphicPropertiesNode <> nil then
788   begin
789     for i := 0 to lGraphicPropertiesNode.Attributes.Length - 1 do
790     begin
791       lNodeName := LowerCase(lGraphicPropertiesNode.Attributes.Item[i].NodeName);
792       lNodeValue := LowerCase(lGraphicPropertiesNode.Attributes.Item[i].NodeValue);
793       ApplyGraphicAttributeToPenAndBrush(lNodeName, lNodeValue, lStyle.Pen, lStyle.Brush);
794     end;
795   end;
796   FStyles.Add(lStyle);
797 end;
798 
799 {
800 <draw:custom-shape draw:style-name="gr1" draw:text-style-name="P1" draw:layer="layout"
801  svg:width="5.2cm" svg:height="1.3cm" svg:x="2.6cm" svg:y="3cm">
802   <text:p text:style-name="P1">Rectangle</text:p>
803   <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:type="rectangle"
804    draw:enhanced-path="M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N"/>
805 </draw:custom-shape>
806 
807   For the drawing units see http://stackoverflow.com/questions/15335926/svg-viewbox-attribute
808 }
809 procedure TvODGVectorialReader.ReadEnhancedGeometryNodeToTPath(ANode: TDOMNode;
810   AData: TvVectorialPage; ADest: TPath; ADeltaX, ADeltaY: Double; var AInfo: TCustomShapeInfo);
811 var
812   i, Len: Integer;
813   lNodeName, lNodeValue, lAttrName, lAttrValue: string;
814   l10Strings: T10Strings;
815   lCurNode: TDOMNode;
816 begin
817   // Initial values to avoid invalid initial ones
818   if AInfo = nil then AInfo := TCustomShapeInfo.Create;
819   AInfo.ViewBox_Left := 0;
820   AInfo.ViewBox_Top := 0;
821   AInfo.ViewBox_Width := 10000;
822   AInfo.ViewBox_Height := 10000;
823 
824   // First of all we need the viewBox, or else we can't map the coordinates
825   for i := 0 to ANode.Attributes.Length - 1 do
826   begin
827     lNodeName := ANode.Attributes.Item[i].NodeName;
828     lNodeValue := ANode.Attributes.Item[i].NodeValue;
829     if (lNodeName = 'draw:viewBox') or (lNodeName = 'svg:viewBox') then
830     begin
831       // From OpenDocument 1.1 specs page 300
832       // The syntax for using this attribute is the same as the [SVG] syntax.
833       // The value of the attribute are four numbers separated by white spaces,
834       // which define the left, top, right, and bottom dimensions of the user
835       // coordinate system.
836       l10Strings := SeparateString(lNodeValue, ' ');
837       AInfo.ViewBox_Left := StringWithUnitToFloat(l10Strings[0]);
838       AInfo.ViewBox_Top := StringWithUnitToFloat(l10Strings[1]);
839       AInfo.ViewBox_Width := StringWithUnitToFloat(l10Strings[2]) - AInfo.ViewBox_Left;
840       AInfo.ViewBox_Height := StringWithUnitToFloat(l10Strings[3]) - AInfo.ViewBox_Top;
841     end;
842   end;
843 
844   // Read child elements
845   lCurNode := ANode.FirstChild;
846   while lCurNode <> nil do
847   begin
848     lNodeName := lCurNode.NodeName;
849 
850     case lNodeName of
851     // Read variables
852     // <draw:equation draw:name="f0" draw:formula="$0 " />
853     // <draw:equation draw:name="f1" draw:formula="10800-$0 " />
854     'draw:equation':
855     begin
856       for i := 0 to lCurNode.Attributes.Length - 1 do
857       begin
858         lAttrName := lCurNode.Attributes.Item[i].NodeName;
859         lAttrValue := lCurNode.Attributes.Item[i].NodeValue;
860 
861         if (lAttrName = 'draw:name') then
862         begin
863           Len := Length(AInfo.Formulas);
864           SetLength(AInfo.Formulas, Len+1);
865           AInfo.Formulas[Len] := TvFormula.Create(nil);
866           AInfo.Formulas[Len].Name := lAttrValue;
867         end
868         else if (lAttrName = 'draw:formula') then
869         begin
870           Len := Length(AInfo.Formulas);
871 
872           // Replace the formulas and variables
873           lAttrValue := StringReplace(lAttrValue, '$0', '0', [rfReplaceAll, rfIgnoreCase]);
874           lAttrValue := StringReplace(lAttrValue, '$1', '0', [rfReplaceAll, rfIgnoreCase]);
875           lAttrValue := StringReplace(lAttrValue, '$2', '0', [rfReplaceAll, rfIgnoreCase]);
876           lAttrValue := StringReplace(lAttrValue, '$3', '0', [rfReplaceAll, rfIgnoreCase]);
877           lAttrValue := StringReplace(lAttrValue, '?f0', '0', [rfReplaceAll, rfIgnoreCase]);
878           lAttrValue := StringReplace(lAttrValue, '?f1', '0', [rfReplaceAll, rfIgnoreCase]);
879           lAttrValue := StringReplace(lAttrValue, '?f2', '0', [rfReplaceAll, rfIgnoreCase]);
880           lAttrValue := StringReplace(lAttrValue, '?f3', '0', [rfReplaceAll, rfIgnoreCase]);
881           lAttrValue := StringReplace(lAttrValue, '?f4', '0', [rfReplaceAll, rfIgnoreCase]);
882           lAttrValue := StringReplace(lAttrValue, '?f5', '0', [rfReplaceAll, rfIgnoreCase]);
883           lAttrValue := StringReplace(lAttrValue, '?f6', '0', [rfReplaceAll, rfIgnoreCase]);
884           lAttrValue := StringReplace(lAttrValue, '?f7', '0', [rfReplaceAll, rfIgnoreCase]);
885           lAttrValue := StringReplace(lAttrValue, '?f8', '0', [rfReplaceAll, rfIgnoreCase]);
886           lAttrValue := StringReplace(lAttrValue, '?f9', '0', [rfReplaceAll, rfIgnoreCase]);
887           lAttrValue := StringReplace(lAttrValue, '?f10', '0', [rfReplaceAll, rfIgnoreCase]);
888           lAttrValue := StringReplace(lAttrValue, 'right', FloatToStr(AInfo.ViewBox_Left + AInfo.ViewBox_Width, FPointSeparator), [rfReplaceAll, rfIgnoreCase]);
889           lAttrValue := StringReplace(lAttrValue, 'bottom', FloatToStr(AInfo.ViewBox_Top + AInfo.ViewBox_Height, FPointSeparator), [rfReplaceAll, rfIgnoreCase]);
890 
891           AInfo.Formulas[Len-1].AddItemsByConvertingInfixStringToRPN(lAttrValue);
892         end;
893       end;
894     end;
895     // <draw:handle draw:handle-position="$0 10800" draw:handle-range-x-minimum="0"
896     //  draw:handle-range-x-maximum="10800" />
897     'draw:handle':
898     begin
899 
900     end;
901     end;
902 
903     lCurNode := lCurNode.NextSibling;
904   end;
905 
906 
907   // read the attributes
908   for i := 0 to ANode.Attributes.Length - 1 do
909   begin
910     lNodeName := ANode.Attributes.Item[i].NodeName;
911     lNodeValue := ANode.Attributes.Item[i].NodeValue;
912     if lNodeName = 'draw:enhanced-path' then
913     begin
914       ConvertPathStringToTPath(lNodeValue, AData, ADest, ADeltaX, ADeltaY, AInfo);
915     end;
916   end;
917 end;
918 
919 procedure TvODGVectorialReader.ConvertPathStringToTPath(AStr: string;
920   AData: TvVectorialPage; ADest: TPath; ADeltaX, ADeltaY: Double; AInfo: TCustomShapeInfo);
921 var
922   x1, y1, x2, y2: double;
923   t1, t2, lSrcX, lSrcY, lDestX, lDestY: Double;
924   j: Integer;
925   lTokenizer: TSVGPathTokenizer;
926   CurToken: TSVGToken;
927 begin
928   x1 := 0.0;
929   y1 := 0.0;
930   x2 := 0.0;
931   y2 := 0.0;
932 
933   lTokenizer := TSVGPathTokenizer.Create;
934   try
935     lTokenizer.TokenizePathString(AStr);
936 
937     j := 0;
938     while j < lTokenizer.Tokens.Count do
939     begin
940       CurToken := TSVGToken(lTokenizer.Tokens.Items[j]);
941 
942       case CurToken.TokenType of
943       // moves
944       sttMoveTo, sttRelativeMoveTo:
945       begin
946         CurToken := TSVGToken(lTokenizer.Tokens.Items[j+1]);
947         x1 := CurToken.Value + ADeltaX;
948         CurToken := TSVGToken(lTokenizer.Tokens.Items[j+2]);
949         y1 := CurToken.Value + ADeltaY;
950         ConvertODGCoordinatesToFPVCoordinates(
951               AData, x1, y1, x1, y1);
952         ADest.AppendMoveToSegment(x1, y1);
953         Inc(j, 3);
954       end;
955       // Close Path
956       sttClosePath: Inc(j);
957       // lines
958       sttLineTo, sttRelativeLineTo,
959       sttHorzLineTo, sttRelativeHorzLineTo, sttVertLineTo, sttRelativeVertLineTo:
960       begin
961         Inc(j);
962         while TSVGToken(lTokenizer.Tokens.Items[j]).TokenType = sttFloatValue do
963         begin
964           CurToken := TSVGToken(lTokenizer.Tokens.Items[j+1]);
965           x1 := CurToken.Value + ADeltaX;
966           CurToken := TSVGToken(lTokenizer.Tokens.Items[j+2]);
967           y1 := CurToken.Value + ADeltaY;
968           ConvertODGCoordinatesToFPVCoordinates(
969                 AData, x1, y1, x1, y1);
970           ADest.AppendLineToSegment(x1, y1);
971 
972           Inc(j, 2);
973 
974           if j >= lTokenizer.Tokens.Count then Break;
975         end;
976       end;
977 {      // cubic beziers
978       sttBezierTo, sttRelativeBezierTo,
979       // quadratic beziers
980       sttQuadraticBezierTo, sttRelativeQuadraticBezierTo,}
981       // Elliptic curves
982       //
983       // A -> arcto
984       // (x1 y1 x2 y2 x3 y3 x y) +
985       // (x1, y1) and (x2, y2) is defining the bounding box of a ellipse.
986       // A line is then drawn from the current point to the start angle of
987       // the arc that is specified by the radial vector of point (x3, y3)
988       // and then counter clockwise to the end-angle that is specified by point (x4, y4).
989       //
990       // T -> angle- ellipseto
991       // (x y w h t0 t1) +
992       // Draws a segment of an ellipse. The ellipse is specified by
993       // the center(x, y), the size(w, h) and the start-angle t0 and end-angle t1.
994       sttEllipticArcTo, sttRelativeEllipticArcTo, sttEllipticArcToWithAngle:
995       begin
996         CurToken := TSVGToken(lTokenizer.Tokens.Items[j+1]);
997         x1 := CurToken.Value;
998         CurToken := TSVGToken(lTokenizer.Tokens.Items[j+2]);
999         y1 := CurToken.Value;
1000         CurToken := TSVGToken(lTokenizer.Tokens.Items[j+3]);
1001         x2 := ResolveEnhancedGeometryFormula(CurToken, AInfo) / 2;
1002         CurToken := TSVGToken(lTokenizer.Tokens.Items[j+4]);
1003         y2 := ResolveEnhancedGeometryFormula(CurToken, AInfo) / 2;
1004         CurToken := TSVGToken(lTokenizer.Tokens.Items[j+5]);
1005         t1 := CurToken.Value;
1006         t1 := DegToRad(t1);
1007         CurToken := TSVGToken(lTokenizer.Tokens.Items[j+6]);
1008         t2 := CurToken.Value;
1009         t2 := DegToRad(t2);
1010 
1011         ConvertViewBoxCoordinatesToFPVCoordinates(AData, AInfo, x1, y1, x1, y1);
1012 
1013         ConvertViewBoxDeltaToFPVDelta(AInfo, x2, y2, x2, y2);
1014 
1015         // Parametrized Ellipse equation
1016         lSrcX := x2 * Cos(t1) + x1;
1017         lSrcY := y2 * Sin(t1) + y1;
1018         lDestX := x2 * Cos(t2) + x1;
1019         lDestY := y2 * Sin(t2) + y1;
1020 
1021         // See http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
1022         ADest.AppendMoveToSegment(lSrcX, lSrcY);
1023         ADest.AppendEllipticalArcWithCenter(x2, y2, 0, lDestX, lDestY, x1, y1, t2 > t1);
1024 
1025         Inc(j, 7);
1026       end;
1027       {// numbers
1028       sttFloatValue);}
1029       else
1030         Inc(j);
1031         //raise Exception.Create('[TvODGVectorialReader.ConvertPathStringToTPath] Unexpected token type!');
1032       end;
1033     end;
1034   finally
1035     lTokenizer.Free;
1036   end;
1037 end;
1038 
ResolveEnhancedGeometryFormulanull1039 function TvODGVectorialReader.ResolveEnhancedGeometryFormula(AInput: TSVGToken;
1040   AInfo: TCustomShapeInfo): Double;
1041 var
1042   i: Integer;
1043   lStr: string;
1044 begin
1045   Result := 0;
1046 
1047   case AInput.TokenType of
1048   sttFloatValue: Result := AInput.Value;
1049   sttNamedEquation:
1050   begin
1051     for i := 0 to Length(AInfo.Formulas)-1 do
1052     begin
1053       lStr := '?' + AInfo.Formulas[i].Name;
1054       if lStr = AInput.StrValue then
1055         Result := AInfo.Formulas[i].CalculateRPNFormulaValue();
1056     end;
1057   end;
1058   end;
1059 end;
1060 
1061 procedure TvODGVectorialReader.ReadElement(ANode: TDOMNode;
1062   AData: TvVectorialPage; ADoc: TvVectorialDocument);
1063 var
1064   Str: String;
1065 begin
1066   Str := LowerCase(ANode.NodeName);
1067   case Str of
1068   'draw:custom-shape': ReadCustomShapeNode(ANode, AData, ADoc);
1069   'draw:ellipse': ReadEllipseNode(ANode, AData, ADoc);
1070   'draw:frame': ReadFrameNode(ANode, AData, ADoc);
1071   'draw:line': ReadLineNode(ANode, AData, ADoc);
1072   'draw:path': ReadPathNode(ANode, AData, ADoc);
1073   end;
1074 end;
1075 
1076 {
1077 <draw:custom-shape draw:style-name="gr1" draw:text-style-name="P1" draw:layer="layout"
1078  svg:width="5.2cm" svg:height="1.3cm" svg:x="2.6cm" svg:y="3cm">
1079   <text:p text:style-name="P1">Rectangle</text:p>
1080   <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:type="rectangle"
1081    draw:enhanced-path="M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N"/>
1082 </draw:custom-shape>
1083 }
1084 procedure TvODGVectorialReader.ReadCustomShapeNode(ANode: TDOMNode;
1085   AData: TvVectorialPage; ADoc: TvVectorialDocument);
1086 var
1087   x1, y1, x2, y2, lWidth, lHeight: double;
1088   i: Integer;
1089   lNodeName, lNodeValue: string;
1090   lCurNode: TDOMNode;
1091   lSkewX, lSkewY, lRotate, lTranslateX, lTranslateY: Double;
1092   // various possible custom shape types
1093   lGroup: TvEntityWithSubEntities;
1094   lPath: TPath;
1095   lText: TvText;
1096   lInfo: TCustomShapeInfo;
1097 begin
1098   x1 := 0.0;
1099   y1 := 0.0;
1100   x2 := 0.0;
1101   y2 := 0.0;
1102   lWidth := 0.0;
1103   lHeight := 0.0;
1104 
1105   lSkewX := 0.0;
1106   lSkewY := 0.0;
1107   lRotate := 0.0;
1108   lTranslateX := 0.0;
1109   lTranslateY := 0.0;
1110 
1111   lGroup := TvEntityWithSubEntities.Create(AData);
1112   lPath := TPath.Create(Adata);
1113   lGroup.AddEntity(lPath);
1114 
1115   // read the attributes
1116   for i := 0 to ANode.Attributes.Length - 1 do
1117   begin
1118     lNodeName := ANode.Attributes.Item[i].NodeName;
1119     lNodeValue := ANode.Attributes.Item[i].NodeValue;
1120     if lNodeName = 'svg:width' then
1121       lWidth := StringWithUnitToFloat(lNodeValue)
1122     else if lNodeName = 'svg:height' then
1123       lHeight := StringWithUnitToFloat(lNodeValue)
1124     else if lNodeName = 'draw:transform' then
1125       GetDrawTransforms(ANode.Attributes.Item[i].NodeValue, lSkewX, lSkewY, lRotate, lTranslateX, lTranslateY)
1126     else if lNodeName = 'svg:x' then
1127       x1 := StringWithUnitToFloat(lNodeValue)
1128     else if lNodeName = 'svg:y' then
1129       y1 := StringWithUnitToFloat(lNodeValue)
1130     else if lNodeName = 'draw:style-name' then
1131       ApplyStyleByNameToEntity(lNodeValue, lPath)
1132     else if lNodeName = 'draw:text-style-name' then
1133       ApplyTextStyleByNameToEntity(lNodeValue, lPath);
1134 //    else if lNodeName = 'id' then
1135 //      lEllipse.Name := UTF16ToUTF8(ANode.Attributes.Item[i].NodeValue)
1136   end;
1137 
1138   ConvertMilimiterCoordinatesToFPVCoordinates(AData, x1, y1, x2, y2);
1139 
1140   // Go through sub-nodes
1141   lCurNode := ANode.FirstChild;
1142   while lCurNode <> nil do
1143   begin
1144     lNodeName := lCurNode.NodeName;
1145 
1146     case lNodeName of
1147     'text:p':
1148     begin
1149       if lCurNode.FirstChild <> nil then
1150       begin
1151         lText := TvText.Create(AData);
1152         lNodeValue := lCurNode.FirstChild.NodeValue;
1153         lText.Value.Add(lNodeValue);
1154         lGroup.AddEntity(lText);
1155       end;
1156     end;
1157     'draw:enhanced-geometry':
1158     begin
1159       lInfo := TCustomShapeInfo.Create;
1160       try
1161         lInfo.Left := x1 + lTranslateX;
1162         lInfo.Top := y1 + lTranslateY;
1163         lInfo.Width := lWidth;
1164         lInfo.Height := lHeight;
1165         ReadEnhancedGeometryNodeToTPath(lCurNode, AData, lPath, x1, y1, lInfo);
1166       finally
1167         lInfo.Free;
1168       end;
1169     end;
1170     end;
1171 
1172     lCurNode := lCurNode.NextSibling;
1173   end;
1174 
1175   AData.AddEntity(lGroup);
1176 end;
1177 
1178 {
1179   <draw:ellipse
1180     draw:style-name="gr2" draw:text-style-name="P1" draw:layer="layout"
1181     svg:width="11cm" svg:height="3cm" svg:x="5.5cm" svg:y="6.5cm">
1182     <text:p/>
1183   </draw:ellipse>
1184 }
1185 procedure TvODGVectorialReader.ReadEllipseNode(ANode: TDOMNode;
1186   AData: TvVectorialPage; ADoc: TvVectorialDocument);
1187 var
1188   cx, cy, crx, cry: double;
1189   lEllipse: TvEllipse;
1190   i: Integer;
1191   lNodeName: DOMString;
1192 begin
1193   cx := 0.0;
1194   cy := 0.0;
1195   crx := 0.0;
1196   cry := 0.0;
1197 
1198   lEllipse := TvEllipse.Create(AData);
1199   // SVG entities start without any pen drawing, but with a black brush
1200   lEllipse.Pen.Style := psClear;
1201   lEllipse.Brush.Style := bsSolid;
1202   lEllipse.Brush.Color := colBlack;
1203 
1204   // read the attributes
1205   for i := 0 to ANode.Attributes.Length - 1 do
1206   begin
1207     lNodeName := ANode.Attributes.Item[i].NodeName;
1208     if  lNodeName = 'svg:x' then
1209       cx := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
1210     else if lNodeName = 'svg:y' then
1211       cy := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
1212     else if lNodeName = 'svg:width' then
1213       crx := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue) / 2
1214     else if lNodeName = 'svg:height' then
1215       cry := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue) / 2
1216     else if lNodeName = 'draw:style-name' then
1217       ApplyStyleByNameToEntity(ANode.Attributes.Item[i].NodeValue, lEllipse)
1218     else if lNodeName = 'draw:text-style-name' then
1219       ApplyTextStyleByNameToEntity(ANode.Attributes.Item[i].NodeValue, lEllipse)
1220     //    else if lNodeName = 'id' then
1221     //      lEllipse.Name := UTF16ToUTF8(ANode.Attributes.Item[i].NodeValue)
1222     else
1223       ApplyGraphicAttributeToEntity(lNodeName, ANode.Attributes.Item[i].NodeValue, lEllipse);
1224   end;
1225 
1226   // The svg:x and svg:y coordinates are relative to the top-left in ODG,
1227   // but in fpvectorial we use the center, so correct now
1228   cx := cx + crx;
1229   cy := cy + cry;
1230 
1231   ConvertODGCoordinatesToFPVCoordinates(
1232         AData, cx, cy, lEllipse.X, lEllipse.Y);
1233   ConvertODGDeltaToFPVDelta(
1234         AData, crx, cry, lEllipse.HorzHalfAxis, lEllipse.VertHalfAxis);
1235 
1236   AData.AddEntity(lEllipse);
1237 end;
1238 
1239 {
1240 <draw:frame draw:style-name="gr12" draw:text-style-name="P2" draw:layer="layout" svg:width="4.5cm" svg:height="1.25cm" svg:x="4cm" svg:y="20cm">
1241   <draw:text-box>
1242     <text:p text:style-name="P2">
1243       <text:span text:style-name="T1">Kesäyö</text:span>
1244     </text:p>
1245   </draw:text-box>
1246 </draw:frame>
1247 }
1248 procedure TvODGVectorialReader.ReadFrameNode(ANode: TDOMNode;
1249   AData: TvVectorialPage; ADoc: TvVectorialDocument);
1250 var
1251   x1, y1, x2, y2, lWidth, lHeight: double;
1252   i: Integer;
1253   lNodeName, lNodeValue, lSubNodeName, lSubNodeValue: string;
1254   lCurNode, lCurSubNode: TDOMNode;
1255   lSkewX, lSkewY, lRotate, lTranslateX, lTranslateY: Double;
1256   // various possible frame types contents
1257   lGroup: TvEntityWithSubEntities;
1258   lBorder: TvRectangle;
1259   lRichText: TvRichText;
1260 begin
1261   x1 := 0.0;
1262   y1 := 0.0;
1263   x2 := 0.0;
1264   y2 := 0.0;
1265   lWidth := 0.0;
1266   lHeight := 0.0;
1267 
1268   lSkewX := 0.0;
1269   lSkewY := 0.0;
1270   lRotate := 0.0;
1271   lTranslateX := 0.0;
1272   lTranslateY := 0.0;
1273 
1274   lGroup := TvEntityWithSubEntities.Create(AData);
1275   lBorder := TvRectangle.Create(Adata);
1276   lGroup.AddEntity(lBorder);
1277 
1278   // read the attributes
1279   for i := 0 to ANode.Attributes.Length - 1 do
1280   begin
1281     lNodeName := ANode.Attributes.Item[i].NodeName;
1282     lNodeValue := ANode.Attributes.Item[i].NodeValue;
1283     if lNodeName = 'svg:width' then
1284       lWidth := StringWithUnitToFloat(lNodeValue)
1285     else if lNodeName = 'svg:height' then
1286       lHeight := StringWithUnitToFloat(lNodeValue)
1287     //else if lNodeName = 'draw:transform' then
1288       //GetDrawTransforms(ANode.Attributes.Item[i].NodeValue, lSkewX, lSkewY, lRotate, lTranslateX, lTranslateY)
1289     else if lNodeName = 'svg:x' then
1290       x1 := StringWithUnitToFloat(lNodeValue)
1291     else if lNodeName = 'svg:y' then
1292       y1 := StringWithUnitToFloat(lNodeValue)
1293     //else if lNodeName = 'draw:style-name' then
1294       //ApplyStyleByNameToEntity(lNodeValue, lPath)
1295     //else if lNodeName = 'draw:text-style-name' then
1296       //ApplyTextStyleByNameToEntity(lNodeValue, lPath);
1297 //    else if lNodeName = 'id' then
1298 //      lEllipse.Name := UTF16ToUTF8(ANode.Attributes.Item[i].NodeValue)
1299   end;
1300 
1301   ConvertMilimiterCoordinatesToFPVCoordinates(AData, x1, y1, x2, y2);
1302   lGroup.X := x2;
1303   lGroup.Y := y2;
1304 
1305   // Go through sub-nodes
1306   lCurNode := ANode.FirstChild;
1307   while lCurNode <> nil do
1308   begin
1309     lNodeName := lCurNode.NodeName;
1310 
1311     case lNodeName of
1312     'draw:text-box':
1313     begin
1314       lRichText := ReadTextPSequenceNode(lCurNode, AData, ADoc);
1315       lRichText.X := x2;
1316       lRichText.Y := y2;
1317       lGroup.AddEntity(lRichText);
1318     end;
1319     end;
1320 
1321     lCurNode := lCurNode.NextSibling;
1322   end;
1323 
1324   AData.AddEntity(lGroup);
1325 end;
1326 
1327 {
1328 <draw:line draw:style-name="gr9" draw:text-style-name="P1" draw:layer="layout"
1329  svg:x1="14cm" svg:y1="22.5cm" svg:x2="14cm" svg:y2="23.5cm"><text:p/></draw:line>
1330 <draw:line draw:style-name="gr9" draw:text-style-name="P1" draw:layer="layout"
1331  svg:x1="14cm" svg:y1="23.5cm" svg:x2="13.5cm" svg:y2="25.5cm"><text:p/></draw:line>
1332 }
1333 procedure TvODGVectorialReader.ReadLineNode(ANode: TDOMNode;
1334   AData: TvVectorialPage; ADoc: TvVectorialDocument);
1335 var
1336   x1, y1, x2, y2: double;
1337   lPath: TPath;
1338   i: Integer;
1339   lNodeName, lNodeValue: DOMString;
1340 begin
1341   x1 := 0.0;
1342   y1 := 0.0;
1343   x2 := 0.0;
1344   y2 := 0.0;
1345 
1346   lPath := TPath.Create(nil);
1347 
1348   // read the attributes
1349   for i := 0 to ANode.Attributes.Length - 1 do
1350   begin
1351     lNodeName := ANode.Attributes.Item[i].NodeName;
1352     lNodeValue := ANode.Attributes.Item[i].NodeValue;
1353     if  lNodeName = 'svg:x1' then
1354       x1 := StringWithUnitToFloat(lNodeValue)
1355     else if lNodeName = 'svg:y1' then
1356       y1 := StringWithUnitToFloat(lNodeValue)
1357     else if lNodeName = 'svg:x2' then
1358       x2 := StringWithUnitToFloat(lNodeValue)
1359     else if lNodeName = 'svg:y2' then
1360       y2 := StringWithUnitToFloat(lNodeValue)
1361     else if lNodeName = 'draw:style-name' then
1362       ApplyStyleByNameToEntity(lNodeValue, lPath)
1363     else if lNodeName = 'draw:text-style-name' then
1364       ApplyTextStyleByNameToEntity(lNodeValue, lPath)
1365 //    else if lNodeName = 'id' then
1366 //      lEllipse.Name := UTF16ToUTF8(lNodeValue)
1367     else
1368       ApplyGraphicAttributeToEntity(lNodeName, lNodeValue, lPath);
1369   end;
1370 
1371   ConvertODGCoordinatesToFPVCoordinates(
1372         AData, x1, y1, x1, y1);
1373   ConvertODGCoordinatesToFPVCoordinates(
1374         AData, x2, y2, x2, y2);
1375 
1376   lPath.AppendMoveToSegment(x1, y1);
1377   lPath.AppendLineToSegment(x2, y2);
1378   AData.AddEntity(lPath);
1379 end;
1380 
1381 {
1382 <draw:path draw:style-name="gr11" draw:text-style-name="P1" draw:layer="layout"
1383  svg:width="9.429cm" svg:height="4.491cm"
1384  draw:transform="skewX (-4.3967693286338E-017) rotate (0.417482757076965) translate (6.73314019682066cm 26.0928070675985cm)"
1385  svg:viewBox="0 0 9430 4492" svg:d="m0 5c688-5 1345-23 2075 66 1374 167-412 989 814 1282 591 141 1129 504 1795 401 694-107 1142 607 1686 945 551 342 1077 719 1509 1195l501 355 549 243 501-238">
1386  <text:p/></draw:path>
1387 }
1388 procedure TvODGVectorialReader.ReadPathNode(ANode: TDOMNode;
1389   AData: TvVectorialPage; ADoc: TvVectorialDocument);
1390 var
1391   x1, y1, x2, y2, lWidth, lHeight: double;
1392   lPath: TPath;
1393   i: Integer;
1394   lNodeName: DOMString;
1395   lSkewX, lSkewY, lRotate, lTranslateX, lTranslateY: Double;
1396 begin
1397   x1 := 0.0;
1398   y1 := 0.0;
1399   x2 := 0.0;
1400   y2 := 0.0;
1401   lWidth := 0.0;
1402   lHeight := 0.0;
1403 
1404   lPath := TPath.Create(nil);
1405 
1406   // read the attributes
1407   for i := 0 to ANode.Attributes.Length - 1 do
1408   begin
1409     lNodeName := ANode.Attributes.Item[i].NodeName;
1410     if  lNodeName = 'svg:width' then
1411       lWidth := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
1412     else if lNodeName = 'svg:height' then
1413       lHeight := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
1414     else if lNodeName = 'draw:transform' then
1415       GetDrawTransforms(ANode.Attributes.Item[i].NodeValue, lSkewX, lSkewY, lRotate, lTranslateX, lTranslateY)
1416     else if lNodeName = 'svg:d' then
1417       ParsePathString(ANode.Attributes.Item[i].NodeValue, lPath)
1418     else if lNodeName = 'draw:style-name' then
1419       ApplyStyleByNameToEntity(ANode.Attributes.Item[i].NodeValue, lPath)
1420     else if lNodeName = 'draw:text-style-name' then
1421       ApplyTextStyleByNameToEntity(ANode.Attributes.Item[i].NodeValue, lPath)
1422 //    else if lNodeName = 'id' then
1423 //      lEllipse.Name := UTF16ToUTF8(ANode.Attributes.Item[i].NodeValue)
1424     else
1425       ApplyGraphicAttributeToEntity(lNodeName, ANode.Attributes.Item[i].NodeValue, lPath);
1426   end;
1427 
1428   ConvertODGCoordinatesToFPVCoordinates(
1429         AData, x1, y1, x1, y1);
1430   ConvertODGCoordinatesToFPVCoordinates(
1431         AData, x2, y2, x2, y2);
1432   ConvertODGDeltaToFPVDelta(
1433         AData, lWidth, lHeight, lWidth, lHeight);
1434 
1435   AData.AddEntity(lPath);
1436 end;
1437 
ReadTextPSequenceNodenull1438 function TvODGVectorialReader.ReadTextPSequenceNode(ANode: TDOMNode;
1439   AData: TvVectorialPage; ADoc: TvVectorialDocument): TvRichText;
1440 var
1441   lNodeName, lNodeValue: string;
1442   lCurNode: TDOMNode;
1443   lParagraph: TvParagraph;
1444 begin
1445   Result := TvRichText.Create(AData);
1446 
1447   lCurNode := ANode.FirstChild;
1448   while lCurNode <> nil do
1449   begin
1450     lNodeName := lCurNode.NodeName;
1451     lNodeValue := lCurNode.NodeValue;
1452 
1453     case lNodeName of
1454     'text:p':
1455     begin
1456       lParagraph := ReadTextPNode(lCurNode, AData, ADoc);
1457       Result.AddEntity(lParagraph);
1458     end;
1459     end;
1460 
1461     lCurNode := lCurNode.NextSibling;
1462   end;
1463 end;
1464 
1465 {
1466 Examples:
1467 
1468 <text:p text:style-name="P2">
1469   <text:span text:style-name="T1">Kesäyö</text:span>
1470 </text:p>
1471 
1472 <text:p>Jump opposite arm and leg up</text:p>
1473 
1474 <text:p>
1475   <text:span text:style-name="T1">Back muscle movement</text:span>
1476   <text:span text:style-name="T2">opposite</text:span>
1477   <text:span text:style-name="T3">arm</text:span>
1478   and
1479   <text:span text:style-name="T3">leg</text:span>
1480   up
1481 </text:p>
1482 }
ReadTextPNodenull1483 function TvODGVectorialReader.ReadTextPNode(ANode: TDOMNode;
1484   AData: TvVectorialPage; ADoc: TvVectorialDocument): TvParagraph;
1485 var
1486   lNodeName, lNodeValue: string;
1487   lCurNode: TDOMNode;
1488 begin
1489   Result := TvParagraph.Create(AData);
1490 
1491   lCurNode := ANode.FirstChild;
1492   while lCurNode <> nil do
1493   begin
1494     lNodeName := lCurNode.NodeName;
1495     lNodeValue := lCurNode.NodeValue;
1496 
1497     // Check if this is a text:span
1498     if (lNodeValue = '') and (lCurNode.FirstChild <> nil) then
1499     begin
1500       lNodeValue := lCurNode.FirstChild.NodeValue;
1501     end;
1502 
1503     Result.AddText(lNodeValue);
1504 
1505     lCurNode := lCurNode.NextSibling;
1506   end;
1507 end;
1508 
1509 {
1510 <office:master-styles>
1511   <style:master-page style:name="Oletus" style:page-layout-name="PM0" draw:style-name="Mdp1"/>
1512 </office:master-styles>
1513 }
1514 procedure TvODGVectorialReader.ReadStylesMasterPage(ANode: TDOMNode;
1515   AData: TvVectorialPage; ADoc: TvVectorialDocument);
1516 var
1517   lMasterPage: TODGMasterPage;
1518   i: Integer;
1519   lNodeName, lNodeValue: string;
1520 begin
1521   lMasterPage := TODGMasterPage.Create;
1522 
1523   // Read properties
1524   for i := 0 to ANode.Attributes.Length - 1 do
1525   begin
1526     lNodeName := LowerCase(ANode.Attributes.Item[i].NodeName);
1527     lNodeValue := ANode.Attributes.Item[i].NodeValue;
1528 
1529     case lNodeName of
1530     'style:name': lMasterPage.Name := lNodeValue;
1531     'style:page-layout-name': lMasterPage.PageLayoutName := lNodeValue;
1532     'draw:style-name': lMasterPage.StyleName := lNodeValue;
1533     end;
1534   end;
1535   FMasterPages.Add(lMasterPage);
1536 end;
1537 
1538 {
1539 <style:page-layout style:name="PM0">
1540   <style:page-layout-properties fo:margin-top="1cm" fo:margin-bottom="1cm"
1541    fo:margin-left="1cm" fo:margin-right="1cm"
1542    fo:page-width="21cm" fo:page-height="29.7cm"
1543    style:print-orientation="portrait"/>
1544 </style:page-layout>
1545 }
1546 procedure TvODGVectorialReader.ReadStylesPageLayout(ANode: TDOMNode;
1547   AData: TvVectorialPage; ADoc: TvVectorialDocument);
1548 var
1549   lPageLayout: TODGPageLayout;
1550   lPageLayoutPropertiesNode: TDOMNode;
1551   i: Integer;
1552   lNodeName, lNodeValue: string;
1553 begin
1554   lPageLayout := TODGPageLayout.Create;
1555 
1556   // Read properties
1557   for i := 0 to ANode.Attributes.Length - 1 do
1558   begin
1559     lNodeName := LowerCase(ANode.Attributes.Item[i].NodeName);
1560     lNodeValue := ANode.Attributes.Item[i].NodeValue;
1561 
1562     case lNodeName of
1563     'style:name':  lPageLayout.Name := lNodeValue;
1564     end;
1565   end;
1566 
1567   // Read properties in the internal item
1568   lPageLayoutPropertiesNode := ANode.FindNode('style:page-layout-properties');
1569   if lPageLayoutPropertiesNode <> nil then
1570   begin
1571     for i := 0 to lPageLayoutPropertiesNode.Attributes.Length - 1 do
1572     begin
1573       lNodeName := LowerCase(lPageLayoutPropertiesNode.Attributes.Item[i].NodeName);
1574       lNodeValue := lPageLayoutPropertiesNode.Attributes.Item[i].NodeValue;
1575 
1576       case lNodeName of
1577       'fo:margin-top':  lPageLayout.MarginTop := StringWithUnitToFloat(lNodeValue);
1578       'fo:margin-bottom':lPageLayout.MarginBottom := StringWithUnitToFloat(lNodeValue);
1579       'fo:margin-left': lPageLayout.MarginLeft := StringWithUnitToFloat(lNodeValue);
1580       'fo:margin-right':lPageLayout.MarginRight := StringWithUnitToFloat(lNodeValue);
1581       'fo:page-width':  lPageLayout.PageWidth := StringWithUnitToFloat(lNodeValue);
1582       'fo:page-height': lPageLayout.PageHeight := StringWithUnitToFloat(lNodeValue);
1583       end;
1584     end;
1585   end;
1586   FPageLayouts.Add(lPageLayout);
1587 end;
1588 
1589 procedure TvODGVectorialReader.ParsePathString(AInputStr: string; ADest: TPath);
1590 begin
1591 
1592 end;
1593 
1594 procedure TvODGVectorialReader.GetDrawTransforms(AInputStr: string; out ASkewX,
1595   ASkewY, ARotate, ATranslateX, ATranslateY: Double);
1596 var
1597   // transform
1598   MA, MB, MC, MD, ME, MF: Double;
1599   lMTranslateX, lMTranslateY, lMScaleX, lMScaleY, lMSkewX, lMSkewY, lMRotate: Double;
1600   lTokenizer: TSVGPathTokenizer;
1601   i: Integer;
1602   lFunctionName, lParamStr: string;
1603   lMatrixElements: array of Double;
1604   lMatrixStrElements: TStringList;
1605 begin
1606   ASkewX := 0.0;
1607   ASkewY := 0.0;
1608   ARotate := 0.0;
1609   ATranslateX := 0.0;
1610   ATranslateY := 0.0;
1611 
1612   // Examples:
1613   // transform="matrix(0.860815 0 -0 1.07602 339.302 489.171)"
1614   // transform="scale(0.24) translate(0, 35)"
1615   // transform="rotate(90)"
1616   lTokenizer := TSVGPathTokenizer.Create;
1617   try
1618     lTokenizer.TokenizeFunctions(AInputStr);
1619 
1620     i := 0;
1621     while i < lTokenizer.Tokens.Count-1 do
1622     begin
1623       lFunctionName := Trim(lTokenizer.Tokens.Items[i].StrValue);
1624       lParamStr := lTokenizer.Tokens.Items[i+1].StrValue;
1625       //lMatrixElements := ReadSpaceSeparatedFloats(lParamStr, ',');
1626 
1627       if lFunctionName = 'matrix' then
1628       begin
1629         {ReadSVGTransformationMatrix(lParamStr, MA, MB, MC, MD, ME, MF);
1630 
1631         ConvertTransformationMatrixToOperations(MA, MB, MC, MD, ME, MF,
1632           lMTranslateX, lMTranslateY, lMScaleX, lMScaleY, lMSkewX, lMSkewY, lMRotate);
1633 
1634         ConvertSVGDeltaToFPVDelta(nil,
1635           lMTranslateX, lMTranslateY,
1636           lMTranslateX, lMTranslateY);
1637 
1638         ADestEntity.Move(lMTranslateX, lMTranslateY);
1639         ADestEntity.Scale(lMScaleX, lMScaleY); }
1640       end
1641       else if lFunctionName = 'scale' then
1642       begin
1643         ;
1644       end
1645       else if lFunctionName = 'translate' then
1646       begin
1647         lMatrixStrElements := ReadSpaceSeparatedStrings(lParamStr, ',');
1648         try
1649           ATranslateX := StringWithUnitToFloat(lMatrixStrElements.Strings[0]);
1650           ATranslateY := StringWithUnitToFloat(lMatrixStrElements.Strings[1]);
1651         finally
1652           lMatrixStrElements.Free;
1653         end;
1654         //ADestEntity.Move(lMatrixElements[0], lMatrixElements[1]);
1655       end
1656       else if lFunctionName = 'rotate' then
1657       begin
1658         //ADestEntity.Rotate(lMatrixElements[0], Make3DPoint(0, 0, 0));
1659       end;
1660 
1661       Inc(i, 2);
1662     end;
1663   finally
1664     lTokenizer.Free;
1665   end;
1666 end;
1667 
ReadSVGColornull1668 function TvODGVectorialReader.ReadSVGColor(AValue: string): TFPColor;
1669 var
1670   lValue, lStr: string;
1671   lStrings: TStringList;
1672 begin
1673   Result := colBlack;
1674   lValue := Trim(LowerCase(AValue));
1675 
1676   // Support for rgb(255,255,0)
1677   if (Length(lValue) > 3) and (Copy(lValue, 0, 3) = 'rgb') then
1678   begin
1679     lStrings := TStringList.Create;
1680     try
1681       lStr := Copy(lValue, 5, Length(lValue)-5);
1682       lStrings.Delimiter := ',';
1683       lStrings.DelimitedText := lStr;
1684       if lStrings.Count = 3 then
1685       begin
1686         Result.Red := StrToInt(lStrings.Strings[0]) * $101;
1687         Result.Blue := StrToInt(lStrings.Strings[1]) * $101;
1688         Result.Green := StrToInt(lStrings.Strings[2]) * $101;
1689       end
1690       else
1691         raise Exception.Create(Format('[TvSVGVectorialReader.ReadSVGColor] An unexpected number of channels was found: %d', [lStrings.Count]));
1692     finally
1693       lStrings.Free;
1694     end;
1695     Exit;
1696   end;
1697 
1698   // Support for RGB hex
1699   // ex: #0000ff
1700   // Another wierd valid variant: #000
1701   if (Length(lValue) > 1) and (lValue[1] = '#') then
1702   begin
1703     lStr := Copy(lValue, 2, 2);
1704     Result.Red := StrToInt('$'+lStr)*$101;
1705     lStr := Copy(lValue, 4, 2);
1706     if lStr = '' then Result.Green := 0
1707     else Result.Green := StrToInt('$'+lStr)*$101;
1708     lStr := Copy(lValue, 6, 2);
1709     if lStr = '' then Result.Blue := 0
1710     else Result.Blue := StrToInt('$'+lStr)*$101;
1711     Exit;
1712   end;
1713 
1714   // Support for named colors
1715   // List here: http://www.december.com/html/spec/colorsvghex.html
1716   case lValue of
1717   'black':   Result := colBlack;
1718   'navy':    Result.Blue := $8080;
1719   'darkblue':Result.Blue := $8B8B;
1720   'mediumblue':Result.Blue := $CDCD;
1721   'blue':    Result := colBlue;
1722   'darkgreen':Result.Green := $6464;
1723   'green':   Result.Green := $8080;
1724   'teal':
1725   begin
1726     Result.Green := $8080;
1727     Result.Blue := $8080;
1728   end;
1729   'darkcyan':
1730   begin
1731     Result.Green := $8B8B;
1732     Result.Blue := $8B8B;
1733   end;
1734   'deepskyblue':
1735   begin
1736     Result.Green := $BFBF;
1737     Result.Blue := $FFFF;
1738   end;
1739   'darkturquoise':
1740   begin
1741     Result.Green := $CECE;
1742     Result.Blue := $D1D1;
1743   end;
1744   'mediumspringgreen':
1745   begin
1746     Result.Green := $FAFA;
1747     Result.Blue := $9A9A;
1748   end;
1749   'lime': Result := colGreen;
1750   'springgreen':
1751   begin
1752     Result.Green := $FFFF;
1753     Result.Blue := $7F7F;
1754   end;
1755   'cyan':   Result := colCyan;
1756   'aqua':   Result := colCyan;
1757   'midnightblue':
1758   begin
1759     Result.Red := $1919;
1760     Result.Green := $1919;
1761     Result.Blue := $7070;
1762   end;
1763   'dodgerblue':
1764   begin
1765     Result.Red := $1E1E;
1766     Result.Green := $9090;
1767     Result.Blue := $FFFF;
1768   end;
1769   'lightseagreen':
1770   begin
1771     Result.Red := $2020;
1772     Result.Green := $B2B2;
1773     Result.Blue := $AAAA;
1774   end;
1775   'forestgreen':
1776   begin
1777     Result.Red := $2222;
1778     Result.Green := $8B8B;
1779     Result.Blue := $2222;
1780   end;
1781   'seagreen':
1782   begin
1783     Result.Red := $2E2E;
1784     Result.Green := $8B8B;
1785     Result.Blue := $5757;
1786   end;
1787   'darkslategray', 'darkslategrey':
1788   begin
1789     Result.Red := $2F2F;
1790     Result.Green := $4F4F;
1791     Result.Blue := $4F4F;
1792   end;
1793   'limegreen':
1794   begin
1795     Result.Red := $3232;
1796     Result.Green := $CDCD;
1797     Result.Blue := $3232;
1798   end;
1799   'mediumseagreen':
1800   begin
1801     Result.Red := $3C3C;
1802     Result.Green := $CBCB;
1803     Result.Blue := $7171;
1804   end;
1805   'turquoise':
1806   begin
1807     Result.Red := $4040;
1808     Result.Green := $E0E0;
1809     Result.Blue := $D0D0;
1810   end;
1811   'royalblue':
1812   begin
1813     Result.Red := $4141;
1814     Result.Green := $6969;
1815     Result.Blue := $E1E1;
1816   end;
1817   'steelblue':
1818   begin
1819     Result.Red := $4646;
1820     Result.Green := $8282;
1821     Result.Blue := $B4B4;
1822   end;
1823   'darkslateblue':
1824   begin
1825     Result.Red := $4848;
1826     Result.Green := $3D3D;
1827     Result.Blue := $8B8B;
1828   end;
1829   'mediumturquoise':
1830   begin
1831     Result.Red := $4848;
1832     Result.Green := $D1D1;
1833     Result.Blue := $CCCC;
1834   end;
1835 {
1836 indigo #4B0082
1837  	darkolivegreen #556B2F		cadetblue #5F9EA0
1838 cornflowerblue #6495ED
1839  	mediumaquamarine #66CDAA		dimgrey #696969
1840 dimgray #696969
1841  	slateblue #6A5ACD		olivedrab #6B8E23
1842 slategrey #708090
1843  	slategray #708090		lightslategray(Hex3) #778899
1844 lightslategrey(Hex3) #778899
1845  	mediumslateblue #7B68EE		lawngreen #7CFC00
1846 chartreuse #7FFF00
1847 }
1848   'aquamarine':
1849   begin
1850     Result.Red := $7F7F;
1851     Result.Green := $FFFF;
1852     Result.Blue := $D4D4;
1853   end;
1854   'maroon': Result.Red := $8080;
1855   'purple': Result := colPurple;
1856   'olive':  Result := colOlive;
1857   'gray', 'grey': Result := colGray;
1858   'skyblue':
1859   begin
1860     Result.Red := $8787;
1861     Result.Green := $CECE;
1862     Result.Blue := $EBEB;
1863   end;
1864   'lightskyblue':
1865   begin
1866     Result.Red := $8787;
1867     Result.Green := $CECE;
1868     Result.Blue := $FAFA;
1869   end;
1870   'blueviolet':
1871   begin
1872     Result.Red := $8A8A;
1873     Result.Green := $2B2B;
1874     Result.Blue := $E2E2;
1875   end;
1876   'darkred': Result.Red := $8B8B;
1877   'darkmagenta':
1878   begin
1879     Result.Red := $8B8B;
1880     Result.Blue := $8B8B;
1881   end;
1882 {
1883 saddlebrown #8B4513
1884  	darkseagreen #8FBC8F		lightgreen #90EE90
1885 mediumpurple #9370DB
1886  	darkviolet #9400D3		palegreen #98FB98
1887 darkorchid #9932CC
1888  	yellowgreen #9ACD32		sienna #A0522D
1889 brown #A52A2A
1890  	darkgray #A9A9A9		darkgrey #A9A9A9
1891 lightblue #ADD8E6
1892  	greenyellow #ADFF2F		paleturquoise #AFEEEE
1893 lightsteelblue #B0C4DE
1894  	powderblue #B0E0E6		firebrick #B22222
1895 darkgoldenrod #B8860B
1896  	mediumorchid #BA55D3		rosybrown #BC8F8F
1897 darkkhaki #BDB76B
1898 }
1899   'silver': Result := colSilver;
1900   'mediumvioletred':
1901   begin
1902     Result.Red := $C7C7;
1903     Result.Green := $1515;
1904     Result.Blue := $8585;
1905   end;
1906   'indianred':
1907   begin
1908     Result.Red := $CDCD;
1909     Result.Green := $5C5C;
1910     Result.Blue := $5C5C;
1911   end;
1912   'peru':
1913   begin
1914     Result.Red := $CDCD;
1915     Result.Green := $8585;
1916     Result.Blue := $3F3F;
1917   end;
1918   'chocolate':
1919   begin
1920     Result.Red := $D2D2;
1921     Result.Green := $6969;
1922     Result.Blue := $1E1E;
1923   end;
1924 {
1925 tan #D2B48C
1926  	lightgray #D3D3D3		lightgrey #D3D3D3
1927 thistle #D8BFD8
1928  	orchid #DA70D6		goldenrod #DAA520
1929 palevioletred #DB7093
1930  	crimson #DC143C		gainsboro #DCDCDC
1931 plum #DDA0DD
1932  	burlywood #DEB887		lightcyan #E0FFFF
1933 lavender #E6E6FA
1934 }
1935   'darksalmon':
1936   begin
1937     Result.Red := $E9E9;
1938     Result.Green := $9696;
1939     Result.Blue := $7A7A;
1940   end;
1941   'violet':
1942   begin
1943     Result.Red := $EEEE;
1944     Result.Green := $8282;
1945     Result.Blue := $EEEE;
1946   end;
1947   'palegoldenrod':
1948   begin
1949     Result.Red := $EEEE;
1950     Result.Green := $E8E8;
1951     Result.Blue := $AAAA;
1952   end;
1953   'lightcoral':
1954   begin
1955     Result.Red := $F0F0;
1956     Result.Green := $8080;
1957     Result.Blue := $8080;
1958   end;
1959   'khaki':
1960   begin
1961     Result.Red := $F0F0;
1962     Result.Green := $E6E6;
1963     Result.Blue := $8C8C;
1964   end;
1965   'aliceblue':
1966   begin
1967     Result.Red := $F0F0;
1968     Result.Green := $F8F8;
1969     Result.Blue := $FFFF;
1970   end;
1971   'honeydew':
1972   begin
1973     Result.Red := $F0F0;
1974     Result.Green := $FFFF;
1975     Result.Blue := $F0F0;
1976   end;
1977   'azure':
1978   begin
1979     Result.Red := $F0F0;
1980     Result.Green := $FFFF;
1981     Result.Blue := $FFFF;
1982   end;
1983   'sandybrown':
1984   begin
1985     Result.Red := $F4F4;
1986     Result.Green := $A4A4;
1987     Result.Blue := $6060;
1988   end;
1989 {
1990  	wheat #F5DEB3		beige #F5F5DC
1991 whitesmoke #F5F5F5
1992  	mintcream #F5FFFA		ghostwhite #F8F8FF
1993 salmon #FA8072
1994  	antiquewhite #FAEBD7		linen #FAF0E6
1995 lightgoldenrodyellow #FAFAD2
1996  	oldlace #FDF5E6
1997 }
1998   'red':   Result := colRed;
1999   'fuchsia':   Result := colFuchsia;
2000   'magenta':   Result := colMagenta;
2001 {	deeppink #FF1493
2002 orangered #FF4500
2003  	tomato #FF6347		hotpink #FF69B4
2004 coral #FF7F50
2005  	darkorange #FF8C00		lightsalmon #FFA07A
2006 orange #FFA500
2007  	lightpink #FFB6C1		pink #FFC0CB
2008 gold #FFD700
2009  	peachpuff #FFDAB9		navajowhite #FFDEAD
2010 moccasin #FFE4B5
2011  	bisque #FFE4C4		mistyrose #FFE4E1
2012 blanchedalmond #FFEBCD
2013  	papayawhip #FFEFD5		lavenderblush #FFF0F5
2014 seashell #FFF5EE
2015  	cornsilk #FFF8DC		lemonchiffon #FFFACD
2016 floralwhite #FFFAF0
2017 }
2018   'snow':
2019   begin
2020     Result.Red := $FFFF;
2021     Result.Green := $FAFA;
2022     Result.Blue := $FAFA;
2023   end;
2024   'yellow': Result := colYellow;
2025   'lightyellow':
2026   begin
2027     Result.Red := $FFFF;
2028     Result.Green := $FEFE;
2029   end;
2030   'ivory':
2031   begin
2032     Result.Red := $FFFF;
2033     Result.Green := $FFFF;
2034     Result.Blue := $F0F0;
2035   end;
2036   'white': Result := colWhite;
2037   end;
2038 end;
2039 
GetAttrValuenull2040 function TvODGVectorialReader.GetAttrValue(ANode : TDOMNode; AAttrName : string) : string;
2041 var
2042   i : integer;
2043   Found : Boolean;
2044 begin
2045   Found:=false;
2046   i:=0;
2047   Result:='';
2048   while not Found and (i<ANode.Attributes.Length) do begin
2049     if ANode.Attributes.Item[i].NodeName=AAttrName then begin
2050       Found:=true;
2051       Result:=ANode.Attributes.Item[i].NodeValue;
2052     end;
2053     inc(i);
2054   end;
2055 end;
2056 
StringWithUnitToFloatnull2057 function TvODGVectorialReader.StringWithUnitToFloat(AStr: string): Double;
2058 var
2059   UnitStr, ValueStr: string;
2060   Len: Integer;
2061 begin
2062   if AStr = '' then Exit(0.0);
2063 
2064   // Check the unit
2065   Len := Length(AStr);
2066   UnitStr := Copy(AStr, Len-1, 2);
2067   if UnitStr = 'mm' then
2068   begin
2069     ValueStr := Copy(AStr, 1, Len-2);
2070     Result := StrToFloat(ValueStr, FPointSeparator);
2071   end
2072   else if UnitStr = 'cm' then
2073   begin
2074     ValueStr := Copy(AStr, 1, Len-2);
2075     Result := StrToFloat(ValueStr, FPointSeparator) * 10;
2076   end
2077   else if UnitStr = 'px' then
2078   begin
2079     ValueStr := Copy(AStr, 1, Len-2);
2080     Result := StrToInt(ValueStr);
2081   end
2082   else // If there is no unit, just use StrToFloat
2083   begin
2084     Result := StrToFloat(AStr, FPointSeparator);
2085   end;
2086 end;
2087 
2088 procedure TvODGVectorialReader.ConvertODGCoordinatesToFPVCoordinates(
2089   const AData: TvVectorialPage; const ASrcX, ASrcY: Double;
2090   var ADestX,ADestY: Double);
2091 begin
2092   ADestX := ASrcX * FLOAT_MILIMETERS_PER_PIXEL;
2093   ADestY := AData.Height - ASrcY * FLOAT_MILIMETERS_PER_PIXEL;
2094 end;
2095 
2096 procedure TvODGVectorialReader.ConvertODGDeltaToFPVDelta(
2097   const AData: TvVectorialPage; const ASrcX, ASrcY: Double; var ADestX,
2098   ADestY: Double);
2099 begin
2100   ADestX := ASrcX * FLOAT_MILIMETERS_PER_PIXEL;
2101   ADestY := - ASrcY * FLOAT_MILIMETERS_PER_PIXEL;
2102 end;
2103 
2104 {
2105   For the drawing units see http://stackoverflow.com/questions/15335926/svg-viewbox-attribute
2106 
2107   We should use these formulas to obtain the X, Y position from something in the drawing:
2108 
2109   Xreal = (Xenhanced-path - ViewBox_X0) * (draw:custom-shape_svg:width / svg:viewBox_Width) + svg:viewBox_Xo;
2110   And the same for Y
2111 
2112   For sizes just use without Xo
2113 
2114   <draw:custom-shape draw:style-name="gr6" draw:text-style-name="P1" draw:layer="layout" svg:width="3.783cm" svg:height="3.602cm" draw:transform="skewX (-0.000872664625997166) rotate (-1.62385433605552) translate (19.087cm 20.266cm)">
2115      <text:p />
2116      <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 3163 3163 0 10800 3163 18437 10800 21600 18437 18437 21600 10800 18437 3163" draw:text-areas="3163 3163 18437 18437" draw:type="ring" draw:modifiers="647.870425914817"
2117          draw:enhanced-path="U 10800 10800 10800 10800 0 360 Z U 10800 10800 ?f1 ?f1 0 360 N">
2118         <draw:equation draw:name="f0" draw:formula="$0 " />
2119         <draw:equation draw:name="f1" draw:formula="10800-$0 " />
2120         <draw:handle draw:handle-position="$0 10800" draw:handle-range-x-minimum="0" draw:handle-range-x-maximum="10800" />
2121      </draw:enhanced-geometry>
2122   </draw:custom-shape>
2123 }
2124 procedure TvODGVectorialReader.ConvertViewBoxCoordinatesToFPVCoordinates(
2125   const AData: TvVectorialPage; const AInfo: TCustomShapeInfo;
2126   const ASrcX, ASrcY: Double; var ADestX, ADestY: Double);
2127 begin
2128   ADestX := (ASrcX - AInfo.ViewBox_Left) * (AInfo.Width / AInfo.ViewBox_Width) + AInfo.Left;
2129   ADestY := (ASrcY - AInfo.ViewBox_Top) * (AInfo.Height / AInfo.ViewBox_Height) + AInfo.Top;
2130   ADestY := AData.Height - ADestY;
2131 end;
2132 
2133 procedure TvODGVectorialReader.ConvertViewBoxDeltaToFPVDelta(
2134   const AInfo: TCustomShapeInfo; const ASrcX, ASrcY: Double; var ADestX,
2135   ADestY: Double);
2136 begin
2137   ADestX := (ASrcX - AInfo.ViewBox_Left) * (AInfo.Width / AInfo.ViewBox_Width);
2138   ADestY := (ASrcY - AInfo.ViewBox_Top) * (AInfo.Height / AInfo.ViewBox_Height);
2139 end;
2140 
2141 procedure TvODGVectorialReader.ConvertMilimiterCoordinatesToFPVCoordinates(
2142   const AData: TvVectorialPage; const ASrcX, ASrcY: Double; var ADestX,
2143   ADestY: Double);
2144 begin
2145   ADestX := ASrcX;
2146   ADestY := AData.Height - ASrcY;
2147 end;
2148 
2149 constructor TvODGVectorialReader.Create;
2150 begin
2151   inherited Create;
2152 
2153   FPointSeparator := DefaultFormatSettings;
2154   FPointSeparator.DecimalSeparator := '.';
2155   FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
2156 
2157 //  FSVGPathTokenizer := TSVGPathTokenizer.Create;
2158   FStyles := TFPList.Create;
2159   FAutomaticStyles := TFPList.Create;
2160   FPageLayouts := TFPList.Create;
2161   FMasterPages := TFPList.Create;
2162 end;
2163 
2164 destructor TvODGVectorialReader.Destroy;
2165 begin
2166   FStyles.ForEachCall(@DeleteStyle, nil);
2167   FStyles.Free;
2168   FAutomaticStyles.ForEachCall(@DeleteStyle, nil);
2169   FAutomaticStyles.Free;
2170   FPageLayouts.ForEachCall(@DeleteStyle, nil);
2171   FPageLayouts.Free;
2172   FMasterPages.ForEachCall(@DeleteStyle, nil);
2173   FMasterPages.Free;
2174 //  FSVGPathTokenizer.Free;
2175 
2176   inherited Destroy;
2177 end;
2178 
2179 procedure TvODGVectorialReader.ReadFromStream(AStream: TStream;
2180   AData: TvVectorialDocument);
2181 var
2182   Doc: TXMLDocument;
2183   lCurNode: TDOMNode;
2184   lPage: TvVectorialPage;
2185 begin
2186 {  try
2187     // Read in xml file from the stream
2188     ReadXMLFile(Doc, AStream);
2189 
2190     // Read the properties of the <svg> tag
2191     AData.Width := StringWithUnitToFloat(Doc.DocumentElement.GetAttribute('width'));
2192     AData.Height := StringWithUnitToFloat(Doc.DocumentElement.GetAttribute('height'));
2193 
2194     // Now process the elements
2195     lCurNode := Doc.DocumentElement.FirstChild;
2196     lPage := AData.AddPage();
2197     lPage.Width := AData.Width;
2198     lPage.Height := AData.Height;
2199     while Assigned(lCurNode) do
2200     begin
2201       ReadEntityFromNode(lCurNode, lPage, AData);
2202       lCurNode := lCurNode.NextSibling;
2203     end;
2204   finally
2205     // finally, free the document
2206     Doc.Free;
2207   end;}
2208 end;
2209 
2210 procedure TvODGVectorialReader.ReadFromFile(AFileName: string; AData: TvVectorialDocument);
2211 var
2212   FilePath : string;
2213   UnZip : TUnZipper;
2214   FileList : TStringList;
2215   Doc : TXMLDocument;
2216 begin
2217   //unzip content.xml into AFileName path
2218   FilePath:=GetTempDir(false);
2219   UnZip:=TUnZipper.Create;
2220   UnZip.OutputPath:=FilePath;
2221   FileList:=TStringList.Create;
2222   FileList.Add('content.xml');
2223   FileList.Add('styles.xml');
2224   try
2225     Unzip.UnZipFiles(AFileName,FileList);
2226   finally
2227     FreeAndNil(FileList);
2228     FreeAndNil(UnZip);
2229   end; //try
2230 
2231   Doc:=nil;
2232   try
2233     // First read the master styles
2234     ReadXMLFile(Doc,FilePath+'styles.xml');
2235     DeleteFile(FilePath+'styles.xml');
2236     ReadFromStylesXMLDocument(Doc, AData);
2237 
2238     // Now process the contents
2239     ReadXMLFile(Doc,FilePath+'content.xml');
2240     DeleteFile(FilePath+'content.xml');
2241     ReadFromStylesXMLDocument(Doc, AData); // The content.xml may also contain styles
2242     ReadFromContentXMLDocument(Doc, AData);
2243   finally
2244     Doc.Free;
2245   end;
2246 end;
2247 
2248 {
2249 <draw:page draw:name="page1" draw:style-name="dp1" draw:master-page-name="Oletus">
2250 }
2251 procedure TvODGVectorialReader.ReadFromContentXMLDocument(
2252   AXMLDocument: TXMLDocument; AData: TvVectorialDocument);
2253 var
2254   BodyNode, DrawingNode, PageNode, ElementNode: TDOMNode;
2255   CurPage: TvVectorialPage;
2256   i: Integer;
2257   lNodeName, lNodeValue: String;
2258 begin
2259   BodyNode := AXMLDocument.DocumentElement.FindNode('office:body');
2260   if not Assigned(BodyNode) then raise Exception.Create('[TvODGVectorialReader.ReadFromContentXMLDocument] node office:body not found');
2261 
2262   DrawingNode := BodyNode.FindNode('office:drawing');
2263   if not Assigned(DrawingNode) then raise Exception.Create('[TvODGVectorialReader.ReadFromContentXMLDocument] node office:drawing not found');
2264 
2265   //process each page
2266   PageNode := DrawingNode.FindNode('draw:page');
2267   while Assigned(PageNode) do
2268   begin
2269     CurPage := aData.AddPage();
2270     //CurPage..AddWorksheet(GetAttrValue(TableNode,'table:name'));
2271 
2272     //process attributes of the page
2273     for i := 0 to PageNode.Attributes.Length - 1 do
2274     begin
2275       lNodeName := LowerCase(PageNode.Attributes.Item[i].NodeName);
2276       lNodeValue := PageNode.Attributes.Item[i].NodeValue;
2277       case lNodeName of
2278       'draw:master-page-name': ApplyMasterPageToPage(lNodeValue, CurPage);
2279       end;
2280     end;
2281 
2282     //process each element inside the page
2283     ElementNode := PageNode.FirstChild;
2284     while Assigned(ElementNode) do
2285     begin
2286       ReadElement(ElementNode, CurPage, AData);
2287 
2288       ElementNode:=ElementNode.NextSibling;
2289     end; // while Assigned(ElementNode)
2290 
2291     PageNode:=PageNode.NextSibling;
2292   end; //while Assigned(PageNode)
2293 end;
2294 
2295 procedure TvODGVectorialReader.ReadFromStylesXMLDocument(
2296   AXMLDocument: TXMLDocument; AData: TvVectorialDocument);
2297 var
2298   DocStylesNode, AutomaticStylesNode, MasterStylesNode, ElementNode: TDOMNode;
2299   CurPage: TvVectorialPage;
2300   lNodeName: String;
2301 begin
2302   DocStylesNode := AXMLDocument.DocumentElement;//.FindNode('office:document-styles');
2303   if not Assigned(DocStylesNode) then raise Exception.Create('[TvODGVectorialReader.ReadFromStylesXMLDocument] node document-styles not found');
2304 
2305   AutomaticStylesNode := DocStylesNode.FindNode('office:automatic-styles');
2306   if Assigned(AutomaticStylesNode) then
2307   begin
2308     //process each master style
2309     ElementNode := AutomaticStylesNode.FirstChild;
2310     while Assigned(ElementNode) do
2311     begin
2312       lNodeName := LowerCase(ElementNode.NodeName);
2313       case lNodeName of
2314       'style:page-layout': ReadStylesPageLayout(ElementNode, CurPage, AData);
2315       'style:style': ReadStyleStyleNode(ElementNode, CurPage, AData);
2316       end;
2317 
2318       ElementNode := ElementNode.NextSibling;
2319     end; //while Assigned(MasterStyleNode)
2320   end;
2321 
2322   MasterStylesNode := DocStylesNode.FindNode('office:master-styles');
2323   if Assigned(MasterStylesNode) then
2324   begin
2325     //process each master style
2326     ElementNode := MasterStylesNode.FirstChild;
2327     while Assigned(ElementNode) do
2328     begin
2329       lNodeName := LowerCase(ElementNode.NodeName);
2330       case lNodeName of
2331       'style:master-page': ReadStylesMasterPage(ElementNode, CurPage, AData);
2332       end;
2333 
2334       ElementNode := ElementNode.NextSibling;
2335     end; //while Assigned(MasterStyleNode)
2336   end;
2337 end;
2338 
2339 initialization
2340 
2341   RegisterVectorialReader(TvODGVectorialReader, vfODG);
2342 
2343 end.
2344 
2345