{ Writes an ODT Document License: The same modified LGPL as the Free Pascal RTL See the file COPYING.modifiedLGPL for more details An OpenDocument document is a compressed ZIP file with the following files inside: content.xml - Actual contents meta.xml - Authoring data settings.xml - User persistent viewing information, such as zoom, cursor position, etc. styles.xml - Styles, which are the only way to do formatting mimetype - application/vnd.oasis.opendocument.text META-INF\manifest.xml - Describes the other files in the archive Specifications obtained from: http://docs.oasis-open.org/office/v1.1/OS/OpenDocument-v1.1.pdf Example of content.xml structure: .... ... other elements in the page... Validator for ODF 1.0 http://opendocumentfellowship.com/validator Validator for ODF 1.2 http://odf-validator2.rhcloud.com/odf-validator2/ AUTHORS: Felipe Monteiro de Carvalho } unit odtvectorialwriter; {$mode objfpc}{$H+} interface uses Classes, SysUtils, zipper, zstream, {NOTE: might require zipper from FPC 2.6.2+ } fpimage, fpcanvas, fpvectorial, fpvutils, lazutf8; type // Forward declarations TvODTVectorialWriter = class; { TListStyle_Style } TListStyle_Style = Class Style : TvStyle; ListStyle : TvListStyle; End; { TListStyle_StyleList } TListStyle_StyleList = Class(TFPList) Writer : TvODTVectorialWriter; Data : TvVectorialDocument; destructor Destroy; override; function AddCrossReference(AStyle : TvStyle; AListStyle: TvListStyle) : Integer; function AsText(AIndex : Integer) : String; End; { TvODTVectorialWriter } // Writes ODT 1.2 with LibreOffice extensions... TvODTVectorialWriter = class(TvCustomVectorialWriter) private FDateCount : Integer; // Used to track Date Style Formats... FPointSeparator: TFormatSettings; // Strings with the contents of files FMeta, FSettings, FStyles, FContent, FMimetype: string; FAutomaticStyles, FMasterStyles: string; // built during writedocument, used during writestyle FAutomaticStyleID : Integer; FContentAutomaticStyles : string; // built during writedocument, used during writedocument FRasterImageFileNames:TStringList; FList_StyleCrossRef : TListStyle_StyleList; FNewPageSequence : Boolean; FMetaInfManifest, FManifestRDF: string; // helper routines function StyleNameToODTStyleName(AData: TvVectorialDocument; AStyleIndex: Integer; AToContentAutoStyle: Boolean = False): string; overload; function StyleNameToODTStyleName(AData: TvVectorialDocument; AStyle: TvStyle; AToContentAutoStyle: Boolean = False): string; overload; function ListStyleNameToODTText(AData: TvVectorialDocument; AListStyle : TvListStyle) : string; function FloatToODTText(AFloat: Double): string; function BordersToString(ATableBorders, ACellBorders: TvTableBorders; ATopCell, ABottomCell, ALeftCell, ARightCell: Boolean): String; function TextPropertiesToString(AStyle: TvStyle): String; // Routines to write those files procedure WriteMimetype; procedure WriteMetaInfManifest; procedure WriteManifestRDF; procedure WriteMeta; procedure WriteSettings; procedure WriteStyles(AData: TvVectorialDocument); procedure WriteDocument(AData: TvVectorialDocument); procedure WritePage(ACurPage: TvTextPageSequence; AData: TvVectorialDocument); // procedure WriteParagraph(AEntity: TvParagraph; ACurPage: TvTextPageSequence; AData: TvVectorialDocument); procedure WriteTable(ATable: TvTable; ACurPage: TvTextPageSequence; AData: TvVectorialDocument); procedure WriteTextSpan(AEntity: TvText; AParagraph: TvParagraph; ACurPage: TvTextPageSequence; AData: TvVectorialDocument); procedure WriteField(AEntity: TvField; AParagraph: TvParagraph; ACurPage: TvTextPageSequence; AData: TvVectorialDocument); procedure WriteList(AEntity: TvList; ACurPage: TvTextPageSequence; AData: TvVectorialDocument); procedure WriteRasterImage(AEntity:TvRasterImage; AParagraph: TvParagraph; ACurPage: TvTextPageSequence; AData: TvVectorialDocument); // Routines to write parts of those files function WriteStylesXMLAsString: string; // public { General reading methods } constructor Create; override; destructor Destroy; override; procedure WriteToFile(AFileName: string; AData: TvVectorialDocument); override; procedure WriteToStream(AStream: TStream; AData: TvVectorialDocument); override; end; implementation uses htmlelements, FPWritePNG, Math; const { OpenDocument general XML constants } XML_HEADER = ''; { OpenDocument Directory structure constants } OPENDOC_PATH_CONTENT = 'content.xml'; OPENDOC_PATH_META = 'meta.xml'; OPENDOC_PATH_SETTINGS = 'settings.xml'; OPENDOC_PATH_STYLES = 'styles.xml'; OPENDOC_PATH_MIMETYPE = 'mimetype'; OPENDOC_PATH_METAINF = 'META-INF' + '/'; OPENDOC_PATH_METAINF_MANIFEST = 'META-INF' + '/' + 'manifest.xml'; OPENDOC_PATH_MANIFESTRDF = 'manifest.rdf'; { OpenDocument schemas constants } SCHEMAS_XMLNS = 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties'; SCHEMAS_XMLNS_CALCEXT = 'urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0'; SCHEMAS_XMLNS_CHART = 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'; SCHEMAS_XMLNS_CONFIG = 'urn:oasis:names:tc:opendocument:xmlns:config:1.0'; SCHEMAS_XMLNS_CSS3T = 'http://www.w3.org/TR/css3-text/'; SCHEMAS_XMLNS_DC = 'http://purl.org/dc/elements/1.1/'; SCHEMAS_XMLNS_DCTERMS = 'http://purl.org/dc/terms/'; SCHEMAS_XMLNS_DOM = 'http://www.w3.org/2001/xml-events'; SCHEMAS_XMLNS_DR3D = 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'; SCHEMAS_XMLNS_DRAW = 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'; SCHEMAS_XMLNS_DRAWOOO = 'http://openoffice.org/2010/draw'; SCHEMAS_XMLNS_FIELD = 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0'; SCHEMAS_XMLNS_FO = 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'; SCHEMAS_XMLNS_FORM = 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'; SCHEMAS_XMLNS_FORMX = 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0'; SCHEMAS_XMLNS_GRDDL = 'http://www.w3.org/2003/g/data-view#'; SCHEMAS_XMLNS_MANIFEST = 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'; SCHEMAS_XMLNS_MATH = 'http://www.w3.org/1998/Math/MathML'; SCHEMAS_XMLNS_META = 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'; SCHEMAS_XMLNS_NUMBER = 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'; SCHEMAS_XMLNS_OF = 'urn:oasis:names:tc:opendocument:xmlns:of:1.2'; SCHEMAS_XMLNS_OFFICE = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'; SCHEMAS_XMLNS_OFFICEOOO= 'http://openoffice.org/2009/office'; SCHEMAS_XMLNS_OOO = 'http://openoffice.org/2004/office'; SCHEMAS_XMLNS_OOOC = 'http://openoffice.org/2004/calc'; SCHEMAS_XMLNS_OOOW = 'http://openoffice.org/2004/writer'; SCHEMAS_XMLNS_RPT = 'http://openoffice.org/2005/report'; SCHEMAS_XMLNS_SCRIPT = 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'; SCHEMAS_XMLNS_STYLE = 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'; SCHEMAS_XMLNS_SVG = 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'; SCHEMAS_XMLNS_TABLE = 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'; SCHEMAS_XMLNS_TABLEOOO = 'http://openoffice.org/2009/table'; SCHEMAS_XMLNS_TEXT = 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'; SCHEMAS_XMLNS_V = 'urn:schemas-microsoft-com:vml'; SCHEMAS_XMLNS_XFORMS = 'http://www.w3.org/2002/xforms'; SCHEMAS_XMLNS_XHTML = 'http://www.w3.org/1999/xhtml'; SCHEMAS_XMLNS_XLINK = 'http://www.w3.org/1999/xlink'; SCHEMAS_XMLNS_XSD = 'http://www.w3.org/2001/XMLSchema'; SCHEMAS_XMLNS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'; // SVG requires hardcoding a DPI value // The Opera Browser and Inkscape use 90 DPI, so we follow that // 1 Inch = 25.4 milimiters // 90 inches per pixel = (1 / 90) * 25.4 = 0.2822 // FLOAT_MILIMETERS_PER_PIXEL = 0.3528; // DPI 72 = 1 / 72 inches per pixel FLOAT_MILIMETERS_PER_PIXEL = 0.2822; // DPI 90 = 1 / 90 inches per pixel FLOAT_PIXELS_PER_MILIMETER = 3.5433; // DPI 90 = 1 / 90 inches per pixel // Lookups LU_ALIGN: Array [TvStyleAlignment] Of String = ('start', 'end', 'justify', 'center'); LU_V_ALIGN: Array[TvVerticalAlignment] Of String = ('top', 'bottom', 'middle', 'automatic'); LU_NUMBERFORMAT: Array [TvNumberFormat] Of String = ('1', 'a', 'i', 'A', 'I'); LU_BORDERTYPE: Array[TvTableBorderType] Of String = ('solid', 'dashed', 'solid', 'none', 'default'); // ('solid', 'dashed', 'double', 'none', 'default'); // NOTE: double not supported function FPColorToHTML(AColor: TFPColor): String; begin Result := Format('#%.2x%.2x%.2x', [AColor.Red shr 8, AColor.Green shr 8, AColor.Blue shr 8]); end; { TListStyle_StyleList } destructor TListStyle_StyleList.Destroy; begin while (Count>0) do begin TListStyle_Style(Last).Free; Delete(Count-1); end; inherited destroy; end; function TListStyle_StyleList.AddCrossReference(AStyle: TvStyle; AListStyle: TvListStyle): Integer; Var i : Integer; oCrossRef : TListStyle_Style; begin // Only add unique instances of the cross references Result := -1; for i := 0 To Count-1 Do begin oCrossRef := TListStyle_Style(Items[i]); if (oCrossRef.Style = AStyle) And (oCrossRef.ListStyle=AListStyle) Then exit(i); end; // We will only get here if the supplied combination is not already in the list oCrossRef := TListStyle_Style.Create; oCrossRef.Style := AStyle; oCrossRef.ListStyle := AListStyle; Result := Add(oCrossRef); end; function TListStyle_StyleList.AsText(AIndex: Integer): String; begin if (AIndex>=0) And (AIndex' + LineEnding + ' ' + LineEnding; // manifest:version="1.2" for i:= 0 to FRasterImageFileNames.Count-1 do begin FMetaInfManifest := FMetaInfManifest+ ' ' + LineEnding; end; FMetaInfManifest := FMetaInfManifest+ ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ''; end; procedure TvODTVectorialWriter.WriteManifestRDF; begin FManifestRDF := XML_HEADER + LineEnding + '' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + '' + LineEnding; end; procedure TvODTVectorialWriter.WriteMeta; begin FMeta := XML_HEADER + LineEnding + '' + LineEnding + ' ' + LineEnding + // 2013-07-21T09:29:41.06 // 2013-07-21T20:13:32.29 ' FPVectorial Library' + LineEnding + // ' ' + LineEnding + ''; end; procedure TvODTVectorialWriter.WriteSettings; begin FSettings := XML_HEADER + LineEnding + '' + LineEnding + '' + LineEnding + ' ' + LineEnding + ' 0' + LineEnding + ' 0' + LineEnding + ' 25534' + LineEnding + ' 9289' + LineEnding + { true false view2 4267 2925 0 0 25532 9287 0 0 false 100 false } ' ' + LineEnding + ' ' + LineEnding + ' true' + LineEnding + { false true 490666 false true false false true false false false false false false false false true false 0 false false false false false false false false false false true true false false true true true false high-resolution 470846 1 0 false false false true false false true false true true false false false false true true false true true true false false false 0 false false true true } ' ' + LineEnding + ' ' + LineEnding + ''; end; procedure TvODTVectorialWriter.WriteStyles(AData: TvVectorialDocument); var i: Integer; CurStyle: TvStyle; lTextPropsStr, lParagraphPropsStr, lCurStyleTmpStr, CurStyleParent : string; CurListStyle: TvListStyle; j: Integer; CurListLevelStyle: TvListLevelStyle; CurLevel, sLevelAttr: String; begin FStyles := XML_HEADER + LineEnding + '' + LineEnding; // TODO: Parse Styles for Fonts not included in the list below... FStyles := FStyles + '' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + '' + LineEnding; // ---------------------------- // Styles // ---------------------------- FStyles := FStyles + '' + LineEnding; FStyles := FStyles + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding; FStyles := FStyles + ' ' + LineEnding; for i := 0 to AData.GetStyleCount() - 1 do begin lParagraphPropsStr := ''; CurStyle := AData.GetStyle(i); if CurStyle.Parent = nil then CurStyleParent := 'Standard' else CurStyleParent := StyleNameToODTStyleName(AData, AData.FindStyleIndex(CurStyle.Parent), False); lTextPropsStr := TextPropertiesToString(CurStyle); if CurStyle.GetKind() = vskTextSpan then begin { } lCurStyleTmpStr := // tmp string to help see the text in the debugger ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding; FStyles := FStyles + lCurStyleTmpStr; end // Paragraph kind else begin lParagraphPropsStr := ''; // If any one value in here is set, then ALL inherited values are overridden // In other words, we must fully define the style paragraph properties, // we can't rely on LibreOffice Style Inheritance... // TODO: Confirm if this applies to Text Properties as well... if sseMarginTop in CurStyle.SetElements then lParagraphPropsStr := lParagraphPropsStr + 'fo:margin-top="'+FloatToODTText(CurStyle.MarginTop)+'mm" '; if sseMarginBottom in CurStyle.SetElements then lParagraphPropsStr := lParagraphPropsStr + 'fo:margin-bottom="'+FloatToODTText(CurStyle.MarginBottom)+'mm" '; if sseMarginLeft in CurStyle.SetElements then lParagraphPropsStr := lParagraphPropsStr + 'fo:margin-left="'+FloatToODTText(CurStyle.MarginLeft)+'mm" '; if sseMarginRight in CurStyle.SetElements then lParagraphPropsStr := lParagraphPropsStr + 'fo:margin-right="'+FloatToODTText(CurStyle.MarginRight)+'mm" '; if (spbfAlignment in CurStyle.SetElements) then lParagraphPropsStr := lParagraphPropsStr + 'fo:text-align="'+LU_ALIGN[CurStyle.Alignment]+'" '; if CurStyle.SuppressSpacingBetweenSameParagraphs then lParagraphPropsStr := lParagraphPropsStr + 'style:contextual-spacing="true" '; //else // lParagraphPropsStr := lParagraphPropsStr + 'style:contextual-spacing="false" '; lCurStyleTmpStr := // tmp string to help see the text in the debugger ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding; FStyles := FStyles + lCurStyleTmpStr; end; { } end; FStyles := FStyles + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding; FStyles := FStyles + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding; // Build up the List definitions - store in Styles.xml, not content.xml For i := 0 To AData.GetListStyleCount-1 Do begin CurListStyle := AData.GetListStyle(i); FStyles := FStyles + ' ' + LineEnding; For j := 0 To CurListStyle.GetListLevelStyleCount-1 Do Begin CurListLevelStyle := CurListStyle.GetListLevelStyle(j); CurLevel := IntToStr(CurListLevelStyle.Level+1); // Note the +1... // Open Bullet or Number... If CurListLevelStyle.Kind=vlskBullet Then FStyles := FStyles + ' ' + LineEnding Else Begin sLevelAttr:='text:level="'+CurLevel+'" '; If CurListLevelStyle.Prefix<>'' Then sLevelAttr := Format('%s style:num-prefix="%s"', [sLevelAttr, CurListLevelStyle.Prefix]); If CurListLevelStyle.Suffix<>'' Then sLevelAttr := Format('%s style:num-suffix="%s"', [sLevelAttr, CurListLevelStyle.Suffix]); sLevelAttr := sLevelAttr + ' style:num-format="'+LU_NUMBERFORMAT[CurListLevelStyle.NumberFormat]+'"'; // Display previous levels in Leader? If (CurListLevelStyle.DisplayLevels) And (CurLevel<>'1') Then sLevelAttr := Format('%s text:display-levels="%s"', [sLevelAttr, CurLevel]); FStyles := FStyles + ' ' + LineEnding; End; // Common Level properties FStyles:=FStyles + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding; // Close Bullet or Number If CurListLevelStyle.Kind=vlskBullet Then FStyles:=FStyles + ' ' + LineEnding Else FStyles:=FStyles + ' ' + LineEnding end; FStyles := FStyles + ' ' + LineEnding; end; FStyles := FStyles + ' ' + LineEnding; FStyles := FStyles + ' ' + LineEnding; FStyles := FStyles + ' ' + LineEnding; FStyles := FStyles + '' + LineEnding; // ---------------------------- // Automatic Styles // ---------------------------- FStyles := FStyles + '' + LineEnding + FAutomaticStyles + LineEnding + '' + LineEnding; FStyles := FStyles + '' + LineEnding + FMasterStyles + LineEnding + '' + LineEnding; FStyles := FStyles + ''; end; procedure TvODTVectorialWriter.WriteDocument(AData: TvVectorialDocument); var i: Integer; sPrefix : String; sAutomaticStyles : String; CurPage: TvPage; CurTextPage: TvTextPageSequence absolute CurPage; oCrossRef: TListStyle_Style; begin // content.xml will be built up by // sPrefix + sAutomaticStyles + FContent sPrefix := XML_HEADER + LineEnding + '' + LineEnding; sPrefix := sPrefix + ' ' + LineEnding; sPrefix := sPrefix + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding; // Build the main content of the document FContent := ' ' + LineEnding; FContent := FContent + ' ' + LineEnding; FContent := FContent + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding + ' ' + LineEnding; FNewPageSequence := False; // During each WritePage (and nested calls) FContentAutomaticStyles gets built up for i := 0 to AData.GetPageCount()-1 do begin CurPage := AData.GetPage(i); if CurPage is TvTextPageSequence then WritePage(CurTextPage, AData); end; FContent := FContent + ' ' + LineEnding; FContent := FContent + ' ' + LineEnding; FContent := FContent + '' + LineEnding; // Build up the automatic styles detailed in the content.xml sAutomaticStyles := ' ' + LineEnding; // Add all the List Definition / Paragraph Style // cross references for i := 0 to FList_StyleCrossRef.Count-1 Do begin oCrossRef := TListStyle_Style(FList_StyleCrossRef[i]); sAutomaticStyles := sAutomaticStyles + ' ' + LineEnding; end; // Now add any Automatic Styles built during WritePage.. sAutomaticStyles := sAutomaticStyles + FContentAutomaticStyles; sAutomaticStyles := sAutomaticStyles + ' ' + LineEnding; // Now piece it all together FContent := sPrefix + sAutomaticStyles + FContent; end; procedure TvODTVectorialWriter.WritePage(ACurPage: TvTextPageSequence; AData: TvVectorialDocument); var i: Integer; lCurEntity: TvEntity; begin FNewPageSequence := True; for i := 0 to ACurPage.GetEntitiesCount()-1 do begin lCurEntity := ACurPage.GetEntity(i); if (lCurEntity is TvParagraph) then WriteParagraph(TvParagraph(lCurEntity), ACurPage, AData); if (lCurEntity is TvList) then WriteList(TvList(lCurEntity), ACurPage, AData); if (lCurEntity is TvTable) then WriteTable(TvTable(lCurEntity), ACurPage, AData); end; end; procedure TvODTVectorialWriter.WriteParagraph(AEntity: TvParagraph; ACurPage: TvTextPageSequence; AData: TvVectorialDocument); var EntityKindName, AEntityStyleName, lOutlineLevel: string; sAutoStyleName, sPageMasterName, sPageLayoutName : String; sOrientation : String; i: Integer; lCurEntity: TvEntity; dWidth, dHeight : Double; begin lOutlineLevel := ''; if AEntity.Style = nil then begin EntityKindName := 'p'; AEntityStyleName := 'Standard'; end else begin case AEntity.Style.GetKind() of vskHeading: begin EntityKindName := 'h'; lOutlineLevel := 'text:outline-level="'+IntToStr(AEntity.Style.HeadingLevel)+'" '; end; else // vskTextBody; EntityKindName := 'p'; end; AEntityStyleName := StyleNameToODTStyleName(AData, AEntity.Style, False); end; If FNewPageSequence Then begin // Create an automatic style in both content.xml and style.xml // and reference the newly created style in the text we're just // about to write // TODO: Find out how to deal with new Page Sequences with other // objects at the start of the page... Inc(FAutomaticStyleID); i := AData.GetPageIndex(ACurPage); sAutoStyleName := AEntityStyleName+'_P' + IntToStr(FAutomaticStyleID); sPageMasterName := 'Page_Sequence_'+IntToStr(i+1); sPageLayoutName := 'MPM'+IntToStr(i+1); // Create an automatic style descended from AEntityStyleName FContentAutomaticStyles := FContentAutomaticStyles + '' + LineEnding + ''+ LineEnding; // Define the MasterStyles in Styles.xml // TODO: Add Header and Footer content to FMasterStyles FMasterStyles := FMasterStyles + '' + LineEnding; dWidth := ACurPage.Width; If dWidth=0 Then dWidth := AData.Width; If dWidth=0 Then dWidth := 210; // Default A4 dHeight := ACurPage.Height; If dHeight=0 Then dHeight := AData.Height; If dHeight=0 Then dHeight := 297; // Default A4 If dWidth>dHeight Then sOrientation := 'landscape' else sOrientation := 'portrait'; // Define the page layout in Styles.xml // TODO: Add Page Margins... FAutomaticStyles := FAutomaticStyles + ''+ LineEnding+ ' '+ LineEnding; FAutomaticStyles := FAutomaticStyles + ' '+ LineEnding+ ' '+ LineEnding+ ' '+ LineEnding+ ' '+ LineEnding+ '' + LineEnding; // Ensure the text is written out using the new Automatic Style AEntityStyleName:=sAutoStyleName; FNewPageSequence:=False; end; FContent := FContent + ' '; for i := 0 to AEntity.GetEntitiesCount()-1 do begin lCurEntity := AEntity.GetEntity(i); if (lCurEntity is TvText) then WriteTextSpan(TvText(lCurEntity), AEntity, ACurPage, AData) else if (lCurEntity is TvField) then WriteField(TvField(lCurEntity), AEntity, ACurPage, AData) else if (lCurEntity is TvRasterImage) then WriteRasterImage(TvRasterImage(lCurEntity), AEntity, ACurPage, AData) else raise exception.create('TvParagraph subentity '+lCurEntity.ClassName+' not handled'); end; FContent := FContent + '' + LineEnding; end; procedure TvODTVectorialWriter.WriteTextSpan(AEntity: TvText; AParagraph: TvParagraph; ACurPage: TvTextPageSequence; AData: TvVectorialDocument); var AEntityStyleName: string; lStyle: TvStyle; sText: String; begin lStyle := AEntity.Style; If lStyle<>Nil Then AEntityStyleName:=StyleNameToODTStyleName(AData, lStyle, False); // No need to all GetCombinedStyle as Paragraph Style already applied in text:p tag (* lStyle := AEntity.GetCombinedStyle(AParagraph); if lStyle = nil then begin AEntityStyleName := 'Standard'; end else begin AEntityStyleName := StyleNameToODTStyleName(AData, lStyle, False); end; *) { Lazaru s is a fre e and open sou rce development tool for the Free Pascal compiler, which is also free and open source. } // Note that here we write only text spans! sText := EscapeHTML(AEntity.Value.Text); // Trim extra CRLF appended by TStringList.Text If DefaultTextLineBreakStyle = tlbsCRLF Then sText := Copy(sText, 1, Length(sText) - 2) Else sText := Copy(sText, 1, Length(sText) - 1); sText := StringReplace(sText, ' ', ' ', [rfReplaceAll]); sText := StringReplace(sText, #09, '', [rfReplaceAll]); sText := StringReplace(sText, #13, '', [rfReplaceAll]); sText := StringReplace(sText, #10, '', [rfReplaceAll]); If lStyle <> nil Then sText := Format('%s', [ AEntityStyleName, sText]) else sText := Format('%s', [sText]); FContent := FContent + sText; end; procedure TvODTVectorialWriter.WriteField(AEntity: TvField; AParagraph: TvParagraph; ACurPage: TvTextPageSequence; AData: TvVectorialDocument); Var sDateStyleName : String; i: Integer; cCurrChar: Char; cPrevChar: Char; sTag: String; iLen: Integer; begin // // / // // / // // // // : // // // if AEntity.Kind in [vfkDate, vfkDateCreated] Then begin inc(FDateCount); sDateStyleName := Format('Date_%d', [FDateCount]); FContentAutomaticStyles:=FContentAutomaticStyles + ' '+LineEnding; cPrevChar := Chr(0); i := 1; while (i<=Length(AEntity.DateFormat)) do begin cCurrChar := AEntity.DateFormat[i]; iLen := 1; if cCurrChar<>cPrevChar Then begin // Find out how many characters repeat in a row... while (i+iLen<=Length(AEntity.DateFormat)) And (AEntity.DateFormat[i+iLen]=cCurrChar) do inc(iLen); sTag := ''; case cCurrChar Of 'd' : begin Case iLen Of 1 : sTag := ''; 2 : sTag := ''; 3 : sTag := ''; else sTag := ''; end; end; 'M' : begin case iLen Of 1 : sTag := ''; 2 : sTag := ''; 3 : sTag := ''; else sTag := ''; end; end; 'y' : begin if iLen=2 then sTag := '' else sTag := ''; end; 'h' : begin if iLen=1 then sTag := '' else sTag := ''; end; 'm' : begin if iLen=1 then sTag := '' else sTag := ''; end; 's' : begin if iLen=1 then sTag := '' else sTag := ''; end; 'a' : begin sTag := ''; iLen := 5; end; else sTag := ''+cCurrChar+''; end; cPrevChar := cCurrChar; end; FContentAutomaticStyles:=FContentAutomaticStyles + ' '+sTag + LineEnding; Inc(i, iLen); end; FContentAutomaticStyles:=FContentAutomaticStyles + ' '+LineEnding; end; case AEntity.Kind of vfkNumPages: begin FContent:=FContent + ''+IntToStr(AData.GetPageCount)+''; end; vfkPage: begin FContent:=FContent + ''+IntToStr(AData.GetPageIndex(ACurPage))+''; end; vfkAuthor: begin FContent:=FContent + 'FPVECTORIAL'; end; vfkDateCreated: begin FContent:=FContent + ''+DateToStr(Now)+''; end; vfkDate: begin FContent:=FContent + ''+DateToStr(Now)+''; end; end; end; function TvODTVectorialWriter.BordersToString(ATableBorders, ACellBorders : TvTableBorders; ATopCell, ABottomCell, ALeftCell, ARightCell : Boolean):String; (* double line thickness requires a completely different configuration, so for now, we won't support it... From the OASIS Open Office Specification: The style:border-line-width specifies the line widths of all four sides, while the other attributes specify the line widths of one side only. The value of the attributes can be a list of three space-separated lengths, as follows: • The first value specifies the width of the inner line • The second value specified the distance between the two lines • The third value specifies the width of the outer line The result of specifying a border line width without specifying a border width style of double for the same border is undefined. *) Function BorderToString(AAttrib : String; ABorder: TvTableBorder) : String; Begin Result := ''; If ABorder.LineType<>tbtDefault Then Begin If ABorder.LineType=tbtNone Then Result := 'none' Else Begin If ABorder.Width <> 0 Then Result := Format('%s %smm', [Result, FloatToODTText(ABorder.Width)]) Else Result := Format('%s 0.05pt', [Result]); Result := Format('%s %s', [Result, LU_BORDERTYPE[ABorder.LineType]]); Result := Format('%s #%s', [Result, FPColorToRGBHexString(ABorder.Color)]); end; Result := Format('%s="%s"', [AAttrib, Trim(Result)]); end; end; Var sLeft, sRight, sTop, sBottom : String; Begin (* OpenDocument does not support setting borders at the Table Level, only at the cell level. For end user convenience, FPVectorial supports setting borders at the table level, but allows the end user fine control, if they prefer, by providing support for borders at the cell level as well. This means we're going to need to calculate actual border based on TvTable.Borders (which includes InsideHoriz and InsideVert) as default values, which can be overridden if specific TvTableCell.Borders are defined (ie LineType<>tbtDefault) Matters are complicated by the need to work out if we need to draw the right and top borders (if we always draw right borders then two lines will be visible on internal border, the left border from the cell to the right and the right border from this cell). To deal with this, we only set the Right and Top borders if either the Cell.Borders specify (they overrule all), or if we're actually at the top or right cells (which the calling function will calculate for us) *) sLeft := BorderToString('fo:border-left', ACellBorders.Left); if sLeft='' then begin if ALeftCell then sLeft := BorderToString('fo:border-left', ATableBorders.Left) else // Really need to look at cell to the left and determine if it has overriding Cell.Borders.Right :-( sLeft := BorderToString('fo:border-left', ATableBorders.InsideVert); end; sRight := BorderToString('fo:border-right', ACellBorders.Right); if sRight='' then begin if ARightCell then sRight := BorderToString('fo:border-right', ATableBorders.Right) else sRight := 'fo:border-right="none"'; end; sTop := BorderToString('fo:border-top', ACellBorders.Top); if sTop='' then begin if ATopCell then sTop := BorderToString('fo:border-top', ATableBorders.Top) else sTop := 'fo:border-top="none"'; end; sBottom := BorderToString('fo:border-bottom', ACellBorders.Bottom); if sBottom='' then begin if ABottomCell then sBottom := BorderToString('fo:border-bottom', ATableBorders.Bottom) else // Really need to look at cell below, and determine if it has overriding Cell.Borders.Top :-( sBottom := BorderToString('fo:border-bottom', ATableBorders.InsideHoriz); end; Result := Format('%s %s %s %s', [sLeft, sRight, sTop, sBottom]); end; function TvODTVectorialWriter.TextPropertiesToString(AStyle: TvStyle): String; var fontStyleChanged: Boolean; begin Result := ''; fontStyleChanged := false; if spbfFontSize in AStyle.SetElements then begin Result := Result + ' fo:font-size="'+IntToStr(AStyle.Font.Size)+'pt" '; Result := Result + ' fo:font-size-asian="'+IntToStr(AStyle.Font.Size)+'pt" '; Result := Result + ' fo:font-size-complex="'+IntToStr(AStyle.Font.Size)+'pt" '; end; if spbfFontName in AStyle.SetElements then begin Result := Result + ' style:font-name="'+AStyle.Font.Name+'" '; Result := Result + ' style:font-name-asian="Microsoft YaHei" '; Result := Result + ' style:font-name-complex="Mangal" '; end; if (spbfFontColor in AStyle.SetElements) then begin Result := Result + Format(' fo:color="%s" loext:opacity="100%%"', [FPColorToHtml(AStyle.Font.Color)]); end; if (spbfFontBold in AStyle.SetElements) then begin if AStyle.Font.Bold then begin Result := Result + ' fo:font-weight="bold" '; Result := Result + ' style:font-weight-asian="bold" '; Result := Result + ' style:font-weight-complex="bold" '; fontStyleChanged := true; end; end; if (spbfFontItalic in AStyle.SetElements) then begin if AStyle.Font.Italic then begin Result := Result + ' fo:font-style="italic" '; Result := Result + ' style:font-style-asian="italic" '; Result := Result + ' style:font-style-complex="italic" '; fontStyleChanged := true; end; end; if (spbfFontUnderline in AStyle.SetElements) then begin if AStyle.Font.Underline then begin Result := Result + ' style:text-underline-style="solid"'; Result := Result + ' style:text-underline-width="auto"'; Result := Result + ' style:text-underline-color="font-color"'; fontStyleChanged := true; end; end; if (spbfFontStrikeThrough in AStyle.SetElements) then begin if AStyle.Font.StrikeThrough then begin Result := Result + ' style:text-line-through-style="solid" '; fontStyleChanged := true; end; end; if not fontStyleChanged then begin Result := Result + ' fo:font-weight="normal" '; Result := Result + ' style:font-weight-asian="normal" '; Result := Result + ' style:font-weight-complex="normal" '; end; end; procedure TvODTVectorialWriter.WriteTable(ATable: TvTable; ACurPage: TvTextPageSequence; AData: TvVectorialDocument); procedure AddBody(AString : String); begin FContent := FContent + ' ' + AString + LineEnding; end; procedure AddStyle(AString : String); begin FContentAutomaticStyles:=FContentAutomaticStyles + ' ' + AString + LineEnding; end; Var iRow, iCell, iCol, k: Integer; oRow: TvTableRow; oCell: TvTableCell; lCurEntity: TvEntity; sTableName : String; iColCount : Integer; sTableStyle, sColStyle, sRowStyle, sCellStyle, sTemp, sTemp2: String; bInHeader: Boolean; Begin // TODO: Add support for TvTableBorder.Spacing // TODO: Add support for TvTableRow.CellSpacing // TODO: Add support for TvTable.CellSpacing if ATable.GetRowCount=0 Then Exit; // Style information stored in content.xml -> office:automatic-styles // Table information stored in content.xml -> office:body sTableName := Trim(ATable.Name); If sTableName='' Then sTableName := Format('Table_%d.%d', [AData.GetPageIndex(ACurPage)+1, ACurPage.GetEntityIndex(ATable)+1]); sTableStyle := sTableName; // Table meta properties AddStyle(''); Case ATable.PreferredWidth.Units of dimMillimeter: sTemp := 'style:width="'+FloatToODTText(ATable.PreferredWidth.Value)+'mm"'; dimPoint: sTemp := 'style:width="'+FloatToODTText(ATable.PreferredWidth.Value)+'pt"'; dimPercent: sTemp := 'style:rel-width="'+FloatToODTText(ATable.PreferredWidth.Value)+'%"'; End; if ATable.BackgroundColor <> FPColor(0, 0, 0, 0) Then sTemp := sTemp + ' fo:background-color="#'+FPColorToRGBHexString(ATable.BackgroundColor)+'"'; AddStyle(' '); AddStyle(''); AddBody(Format('', [sTableName, sTableStyle])); // Now define any column specific properties If Length(ATable.ColWidths)>0 Then iColCount := Length(ATable.ColWidths) Else // No ColWidths defined means simple tables only (no merged cells) iColCount := TvTableRow(ATable.GetRow(0)).GetCellCount; For iCol := 0 To iColCount-1 Do Begin sColStyle := Format('%s.Col_%d', [sTableStyle, iCol+1]); If Length(ATable.ColWidths)>0 Then begin AddStyle(''); Case ATable.ColWidthsUnits Of dimMillimeter: sTemp := 'style:column-width="'+FloatToODTText(ATable.ColWidths[iCol])+'mm"'; dimPoint: sTemp := 'style:column-width="'+FloatToODTText(ATable.ColWidths[iCol])+'pt"'; dimPercent: sTemp := 'style:rel-column-width="'+FloatToODTText(65535 * ATable.ColWidths[iCol] / 100)+'*"'; End; AddStyle(' '); AddStyle(''); end; AddBody(' '); end; // Write out the table row by row, defining row and cell styles as we go.. bInHeader := False; For iRow := 0 To ATable.GetRowCount-1 Do Begin oRow := ATable.GetRow(iRow); // Current Header functionality will only work // if header rows correctly defined... If (bInHeader) And not (oRow.Header) Then Begin bInHeader := False; // Close header rows... AddBody(' '); end; If (oRow.Header) And (iRow=0) Then Begin bInHeader := True; // Open header rows AddBody(' '); end; sTemp := ''; sRowStyle := Format('%s.Row_%d', [sTableStyle, iRow+1]); if oRow.BackgroundColor <> FPColor(0, 0, 0, 0) Then sTemp := sTemp + ' fo:background-color="#'+FPColorToRGBHexString(oRow.BackgroundColor)+'"'; If oRow.Height<>0 Then sTemp := sTemp + ' style:row-height="'+FloatToODTText(oRow.Height)+'mm"'; if Not oRow.AllowSplitAcrossPage Then sTemp := sTemp + ' fo:keep-together="always"'; // else // sTemp := sTemp + ' fo:keep-together="auto"'; // Only define the style if it is required... If sTemp<>'' Then begin AddStyle(''); AddStyle(' '); AddStyle(''); AddBody(' '); end Else AddBody(' '); For iCell := 0 To oRow.GetCellCount-1 Do Begin oCell := oRow.GetCell(iCell); sTemp := ''; sCellStyle := Format('%s.Cell_%dx%d', [sTableStyle, iRow + 1, iCell + 1]); (* // I cannot find a mechanism for setting cell width in ODT... If oCell.PreferredWidth.Value<>0 Then Begin Case oCell.PreferredWidth.Units Of dimMillimeter: sTemp := sTemp + 'style:cell-width="'+FloatToODTText(oCell.PreferredWidth)+'mm"'; dimPoint: sTemp := sTemp + 'style:cell-width="'+FloatToODTText(oCell.PreferredWidth)+'pt"'; dimPercent: sTemp := sTemp + 'style:rel-cell-width="'+FloatToODTText(65535 * oCell.PreferredWidth / 100)+'*"'; End; end; *) // Top is default in LibreOffice Write If oCell.VerticalAlignment<>vaTop Then sTemp := sTemp + ' style:vertical-align="'+LU_V_ALIGN[oCell.VerticalAlignment]+'"'; if oCell.BackgroundColor <> FPColor(0, 0, 0, 0) Then sTemp := sTemp + ' fo:background-color="#'+FPColorToRGBHexString(oCell.BackgroundColor)+'"'; sTemp := sTemp + ' ' + BordersToString(ATable.Borders, oCell.Borders, iRow=0, iRow=ATable.GetRowCount-1, iCell=0, iCell=oRow.GetCellCount-1); sTemp2 := ''; If oCell.SpannedCols>1 Then sTemp2 := 'table:number-columns-spanned="'+IntToStr(oCell.SpannedCols)+'" '; // Only define the style if it is required... sTemp := Trim(sTemp); if sTemp<>'' Then begin AddStyle(''); AddStyle(' '); AddStyle(''); AddBody(' '); end Else AddBody(' '); FContent := FContent + ' '; // oCell is a TvRichText descendant, so process it similarly... for k := 0 to oCell.GetEntitiesCount()-1 do begin lCurEntity := oCell.GetEntity(k); if (lCurEntity is TvParagraph) then WriteParagraph(TvParagraph(lCurEntity), ACurPage, AData); if (lCurEntity is TvList) then WriteList(TvList(lCurEntity), ACurPage, AData); if (lCurEntity is TvTable) then WriteTable(TvTable(lCurEntity), ACurPage, AData); end; AddBody(' '); // FPVectorial doesn't directly support covered (merged) cells, // instead they're implied by SpannedCols count > 1 for k := 2 to oCell.SpannedCols Do AddBody(''); end; AddBody(' '); end; AddBody(''); end; procedure TvODTVectorialWriter.WriteList(AEntity: TvList; ACurPage: TvTextPageSequence; AData: TvVectorialDocument); var i, j: Integer; lCurEntity, lCurSubEntity: TvEntity; lCurParagraph: TvParagraph; iCrossRef: Integer; begin // See http://docs.oasis-open.org/office/v1.1/OS/OpenDocument-v1.1.pdf // page 75 "Example: Lists and sublists" FContent := FContent + ' ' + LineEnding; for i := 0 to AEntity.GetEntitiesCount()-1 do begin lCurEntity := AEntity.GetEntity(i); FContent := FContent + ' ' + LineEnding; if (lCurEntity is TvParagraph) then begin lCurParagraph := lCurEntity as TvParagraph; iCrossRef := FList_StyleCrossRef.AddCrossReference(AEntity.Style, AEntity.ListStyle); // Special Style correlating the Paragraph Style and the List style // should be added to Content.xml Automatic Styles FContent := FContent + ' '; for j := 0 to lCurParagraph.GetEntitiesCount()-1 do begin lCurSubEntity := lCurParagraph.GetEntity(j); if (lCurSubEntity is TvText) then WriteTextSpan(TvText(lCurSubEntity), lCurParagraph, ACurPage, AData); end; FContent := FContent + ' ' + LineEnding; end else if lCurEntity Is TvList Then WriteList(TvList(lCurEntity), ACurPage, AData); FContent := FContent + ' ' + LineEnding; end; FContent := FContent + ' ' + LineEnding; end; procedure TvODTVectorialWriter.WriteRasterImage(AEntity: TvRasterImage; AParagraph: TvParagraph; ACurPage: TvTextPageSequence; AData: TvVectorialDocument); var FRasterImageName:string; FRasterImageHeight:double; FRasterImageWidth:double; begin if AEntity.RasterImage=nil then Exit; if IsZero(AEntity.Height) then FRasterImageHeight:=RoundTo((AEntity.RasterImage.Height*2.54)/96,-3) // default 96 dpi of document, unit used cm else FRasterImageHeight:=AEntity.Height; if IsZero(AEntity.Width) then FRasterImageWidth:=RoundTo((AEntity.RasterImage.Width*2.54)/96,-3) // default 96 dpi of document, unit used cm else FRasterImageWidth:=AEntity.Width; FRasterImageName:='Pictures/'+IntTostr(FRasterImageFileNames.Count+1)+'.png'; FContent:=FContent+''; FContent:=FContent+''; FContent:=FContent+''; FRasterImageFileNames.AddObject(FRasterImageName,AEntity); end; function TvODTVectorialWriter.WriteStylesXMLAsString: string; begin end; constructor TvODTVectorialWriter.Create; begin inherited Create; FPointSeparator := DefaultFormatSettings; FPointSeparator.DecimalSeparator := '.'; FPointSeparator.ThousandSeparator := '#';// disable the thousand separator FAutomaticStyles := ''; FMasterStyles := ''; FList_StyleCrossRef := TListStyle_StyleList.Create; FList_StyleCrossRef.Writer := Self; FDateCount := 0; FRasterImageFileNames:=TStringList.Create; end; destructor TvODTVectorialWriter.Destroy; begin FRasterImageFileNames.Free; FList_StyleCrossRef.Free; inherited Destroy; end; procedure TvODTVectorialWriter.WriteToFile(AFileName: string; AData: TvVectorialDocument); Var oStream: TFileStream; Begin If ExtractFileExt(AFilename) = '' Then AFilename := AFilename + STR_ODT_EXTENSION; oStream := TFileStream.Create(AFileName, fmCreate); Try WriteToStream(oStream, AData); Finally FreeAndNil(oStream); End; End; procedure TvODTVectorialWriter.WriteToStream(AStream: TStream; AData: TvVectorialDocument); var FZip: TZipper; // Streams with the contents of files FSMeta, FSSettings, FSStyles, FSContent, FSMimetype: TStringStream; FSMetaInfManifest, FSManifestRDF: TStringStream; FSRasterImage:TMemoryStream; i:integer; WriterPNG:TFPWriterPNG; FRasterImageStreamList:TFPList; begin FList_StyleCrossRef.Data := AData; { Fill the strings with the contents of the files } WriteMimetype(); WriteManifestRDF(); WriteMeta(); WriteSettings(); // Reversed order of Document and Styles to allow embedding Automatic Styles // built up during WriteDocument... WriteDocument(AData); WriteStyles(AData); WriteMetaInfManifest(); { Write the data to streams } FSMeta := TStringStream.Create(FMeta); FSSettings := TStringStream.Create(FSettings); FSStyles := TStringStream.Create(FStyles); FSContent := TStringStream.Create(FContent); FSMimetype := TStringStream.Create(FMimetype); FSMetaInfManifest := TStringStream.Create(FMetaInfManifest); FSManifestRDF := TStringStream.Create(FManifestRDF); FRasterImageStreamList:=nil; WriterPNG:=TFPWriterPNG.Create; WriterPNG.UseAlpha:=true; { Now compress the files } FZip := TZipper.Create; try // MimeType must be first file, and should be uncompressed // TODO: CompressionLevel is not working. Bug, or misuse? // See http://mantis.freepascal.org/view.php?id=24897 for patch... FZip.Entries.AddFileEntry(FSMimetype, OPENDOC_PATH_MIMETYPE).CompressionLevel:=clNone; FZip.Entries.AddFileEntry(FSMeta, OPENDOC_PATH_META); FZip.Entries.AddFileEntry(FSSettings, OPENDOC_PATH_SETTINGS); FZip.Entries.AddFileEntry(FSStyles, OPENDOC_PATH_STYLES); FZip.Entries.AddFileEntry(FSContent, OPENDOC_PATH_CONTENT); FZip.Entries.AddFileEntry(FSMetaInfManifest, OPENDOC_PATH_METAINF_MANIFEST); FZip.Entries.AddFileEntry(FSManifestRDF, OPENDOC_PATH_MANIFESTRDF); FRasterImageStreamList:=TFPList.Create; for i:=0 to FRasterImageFileNames.Count-1 do begin FSRasterImage:=TMemoryStream.Create; TvRasterImage(FRasterImageFileNames.Objects[i]).RasterImage.SaveToStream(FSRasterImage,WriterPNG); FRasterImageStreamList.Add(FSRasterImage); FSRasterImage.Seek(0,soFromBeginning); FZip.Entries.AddFileEntry(FSRasterImage, FRasterImageFileNames[i]); end; FZip.SaveToStream(AStream); finally if FRasterImageStreamList<> nil then begin for i:=0 to FRasterImageStreamList.Count-1 do TvRasterImage(FRasterImageStreamList[i]).Free; FRasterImageStreamList.Free; end; FZip.Free; FSMeta.Free; FSSettings.Free; FSStyles.Free; FSContent.Free; FSMimetype.Free; FSMetaInfManifest.Free; FSManifestRDF.Free; WriterPNG.Free; end; end; initialization RegisterVectorialWriter(TvODTVectorialWriter, vfODT); end.