From 8e7a3b741a67f030ba2b6c89bd16258964c86ea6 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Sat, 14 Feb 2015 22:27:49 +0000 Subject: [PATCH] fpspreadsheet: Add unit xlsEscher for writing Microsoft Office shapes needed for cell comments in BIFF8. Writing of comments not yet working. Lots of additions to BIFFExplorer. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3942 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../fpspreadsheet/examples/fpsctrls/main.lfm | 1 + components/fpspreadsheet/fpspreadsheet.pas | 5 +- .../fpspreadsheet/laz_fpspreadsheet.lpk | 6 +- .../fpspreadsheet/laz_fpspreadsheet.pas | 2 +- .../reference/BIFFExplorer/bebiffgrid.pas | 1085 ++++++++++++----- .../reference/BIFFExplorer/bebiffutils.pas | 6 +- .../reference/BIFFExplorer/bemain.lfm | 84 +- .../reference/BIFFExplorer/bemain.pas | 10 +- components/fpspreadsheet/xlsbiff8.pas | 539 ++++++-- components/fpspreadsheet/xlscommon.pas | 62 +- components/fpspreadsheet/xlsescher.pas | 562 +++++++++ 11 files changed, 1883 insertions(+), 479 deletions(-) create mode 100644 components/fpspreadsheet/xlsescher.pas diff --git a/components/fpspreadsheet/examples/fpsctrls/main.lfm b/components/fpspreadsheet/examples/fpsctrls/main.lfm index b7aee4abb..75d88dbdc 100644 --- a/components/fpspreadsheet/examples/fpsctrls/main.lfm +++ b/components/fpspreadsheet/examples/fpsctrls/main.lfm @@ -37,6 +37,7 @@ object MainForm: TMainForm Font.Color = clBlack Font.Height = -13 Font.Name = 'Arial' + MouseWheelOption = mwGrid Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goRowSizing, goColSizing, goEditing, goThumbTracking, goDblClickAutoSize, goSmoothScroll, goHeaderHotTracking, goCellHints] ParentFont = False RowCount = 101 diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index 7f66ed716..351c5c5a5 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -8735,9 +8735,12 @@ end; {@@ ---------------------------------------------------------------------------- (Pseudo-) abstract method writing a cell comment to the stream. + The cell comment is written immediately after the cell content. + NOTE: This is not good for XLSX and BIFF8. + Must be overridden by descendents. - @param ACell Pointer to the cell to be written + @param ACell Pointer to the cell containing the comment to be written -------------------------------------------------------------------------------} procedure TsCustomSpreadWriter.WriteComment(AStream: TStream; ACell: PCell); begin diff --git a/components/fpspreadsheet/laz_fpspreadsheet.lpk b/components/fpspreadsheet/laz_fpspreadsheet.lpk index cffa1c5e4..0b88121a1 100644 --- a/components/fpspreadsheet/laz_fpspreadsheet.lpk +++ b/components/fpspreadsheet/laz_fpspreadsheet.lpk @@ -28,7 +28,7 @@ This package is all you need if you don't want graphical components (like grids and charts)."/> - + @@ -145,6 +145,10 @@ This package is all you need if you don't want graphical components (like grids + + + + diff --git a/components/fpspreadsheet/laz_fpspreadsheet.pas b/components/fpspreadsheet/laz_fpspreadsheet.pas index 0aa866831..ebe68ab6e 100644 --- a/components/fpspreadsheet/laz_fpspreadsheet.pas +++ b/components/fpspreadsheet/laz_fpspreadsheet.pas @@ -12,7 +12,7 @@ uses fpsutils, fpszipper, uvirtuallayer_types, uvirtuallayer, uvirtuallayer_ole, uvirtuallayer_ole_helpers, uvirtuallayer_ole_types, uvirtuallayer_stream, fpolebasic, wikitable, fpsNumFormatParser, fpsfunc, fpsRPN, fpsStrings, - fpscsv, fpsCsvDocument, fpspatches, fpsTypes; + fpscsv, fpsCsvDocument, fpspatches, fpsTypes, xlsEscher; implementation diff --git a/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas b/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas index 1c5c72607..599eaf3b1 100644 --- a/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas +++ b/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas @@ -75,6 +75,7 @@ type procedure ShowLeftMargin; procedure ShowMergedCells; procedure ShowMMS; + procedure ShowMSODrawing; procedure ShowMulBlank; procedure ShowMulRK; procedure ShowNote; @@ -125,7 +126,8 @@ type procedure ExtractString(ABufIndex: Integer; ALenBytes: Byte; AUnicode: Boolean; out AString: String; out ANumBytes: Integer; IgnoreCompressedFlag: Boolean = false); procedure PopulateGrid; - procedure ShowInRow(var ARow: Integer; var AOffs: LongWord; ASize: Word; AValue,ADescr: String); + procedure ShowInRow(var ARow: Integer; var AOffs: LongWord; ASize: Word; + AValue,ADescr: String; ADescrOnly: Boolean = false); procedure ShowRowColData(var ABufIndex: LongWord); public @@ -162,7 +164,10 @@ begin ColWidths[1] := 60; ColWidths[2] := 120; ColWidths[3] := 350; - Options := Options + [goThumbTracking, goColSizing, goTruncCellHints, goCellHints] - [goVertLine]; + Options := Options + + [goThumbTracking, goColSizing, goTruncCellHints, goCellHints] + - [goVertLine, goSmoothScroll]; + MouseWheelOption := mwGrid; FDetails := TStringList.Create; end; @@ -409,6 +414,8 @@ begin ShowInterfaceEnd; $00E5: ShowMergedCells; + $00EC: + ShowMSODrawing; $00FC: ShowSST; $00FD: @@ -466,6 +473,8 @@ end; procedure TBIFFGrid.SetBIFFNodeData(AData: TBIFFNodeData; ABuffer: TBIFFBuffer; AFormat: TsSpreadsheetFormat); begin + if AData = nil then + exit; FFormat := AFormat; FRecType := AData.RecordID; FInfo := AData.Tag; @@ -1163,6 +1172,9 @@ var sw: widestring; ls: Integer; i: Integer; + w: Word; + n: Integer; + run: Integer; begin case FInfo of BIFFNODE_TXO_CONTINUE1: @@ -1188,6 +1200,56 @@ begin BIFFNODE_TXO_CONTINUE2: begin + RowCount := FixedRows + 1000; + n := 0; + numBytes := 2; + run := 1; + while FBufferIndex < Length(FBuffer) - 4*SizeOf(Word) do begin + Move(FBuffer[FBufferIndex], w, numBytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), Format( + 'Run %d: Index of first character using this font (0-based)', [run])); + inc(n); + + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToSTr(WordLEToN(w)), + Format('Run %d: Index to FONT record', [run])); + inc(n); + + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInrow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + 'Not used'); + inc(n); + + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInrow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + 'Not used'); + inc(n); + + inc(run); + end; + + // lastRun + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInrow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + 'Number of characters'); + inc(n); + + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInrow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + 'Not used'); + inc(n); + + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInrow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + 'Not used'); + inc(n); + + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInrow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + 'Not used'); + inc(n); + + RowCount := FixedRows + n; end; end; end; @@ -1903,7 +1965,7 @@ begin numBytes := 2; Move(FBuffer[FBufferIndex], w, numBytes); w := WordLEToN(w); - ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(w), + ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('%d (= %.1gpt)', [w, w/20]), 'Font height in twips (=1/20 point)'); numBytes := 2; @@ -1912,29 +1974,29 @@ begin if Row = FCurrRow then begin FDetails.Add('Option flags:'#13); if w and $0001 = 0 - then FDetails.Add('Bit $0001 = 0: not bold') - else FDetails.Add('Bit $0001 = 1: bold (redundant in BIFF5-BIFF8)'); + then FDetails.Add(' Bit $0001 = 0: not bold') + else FDetails.Add('x Bit $0001 = 1: bold (redundant in BIFF5-BIFF8)'); if w and $0002 = 0 - then FDetails.Add('Bit $0002 = 0: not italic') - else FDetails.Add('Bit $0002 = 1: italic'); + then FDetails.Add(' Bit $0002 = 0: not italic') + else FDetails.Add('x Bit $0002 = 1: italic'); if w and $0004 = 0 - then FDetails.Add('Bit $0004 = 0: not underlined') - else FDetails.Add('Bit $0004 = 1: underlined (redundant in BIFF5-BIFF8)'); + then FDetails.Add(' Bit $0004 = 0: not underlined') + else FDetails.Add('x Bit $0004 = 1: underlined (redundant in BIFF5-BIFF8)'); if w and $0008 = 0 - then FDetails.Add('Bit $0008 = 0: not struck out') - else FDetails.Add('Bit $0008 = 1: struck out'); + then FDetails.Add(' Bit $0008 = 0: not struck out') + else FDetails.Add('x Bit $0008 = 1: struck out'); if w and $0010 = 0 - then FDetails.Add('Bit $0010 = 0: not outlined') - else FDetails.Add('Bit $0010 = 1: outlined'); + then FDetails.Add(' Bit $0010 = 0: not outlined') + else FDetails.Add('x Bit $0010 = 1: outlined'); if w and $0020 = 0 - then FDetails.Add('Bit $0020 = 0: not shadowed') - else FDetails.Add('Bit $0020 = 1: shadowed'); + then FDetails.Add(' Bit $0020 = 0: not shadowed') + else FDetails.Add('x Bit $0020 = 1: shadowed'); if w and $0040 = 0 - then FDetails.Add('Bit $0040 = 0: not condensed') - else FDetails.Add('Bit $0040 = 1: condensed'); + then FDetails.Add(' Bit $0040 = 0: not condensed') + else FDetails.Add('x Bit $0040 = 1: condensed'); if w and $0080 = 0 - then FDetails.Add('Bit $0080 = 0: not extended') - else FDetails.Add('Bit $0080 = 1: extended'); + then FDetails.Add(' Bit $0080 = 0: not extended') + else FDetails.Add('x Bit $0080 = 1: extended'); end; ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d ($%.4x)', [w, w]), 'Option flags'); @@ -2026,7 +2088,7 @@ begin numBytes := 1; Move(FBuffer[FBufferIndex], b, numBytes); - ShowInRow(FCurrRow, FBufferIndex, numBytes, '', 'Not used'); + ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.2x', [b]), 'Not used'); end; ExtractString(FBufferIndex, 1, FFormat=sfExcel8, s, numbytes); @@ -2143,14 +2205,14 @@ begin Move(FBuffer[FBufferIndex], b, numBytes); if Row = FCurrRow then begin FDetails.Add('Cell protection and XF index:'#13); - FDetails.Add(Format('Bits 5-0 = %d: XF Index', [b and $3F])); + FDetails.Add(Format('x Bits 5-0 = %d: XF Index', [b and $3F])); case b and $40 of - 0: FDetails.Add('Bit 6 = 0: Cell is NOT locked.'); - 1: FDetails.Add('Bit 6 = 1: Cell is locked.'); + 0: FDetails.Add(' Bit 6 = 0: Cell is NOT locked.'); + 1: FDetails.Add('x Bit 6 = 1: Cell is locked.'); end; case b and $80 of - 0: FDetails.Add('Bit 7 = 0: Formula is NOT hidden.'); - 1: FDetails.Add('Bit 7 = 1: Formula is hidden.'); + 0: FDetails.Add(' Bit 7 = 0: Formula is NOT hidden.'); + 1: FDetails.Add('x Bit 7 = 1: Formula is hidden.'); end; end; ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d ($%.2x)', [b,b]), @@ -2742,11 +2804,19 @@ end; procedure TBIFFGrid.ShowInRow(var ARow: Integer; var AOffs: LongWord; - ASize: Word; AValue,ADescr: String); + ASize: Word; AValue,ADescr: String; ADescrOnly: Boolean = false); begin - Cells[0, ARow] := IntToStr(AOffs); - Cells[1, ARow] := IntToStr(ASize); - Cells[2, ARow] := AValue; + if ADescrOnly then + begin + Cells[0, ARow] := ''; + Cells[1, ARow] := ''; + Cells[2, ARow] := ''; + end else + begin + Cells[0, ARow] := IntToStr(AOffs); + Cells[1, ARow] := IntToStr(ASize); + Cells[2, ARow] := AValue; + end; Cells[3, ARow] := ADescr; inc(ARow); inc(AOffs, ASize); @@ -2770,14 +2840,14 @@ begin b := FBuffer[FBufferIndex]; if Row = FCurrRow then begin FDetails.Add('Cell protection and XF index:'#13); - FDetails.Add(Format('Bits 5-0 = %d: XF Index', [b and $3F])); + FDetails.Add(Format('x Bits 5-0 = %d: XF Index', [b and $3F])); case b and $40 of - 0: FDetails.Add('Bit 6 = 0: Cell is NOT locked.'); - 1: FDetails.Add('Bit 6 = 1: Cell is locked.'); + 0: FDetails.Add(' Bit 6 = 0: Cell is NOT locked.'); + 1: FDetails.Add('x Bit 6 = 1: Cell is locked.'); end; case b and $80 of - 0: FDetails.Add('Bit 7 = 0: Formula is NOT hidden.'); - 1: FDetails.Add('Bit 7 = 1: Formula is hidden.'); + 0: FDetails.Add(' Bit 7 = 0: Formula is NOT hidden.'); + 1: FDetails.Add('x Bit 7 = 1: Formula is hidden.'); end; end; ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d ($%.2x)', [b,b]), @@ -2796,27 +2866,27 @@ begin if Row = FCurrRow then begin FDetails.Add('Cell style:'#13); case b and $07 of - 0: FDetails.Add('Bits 2-0 = 0: Horizontal alignment is GENERAL'); - 1: FDetails.Add('Bits 2-0 = 1: Horizontal alignment is LEFT'); - 2: FDetails.Add('Bits 2-0 = 2: Horizontal alignment is CENTERED'); - 3: FDetails.Add('Bits 2-0 = 3: Horizontal alignment is RIGHT'); - 4: FDetails.Add('Bits 2-0 = 4: Horizontal alignment is FILLED'); + 0: FDetails.Add('x Bits 2-0 = 0: Horizontal alignment is GENERAL'); + 1: FDetails.Add('x Bits 2-0 = 1: Horizontal alignment is LEFT'); + 2: FDetails.Add('x Bits 2-0 = 2: Horizontal alignment is CENTERED'); + 3: FDetails.Add('x Bits 2-0 = 3: Horizontal alignment is RIGHT'); + 4: FDetails.Add('x Bits 2-0 = 4: Horizontal alignment is FILLED'); end; if b and $08 = 0 - then FDetails.Add('Bit 3 = 0: Cell has NO left border') - else FDetails.Add('Bit 3 = 1: Cell has left black border'); + then FDetails.Add(' Bit 3 = 0: Cell has NO left border') + else FDetails.Add('x Bit 3 = 1: Cell has left black border'); if b and $10 = 0 - then FDetails.Add('Bit 4 = 0: Cell has NO right border') - else FDetails.Add('Bit 4 = 1: Cell has right black border'); + then FDetails.Add(' Bit 4 = 0: Cell has NO right border') + else FDetails.Add('x Bit 4 = 1: Cell has right black border'); if b and $20 = 0 - then FDetails.Add('Bit 5 = 0: Cell has NO top border') - else FDetails.Add('Bit 5 = 1: Cell has top black border'); + then FDetails.Add(' Bit 5 = 0: Cell has NO top border') + else FDetails.Add('x Bit 5 = 1: Cell has top black border'); if b and $40 = 0 - then FDetails.Add('Bit 6 = 0: Cell has NO bottom border') - else FDetails.Add('Bit 6 = 1: Cell has bottom black border'); + then FDetails.Add(' Bit 6 = 0: Cell has NO bottom border') + else FDetails.Add('x Bit 6 = 1: Cell has bottom black border'); if b and $80 = 0 - then FDetails.Add('Bit 7 = 0: Cell has NO shaded background') - else FDetails.Add('Bit 7 = 1: Cell has shaded background'); + then FDetails.Add(' Bit 7 = 0: Cell has NO shaded background') + else FDetails.Add('x Bit 7 = 1: Cell has shaded background'); end; ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('%d ($%.2x)', [b,b]), 'Cell style'); @@ -2904,14 +2974,14 @@ begin Move(FBuffer[FBufferIndex], b, numBytes); if Row = FCurrRow then begin FDetails.Add('Cell protection and XF index:'#13); - FDetails.Add(Format('Bits 5-0 = %d: XF Index', [b and $3F])); + FDetails.Add(Format('x Bits 5-0 = %d: XF Index', [b and $3F])); case b and $40 of - 0: FDetails.Add('Bit 6 = 0: Cell is NOT locked.'); - 1: FDetails.Add('Bit 6 = 1: Cell is locked.'); + 0: FDetails.Add(' Bit 6 = 0: Cell is NOT locked.'); + 1: FDetails.Add('x Bit 6 = 1: Cell is locked.'); end; case b and $80 of - 0: FDetails.Add('Bit 7 = 0: Formula is NOT hidden.'); - 1: FDetails.Add('Bit 7 = 1: Formula is hidden.'); + 0: FDetails.Add(' Bit 7 = 0: Formula is NOT hidden.'); + 1: FDetails.Add('x Bit 7 = 1: Formula is hidden.'); end; end; ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d ($%.2x)', [b,b]), @@ -3052,6 +3122,515 @@ begin ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(w), 'Reserved, MUST be ignored'); end; +{ Only those records needed for comments are deciphered. } +procedure TBIFFGrid.ShowMSODrawing; +var + n: Integer; + numBytes: Integer; + w: Word; + dw: DWord; + recType: Word; + recLen: DWord; + isContainer: Boolean; + indent: String; + level: Integer; + + function PrepareIndent(ALevel: Integer): String; + var + i: Integer; + begin + Result := ''; + for i := 1 to ALevel do Result := Result + ' '; + end; + + procedure DoShowHeader(out ARecType: Word; out ARecLen: DWord; + out IsContainer: Boolean); + var + s: String; + instance: Word; + version: Word; + begin + numbytes := 2; + Move(FBuffer[FBufferIndex], w, numbytes); + w := WordLEToN(w); + IsContainer := (w and $000F = $000F); + + if IsContainer and (FBufferIndex > 0) then + indent := PrepareIndent(level+1); + + s := IfThen(IsContainer, '***** CONTAINER *****', '--- ATOM ---'); + ShowInRow(FCurrRow, FBufferIndex, 0, '', indent + s, TRUE); + inc(n); + + version := w and $000F; + instance := (w and $FFF0) shr 4; + if Row = FCurrRow then begin + FDetails.Add('OfficeArtDrawing Header:'#13); + FDetails.Add(Format('Bits 3-0 = $%.1x: Version', [version])); + FDetails.Add(Format('Bits 15-4 = $%.3x: Instance', [instance])); + end; + s := Format('OfficeArtDrawing Header: Version %d, instance %d', [version, instance]); + ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('$%.04x', [w]), indent + s); + inc(n); + + Move(FBuffer[FBufferIndex], w, numbytes); + w := WordLEToN(w); + ARecType := w; + case ARecType of + $F00A: //, $F00B: + case instance of + $01: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Rectangle)'; + $02: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Rounded rectangle)'; + $03: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Ellipse)'; + $04: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Diamond)'; + $05: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Triangle)'; + $06: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Right triangle)'; + $07: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Parallelogram)'; + $08: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Trapezoid)'; + $09: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Hexagon)'; + $0A: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Pentagon)'; + $0B: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Plus)'; + $0C: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Star)'; + $0D: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Arrow)'; + $0E: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Thick arrow)'; + $0F: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Irregular pentagon)'; + $10: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Cube)'; + $11: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Speech balloon)'; + $12: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Seal)'; + $13: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Curved arc)'; + $14: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Line)'; + $15: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Plaque)'; + $16: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Cylinder)'; + $17: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Donut)'; + $CA: Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + ' (Text box)'; + end; + $F00B: + Cells[3, FCurrRow-1] := Cells[3, FCurrRow-1] + Format(' (i.e., %d properties)', [instance]); + end; + + case ARecType of + $F000: s := 'OfficeArtDggContainer (all the OfficeArt file records containing document-wide data)'; + $F001: s := 'OfficeArtBStoreContainer (all the BLIPs used in all the drawings associated with parent OfficeArtDggContainer record)'; + $F002: s := 'OfficeArtDgContainer (all file records for the objects in a drawing)'; + $F003: s := 'OfficeArtSpgrContainer (groups of shapes)'; + $F004: s := 'OfficeArtSpContainer (shape)'; + $F005: s := 'OfficeArtSolverContainer (rules applicable to the shapes contained in an OfficeArtDgContainer record)'; + $F006: s := 'OfficeArtFDGGBlock record (document-wide information about all drawings saved in the file)'; + $F007: s := 'OfficeArtFBSE record (File BLIP Store Entry (FBSE) containing information about the BLIP)'; + $F008: s := 'OfficeArtFDG record (number of shapes, drawing identifier, and shape identifier of the last shape in a drawing)'; + $F009: s := 'OfficeArtFSPGR record (coordinate system of the group shape that the anchors of the child shape are expressed in)'; + $F00A: s := 'OfficeArtFSP record (instance of a shape)'; + $F00B: s := 'OfficeArtFOPT record (table of OfficeArtRGFOPTE records)'; + $F00D: s := 'OfficeArtClientTextBox (text related data for a shape)'; + $F00F: s := 'OfficeArtChildAnchor record (anchors for the shape containing this record)'; + $F010: s := 'OfficeArtClientAnchor record (location of a shape)'; + $F011: s := 'OfficeArtClientData record (information about a shape)'; + $F012: s := 'OfficeArtFConnectorRule record (connection between two shapes by a connector shape)'; + $F014: s := 'OfficeArtFArcRule record (Specifies an arc rule. Each arc shape MUST correspond to a unique arc rule)'; + $F017: s := 'OfficeArtFCalloutRule record (Callout rule: One callout rule MUST exist per callout shape)'; + $F01A: s := 'OfficeArtBlipEMF record (BLIP file data for the enhanced metafile format (EMF))'; + $F01B: s := 'OfficeArtBlipWMF record (BLIP file data for the Windows Metafile Format (WMF))'; + $F01C: s := 'OfficeArtBlipPICT record (BLIP file data for the Macintosh PICT format)'; + $F01D: s := 'OfficeArtBlipJPEG record (BLIP file data for the Joint Photographic Experts Group (JPEG) format)'; + $F01E: s := 'OfficeArtBlipPNG record (BLIP file data for the Portable Network Graphics (PNG) format)'; + $F01F: s := 'OfficeArtBlipDIB record (BLIP file data for the device-independent bitmap (DIB) format)'; + $F029: s := 'OfficeArtBlipTIFF record (BLIP file data for the TIFF format)'; + $F118: s := 'OfficeArtFRITContainer record (container for the table of group identifiers that are used for regrouping ungrouped shapes)'; + $F119: s := 'OfficeArtFDGSL record (specifies both the selected shapes and the shape that is in focus in the drawing)'; + $F11A: s := 'OfficeArtColorMRUContainer record (most recently used custom colors)'; + $F11D: s := 'OfficeArtFPSPL record (former hierarchical position of the containing object that is either a shape or a group of shapes)'; + $F11E: s := 'OfficeArtSplitMenuColorContainer record (container for the colors that were most recently used to format shapes)'; + $F121: s := 'OfficeArtSecondaryFOPT record (table of OfficeArtRGFOPTE records)'; + $F122: s := 'OfficeArtTertiaryFOPT record (table of OfficeArtRGFOPTE records)'; + else s := 'Not known to BIFFExplorer'; + end; + if Row = FCurrRow then begin + FDetails.Add('OfficeArtDrawing Record Type:'#13); + FDetails.Add(Format('$%.4x: %s', [ARecType, s])); + end; + ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('$%.4x', [w]), + indent + 'Record type: ' + s); + inc(n); + + numbytes := 4; + Move(FBuffer[FbufferIndex], ARecLen, Numbytes); + ARecLen := DWordLEToN(ARecLen); + ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('%d', [ARecLen]), + indent + 'Record length'); + inc(n); + end; + + procedure DoShowOfficeArtFDG; // $F008 + // The OfficeArtFDG record specifies the number of shapes, the drawing + // identifier, and the shape identifier of the last shape in a drawing. + begin + numbytes := 4; + Move(FBuffer[FBufferIndex], dw, numbytes); + dw := DWordLEToN(dw); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(dw), + indent + 'Number of shapes in this drawing'); + inc(n); + + Move(FBuffer[FBufferIndex], dw, numbytes); + dw := DWordLEToN(dw); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(dw), + indent + 'Shape identifier of the last shape in this drawing'); + inc(n); + end; + + procedure DoShowOfficeArtFSPGR; // $F009 + // The OfficeArtFSPGR record specifies the coordinate system of the group + // shape that the anchors of the child shape are expressed in. This record + // is present only for group shapes. + var + rt: Word; + begin + numbytes := 4; + Move(FBuffer[FBufferIndex], dw, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(LongInt(DWordLEToN(dw))), + indent + 'xLeft'); + inc(n); + + Move(FBuffer[FBufferIndex], dw, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(LongInt(DWordLEToN(dw))), + indent + 'yTop'); + inc(n); + + Move(FBuffer[FBufferIndex], dw, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(LongInt(DWordLEToN(dw))), + indent + 'xRight'); + inc(n); + + Move(FBuffer[FBufferIndex], dw, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(LongInt(DWordLEToN(dw))), + indent + 'yBottom'); + inc(n); + end; + + procedure DoShowOfficeArtFSP; // §F00A + // The OfficeArtFSP record specifies an instance of a shape. + // The record header contains the shape type, and the record itself + // contains the shape identifier and a set of bits that further define the shape. + var + rt: word; + s: String; + begin + numbytes := 4; + Move(FBuffer[FBufferIndex], dw, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(DWordLEToN(dw)), + indent + 'Shape identifier'); + inc(n); + + numbytes := 4; + Move(FBuffer[FBufferIndex], dw, numbytes); + dw := DWordLEToN(dw); + s := ''; + if Row = FCurrRow then begin + FDetails.Add('Shape options:'#13); + if dw and $0001 = 0 + then FDetails.Add(' Bit 1=0: Shape is NOT a group shape') + else FDetails.Add('x Bit 1=1: Shape is a group shape'); + if dw and $0002 = 0 + then FDetails.Add(' Bit 2=0: Shape is NOT a child shape') + else FDetails.Add('x Bit 2=1: Shape is a child shape'); + if dw and $0004 = 0 + then FDetails.Add(' Bit 3=0: Shape is NOT the top-most group shape (patriarch)') + else FDetails.Add('x Bit 3=1: Shaoe is the top-most group shape (patriarch)'); + if dw and $0008 = 0 + then FDetails.Add(' Bit 4=0: Shape has NOT been deleted') + else FDetails.Add('x Bit 4=1: Shape has been deleted'); + if dw and $0010 = 0 + then FDetails.Add(' Bit 5=0: Shape is NOT an OLE object') + else FDetails.Add('x Bit 5=1: Shape is an OLE object'); + if dw and $0020 = 0 + then FDetails.Add(' Bit 6=0: Shape does NOT have a valid master in the hspMaster property') + else FDetails.Add('x Bit 6=1: Shape has a valid master in the hspMaster property'); + if dw and $0040 = 0 + then FDetails.Add(' Bit 7=0: Shape is NOT horizontally flipped') + else FDetails.Add('x Bit 7=1: Shape is horizontally flipped'); + if dw and $0080 = 0 + then FDetails.Add(' Bit 8=0: Shape is NOT vertically flipped') + else FDetails.Add('x Bit 8=1: Shape is vertically flipped'); + if dw and $0100 = 0 + then FDetails.Add(' Bit 9=0: Shape is NOT a connector shape') + else FDetails.Add('x Bit 9=1: Shape is a connector shape'); + if dw and $0200 = 0 + then FDetails.Add(' Bit 10=0: Shape doe NOT have an anchor') + else FDetails.Add('x Bit 10=1: Shape has an anchor'); + if dw and $0400 = 0 + then FDetails.Add(' Bit 11=0: Shape is NOT a background shape') + else FDetails.Add('x Bit 11=1: Shape is a background shape'); + if dw and $0800 = 0 + then FDetails.Add(' Bit 12=0: Shape does NOT have a shape type property') + else FDetails.Add('x Bit 12=1: Shape has a shape type property'); + FDetails.Add(' Bits 13-32: unused'); + end; + s := ''; + if dw and $0001 <> 0 then s := s + 'group shape, '; + if dw and $0002 <> 0 then s := s + 'child shape, '; + if dw and $0004 <> 0 then s := s + 'patriarch, '; + if dw and $0008 <> 0 then s := s + 'deleted, '; + if dw and $0010 <> 0 then s := s + 'OLE object, '; + if dw and $0020 <> 0 then s := s + 'master, '; + if dw and $0040 <> 0 then s := s + 'flipped hor, '; + if dw and $0080 <> 0 then s := s + 'flipped vert, '; + if dw and $0100 <> 0 then s := s + 'connector shape, '; + if dw and $0200 <> 0 then s := s + 'anchor, '; + if dw and $0400 <> 0 then s := s + 'background, '; + if dw and $0800 <> 0 then s := s + 'shape type, '; + if s <> '' then begin + Delete(s, Length(s)-1, 2); + s := 'Shape options: ' + s; + end else + s := 'Shape options'; + ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('$%.8x (%d)', [dw, dw]), indent + s); + inc(n); + end; + + procedure DoShowOfficeArtFOPT(ARecLen: DWord); // $F00B + // The OfficeArtFOPT record specifies a table of OfficeArtRGFOPTE records. + var + startIndex: Int64; + opid: Word; + op: DWord; + s: String; + begin + startIndex := FBufferIndex; + while FBufferIndex < startIndex + ARecLen do + begin + numbytes := 2; + Move(FBuffer[FBufferIndex], opid, numbytes); + opid := WordLEToN(opid); + case opid of + $007F: s := 'OfficeArtFOPTEOPID: Protection boolean properties'; + $0080: s := 'OfficeArtFOPTEOPID: TextID'; + $0081: s := 'OfficeArtFOPTEOPID: dxTextLeft'; + $0082: s := 'OfficeArtFOPTEOPID: dyTextTop'; + $0083: s := 'OfficeArtFOPTEOPID: dxTextRight'; + $0084: s := 'OfficeArtFOPTEOPID: dyTextBottom'; + $0085: s := 'OfficeArtFOPTEOPID: Wrap text'; + $0086: s := 'OfficeArtFOPTEOPID: Unused'; + $0087: s := 'OfficeArtFOPTEOPID: Text anchor'; + $0088: s := 'OfficeArtFOPTEOPID: Text flow'; + $0089: s := 'OfficeArtFOPTEOPID: Font rotation'; + $008A: s := 'OfficeArtFOPTEOPID: Next shape in sequence of linked shapes'; + $008B: s := 'OfficeArtFOPTEOPID: Text direction'; + $008C: s := 'OfficeArtFOPTEOPID: Unused'; + $008D: s := 'OfficeArtFOPTEOPID: Unused'; + $00BF: s := 'OfficeArtFOPTEOPID: Boolean properties for the text in a shape'; + $00C0: s := 'OfficeArtFOPTEOPID: Text for this shape’s geometry text'; + $00C2: s := 'OfficeArtFOPTEOPID: Alignment of text in the shape'; + $00C3: s := 'OfficeArtFOPTEOPID: Font size, in points, of the geometry text for this shape'; + $00C4: s := 'OfficeArtFOPTEOPID: Amount of spacing between characters in the text'; + $00C5: s := 'OfficeArtFOPTEOPID: Font to use for the text'; + $0158: s := 'OfficeArtFOPTEOPID: Type of connection point'; + $0181: s := 'OfficeArtFOPTEOPID: Fill color'; + $0183: s := 'OfficeArtFOPTEOPID: Background color of the fill'; + $0185: s := 'OfficeArtFOPTEOPID: Foreground color of the fill'; + $01BF: s := 'OfficeArtFOPTEOPID: Fill style boolean properties'; + $01C0: s := 'OfficeArtFOPTEOPID: Line color'; + $01C3: s := 'OfficeArtFOPTEOPID: Line foreground color for black & white display mode'; + $01FF: s := 'OfficeArtFOPTEOPID: Line style boolean properties'; + $0201: s := 'OfficeArtFOPTEOPID: Shadow color'; + $0203: s := 'OfficeArtFOPTEOPID: Shadow primary color modifier if in black & white mode'; + $023F: s := 'OfficeArtFOPTEOPID: Shadow style boolean properties'; + $03BF: s := 'OfficeArtFOPTEOPID: Group shape boolean properties'; + else s := 'OfficeArtFOPTEOPID that specifies the header information for this property'; + end; + s := indent + s; + ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('$%.4x (%d)', [opid, opid]), s); + inc(n); + + numbytes := 4; + Move(FBuffer[FBufferIndex], op, numbytes); + op := DWordLEToN(op); + s := ''; + case opid of + $0087: case op of + 0: s := s + ': text at top'; + 1: s := s + ': text vertically centered'; + 2: s := s + ': text at bottom'; + 3: s := s + ': text at top and centered horizontally'; + 4: s := s + ': text at center of box'; + 5: s := s + ': text at bottom and centered horizontally'; + end; + $0089: case op of + 0: s := s + ': horizontal'; + 1: s := s + ': 90° clockwise, vertical down'; + 2: s := s + ': horizontal, 180° rotated'; + 3: s := s + ': 90° counter-clickwise, vertical up'; + end; + $008B: case op of + 0: s := s + ': left-to-right'; + 1: s := s + ': right-to-left'; + 2: s := s + ': determined from text string'; + end; + $00C2: case op of + 0: s := s + ': stretched'; + 1: s := s + ': centered'; + 2: s := s + ': left-aligned'; + 3: s := s + ': right-aligned'; + 4: s := s + ': justified'; + 5: s := s + ': word-justified'; + end; + $0158: case op of + 0: s := s + ': no connection point'; + 1: s := s + ': Edit points of shape are used as connection points'; + 2: s := s + ': Custom array of connection points'; + 3: s := s + ': standard 4 connection points at the top, bottom, left, and right side centers'; + end; + end; + ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('$%.8x (%d)', [op, op]), + indent + 'Value of this property' + s); + inc(n); + end; + end; + + procedure DoShowOfficeArtClientTextbox(ARecLen: Word); // $F00D + begin + numBytes := ARecLen; + ShowInRow(FCurrRow, FBufferIndex, numbytes, '...', indent + 'Text data follow in TXO record', true); + inc(n); + end; + + procedure DoShowOfficeArtClientAnchorData(StructureKind: Integer); // $F010 + begin + case StructureKind of + 0: begin // OfficeArtClientAnchorSheet + numBytes := 2; + Move(FBuffer[FBufferIndex], w, numbytes); + w := WordLEToN(w); + if Row = FCurrRow then begin + FDetails.Add('Move/resize flags:'#13); + if w and $0001 = 0 + then FDetails.Add(' Bit 0=0: Shape will NOT be kept intact when the cells are moved') + else FDetails.Add('x Bit 0=1: Shape will be kept intact when the cells are moved'); + if w and $0002 = 0 + then FDetails.Add(' Bit 1=0: Shape will NOT be kept intact when the cells are resized') + else FDetails.Add('x Bit 1=1: Shape will be kept intact when the cells are resized'); + FDetails.Add( ' Bit 2=0: reserved (must be 0)'); + FDetails.Add( ' Bit 3=0: reserved (must be 0)'); + FDetails.Add( ' Bit 4=0: reserved (must be 0)'); + FDetails.Add( ' Bits 15-5: unused'); + end; + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(w), indent + 'Move/resize flags'); + inc(n); + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + indent + 'Column of the cell under the top left corner of the bounding rectangle of the shape'); + inc(n); + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + indent + 'x coordinate of the top left corner of the bounding rectangle relative to the corner of the underlying cell (as 1/1024 of that cell’s width)'); + inc(n); + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + indent + 'Row of the cell under the top left corner of the bounding rectangle of the shape'); + inc(n); + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + indent + 'y coordinate of the top left corner of the bounding rectangle relative to the corner of the underlying cell (as 1/256 of that cell’s height'); + inc(n); + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + indent + 'Column of the cell under the bottom right corner of the bounding rectangle of the shape'); + inc(n); + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + indent + 'x coordinate of the bottom right corner of the bounding rectangle relative to the corner of the underlying cell (as 1/1024 of that cell’s width)'); + inc(n); + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + indent + 'Row of the cell under the bottom right corner of the bounding rectangle of the shape'); + inc(n); + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + indent + 'y coordinate of the bottom right corner of the bounding rectangle relative to the corner of the underlying cell (as 1/256 of that cell’s height'); + inc(n); + end; + 1: begin // "Small" rectangle structure + numBytes := 2; + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + indent + 'top'); + inc(n); + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + indent + 'left'); + inc(n); + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + indent + 'right'); + inc(n); + Move(FBuffer[FBufferIndex], w, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)), + indent + 'bottom'); + inc(n); + end; + 2: begin // standard rectangle structure + numbytes := 4; + Move(FBuffer[FBufferIndex], dw, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(DWordLEToN(dw)), + indent + 'top'); + inc(n); + Move(FBuffer[FBufferIndex], dw, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(DWordLEToN(dw)), + indent + 'left'); + inc(n); + Move(FBuffer[FBufferIndex], dw, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(DWordLEToN(dw)), + indent + 'right'); + inc(n); + Move(FBuffer[FBufferIndex], dw, numbytes); + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(DWordLEToN(dw)), + indent + 'bottom'); + inc(n); + end; + end; + end; + + procedure DoShowOfficeArtRecord(out ARecType:Word; out ARecLen: DWord; + out AIsContainer: Boolean); + var + startIndex, endindex: Int64; + totalbytes: Int64; + begin + totalbytes := 0; + startIndex := FBufferIndex + 8; + repeat + DoShowHeader(ARecType, ARecLen, AIsContainer); + if totalbytes = 0 then endindex := startindex + ARecLen; + totalbytes := totalbytes + ARecLen; + if AIsContainer then begin + inc(level); + indent := PrepareIndent(level); + DoShowOfficeArtRecord(ARecType, ARecLen, AIsContainer); + end else + case ARecType of + $F008: DoShowOfficeArtFDG; + $F009: DoShowOfficeArtFSPGR; // shape group + $F00A: DoShowOfficeArtFSP; // instance of a shape + $F00B: DoShowOfficeArtFOPT(ARecLen); // table of OfficeArtRGFOPTE records + $F00D: DoShowOfficeArtClientTextbox(ARecLen); + $F010: DoShowOfficeArtClientAnchorData(0); // 0 - use OfficeArtClientAnchorSheet because contained in a sheet stream +// $F122: DoShowOfficeArtRGFOPTE(AIndent); // (tertiary) table of OfficeArtRGFOPTE records + else if ARecLen <> 0 then begin + ShowInRow(FCurrRow, FBufferIndex, ARecLen, '', indent + 'Skipping this unknown record...'); + inc(n); + end; + end; + until (FBufferIndex >= endindex) or (FBufferIndex >= Length(FBuffer)); + dec(level); + FBufferIndex := endindex; + end; + +begin + RowCount := FixedRows + 1000; + n := 0; + level := -1; + DoShowOfficeArtRecord(recType, recLen, isContainer); + RowCount := FixedRows + n; +end; procedure TBIFFGrid.ShowMulBlank; var @@ -3172,14 +3751,14 @@ begin if Row = FCurrRow then begin FDetails.Add('Comment flags:'#13); if (w and $0002 <> 0) - then FDetails.Add('Bit 1=1: Comment is shown at all times') - else FDetails.Add('Bit 1=0: Comment is not shown at all tiems'); + then FDetails.Add('x Bit 1=1: Comment is shown at all times') + else FDetails.Add(' Bit 1=0: Comment is not shown at all tiems'); if (w and $0080 <> 0) - then FDetails.Add('Bit 7=1: Row with comment is hidden') - else FDetails.Add('Bit 7=0: Row with comment is visible'); + then FDetails.Add('x Bit 7=1: Row with comment is hidden') + else FDetails.Add(' Bit 7=0: Row with comment is visible'); if (w and $0100 <> 0) - then FDetails.Add('Bit 8=1: Column with comment is hidden') - else FDetails.Add('Bit 8=0: Column with comment is visible'); + then FDetails.Add('x Bit 8=1: Column with comment is hidden') + else FDetails.Add(' Bit 8=0: Column with comment is visible'); FDetails.Add('All other bits are reserved and must be ignored.'); end; ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d ($%.4x)', [w, w]), @@ -3225,14 +3804,14 @@ begin Move(FBuffer[FBufferIndex], b, numBytes); if Row = FCurrRow then begin FDetails.Add('Cell protection and XF index:'#13); - FDetails.Add(Format('Bits 5-0 = %d: XF Index', [b and $3F])); + FDetails.Add(Format('x Bits 5-0 = %d: XF Index', [b and $3F])); case b and $40 of - 0: FDetails.Add('Bit 6 = 0: Cell is NOT locked.'); - 1: FDetails.Add('Bit 6 = 1: Cell is locked.'); + 0: FDetails.Add(' Bit 6 = 0: Cell is NOT locked.'); + 1: FDetails.Add('x Bit 6 = 1: Cell is locked.'); end; case b and $80 of - 0: FDetails.Add('Bit 7 = 0: Formula is NOT hidden.'); - 1: FDetails.Add('Bit 7 = 1: Formula is hidden.'); + 0: FDetails.Add(' Bit 7 = 0: Formula is NOT hidden.'); + 1: FDetails.Add('x Bit 7 = 1: Formula is hidden.'); end; end; ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d ($%.2x)', [b,b]), @@ -3253,27 +3832,27 @@ begin if Row = FCurrRow then begin FDetails.Add('Cell style:'#13); case b and $07 of - 0: FDetails.Add('Bits 2-0 = 0: Horizontal alignment is GENERAL'); - 1: FDetails.Add('Bits 2-0 = 1: Horizontal alignment is LEFT'); - 2: FDetails.Add('Bits 2-0 = 2: Horizontal alignment is CENTERED'); - 3: FDetails.Add('Bits 2-0 = 3: Horizontal alignment is RIGHT'); - 4: FDetails.Add('Bits 2-0 = 4: Horizontal alignment is FILLED'); + 0: FDetails.Add('x Bits 2-0 = 0: Horizontal alignment is GENERAL'); + 1: FDetails.Add('x Bits 2-0 = 1: Horizontal alignment is LEFT'); + 2: FDetails.Add('x Bits 2-0 = 2: Horizontal alignment is CENTERED'); + 3: FDetails.Add('x Bits 2-0 = 3: Horizontal alignment is RIGHT'); + 4: FDetails.Add('x Bits 2-0 = 4: Horizontal alignment is FILLED'); end; if b and $08 = 0 - then FDetails.Add('Bit 3 = 0: Cell has NO left border') - else FDetails.Add('Bit 3 = 1: Cell has left black border'); + then FDetails.Add(' Bit 3 = 0: Cell has NO left border') + else FDetails.Add('x Bit 3 = 1: Cell has left black border'); if b and $10 = 0 - then FDetails.Add('Bit 4 = 0: Cell has NO right border') - else FDetails.Add('Bit 4 = 1: Cell has right black border'); + then FDetails.Add(' Bit 4 = 0: Cell has NO right border') + else FDetails.Add('x Bit 4 = 1: Cell has right black border'); if b and $20 = 0 - then FDetails.Add('Bit 5 = 0: Cell has NO top border') - else FDetails.Add('Bit 5 = 1: Cell has top black border'); + then FDetails.Add(' Bit 5 = 0: Cell has NO top border') + else FDetails.Add('x Bit 5 = 1: Cell has top black border'); if b and $40 = 0 - then FDetails.Add('Bit 6 = 0: Cell has NO bottom border') - else FDetails.Add('Bit 6 = 1: Cell has bottom black border'); + then FDetails.Add(' Bit 6 = 0: Cell has NO bottom border') + else FDetails.Add('x Bit 6 = 1: Cell has bottom black border'); if b and $80 = 0 - then FDetails.Add('Bit 7 = 0: Cell has NO shaded background') - else FDetails.Add('Bit 7 = 1: Cell has shaded background'); + then FDetails.Add(' Bit 7 = 0: Cell has NO shaded background') + else FDetails.Add('x Bit 7 = 1: Cell has shaded background'); end; ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('%d ($%.2x)', [b,b]), 'Cell style'); @@ -3421,29 +4000,38 @@ begin if Row = FCurrRow then begin FDetails.Add('Option flags:'#13); if w and $0001 <> 0 - then FDetails.Add('Bit $0001 = 1: Object is locked when sheet is protected.') - else FDetails.Add('Bit $0001 = 0: Object is NOT locked when sheet is protected.'); + then FDetails.Add('x Bit $0001 = 1: Object is locked when sheet is protected.') + else FDetails.Add(' Bit $0001 = 0: Object is NOT locked when sheet is protected.'); if w and $000E <> 0 - then FDetails.Add('Bit $0002 <> 0: Reserved - must be zero - THIS SEEMS TO BE AN ERROR!') - else FDetails.Add('Bit $0002 = 0: Reserved - must be zero'); + then FDetails.Add('! Bit $0002 <> 0: Reserved - must be zero - THIS SEEMS TO BE AN ERROR!') + else FDetails.Add(' Bit $0002 = 0: Reserved - must be zero'); if w and $0010 <> 0 - then FDetails.Add('Bit $0010 = 1: Image of this object is intended to be included when printing') - else FDetails.Add('Bit $0010 = 0: Image of this object is NOT intended to be included when printing'); + then FDetails.Add('x Bit $0010 = 1: Image of this object is intended to be included when printing') + else FDetails.Add(' Bit $0010 = 0: Image of this object is NOT intended to be included when printing'); if w and $1FE0 <> 0 - then FDetails.Add('Bits 12-5 <> 0: Reserved - must be zero - THIS SEEMS TO BE AN ERROR!') - else FDetails.Add('Bits 12-5 = 0: Reserved - must be zero'); + then FDetails.Add('! Bits 12-5 <> 0: Reserved - must be zero - THIS SEEMS TO BE AN ERROR!') + else FDetails.Add(' Bits 12-5 = 0: Reserved - must be zero'); if w and $2000 <> 0 - then FDetails.Add('Bit $2000 = 1: Object uses automatic fill style.') - else FDetails.Add('Bit $2000 = 0: Object does NOT use automatic fill style.'); + then FDetails.Add('x Bit $2000 = 1: Object uses automatic fill style.') + else FDetails.Add(' Bit $2000 = 0: Object does NOT use automatic fill style.'); if w and $4000 <> 0 - then FDetails.Add('Bit $4000 = 1: Object uses automatic line style.') - else FDetails.Add('Bit $4000 = 0: Object does NOT use automatic line style.'); + then FDetails.Add('x Bit $4000 = 1: Object uses automatic line style.') + else FDetails.Add(' Bit $4000 = 0: Object does NOT use automatic line style.'); if w and $8000 <> 0 - then FDetails.Add('Bit $8000 = 1: Reserved - must be zero - THIS SEEMS TO BE AN ERROR!') - else FDetails.Add('Bit $8000 = 0: Reserved - must be zero.'); + then FDetails.Add('! Bit $8000 = 1: Reserved - must be zero - THIS SEEMS TO BE AN ERROR!') + else FDetails.Add(' Bit $8000 = 0: Reserved - must be zero.'); end; - ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.04x', [w]), - 'Option flags'); + s := ''; + if w and $0001 <> 0 then s := s + 'locked, '; + if w and $0010 <> 0 then s := s + 'print, '; + if w and $2000 <> 0 then s := s + 'automatic fill style, '; + if w and $4000 <> 0 then s := s + 'automatic line style, '; + if s <> '' then begin + Delete(s, Length(s)-1, 2); + s := 'Option flags:' + s; + end else + s := 'Option flags'; + ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.04x', [w]), s); inc(n); end; end; @@ -3452,94 +4040,7 @@ begin RowCount := FixedRows + n; end else if FFormat = sfExcel5 then begin - (* - - - - numBytes := 2; - Move(FBuffer[FBufferIndex], w, numBytes); - ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.04x', [WordLEToN(w)]), - 'cb (must be $12)'); - - numBytes := 2; - w := WordLEToN(w); - Move(FBuffer[FBufferIndex], w, numBytes); - if Row = FCurrRow then begin - FDetails.Add('Object type:'#13); - case w of - $00: FDetails.Add('$00 = Group'); - $01: FDetails.Add('$01 = Line'); - $02: FDetails.Add('$02 = Rectangle'); - $03: FDetails.Add('$03 = Oval'); - $04: FDetails.Add('$04 = Arc'); - $05: FDetails.Add('$05 = Chart'); - $06: FDetails.Add('$06 = Text'); - $07: FDetails.Add('$07 = Button'); - $08: FDetails.Add('$08 = Picture'); - $09: FDetails.Add('$09 = Polygon'); - $0B: FDetails.Add('$0B = Checkbox'); - $0C: FDetails.Add('$0C = Radio button'); - $0D: FDetails.Add('$0D = Edit box'); - $0E: FDetails.Add('$0E = Label'); - $0F: FDetails.Add('$0F = Dialog box'); - $10: FDetails.Add('$10 = Spin control'); - $11: FDetails.Add('$11 = Scrollbar'); - $12: FDetails.Add('$12 = List'); - $13: FDetails.Add('$13 = Group box'); - $14: FDetails.ADd('$14 = Dropdown list'); - $19: FDetails.Add('$19 = Note'); - $1E: FDetails.Add('$1E = OfficeArt object'); - else FDetails.Add(IntToStr(w) + ' = (unknown object)'); - end; - end; - ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.04x', [w]), - 'Object type (ot)'); - - numBytes := 2; - Move(FBuffer[FBufferIndex], w, numBytes); - ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.04x', [WordLEToN(w)]), - 'Object ID'); - - numBytes := 2; - Move(FBuffer[FBufferIndex], w, numBytes); - w := WordLEToN(w); - if Row = FCurrRow then begin - FDetails.Add('Object flags:'#13); - if w and $0001 <> 0 - then FDetails.Add('Bit $0001 = 1: Object is locked') - else FDetails.Add('Bit $0001 = 0: Object is NOT locked'); - if w and $0002 <> 0 - then FDetails.Add('Bit $0002 = 1: Reserved - must be zero!!!d') - else FDetails.Add('Bit $0002 = 0: Reserved - must be zero'); - if w and $0004 <> 0 - then FDetails.Add('Bit $0004 = 1: Application is expected to choose object size') - else FDetails.Add('Bit $0004 = 0: Application is NOT expected to choose object size'); - if w and $0008 <> 0 - then FDetails.Add('Bit $0008 = 1: Is a chart that is expected to be published when sheet is published') - else FDetails.Add('Bit $0008 = 0: Is NOT a chart that is expected to be published when sheet is published'); - if w and $0010 <> 0 - then FDetails.Add('Bit $0010 = 1: Image of this object is intended to be included when printing') - else FDetails.Add('Bit $0010 = 0: Image of this object is NOT intended to be included when printing'); - FDetails.Add('Bit $0020 : unused'); - FDetails.Add('Bit $0040 : unused'); - if w and $0080 <> 0 - then FDetails.Add('Bit $0080 = 1: Object is disabled') - else FDetails.ADd('Bit $0080 = 0: Object is NOT disabled'); - if w and $0100 <> 0 - then FDetails.Add('Bit $0100 = 1: is an auxiliary object that can only be automatically inserted by the application') - else FDetails.Add('Bit $0100 = 0: is NOT an auxiliary object that can only be automatically inserted by the application'); - if w and $0200 <> 0 - then FDetails.Add('Bit $0200 = 1: is expected to be updated on load to reflect the values in the range associated with the object') - else FDetails.Add('Bit $0200 = 0: is NOT expected to be updated on load to reflect the values in the range associated with the object'); - FDetails.Add('Bit $0400 : unused'); - FDetails.Add('Bit $0800 : unused'); - if w and $1000 <> 0 - then FDetails.Add('Bit $1000 = 1: is expected to be updated whenever the value of a cell in the range associated with the object changes') - else FDetails.Add('Bit $1000 = 0: is NOT expected to be updated whenever the value of a cell in the range associated with the object changes'); - end; - ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.04x', [w]), - 'Flags'); - *) + // to do end; end; @@ -3883,8 +4384,8 @@ begin if Row = FCurrRow then begin FDetails.Add('Protection state of the workbook:'#13); if w = 0 - then FDetails.Add('0 = Workbook is NOT protected.') - else FDetails.Add('1 = Workbook is protected.'); + then FDetails.Add(' 0 = Workbook is NOT protected.') + else FDetails.Add('x 1 = Workbook is protected.'); end; ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.4x', [w]), 'Protection state of the workbook'); @@ -3903,8 +4404,8 @@ begin if Row = FCurrRow then begin FDetails.Add('"Recalculate before save" option:'#13); if w = 0 - then FDetails.Add('0 = Do not recalculate') - else FDetails.Add('1 = Recalculate before saving the document'); + then FDetails.Add(' 0 = Do not recalculate') + else FDetails.Add('x 1 = Recalculate before saving the document'); end; ShowInRow(FCurrRow, FBufferIndex, numBytes, IntToStr(w), 'Recalculate before saving'); @@ -3943,8 +4444,8 @@ begin if Row = FCurrRow then begin FDetails.Add('RefreshAll record:'#13); if w = 0 - then FDetails.Add('0 = Do not force refresh of external data ranges, PivotTables and XML maps on workbook load.') - else FDetails.Add('1 = Force refresh of external data ranges, PivotTables and XML maps on workbook load.'); + then FDetails.Add(' 0 = Do not force refresh of external data ranges, PivotTables and XML maps on workbook load.') + else FDetails.Add('x 1 = Force refresh of external data ranges, PivotTables and XML maps on workbook load.'); end; ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('%d ($%.4x)', [w, w]), 'Force refresh of external data ranges, Pivot tables and XML maps on workbook load'); @@ -4297,44 +4798,44 @@ begin if Row = FCurrRow then begin FDetails.Add('Option flags:'#13); if w and $0001 = 0 - then FDetails.Add('Bit $0001 = 0: Do not show automatic page breaks') - else FDetails.Add('Bit $0001 = 1: Show automatic page breaks'); + then FDetails.Add(' Bit $0001 = 0: Do not show automatic page breaks') + else FDetails.Add('x Bit $0001 = 1: Show automatic page breaks'); if w and $0010 = 0 - then FDetails.Add('Bit $0010 = 0: Standard sheet') - else FDetails.Add('Bit $0010 = 1: Dialog sheet (BIFF5-BIFF8)'); + then FDetails.Add(' Bit $0010 = 0: Standard sheet') + else FDetails.Add('x Bit $0010 = 1: Dialog sheet (BIFF5-BIFF8)'); if w and $0020 = 0 - then FDetails.Add('Bit $0020 = 0: No automatic styles in outlines') - else FDetails.Add('Bit $0020 = 1: Apply automatic styles to outlines'); + then FDetails.Add(' Bit $0020 = 0: No automatic styles in outlines') + else FDetails.Add('x Bit $0020 = 1: Apply automatic styles to outlines'); if w and $0040 = 0 - then FDetails.Add('Bit $0040 = 0: Outline buttons above outline group') - else FDetails.Add('Bit $0040 = 1: Outline buttons below outline group'); + then FDetails.Add(' Bit $0040 = 0: Outline buttons above outline group') + else FDetails.Add('x Bit $0040 = 1: Outline buttons below outline group'); if w and $0080 = 0 - then FDetails.Add('Bit $0080 = 0: Outline buttons left of outline group') - else FDetails.Add('Bit $0080 = 1: Outline buttons right of outline group'); + then FDetails.Add(' Bit $0080 = 0: Outline buttons left of outline group') + else FDetails.Add('x Bit $0080 = 1: Outline buttons right of outline group'); if w and $0100 = 0 - then FDetails.Add('Bit $0100 = 0: Scale printout in percent') - else FDetails.Add('Bit $0100 = 1: Fit printout to number of pages'); + then FDetails.Add(' Bit $0100 = 0: Scale printout in percent') + else FDetails.Add('x Bit $0100 = 1: Fit printout to number of pages'); if w and $0200 = 0 - then FDetails.Add('Bit $0200 = 0: Save external linked values (BIFF3-BIFF4 only)') - else FDetails.Add('Bit $0200 = 1: Do NOT save external linked values (BIFF3-BIFF4 only)'); + then FDetails.Add(' Bit $0200 = 0: Save external linked values (BIFF3-BIFF4 only)') + else FDetails.Add('x Bit $0200 = 1: Do NOT save external linked values (BIFF3-BIFF4 only)'); if w and $0400 = 0 - then FDetails.Add('Bit $0400 = 0: Do not show row outline symbols') - else FDetails.Add('Bit $0400 = 1: Show row outline symbols'); + then FDetails.Add(' Bit $0400 = 0: Do not show row outline symbols') + else FDetails.Add('x Bit $0400 = 1: Show row outline symbols'); if w and $0800 = 0 - then FDetails.Add('Bit $0800 = 0: Do not show column outline symbols') - else FDetails.Add('Bit $0800 = 1: Show column outline symbols'); + then FDetails.Add(' Bit $0800 = 0: Do not show column outline symbols') + else FDetails.Add('x Bit $0800 = 1: Show column outline symbols'); case (w and $3000) shr 12 of - 0: FDetails.Add('Bits $3000 = $0000: Arrange windows tiled'); - 1: FDetails.Add('Bits $3000 = $1000: Arrange windows horizontal'); - 2: FDetails.Add('Bits $3000 = $2000: Arrange windows vertical'); - 3: FDetails.Add('Bits $3000 = $3000: Arrange windows cascaded'); + 0: FDetails.Add('x Bits $3000 = $0000: Arrange windows tiled'); + 1: FDetails.Add('x Bits $3000 = $1000: Arrange windows horizontal'); + 2: FDetails.Add('x Bits $3000 = $2000: Arrange windows vertical'); + 3: FDetails.Add('x Bits $3000 = $3000: Arrange windows cascaded'); end; if w and $4000 = 0 - then FDetails.Add('Bits $4000 = 0: Excel like expression evaluation (BIFF4-BIFF8 only)') - else FDetails.Add('Bits $4000 = 1: Lotus like expression evaluation (BIFF4-BIFF8 only)'); + then FDetails.Add('x Bits $4000 = 0: Excel like expression evaluation (BIFF4-BIFF8 only)') + else FDetails.Add('x Bits $4000 = 1: Lotus like expression evaluation (BIFF4-BIFF8 only)'); if w and $8000 = 0 - then FDetails.Add('Bits $8000 = 0: Excel like formula editing (BIFF4-BIFF8 only)') - else FDetails.Add('Bits $8000 = 1: Lotus like formula editing (BIFF4-BIFF8 only)'); + then FDetails.Add('x Bits $8000 = 0: Excel like formula editing (BIFF4-BIFF8 only)') + else FDetails.Add('x Bits $8000 = 1: Lotus like formula editing (BIFF4-BIFF8 only)'); end; ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.4x (%d)', [w, w]), 'Option flags'); @@ -4499,8 +5000,8 @@ begin if Row = FCurrRow then begin FDetails.Add('Attributes:'#13); if w and $0001 = 0 - then FDetails.Add('Bit 0 = 0: The containing record does not specify a range of cells.') - else FDetails.Add('Bit 0 = 1: The containing record specifies a range of cells.'); + then FDetails.Add(' Bit 0 = 0: The containing record does not specify a range of cells.') + else FDetails.Add('x Bit 0 = 1: The containing record specifies a range of cells.'); FDetails.Add('Bit 1: specifies wether to alert the user of possible problems '+ 'when saving the file whithout having reckognized this record.'); FDetails.Add('Bits 2-15: reserved (MUST be zero, MUST be ignored)'); @@ -4515,11 +5016,11 @@ begin if Row = FCurrRow then begin FDetails.Add('Flags:'#13); if b and $01 = 0 - then FDetails.Add('Bit 0 = 0: no built-in style') - else FDetails.Add('Bit 0 = 1: built-in style'); + then FDetails.Add(' Bit 0 = 0: no built-in style') + else FDetails.Add('x Bit 0 = 1: built-in style'); if b and $02 = 0 - then FDetails.Add('Bit 1 = 0: NOT hidden') - else FDetails.Add('Bit 1 = 1: hidden (i.e. is displayed in user interface)'); + then FDetails.Add(' Bit 1 = 0: NOT hidden') + else FDetails.Add('x Bit 1 = 1: hidden (i.e. is displayed in user interface)'); FDetails.Add('Bit 2: specifies whether the built-in cell style was modified '+ 'by the user and thus has a custom definition.'); FDetails.Add('Bit 3-7: Reserved'); @@ -4612,28 +5113,35 @@ procedure TBIFFGrid.ShowTXO; var numbytes: Word; w: Word; + s, sh, sv, sl: String; begin RowCount := FixedRows + 9; numbytes := 2; Move(FBuffer[FBufferIndex], w, numBytes); w := WordLEToN(w); + sh := ''; + case (w and $000E) shr 1 of + 1: sh := sh + 'left-aligned, '; + 2: sh := sh + 'centered, '; + 3: sh := sh + 'right-aligned, '; + 4: sh := sh + 'justified, '; + end; + sv := ''; + case (w and $0070) shr 4 of + 1: sv := sv + 'top, '; + 2: sv := sv + 'middle, '; + 3: sv := sv + 'bottom, '; + 4: sv := sv + 'justify, '; + end; + s := sh + sv; + if (w and $0200) shr 9 <> 0 then s := s + 'lock text, '; if Row = FCurrRow then begin FDetails.Add( 'Option flags:'#13); FDetails.Add( 'Bit 0: Reserved'); - case (w and $000E) shr 1 of - 0: FDetails.Add('Bits 1-3: 0 = Horizontal text alignment: none'); - 1: FDetails.Add('Bits 1-3: 1 = Horizontal text alignment: left-aligned'); - 2: FDetails.Add('Bits 1-3: 2 = Horizontal text alignment: centered'); - 3: FDetails.Add('Bits 1-3: 3 = Horizontal text alignment: right-aligned'); - 4: FDetails.Add('Bits 1-3: 4 = Horizontal text alignment: justified'); - end; - case (w and $0070) shr 4 of - 0: FDetails.Add('Bits 4-6: 0 = Vertical text alignment: none'); - 1: FDetails.Add('Bits 4-6: 1 = Vertical text alignment: top'); - 2: FDetails.Add('Bits 4-6: 2 = Vertical text alignment: center'); - 3: FDetails.Add('Bits 4-6: 3 = Vertical text alignment: bottom'); - 4: FDetails.Add('Bits 4-6: 4 = Vertical text alignment: justify'); - end; + if sh = '' then sh := 'none'; + FDetails.Add( 'Bits 1-3: 0 = Horizontal text alignment: ' + sh); + if sv = '' then sv := 'none'; + FDetails.Add( 'Bits 4-6: 0 = Vertical text alignment: ' + sv); FDetails.Add( 'Bits 7-8: Reserved'); case (w and $0200) shr 9 of 0: FDetails.Add('Bit 9: Lock Text Option is off.'); @@ -4641,22 +5149,27 @@ begin end; FDetails.Add( 'Bits 10-15: Reserved'); end; - ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('$%.4x', [w]), - 'Option flags'); + if s <> '' then begin + Delete(s, Length(s)-1, 2); + s := 'Option flags: ' + s; + end else + s := 'Option flags'; + ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('$%.4x', [w]), s); Move(FBuffer[FBufferIndex], w, numbytes); w := WordLEToN(w); + case w of + 0: s := 'No rotation (text appears left to right'; + 1: s := 'Text appears top to bottom; letters are upright'; + 2: s := 'Text is rotated 90 degrees counterclockwise'; + 3: s := 'Text is rotated 90 degrees clockwise'; + end; if Row = FCurrRow then begin FDetails.Add('Orientation of text with the object boundary:'#13); - case w of - 0: FDetails.Add('0 = no rotation (text appears left to right)'); - 1: FDetails.Add('1 = text appears top to bottom; letters are upright'); - 2: FDetails.Add('2 = text is rotated 90 degrees counterclockwise'); - 3: FDetails.Add('3 = text is rotated 90 degrees clockwise'); - end; + FDetails.Add(Format('%d = %s', [w, s])); end; ShowInRow(FCurrRow, FBufferIndex, numBytes, IntToStr(w), - 'Orientation of text within the object boundary'); + 'Orientation of text within the object boundary: ' + s); Move(FBuffer[FBufferIndex], w, numBytes); ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(w), @@ -4676,7 +5189,7 @@ begin Move(FBuffer[FBufferIndex], w, numBytes); ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(w), - 'Length of formatting runs (in seconds following CONTINUE record)'); + 'Length of formatting runs (in second following CONTINUE record)'); Move(FBuffer[FBufferIndex], w, numBytes); ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(w), @@ -4723,20 +5236,20 @@ begin if Row = FCurrRow then begin FDetails.Add('Option flags:'); if w and $0001 = 0 - then FDetails.Add('Bit $0001 = 0: Window is visible') - else FDetails.Add('Bit $0001 = 1: Window is hidden'); + then FDetails.Add(' Bit $0001 = 0: Window is visible') + else FDetails.Add('x Bit $0001 = 1: Window is hidden'); if w and $0002 = 0 - then FDetails.Add('Bit $0002 = 0: Window is open') - else FDetails.Add('Bit $0002 = 1: Window is minimized'); + then FDetails.Add(' Bit $0002 = 0: Window is open') + else FDetails.Add('x Bit $0002 = 1: Window is minimized'); if w and $0008 = 0 - then FDetails.Add('Bit $0008 = 0: Horizontal scrollbar hidden') - else FDetails.Add('Bit $0008 = 1: Horizontal scrollbar visible'); + then FDetails.Add(' Bit $0008 = 0: Horizontal scrollbar hidden') + else FDetails.Add('x Bit $0008 = 1: Horizontal scrollbar visible'); if w and $0010 = 0 - then FDetails.Add('Bit $0010 = 0: Vertical scrollbar hidden') - else FDetails.Add('Bit $0010 = 1: Vertical scrollbar visible'); + then FDetails.Add(' Bit $0010 = 0: Vertical scrollbar hidden') + else FDetails.Add('x Bit $0010 = 1: Vertical scrollbar visible'); if w and $0020 = 0 - then FDetails.Add('Bit $0020 = 0: Worksheet tab bar hidden') - else FDetails.Add('Bit $0020 = 1: Worksheet tab bar visible'); + then FDetails.Add(' Bit $0020 = 0: Worksheet tab bar hidden') + else FDetails.Add('x Bit $0020 = 1: Worksheet tab bar visible'); end; ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('%d ($%.4x)', [w, w]), 'Option flags'); @@ -4809,41 +5322,41 @@ begin if Row = FCurrRow then begin FDetails.Add('Option flags:'); if w and $0001 = 0 - then FDetails.Add('Bit $0001 = 0: Show formula results') - else FDetails.Add('Bit $0001 = 1: Show formulas'); + then FDetails.Add(' Bit $0001 = 0: Show formula results') + else FDetails.Add('x Bit $0001 = 1: Show formulas'); if w and $0002 = 0 - then FDetails.Add('Bit $0002 = 0: Do not show grid lines') - else FDetails.Add('Bit $0002 = 1: Show grid lines'); + then FDetails.Add(' Bit $0002 = 0: Do not show grid lines') + else FDetails.Add('x Bit $0002 = 1: Show grid lines'); if w and $0004 = 0 - then FDetails.Add('Bit $0004 = 0: Do not show sheet headers') - else FDetails.Add('Bit $0004 = 1: Show sheet headers'); + then FDetails.Add(' Bit $0004 = 0: Do not show sheet headers') + else FDetails.Add('x Bit $0004 = 1: Show sheet headers'); if w and $0008 = 0 - then FDetails.Add('Bit $0008 = 0: Panes are not frozen') - else FDetails.Add('Bit $0008 = 1: Panes are frozen'); + then FDetails.Add(' Bit $0008 = 0: Panes are not frozen') + else FDetails.Add('x Bit $0008 = 1: Panes are frozen'); if w and $0010 = 0 - then FDetails.Add('Bit $0010 = 0: Show zero values as empty cells') - else FDetails.Add('Bit $0010 = 1: Show zero values'); + then FDetails.Add(' Bit $0010 = 0: Show zero values as empty cells') + else FDetails.Add('x Bit $0010 = 1: Show zero values'); if w and $0020 = 0 - then FDetails.Add('Bit $0020 = 0: Manual grid line color') - else FDetails.Add('Bit $0020 = 1: Automatic grid line color'); + then FDetails.Add(' Bit $0020 = 0: Manual grid line color') + else FDetails.Add('x Bit $0020 = 1: Automatic grid line color'); if w and $0040 = 0 - then FDetails.Add('Bit $0040 = 0: Columns from left to right') - else FDetails.Add('Bit $0040 = 1: Columns from right to left'); + then FDetails.Add(' Bit $0040 = 0: Columns from left to right') + else FDetails.Add('x Bit $0040 = 1: Columns from right to left'); if w and $0080 = 0 - then FDetails.Add('Bit $0080 = 0: Do not show outline symbols') - else FDetails.Add('Bit $0080 = 1: Show outline symbols'); + then FDetails.Add(' Bit $0080 = 0: Do not show outline symbols') + else FDetails.Add('x Bit $0080 = 1: Show outline symbols'); if w and $0100 = 0 - then FDetails.Add('Bit $0100 = 0: Keep splits if pane freeze is removed') - else FDetails.Add('Bit $0100 = 1: Remove splits if pane freeze is removed'); + then FDetails.Add(' Bit $0100 = 0: Keep splits if pane freeze is removed') + else FDetails.Add('x Bit $0100 = 1: Remove splits if pane freeze is removed'); if w and $0200 = 0 - then FDetails.Add('Bit $0200 = 0: Sheet not selected') - else FDetails.Add('Bit $0200 = 1: Sheet selected'); + then FDetails.Add(' Bit $0200 = 0: Sheet not selected') + else FDetails.Add('x Bit $0200 = 1: Sheet selected'); if w and $0400 = 0 - then FDetails.Add('Bit $0400 = 0: Sheet not active') - else FDetails.Add('Bit $0400 = 1: Sheet active'); + then FDetails.Add(' Bit $0400 = 0: Sheet not active') + else FDetails.Add('x Bit $0400 = 1: Sheet active'); if w and $0800 = 0 - then FDetails.Add('Bit $0800 = 0: Show in normal view') - else FDetails.Add('Bit $0800 = 1: Show in page break preview'); + then FDetails.Add(' Bit $0800 = 0: Show in normal view') + else FDetails.Add('x Bit $0800 = 1: Show in page break preview'); end; ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('%d ($%.4x)', [w, w]), 'Option flags'); @@ -5027,7 +5540,7 @@ begin 5: FDetails.Add('Bits 0-2 = 5: Horizontal alignment "Justified"'); 6: FDetails.Add('Bits 0-2 = 6: Horizontal alignment "Centred across selection"'); 7: if FFormat = sfExcel8 then - FDetails.Add('Bits 0-2 = 7: Horizontal alignment "Distributed"'); + FDetails.Add('x Bits 0-2 = 7: Horizontal alignment "Distributed"'); end; if b and $08 = 0 then FDetails.Add('Bit 3 = 0: Text is not wrapped.') @@ -5042,7 +5555,7 @@ begin end; if FFormat = sfExcel8 then begin if b and $80 = 0 - then FDetails.Add('Bit 3 = 0: Don''t justify last line in justified or distibuted text') + then FDetails.Add('Bit 3 = 0: Do NOT justify last line in justified or distibuted text') else FDetails.Add('Bit 3 = 1: Justify last line in justified or distibuted text'); end; end; @@ -5394,8 +5907,8 @@ begin if Row = FCurrRow then begin FDetails.Add('Attributes:'#13); if w and $0001 = 0 - then FDetails.Add('Bit 0 = 0: The containing record does not specify a range of cells.') - else FDetails.Add('Bit 0 = 1: The containing record specifies a range of cells.'); + then FDetails.Add(' Bit 0 = 0: The containing record does not specify a range of cells.') + else FDetails.Add('x Bit 0 = 1: The containing record specifies a range of cells.'); FDetails.Add('Bit 1: specifies wether to alert the user of possible problems '+ 'when saving the file whithout having reckognized this record.'); FDetails.Add('Bits 2-15: reserved (MUST be zero, MUST be ignored)'); @@ -5453,8 +5966,8 @@ begin if Row = FCurrRow then begin FDetails.Add('Attributes:'#13); if w and $0001 = 0 - then FDetails.Add('Bit 0 = 0: The containing record does not specify a range of cells.') - else FDetails.Add('Bit 0 = 1: The containing record specifies a range of cells.'); + then FDetails.Add(' Bit 0 = 0: The containing record does not specify a range of cells.') + else FDetails.Add('x Bit 0 = 1: The containing record specifies a range of cells.'); FDetails.Add('Bit 1: specifies wether to alert the user of possible problems '+ 'when saving the file whithout having reckognized this record.'); FDetails.Add('Bits 2-15: reserved (MUST be zero, MUST be ignored)'); diff --git a/components/fpspreadsheet/reference/BIFFExplorer/bebiffutils.pas b/components/fpspreadsheet/reference/BIFFExplorer/bebiffutils.pas index a4ddbdde1..2bc1b98ed 100644 --- a/components/fpspreadsheet/reference/BIFFExplorer/bebiffutils.pas +++ b/components/fpspreadsheet/reference/BIFFExplorer/bebiffutils.pas @@ -191,7 +191,7 @@ begin $0006: Result := 'FORMULA'; $0007: Result := 'STRING'; $0008: Result := 'ROW'; - $0009: Result := 'BOF'; + $0009: Result := 'BOF: Begin of file'; $000A: Result := 'EOF: End of file'; $000B: Result := 'INDEX'; $000C: Result := 'CALCCOUNT: Iteration count'; @@ -234,7 +234,7 @@ begin $0037: Result := 'DateTable2'; $003C: Result := 'CONTINUE: Continues long records'; $003D: Result := 'WINDOW1: Window information'; - $003E: Result := 'WINDOW2'; + $003E: Result := 'WINDOW2: Window information'; $0040: Result := 'BACKUP: Save backup version of the file'; $0041: Result := 'PANE: Number of panes and their position'; $0042: Result := 'CODEPAGE: Default code page'; // also: CODENAME: VBE object name ??? @@ -250,7 +250,7 @@ begin $005A: Result := 'CRN: Non-resident operands'; $005B: Result := 'FILESHARING: File-sharing information'; $005C: Result := 'WRITEACCESS: Write access user name'; - $005D: Result := 'OBJ'; + $005D: Result := 'OBJ: Properties of an object in a sheet'; $005E: Result := 'UNCALCED: Recalculation status'; $005F: Result := 'SAVERECALC: Recalculate before saving'; $0060: Result := 'TEMPLATE: Workbook is a template'; diff --git a/components/fpspreadsheet/reference/BIFFExplorer/bemain.lfm b/components/fpspreadsheet/reference/BIFFExplorer/bemain.lfm index 551c7d192..601705a3d 100644 --- a/components/fpspreadsheet/reference/BIFFExplorer/bemain.lfm +++ b/components/fpspreadsheet/reference/BIFFExplorer/bemain.lfm @@ -71,9 +71,9 @@ object MainForm: TMainForm Height = 506 Top = 0 Width = 665 - ActivePage = PgAnalysis + ActivePage = PgValues Align = alClient - TabIndex = 0 + TabIndex = 1 TabOrder = 0 OnChange = PageControlChange object PgAnalysis: TTabSheet @@ -108,12 +108,12 @@ object MainForm: TMainForm end object PgValues: TTabSheet Caption = 'Values' - ClientHeight = 463 + ClientHeight = 478 ClientWidth = 657 object ValueGrid: TStringGrid Left = 0 Height = 158 - Top = 305 + Top = 320 Width = 657 Align = alBottom ColCount = 3 @@ -144,17 +144,17 @@ object MainForm: TMainForm end object HexPanel: TPanel Left = 0 - Height = 300 + Height = 315 Top = 0 Width = 657 Align = alClient Caption = 'HexPanel' - ClientHeight = 300 + ClientHeight = 315 ClientWidth = 657 TabOrder = 1 object HexGrid: TStringGrid Left = 1 - Height = 298 + Height = 313 Top = 1 Width = 373 Align = alClient @@ -171,22 +171,22 @@ object MainForm: TMainForm OnSelection = HexGridSelection ColWidths = ( 28 - 20 - 20 - 20 - 20 - 20 - 20 - 20 - 20 - 20 - 20 - 20 - 20 - 20 - 20 - 20 - 24 + 21 + 21 + 21 + 21 + 21 + 21 + 21 + 21 + 21 + 21 + 21 + 21 + 21 + 21 + 21 + 26 ) Cells = ( 16 @@ -242,7 +242,7 @@ object MainForm: TMainForm end object AlphaGrid: TStringGrid Left = 379 - Height = 298 + Height = 313 Top = 1 Width = 277 Align = alRight @@ -256,22 +256,22 @@ object MainForm: TMainForm OnClick = GridClick OnSelection = AlphaGridSelection ColWidths = ( - 16 - 16 - 16 - 16 - 16 - 16 - 16 - 16 - 16 - 16 - 16 - 16 - 16 - 16 - 16 - 16 + 17 + 17 + 17 + 17 + 17 + 17 + 17 + 17 + 17 + 17 + 17 + 17 + 17 + 17 + 17 + 18 ) Cells = ( 16 @@ -327,7 +327,7 @@ object MainForm: TMainForm end object HexDumpSplitter: TSplitter Left = 374 - Height = 298 + Height = 313 Top = 1 Width = 5 Align = alRight @@ -338,7 +338,7 @@ object MainForm: TMainForm Cursor = crVSplit Left = 0 Height = 5 - Top = 300 + Top = 315 Width = 657 Align = alBottom ResizeAnchor = akBottom diff --git a/components/fpspreadsheet/reference/BIFFExplorer/bemain.pas b/components/fpspreadsheet/reference/BIFFExplorer/bemain.pas index 786823cc7..703a72d9b 100644 --- a/components/fpspreadsheet/reference/BIFFExplorer/bemain.pas +++ b/components/fpspreadsheet/reference/BIFFExplorer/bemain.pas @@ -769,22 +769,24 @@ procedure TMainForm.LoadFile(const AFileName: String); var valid: Boolean; excptn: Exception = nil; + ext: String; begin if not FileExistsUTF8(AFileName) then begin MessageDlg(Format('File "%s" not found.', [AFileName]), mtError, [mbOK], 0); exit; end; - if Lowercase(ExtractFileExt(AFileName)) <> '.xls' then begin + ext := Lowercase(ExtractFileExt(AFilename)); + if ext <> '.xls' then begin MessageDlg('BIFFExplorer can only process binary Excel files (extension ".xls")', mtError, [mbOK], 0); exit; end; // .xls files can contain several formats. We look into the header first. - if Lowercase(ExtractFileExt(AFileName))=STR_EXCEL_EXTENSION then + if ext = STR_EXCEL_EXTENSION then begin - valid := GetFormatFromFileHeader(AFileName, FFormat); + valid := GetFormatFromFileHeader(UTF8ToAnsi(AFileName), FFormat); // It is possible that valid xls files are not detected correctly. Therefore, // we open them explicitly by trial and error - see below. if not valid then @@ -853,7 +855,7 @@ begin // Rewind the stream and read from it MemStream.Position := 0; - FFileName := ExpandFileName(UTF8ToSys(AFileName)); + FFileName := ExpandFileName(AFileName); ReadFromStream(MemStream); FFormat := AFormat; diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index df8c701d1..f36a21e4b 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -117,10 +117,16 @@ type { TsSpreadBIFF8Writer } TsSpreadBIFF8Writer = class(TsSpreadBIFFWriter) + private + FCommentList: TFPList; + procedure WriteCommentsCallback(ACell: PCell; AStream: TStream); + protected { Record writing methods } procedure WriteBOF(AStream: TStream; ADataType: Word); function WriteBoundsheet(AStream: TStream; ASheetName: string): Int64; + procedure WriteComment(AStream: TStream; ACell: PCell); override; + procedure WriteComments(AStream: TStream; AWorksheet: TsWorksheet); procedure WriteDimensions(AStream: TStream; AWorksheet: TsWorksheet); procedure WriteEOF(AStream: TStream); procedure WriteFont(AStream: TStream; AFont: TsFont); @@ -129,8 +135,14 @@ type procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; const AValue: string; ACell: PCell); override; procedure WriteMergedCells(AStream: TStream; AWorksheet: TsWorksheet); + procedure WriteMSODrawing1(AStream: TStream; ANumShapes: Word; ACell: PCell); + procedure WriteMSODrawing2(AStream: TStream; ACell: PCell; AObjID: Word); + procedure WriteMSODrawing2_Data(AStream: TStream; ACell: PCell; AShapeID: Word); + procedure WriteMSODrawing3(AStream: TStream; ACell: PCell); + procedure WriteNOTE(AStream: TStream; ACell: PCell; AObjID: Word); procedure WriteNumFormat(AStream: TStream; AFormatData: TsNumFormatData; AListIndex: Integer); override; + procedure WriteOBJ(AStream: TStream; AObjID: Word); function WriteRPNCellAddress(AStream: TStream; ARow, ACol: Cardinal; AFlags: TsRelFlags): word; override; function WriteRPNCellOffset(AStream: TStream; ARowOffset, AColOffset: Integer; @@ -139,8 +151,9 @@ type AFlags: TsRelFlags): Word; override; function WriteString_8bitLen(AStream: TStream; AString: String): Integer; override; procedure WriteStringRecord(AStream: TStream; AString: string); override; - procedure WriteStyle(AStream: TStream); - procedure WriteWindow2(AStream: TStream; ASheet: TsWorksheet); + procedure WriteSTYLE(AStream: TStream); + procedure WriteTXO(AStream: TStream; ACell: PCell); + procedure WriteWINDOW2(AStream: TStream; ASheet: TsWorksheet); procedure WriteXF(AStream: TStream; AFormatRecord: PsCellFormat; XFType_Prot: Byte = 0); override; public @@ -227,11 +240,13 @@ var implementation uses - Math, lconvencoding, fpsStrings, fpsStreams, fpsExprParser; + Math, lconvencoding, + fpsStrings, fpsStreams, fpsExprParser, xlsEscher; const { Excel record IDs } INT_EXCEL_ID_MERGEDCELLS = $00E5; // BIFF8 only + INT_EXCEL_ID_MSODRAWING = $00EC; // BIFF8 only INT_EXCEL_ID_SST = $00FC; // BIFF8 only INT_EXCEL_ID_LABELSST = $00FD; // BIFF8 only INT_EXCEL_ID_TXO = $01B6; // BIFF8 only @@ -312,6 +327,8 @@ const XF_ROTATION_STACKED ); + SHAPEID_BASE = 1024; + type TBIFF8_DimensionsRecord = packed record RecordID: Word; @@ -357,11 +374,26 @@ type BkGr3: Word; end; + TBIFF8TXORecord = packed record + RecordID: Word; + RecordSize: Word; + OptionFlags: Word; + TextRot: Word; + Reserved1: Word; + Reserved2: Word; + Reserved3: Word; + TextLen: Word; + NumFormattingRuns: Word; + Reserved4: Word; + Reserved5: Word; + end; + TBIFF8Comment = class ID: Integer; Text: String; end; + { TsSpreadBIFF8Reader } destructor TsSpreadBIFF8Reader.Destroy; @@ -1104,23 +1136,11 @@ end; We only extract the length of the comment text (in characters). The text itself is contained in the following CONTINUE record. } procedure TsSpreadBIFF8Reader.ReadTXO(const AStream: TStream); -type - TBIFF8TXORecord = packed record - OptionFlags: Word; - TextRot: Word; - Reserved1: Word; - Reserved2: Word; - Reserved3: Word; - TextLen: Word; - NumFormattingRuns: Word; - Reserved4: Word; - Reserved5: Word; - end; var rec: TBIFF8TXORecord; begin rec.OptionFlags := 0; // to silence the compiler - AStream.ReadBuffer(rec, Sizeof(Rec)); + AStream.ReadBuffer(rec.OptionFlags, Sizeof(Rec) - 2*SizeOf(Word)); FCommentLen := WordLEToN(rec.TextLen); end; @@ -1462,6 +1482,7 @@ begin else begin WriteRows(AStream, FWorksheet); WriteCellsToStream(AStream, FWorksheet.Cells); + WriteComments(AStream, FWorksheet); end; // View settings block @@ -1490,8 +1511,7 @@ end; procedure TsSpreadBIFF8Writer.WriteBOF(AStream: TStream; ADataType: Word); begin { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_BOF)); - AStream.WriteWord(WordToLE(16)); //total record size + WriteBIFFHeader(AStream, INT_EXCEL_ID_BOF, 16); { BIFF version. Should only be used if this BOF is for the workbook globals } { OpenOffice rejects to correctly read xls files if this field is @@ -1515,19 +1535,14 @@ begin AStream.WriteDWord(DWordToLE(0)); //????????? end; -{******************************************************************* -* TsSpreadBIFF8Writer.WriteBoundsheet () -* -* DESCRIPTION: Writes an Excel 8 BOUNDSHEET record -* -* Always located on the workbook globals substream. -* -* One BOUNDSHEET is written for each worksheet. -* -* RETURNS: The stream position where the absolute stream position -* of the BOF of this sheet should be written (4 bytes size). -* -*******************************************************************} +{@@ ---------------------------------------------------------------------------- + Writes an Excel 8 BOUNDSHEET record + Always located in the workbook globals substream. + One BOUNDSHEET is written for each worksheet. + + @return The stream position where the absolute stream position + of the BOF of this sheet should be written (4 bytes size). +-------------------------------------------------------------------------------} function TsSpreadBIFF8Writer.WriteBoundsheet(AStream: TStream; ASheetName: string): Int64; var Len: Byte; @@ -1537,8 +1552,7 @@ begin Len := Length(WideSheetName); { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_BOUNDSHEET)); - AStream.WriteWord(WordToLE(6 + 1 + 1 + Len * Sizeof(WideChar))); + WriteBIFFHeader(AStream, INT_EXCEL_ID_BOUNDSHEET, 8 + Len * Sizeof(WideChar)); { Absolute stream position of the BOF record of the sheet represented by this record } @@ -1558,6 +1572,63 @@ begin AStream.WriteBuffer(WideStringToLE(WideSheetName)[1], Len * Sizeof(WideChar)); end; +{@@ ---------------------------------------------------------------------------- + Inherited method for writing a cell comment immediately after cell content. + A writing method has been implemented by xlscommon. But in BIFF8, this + must not do anything because comments are collected in a list and + written en-bloc. See WriteComments. +-------------------------------------------------------------------------------} +procedure TsSpreadBIFF8Writer.WriteComment(AStream: TStream; ACell: PCell); +begin + // Nothing to do. Reverts the behavior introduced by xlscommon. + Unused(AStream, ACell); +end; + +{@@ ---------------------------------------------------------------------------- + Writes all comments to the worksheet stream +-------------------------------------------------------------------------------} +procedure TsSpreadBIFF8Writer.WriteComments(AStream: TStream; + AWorksheet: TsWorksheet); +var + i: Integer; +begin + exit; // Remove after comments can be written correctly + {$warning TODO: Fix writing of cell comments in BIFF8 (file is readable by OpenOffice, but not by Excel)} + + FCommentList := TFPList.Create; + try + IterateThroughCells(AStream, AWorksheet.Cells, WriteCommentsCallback); + if FCommentList.Count = 0 then + exit; + + for i:=0 to FCommentList.Count-1 do begin + if i = 0 then + WriteMSODRAWING1(AStream, FCommentList.Count, PCell(FCommentList[i])) + else + WriteMSODRAWING2(AStream, PCell(FCommentList[i]), i+1); + WriteOBJ(AStream, i+1); + WriteMSODRAWING3(AStream, PCell(FCommentList[i])); + WriteTXO(AStream, PCell(FCommentList[i])); + end; + + for i:=0 to FCommentList.Count-1 do + WriteNOTE(AStream, PCell(FCommentList[i]), i+1); + finally + FreeAndNil(FCommentList); + end; +end; + +{@@ ---------------------------------------------------------------------------- + Helper method which stores the pointer to a cell in the FCommentsList if the + cell contains a comment +-------------------------------------------------------------------------------} +procedure TsSpreadBIFF8Writer.WriteCommentsCallback(ACell: PCell; + AStream: TStream); +begin + Unused(AStream); + if (ACell <> nil) and (ACell^.Comment <> '') then + FCommentList.Add(ACell); +end; {@@ ---------------------------------------------------------------------------- Writes an Excel 8 DIMENSIONS record @@ -1590,29 +1661,20 @@ begin AStream.WriteBuffer(rec, SizeOf(rec)); end; -{******************************************************************* -* TsSpreadBIFF8Writer.WriteEOF () -* -* DESCRIPTION: Writes an Excel 8 EOF record -* -* This must be the last record on an Excel 8 stream -* -*******************************************************************} +{@@ ---------------------------------------------------------------------------- + Writes an Excel 8 EOF record. + This must be the last record on an Excel 8 stream +-------------------------------------------------------------------------------} procedure TsSpreadBIFF8Writer.WriteEOF(AStream: TStream); begin { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_EOF)); - AStream.WriteWord(WordToLE($0000)); + WriteBIFFHeader(AStream, INT_EXCEL_ID_EOF, 0); end; -{******************************************************************* -* TsSpreadBIFF8Writer.WriteFont () -* -* DESCRIPTION: Writes an Excel 8 FONT record -* -* The font data is passed in an instance of TsFont -* -*******************************************************************} +{@@ ---------------------------------------------------------------------------- + Writes an Excel 8 FONT record. + The font data is passed as an instance of TsFont +-------------------------------------------------------------------------------} procedure TsSpreadBIFF8Writer.WriteFont(AStream: TStream; AFont: TsFont); var Len: Byte; @@ -1631,8 +1693,7 @@ begin Len := Length(WideFontName); { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_FONT)); - AStream.WriteWord(WordToLE(14 + 1 + 1 + Len * Sizeof(WideChar))); + WriteBIFFHeader(AStream, INT_EXCEL_ID_FONT, 16 + Len * Sizeof(WideChar)); { Height of the font in twips = 1/20 of a point } AStream.WriteWord(WordToLE(round(AFont.Size*20))); @@ -1691,7 +1752,179 @@ var i: Integer; begin for i:=0 to Workbook.GetFontCount-1 do - WriteFont(AStream, Workbook.GetFont(i)); + WriteFONT(AStream, Workbook.GetFont(i)); +end; + +{@@ ---------------------------------------------------------------------------- + Writes the first MSODRAWING record to file. It is needed for a comment + attached to a cell, but also for embedded shapes (currently not supported). + +
+  Structure of this record:
+                                                    Type    Ver   Inst
+     Dg container                                   $F002          0
+       |--- FDG record                              $F008    0     1
+       |--- SpGr container                          $F003          0
+             |---- Sp container  (group shape)      $F004          0
+             |       |---- FSpGr record             $F009    1     0
+MSODRAWING1  |       |---- FSp record               $F00A    2     0
+................................................................................
+MSODRAWING2  |---- Sp container  (child shape)      $F004          0
+                     |---- FSp record               $F00A    2    202 (Textbox)
+                     |---- FOpt record              $F00B    3    13 (num props)
+                     |---- Client anchor record     $F010    0     0
+                     |---- Client data record       $F011    0     0
+
+-------------------------------------------------------------------------------} +procedure TsSpreadBiff8Writer.WriteMSODrawing1(AStream: TStream; ANumShapes: Word; + ACell: PCell); +const + DRAWING_ID = 1; +var + len: DWord; + tmpStream: TMemoryStream; +begin + tmpStream := TMemoryStream.Create; + try + { OfficeArtDgContainer record (container of drawing) } + len := 224 + 152*(ANumShapes - 1); + WriteMSODgContainer(tmpStream, len); + + { OfficeArtFdg record (info on shapes: num shapes, drawing ID, last Obj ID ) } + WriteMSOFdgRecord(tmpStream, ANumShapes + 1, DRAWING_ID, SHAPEID_BASE + ANumShapes); + + { OfficeArtSpGrContainer record (shape group container) } + len := 200 + 152*(ANumShapes - 1); + WriteMSOSpGrContainer(tmpStream, len); + + { OfficeArtSpContainer record } + WriteMSOSpContainer(tmpStream, 40); + + { OfficeArtFSpGr record } + WriteMSOFSpGrRecord(tmpStream, 0, 0, 0, 0); // 16 + 8 bytes + + { OfficeArtFSp record } + WriteMSOFSpRecord(tmpStream, SHAPEID_BASE, MSO_SPT_NOTPRIMITIVE, + MSO_FSP_BITS_GROUP + MSO_FSP_BITS_PATRIARCH); // 8 + 8 bytes + + { Data for the 1st comment } + WriteMSODrawing2_Data(tmpStream, ACell, SHAPEID_BASE + 1); + + // Write the BIFF stream + tmpStream.Position := 0; + len := tmpStream.Size; + WriteBiffHeader(AStream, INT_EXCEL_ID_MSODRAWING, tmpStream.Size); + AStream.CopyFrom(tmpStream, tmpStream.Size); + finally + tmpStream.Free; + end; +end; + +{ Write the MSODRAWING record which occurs before the OBJ record. + Do not use for the very first OBJ record where the record must be + WriteMSODrawing1 + WriteMSODrawing2_Data + WriteMSODrawing3_Data} +procedure TsSpreadBiff8Writer.WriteMSODrawing2(AStream: TStream; ACell: PCell; + AObjID: Word); +var + tmpStream: TStream; + len: Word; +begin + tmpStream := TMemoryStream.Create; + try + { Shape data for cell comment } + WriteMSODrawing2_Data(tmpStream, ACell, SHAPEID_BASE + AObjID); + + { Get size of data stream } + len := tmpStream.Size; + + { BIFF Header } + WriteBiffHeader(AStream, INT_EXCEL_ID_MSODRAWING, len); + + { Copy MSO data to BIFF stream } + tmpStream.Position := 0; + AStream.CopyFrom(tmpStream, len); + finally + tmpStream.Free; + end; +end; + +procedure TsSpreadBiff8Writer.WriteMSODrawing2_Data(AStream: TStream; + ACell: PCell; AShapeID: Word); +var + tmpStream: TStream; + len: Cardinal; +begin + // We write all the record data to a temporary stream to get the record + // size (it depends on the number of properties written to the FOPT record. + // The record size is needed in the very first SpContainer record... + + tmpStream := TMemoryStream.Create; + try + { OfficeArtFSp record } + WriteMSOFSpRecord(tmpStream, AShapeID, MSO_SPT_TEXTBOX, + MSO_FSP_BITS_HASANCHOR + MSO_FSP_BITS_HASSHAPETYPE); + + { OfficeArtFOpt record } + WriteMSOFOptRecord_Comment(tmpStream); + + { OfficeArtClientAnchor record } + WriteMSOClientAnchorSheetRecord(tmpStream, + ACell^.Row + 1, ACell^.Col + 1, ACell.Row + 3, ACell^.Col + 5, + 691, 486, 38, 26, + true, true + ); + + { OfficeArtClientData record } + WriteMSOClientDataRecord(tmpStream); + + // Now we know the record size + len := tmpStream.Size; + + // Write an OfficeArtSpContainer to the stream provided... + WriteMSOSpContainer(AStream, len+8); // !!! for some reason, Excel wants here 8 additional bytes !!! + + // ... and append the data from the temporary stream. + tmpStream.Position := 0; + AStream.Copyfrom(tmpStream, len); + + finally + tmpStream.Free; + end; +end; + +{ Writes the MSODRAWING record which must occur immediately before a TXO record } +procedure TsSpreadBiff8Writer.WriteMSODRAWING3(AStream: TStream; ACell: PCell); +begin + { BIFF Header } + WriteBiffHeader(AStream, INT_EXCEL_ID_MSODRAWING, 8); + + { OfficeArtClientTextbox record: Text-related data for a shape } + WriteMSOClientTextBoxRecord(AStream); +end; + +{ Writes a NOTE record for a comment attached to a cell } +procedure TsSpreadBiff8Writer.WriteNOTE(AStream: TStream; ACell: PCell; + AObjID: Word); +const + AUTHOR: ansistring = 'Werner'; +var + len: Integer; +begin + len := Length(AUTHOR) * sizeOf(ansichar); + + { BIFF Header } + AStream.WriteWord(WordToLE(INT_EXCEL_ID_NOTE)); // ID of NOTE record + AStream.WriteWord(WordToLE(12+len)); // Size of NOTE record + + { Record data } + AStream.WriteWord(WordToLE(ACell^.Row)); // Row index of cell + AStream.WriteWord(WordToLE(ACell^.Col)); // Column index of cell + AStream.WriteWord(0); // Flags + AStream.WriteWord(WordToLE(AObjID)); // Object identifier (1, ...) + AStream.WriteWord(len); // Char length of author string + AStream.WriteByte(0); // Flag for 8-bit characters + AStream.WriteBuffer(AUTHOR[1], len); // Author + AStream.WriteByte(0); // Unused end; procedure TsSpreadBiff8Writer.WriteNumFormat(AStream: TStream; @@ -1742,6 +1975,35 @@ begin SetLength(buf, 0); end; +{ Writes an OBJ record - belongs to the record required for cell comments } +procedure TsSpreadBIFF8Writer.WriteOBJ(AStream: TStream; AObjID: Word); +var + guid: TGuid; +begin + AStream.WriteWord(WordToLE(INT_EXCEL_ID_OBJ)); + AStream.WriteWord(WordToLE(52)); + + AStream.WriteWord(WordToLE($0015)); // Subrecord ftCmo + AStream.WriteWord(WordToLE(18)); // Subrecord size: 18 bytes + AStream.WriteWord(WordToLE($0019)); // Object type: Comment + AStream.WriteWord(WordToLE(AObjID)); // Object ID number (1, ... ) + AStream.WriteWord(WordToLE($4011)); // Option flags automatic line style, locked when sheet is protected + AStream.WriteDWord(0); // Unused + AStream.WriteDWord(0); // Unused + AStream.WriteDWord(0); // Unused + + AStream.WriteWord(WordToLE($000D)); // Subrecord ftNts + AStream.WriteWord(WordToLE(22)); // Size of subrecord: 22 bytes +// CreateGUID(guid); + FillChar(guid{%H-}, SizeOf(guid), 0); + AStream.WriteBuffer(guid, 16); // GUID of comment + AStream.WriteWord(WordToLE(0)); // shared note (0 = false) + AStream.WriteDWord(0); // unused + + AStream.WriteWord(WordToLE($0000)); // Subrecord ftEnd + AStream.WriteWord(0); // Size of subrecord: 0 bytes +end; + { Writes the address of a cell as used in an RPN formula and returns the number of bytes written. } function TsSpreadBIFF8Writer.WriteRPNCellAddress(AStream: TStream; @@ -1840,19 +2102,15 @@ begin AStream.WriteBuffer(WideStringToLE(wideStr)[1], len * SizeOf(WideChar)); end; -{******************************************************************* -* TsSpreadBIFF8Writer.WriteIndex () -* -* DESCRIPTION: Writes an Excel 8 INDEX record -* -* nm = (rl - rf - 1) / 32 + 1 (using integer division) -* -*******************************************************************} +{@@ ---------------------------------------------------------------------------- + Writes an Excel 8 INDEX record + + nm = (rl - rf - 1) / 32 + 1 (using integer division) +-------------------------------------------------------------------------------} procedure TsSpreadBIFF8Writer.WriteIndex(AStream: TStream); begin { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_INDEX)); - AStream.WriteWord(WordToLE(16)); + WriteBIFFHeader(AStream, INT_EXCEL_ID_INDEX, 16); { Not used } AStream.WriteDWord(DWordToLE(0)); @@ -1872,16 +2130,12 @@ begin { OBS: It seems to be no problem just ignoring this part of the record } end; -{******************************************************************* -* TsSpreadBIFF8Writer.WriteLabel () -* -* DESCRIPTION: Writes an Excel 8 LABEL record -* -* Writes a string to the sheet -* If the string length exceeds 32758 bytes, the string -* will be silently truncated. -* -*******************************************************************} +{@@ ---------------------------------------------------------------------------- + Writes an Excel 8 LABEL record (string cell value) + + If the string length exceeds 32758 bytes, the string will be truncated, + a note will be left in the workbooks log. +-------------------------------------------------------------------------------} procedure TsSpreadBIFF8Writer.WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; const AValue: string; ACell: PCell); const @@ -1963,8 +2217,7 @@ begin // at most 1026 merged ranges per BIFF record, the rest goes into a new record { BIFF record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_MERGEDCELLS)); - AStream.WriteWord(WordToLE(2 + n*8)); + WriteBIFFHeader(AStream, INT_EXCEL_ID_MERGEDCELLS, 2 + n*8); // Count of cell ranges in this record AStream.WriteWord(WordToLE(n)); @@ -1983,20 +2236,16 @@ begin end; end; -{******************************************************************* -* TsSpreadBIFF8Writer.WriteStyle () -* -* DESCRIPTION: Writes an Excel 8 STYLE record -* -* Registers the name of a user-defined style or -* specific options for a built-in cell style. -* -*******************************************************************} +{@@----------------------------------------------------------------------------- + Writes an Excel 8 STYLE record + + Registers the name of a user-defined style or specific options + for a built-in cell style. +-------------------------------------------------------------------------------} procedure TsSpreadBIFF8Writer.WriteStyle(AStream: TStream); begin { BIFF record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_STYLE)); - AStream.WriteWord(WordToLE(4)); + WriteBiffHeader(AStream, INT_EXCEL_ID_STYLE, 4); { Index to style XF and defines if it's a built-in or used defined style } AStream.WriteWord(WordToLE(MASK_STYLE_BUILT_IN)); @@ -2008,27 +2257,83 @@ begin AStream.WriteByte($FF); end; -{******************************************************************* -* TsSpreadBIFF8Writer.WriteWindow2 () -* -* DESCRIPTION: Writes an Excel 8 WINDOW2 record -* -* This record contains aditional settings for the -* document window (BIFF2-BIFF4) or for a specific -* worksheet (BIFF5-BIFF8). -* -* The values written here are reasonable defaults, -* which should work for most sheets. -* -*******************************************************************} -procedure TsSpreadBIFF8Writer.WriteWindow2(AStream: TStream; - ASheet: TsWorksheet); +{@@ ---------------------------------------------------------------------------- + Writes a TXO and two CONTINUE records as needed for cell comments. + It can safely be assumed that the cell exists and contains a comment. +-------------------------------------------------------------------------------} +procedure TsSpreadBIFF8Writer.WriteTXO(AStream: TStream; ACell: PCell); +var + recTXO: TBIFF8TXORecord; + comment: widestring; + compressed: ansistring; + len: Integer; + wchar: widechar; + i: Integer; + bytesFmtRuns: Integer; +begin + { Prepare comment string. It is stored as a string with 8-bit characters } + comment := UTF8Decode(ACell^.Comment); + SetLength(compressed, length(comment)); + for i:= 1 to Length(comment) do + begin + wchar := comment[i]; + compressed[i] := wchar; + end; + len := Length(compressed); + + { (1) TXO record ---------------------------------------------------------- } + { BIFF record header } + FillChar(recTXO{%H-}, SizeOf(recTXO), 0); + recTXO.RecordID := WordToLE(INT_EXCEL_ID_TXO); + recTXO.RecordSize := SizeOf(recTXO) - 2*SizeOf(word); + { Record data } + recTXO.OptionFlags := WordToLE($0212); // Left & top aligned, lock option on + recTXO.TextRot := 0; // Comment text not rotated + recTXO.TextLen := WordToLE(len); + bytesFmtRuns := 8*SizeOf(Word); // see (3) below + recTXO.NumFormattingRuns := WordToLE(bytesFmtRuns); + { Write out to file } + AStream.WriteBuffer(recTXO, SizeOf(recTXO)); + + { (2) 1st CONTINUE record containing the comment text --------------------- } + { BIFF record header } + AStream.WriteWord(WordToLE(INT_EXCEL_ID_CONTINUE)); + AStream.WriteWord(len+1); + { Record data } + AStream.WriteByte(0); + AStream.WriteBuffer(compressed[1], len); + + { (3) 2nd CONTINUE record containing the formatting runs ------------------ } + { BIFF record header } + AStream.WriteWord(WordToLE(INT_EXCEL_ID_CONTINUE)); + AStream.WriteWord(bytesFmtRuns); + { Record data } + AStream.WriteWord(0); // start index of 1st formatting run (we only use 1 run) + AStream.WriteWord(WordToLE(1)); // Font index to be used (default font) + AStream.WriteWord(0); // Not used + AStream.WriteWord(0); // Not used + AStream.WriteWord(WordToLE(len)); // lastRun: number of characters + AStream.WriteWord(0); // Not used + AStream.WriteWord(0); // Not used + AStream.WriteWord(0); // Not used +end; + +{@@ ---------------------------------------------------------------------------- + Writes an Excel 8 WINDOW2 record + + This record contains additional settings for the document window (BIFF2-BIFF4) + or for a specific worksheet (BIFF5-BIFF8). + + The values written here are reasonable defaults, which should work for most + sheets. +-------------------------------------------------------------------------------} +procedure TsSpreadBIFF8Writer.WriteWINDOW2(AStream: TStream; + ASheet: TsWorksheet); var Options: Word; begin { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_WINDOW2)); - AStream.WriteWord(WordToLE(18)); + WriteBiffHeader(AStream, INT_EXCEL_ID_WINDOW2, 18); { Options flags } Options := @@ -2069,14 +2374,9 @@ begin AStream.WriteDWord(DWordToLE(0)); end; -{******************************************************************* -* TsSpreadBIFF8Writer.WriteXF () -* -* DESCRIPTION: Writes an Excel 8 XF record -* -* -* -*******************************************************************} +{@@ ---------------------------------------------------------------------------- + Writes an Excel 8 XF record (cell format) +-------------------------------------------------------------------------------} procedure TsSpreadBIFF8Writer.WriteXF(AStream: TStream; AFormatRecord: PsCellFormat; XFType_Prot: Byte = 0); var @@ -2087,7 +2387,7 @@ var begin { BIFF record header } rec.RecordID := WordToLE(INT_EXCEL_ID_XF); - rec.RecordSize := WordToLE(SizeOf(TBIFF8_XFRecord) - 2*SizeOf(Word)); + rec.RecordSize := WordToLE(SizeOf(TBIFF8_XFRecord) - SizeOf(TsBIFFHeader)); { Index to font record } rec.FontIndex := 0; @@ -2216,13 +2516,12 @@ begin end; -{******************************************************************* -* Initialization section -* -* Registers this reader / writer on fpSpreadsheet -* Converts the palette to litte-endian -* -*******************************************************************} +{@@ ---------------------------------------------------------------------------- + Initialization section + + Registers this reader / writer on fpSpreadsheet + Converts the palette to litte-endian +-------------------------------------------------------------------------------} initialization diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index 68f99a215..0cbee944d 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -204,6 +204,12 @@ type function ConvertToExcelError(AValue: TsErrorValue): byte; type + { TsBIFFHeader } + TsBIFFHeader = packed record + RecordID: Word; + RecordSize: Word; + end; + { TsBIFFNumFormatList } TsBIFFNumFormatList = class(TsCustomNumFormatList) protected @@ -311,6 +317,9 @@ type function GetLastRowIndex(AWorksheet: TsWorksheet): Integer; procedure GetLastColCallback(ACell: PCell; AStream: TStream); function GetLastColIndex(AWorksheet: TsWorksheet): Word; + + // Helper function for writing the BIFF header + procedure WriteBIFFHeader(AStream: TStream; ARecID, ARecSize: Word); // Helper function for writing a string with 8-bit length } function WriteString_8BitLen(AStream: TStream; AString: String): Integer; virtual; @@ -1817,6 +1826,24 @@ begin Result := FLastCol; end; +{@@ ---------------------------------------------------------------------------- + Writes the BIFF record header consisting of the record ID and the size of + data to be written immediately afterwards. + + @param ARecID ID of the record - see the INT_EXCEL_ID_XXXX constants + @param ARedSize Size (in bytes) of the data which follow immediately + afterwards +-------------------------------------------------------------------------------} +procedure TsSpreadBIFFWriter.WriteBIFFHeader(AStream: TStream; + ARecID, ARecSize: Word); +var + rec: TsBIFFHeader; +begin + rec.RecordID := WordToLE(ARecID); + rec.RecordSize := WordToLE(ARecSize); + AStream.WriteBuffer(rec, SizeOf(rec)); +end; + { Writes an empty ("blank") cell. Needed for formatting empty cells. Valid for BIFF5 and BIFF8. Needs to be overridden for BIFF2 which has a different record structure. } @@ -1844,7 +1871,7 @@ begin end; { Writes a BOOLEAN cell record. - Valie for BIFF3-BIFF8. Override for BIFF2. } + Valid for BIFF3-BIFF8. Override for BIFF2. } procedure TsSpreadBIFFWriter.WriteBool(AStream: TStream; const ARow, ACol: Cardinal; const AValue: Boolean; ACell: PCell); var @@ -1883,8 +1910,7 @@ var cp: Word; begin { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_CODEPAGE)); - AStream.WriteWord(WordToLE(2)); + WriteBIFFHeader(AStream, INT_EXCEL_ID_CODEPAGE, 2); { Codepage } FCodePage := lowercase(ACodePage); @@ -2028,8 +2054,7 @@ procedure TsSpreadBIFFWriter.WriteDateMode(AStream: TStream); begin { BIFF Record header } // todo: check whether this is in the right place. should end up in workbook globals stream - AStream.WriteWord(WordToLE(INT_EXCEL_ID_DATEMODE)); - AStream.WriteWord(WordToLE(2)); + WriteBIFFHeader(AStream, INT_EXCEL_ID_DATEMODE, 2); case FDateMode of dm1900: AStream.WriteWord(WordToLE(0)); @@ -2155,22 +2180,23 @@ begin end; procedure TsSpreadBIFFWriter.WritePalette(AStream: TStream); +const + NUM_COLORS = 56; var i, n: Integer; rgb: TsColorValue; begin { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_PALETTE)); - AStream.WriteWord(WordToLE(2 + 4*56)); + WriteBIFFHeader(AStream, INT_EXCEL_ID_PALETTE, 2 + 4*NUM_COLORS); { Number of colors } - AStream.WriteWord(WordToLE(56)); + AStream.WriteWord(WordToLE(NUM_COLORS)); { Take the colors from the palette of the Worksheet } n := Workbook.GetPaletteSize; { Skip the first 8 entries - they are hard-coded into Excel } - for i:=8 to 63 do + for i := 8 to 8 + NUM_COLORS - 1 do begin rgb := Math.IfThen(i < n, Workbook.GetPaletteColor(i), $FFFFFF); AStream.WriteDWord(DWordToLE(rgb)) @@ -2185,8 +2211,7 @@ var dbl: Double; begin { BIFF record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_PAGESETUP)); - AStream.WriteWord(WordToLE(9*2 + 2*8)); + WriteBIFFHeader(AStream, INT_EXCEL_ID_PAGESETUP, 9*2 + 2*8); { Paper size } AStream.WriteWord(WordToLE(0)); // 1 = Letter, 9 = A4 @@ -2242,9 +2267,8 @@ begin error. They possibly require an additional SELECTION record. } { BIFF record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_PANE)); if isBIFF58 then n := 10 else n := 9; - AStream.WriteWord(WordToLE(n)); + WriteBIFFHeader(AStream, INT_EXCEL_ID_PANE, n); { Position of the vertical split (px, 0 = No vertical split): - Unfrozen pane: Width of the left pane(s) (in twips = 1/20 of a point) @@ -2706,8 +2730,7 @@ begin end; { BIFF record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_ROW)); - AStream.WriteWord(WordToLE(16)); + WriteBIFFHeader(AStream, INT_EXCEL_ID_ROW, 16);; { Index of row } AStream.WriteWord(WordToLE(Word(ARowIndex))); @@ -2792,8 +2815,7 @@ begin end; { BIFF record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_SELECTION)); - AStream.WriteWord(WordToLE(15)); + WriteBIFFHeader(AStream, INT_EXCEL_ID_SELECTION, 15); { Pane identifier } AStream.WriteByte(APane); @@ -2923,8 +2945,7 @@ var flags: Word; begin { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_SHEETPR)); - AStream.WriteWord(WordToLE(2)); + WriteBIFFHeader(AStream, INT_EXCEL_ID_SHEETPR, 2); flags := $04C1; AStream.WriteWord(WordToLE(flags)); @@ -3016,8 +3037,7 @@ end; procedure TsSpreadBIFFWriter.WriteWindow1(AStream: TStream); begin { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_WINDOW1)); - AStream.WriteWord(WordToLE(18)); + WriteBIFFHeader(AStream, INT_EXCEL_ID_WINDOW1, 18); { Horizontal position of the document window, in twips = 1 / 20 of a point } AStream.WriteWord(WordToLE(0)); diff --git a/components/fpspreadsheet/xlsescher.pas b/components/fpspreadsheet/xlsescher.pas new file mode 100644 index 000000000..e99f9be90 --- /dev/null +++ b/components/fpspreadsheet/xlsescher.pas @@ -0,0 +1,562 @@ +{ xlsEscher } + +{@@ ---------------------------------------------------------------------------- + The unit xlsExcel provides basic support for the hierarchy of shapes and + drawings in Microsoft Office files ("Escher", "OfficeArt") as it is needed for + the BIFF record MSODRAWING (Cell comments, charts). + + AUTHORS: Werner Pamler + + DOCUMENTATION: + Office Drawing 97-2007 Binary Format Specification + http://www.digitalpreservation.gov/formats/digformatspecs/OfficeDrawing97-2007BinaryFormatSpecification.pdf + [MS-ODRAW].pdf + https://msdn.microsoft.com/en-us/library/office/cc441433%28v=office.12%29.aspx + [MS-PPT].pdf + https://msdn.microsoft.com/en-us/library/office/cc313106%28v=office.12%29.aspx + [MS-XLS].pdf + https://msdn.microsoft.com/en-us/library/office/cc313154%28v=office.12%29.aspx + + LICENSE: See the file COPYING.modifiedLGPL.txt, included in the Lazarus + distribution, for details about the license. +-------------------------------------------------------------------------------} + +unit xlsEscher; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils; + +const + { Record IDs } + MSO_ID_OFFICEART_DG_CONTAINER = $F002; + MSO_ID_OFFICEART_SPGR_CONTAINER = $F003; + MSO_ID_OFFICEART_SP_CONTAINER = $F004; + MSO_ID_OFFICEART_FDG = $F008; + MSO_ID_OFFICEART_FSPGR = $F009; + MSO_ID_OFFICEART_FSP = $F00A; + MSO_ID_OFFICEART_FOPT = $F00B; + MSO_ID_OFFICEART_CLIENTTEXTBOX = $F00D; + MSO_ID_OFFICEART_CLIENTANCHORSHEET = $F010; + MSO_ID_OFFICEART_CLIENTDATA = $F011; + + { Record version } + MSO_VER_CONTAINER = $0F; + + { Shape types } + MSO_SPT_MIN = 0; + MSO_SPT_NOTPRIMITIVE = MSO_SPT_MIN; + MSO_SPT_RECTANGLE = 1; + MSO_SPT_ROUNDRECTANGLE = 2; + MSO_SPT_ELLIPSE = 3; + MSO_SPT_DIAMOND = 4; + MSO_SPT_ISOCELESTRIANGLE = 5; + MSO_SPT_RIGHT_TRIANGLE = 6; + MSO_SPT_PARALLELOGRAM = 7; + MSO_SPT_TRAPEZOID = 8; + MSO_SPT_HEXAGON = 9; + MSO_SPT_OCTAGON = 10; + MSO_SPT_PLUS = 11; + MSO_SPT_STAR = 12; + MSO_SPT_ARROW = 13; + MSO_SPT_THICKARROW = 14; + MSO_SPT_HOMEPLAT = 15; + MSO_SPT_CUBE = 16; + MSO_SPT_BALLOON = 17; + MSO_SPT_SEAL = 18; + MSO_SPT_ARC = 19; + MSO_SPT_LINE = 20; + MSO_SPT_PLAQUE = 21; + MSO_SPT_CAN = 22; + MSO_SPT_DONUT = 23; + MSO_SPT_TEXTSIMPLE = 24; + MSO_SPT_TEXTOCTAGON = 25; + MSO_SPT_TEXTHEXAGON = 26; + MSO_SPT_TEXTCURVE = 27; + MSO_SPT_TEXTWAVE = 28; + MSO_SPT_TEXTRING = 29; + MSO_SPT_TEXTONCURVE = 30; + MSO_SPT_TEXTONRING = 31; + MSO_SPT_STRAIGHTCONNECTOR1 = 32; + MSO_SPT_BENTCONNECTOR2 = 33; + MSO_SPT_BENTCONNECTOR3 = 34; + MSO_SPT_BENTCONNECTOR4 = 35; + MSO_SPT_BENTCONNECTOR5 = 36; + MSO_SPT_CURVEDCONNECTOR2 = 37; + MSO_SPT_CURVEDCONNECTOR3 = 38; + MSO_SPT_CURVEDCONNECTOR4 = 39; + MSO_SPT_CURVEDCONNECTOR5 = 40; + MSO_SPT_CALLOUT1 = 41; + MSO_SPT_CALLOUT2 = 42; + MSO_SPT_CALLOUT3 = 43; + MSO_SPT_ACCENTCALLOUT1 = 44; + MSO_SPT_ACCENTCALLOUT2 = 45; + MSO_SPT_ACCENTCALLOUT3 = 46; + MSO_SPT_BORDERCALLOUT1 = 47; + MSO_SPT_BORDERCALLOUT2 = 48; + MSO_SPT_BORDERCALLOUT3 = 49; + MSO_SPT_ACCENTBORDERCALLOUT1 = 50; + MSO_SPT_ACCENTBORDERCALLOUT2 = 51; + MSO_SPT_ACCENTBORDERCALLOUT3 = 52; + MSO_SPT_RIBBON = 53; + MSO_SPT_RIBBON2 = 54; + MSO_SPT_CHEVRON = 55; + MSO_SPT_PENTAGON = 56; + MSO_SPT_NOSMOKING = 57; + MSO_SPT_SEAL8 = 58; + MSO_SPT_SEAL16 = 59; + MSO_SPT_SEAL32 = 60; + MSO_SPT_WEDGERECTCALLOUT = 61; + MSO_SPT_WEDGERRECTCALLOUT = 62; + MSO_SPT_WEDGEELLIPSECALLOUT = 63; + MSO_SPT_WAVE = 64; + MSO_SPT_FOLDERCORNER = 65; + MSO_SPT_LEFTARROW = 66; + MSO_SPT_DOWNARROW = 67; + MSO_SPT_UPARROW = 68; + MSO_SPT_LEFTRIGHTARROW = 69; + MSO_SPT_UPDOWNARROW = 70; + MSO_SPT_IRREGULARSEAL1 = 71; + MSO_SPT_IRREGULARSEAL2 = 72; + MSO_SPT_LIGNTNINGBOLT = 73; + MSO_SPT_HEART = 74; + MSO_SPT_PICTUREFRAME = 75; + MSO_SPT_QUADARROW = 76; + MSO_SPT_LEFTARROWCALLOUT = 77; + MSO_SPT_RIGHTARROWCALLOUT = 78; + MSO_SPT_UPARROWCALLOUT = 79; + MSO_SPT_DOWNARROWCALLOUT = 80; + MSO_SPT_LEFTRIGHTARROWCALLOUT = 81; + MSO_SPT_UPDOWNARROWCALLOUT = 82; + MSO_SPT_QUADARROWCALLOUT = 83; + MSO_SPT_BEVEL = 84; + MSO_SPT_LEFTBRACKET = 85; + MSO_SPT_RIGHTBRACKET = 86; + MSO_SPT_LEFTBRACE = 87; + MSO_SPT_RIGHTBRACE = 88; + MSO_SPT_LEFTUPARROW = 89; + MSO_SPT_BENTUPARROW = 90; + MSO_SPT_BENTARROW = 91; + MSO_SPT_SEAL25 = 92; + MSO_SPT_STRIPEDRIGHTARROW = 83; + MSO_SPT_NOTCHEDRIGHTARROW = 84; + MSO_SPT_BLOCKARC = 95; + MSO_SPT_SMILIEYFACE = 96; + MSO_SPT_VERTICALSCROLL = 97; + MSO_SPT_HORIZONTALSCROLL = 98; + MSO_SPT_CICRULARARROW = 99; + MSO_SPT_NOTCHEDCIRCULARARROW = 100; + MSO_SPT_UTURNARROW = 101; + MSO_SPT_CURVEDRIGHTARROW = 102; + MSO_SPT_CURVEDLEFTARROW = 103; + MSO_SPT_CURVEDUPARROW = 104; + MSO_SPT_CURVEDDOWNARROW = 105; + MSO_SPT_CLOUDCALLOUT = 106; + MSO_SPT_ELLIPSERIBBON = 107; + MSO_SPT_ELLIPSERIBBON2 = 108; + MSO_SPT_FLOWCHARTPROCESS = 109; + MSO_SPT_FLOWCHARTDECISION = 110; + MSO_SPT_FLOWCHARTINPUTOUTPUT = 111; + MSO_SPT_FLOWCHARTPREDEFINEDPROCESS = 112; + MSO_SPT_FLOWCHARTINTERNALSTORAGE = 113; + MSO_SPT_FLOWCHARTDOCUMENT = 114; + MSO_SPT_FLOWCHARTMULTIDOCUMENT = 115; + MSO_SPT_FLOWCHARTTERMINATOR = 116; + MSO_SPT_FLOWCHARTPREPARATION = 117; + MSO_SPT_FLOWCHARTMANUALINPUT = 118; + MSO_SPT_FLOWCHARTMANUALOPERATION = 119; + MSO_SPT_FLOWCHARTCONNECTOR = 120; + MSO_SPT_FLOWCHARTPUNCHEDCARD = 121; + MSO_SPT_FLOWCHARTPUNCHEDTAPE = 122; + MSO_SPT_FLOWCHARTSUMMINGJUNCTION = 123; + MSO_SPT_FLOWCHARTOR = 124; + MSO_SPT_FLOWCHARTCOLLATE = 125; + MSO_SPT_FLOWCHARTSORT = 126; + MSO_SPT_FLOWCHARTEXTRACT = 127; + MSO_SPT_FLOWCHARTMERGE = 128; + MSO_SPT_FLOWCHARTOFFLINESTORAGE = 129; + MSO_SPT_FLOWCHARTONLINESTORAGE = 130; + MSO_SPT_FLOWCHARTMAGNETICTAPE = 131; + MSO_SPT_FLOWCHARTMAGNETICDISK = 132; + MSO_SPT_FLOWCHARTMAGNETICDRUM = 133; + MSO_SPT_FLOWCHARTDISPLAY = 134; + MSO_SPT_FLOWCHARTDELAY = 135; + MSO_SPT_TEXTPLAINTEXT = 136; + MSO_SPT_TEXTSTOP = 137; + MSO_SPT_TEXTTRIANGLE = 138; + MSO_SPT_TEXTTRIANGLEINVERTED = 139; + MSO_SPT_TEXTCHEVRON = 140; + MSO_SPT_TEXTCHEVRONINVERTED = 141; + MSO_SPT_TEXTRINGINSIDE = 142; + MSO_SPT_TEXTRINGOUTSIDE = 143; + MSO_SPT_TEXTARCHUPCURVE = 144; + MSO_SPT_TEXTARCHDOWNCURVE = 145; + MSO_SPT_TEXTCIRCLECURVE = 146; + MSO_SPT_TEXTBUTTONCURVE = 147; + MSO_SPT_TEXTARCHUPPOUR = 148; + MSO_SPT_TEXTARCHDOWNPOUR = 149; + MSO_SPT_TEXTCIRCLEPOUR = 150; + MSO_SPT_TEXTBUTTONPOUR = 151; + MSO_SPT_TEXTCURVEUP = 152; + MSO_SPT_TEXTCURVEDOWN = 153; + MSO_SPT_TEXTCASCADEUP = 154; + MSO_SPT_TEXTCASCADEDOWN = 155; + MSO_SPT_TEXTWAVE1 = 156; + MSO_SPT_TEXTWAVE2 = 157; + MSO_SPT_TEXTWAVE3 = 158; + MSO_SPT_TEXTWAVE4 = 159; + MSO_SPT_TEXTINFLATE = 160; + MSO_SPT_TEXTDEFLATE = 161; + MSO_SPT_TEXTINFLATEBOTTOM = 162; + MSO_SPT_TEXTDEFLATEBOTTOM = 163; + MSO_SPT_TEXTINFLATETOP = 164; + MSO_SPT_TEXTDEFLATETOP = 165; + MSO_SPT_TEXTDEFLATEINFLATE = 166; + MSO_SPT_TEXTDEFLATEINFLATEDEFLATE = 167; + MSO_SPT_TEXTFADERIGHT = 168; + MSO_SPT_TEXTFADELEFT = 169; + MSO_SPT_TEXTFADEUP = 170; + MSO_SPT_TEXTFADEDOWN = 171; + MSO_SPT_TEXTSLANTUP = 172; + MSO_SPT_TEXTSLANTDOWN = 173; + MSO_SPT_TEXTCANUP = 174; + MSO_SPT_TEXTCANDOWN = 175; + MSO_SPT_FLOWCHARTALTERNATEPROCESS = 176; + MSO_SPT_FLOWCHARTOFFPAGECONNECTOR = 177; + MSO_SPT_CALLOUT90 = 178; + MSO_SPT_ACCENTCALLOUT90 = 179; + MSO_SPT_BORDERCALLOUT90 = 180; + MSO_SPT_ACCENTBORDERCALLOUT90 = 181; + MSO_SPT_LEFTRIGHTUPARROW = 182; + MSO_SPT_SUN = 183; + MSO_SPT_MOON = 184; + MSO_SPT_BRACKETPAIR = 185; + MSO_SPT_BRACEPAIR = 186; + MSO_SPT_SEAL4 = 187; + MSO_SPT_DOUBLEWAVE = 188; + MSO_SPT_ACTIONBUTTONBLANK = 189; + MSO_SPT_ACTIONBUTTONHOME = 190; + MSO_SPT_ACTIONBUTTONHELP = 191; + MSO_SPT_ACTIONBUTTONINFORMATION = 192; + MSO_SPT_ACTIONBUTTONFORWARDNEXT = 193; + MSO_SPT_ACTIONBUTTONBACKPREVIOUS = 194; + MSO_SPT_ACTIONBUTTONEND = 195; + MSO_SPT_ACTIONBUTTONBEGINNING = 196; + MSO_SPT_ACTIONBUTTONRETURN = 197; + MSO_SPT_ACTIONBUTTONDOCUMENT = 198; + MSO_SPT_ACTIONBUTTONSOUND = 199; + MSO_SPT_ACTIONBUTTONMOVIE = 200; + MSO_SPT_HOSTCONTROL = 201; + MSO_SPT_TEXTBOX = 202; + MSO_SPT_NIL = $0FFF; + MSO_SPT_MAX = MSO_SPT_NIL; + + { Bits in OfficeArtFSp record } + MSO_FSP_BITS_GROUP = $00000001; + MSO_FSP_BITS_CHILD = $00000002; + MSO_FSP_BITS_PATRIARCH = $00000004; + MSO_FSP_BITS_DELETED = $00000008; + MSO_FSP_BITS_OLESHAPE = $00000010; + MSO_FSP_BITS_HASMASTER = $00000020; + MSO_FSP_BITS_FLIPHOR = $00000040; + MSO_FSP_BITS_FLIPVERT = $00000080; + MSO_FSP_BITS_CONNECTOR = $00000100; + MSO_FSP_BITS_HASANCHOR = $00000200; + MSO_FSP_BITS_BACKGROUND = $00000400; + MSO_FSP_BITS_HASSHAPETYPE = $00000800; + + { Identifier of property array items if OfficeArtFOpt record } + MSO_FOPT_ID_TEXTID = $0080; + MSO_FOPT_ID_TEXTDIRECTION = $008B; + MSO_FOPT_ID_TEXTBOOL = $00BF; + MSO_FOPT_ID_CONNECTIONPOINTTYPE = $0158; + MSO_FOPT_ID_FILLCOLOR = $0181; + MSO_FOPT_ID_FILLBACKGROUNDCOLOR = $0183; + MSO_FOPT_ID_FILLFOREGROUNDCOLOR = $0185; + MSO_FOPT_ID_FILLBOOL = $01BF; + MSO_FOPT_ID_SHADOWCOLOR = $0201; + MSO_FOPT_ID_SHADOWBOOL = $023F; + MSO_FOPT_ID_GROUPBOOL = $03BF; + +procedure WriteMSOClientAnchorSheetRecord(AStream: TStream; + ATopRow, ALeftCol, ABottomRow, ARightCol, + ALeftMargin, ARightMargin, ATopMargin, ABottomMargin: Word; + AMoveIntact, AResizeIntact: Boolean); +procedure WriteMSOClientDataRecord(AStream: TStream); +procedure WriteMSOClientTextboxRecord(AStream: TStream); +procedure WriteMSODgContainer(AStream: TStream; ASize: DWord); +procedure WriteMSOFDgRecord(AStream: TStream; ANumShapes, ADrawingID, ALastObjID: Word); +procedure WriteMSOFOptRecord_Comment(AStream: TStream); +procedure WriteMSOProperty(AStream: TStream; APropertyID: Word; AValue: DWord); +procedure WriteMSOFSpRecord(AStream: TStream; AShapeID: DWord; AShapeType: Word; ABits: DWord); +procedure WriteMSOFSpGrRecord(AStream: TStream; ALeft, ATop, ARight, ABottom: DWord); +procedure WriteMSOHeader(AStream: TStream; AType, AVersion, AInstance: Word; ARecSize: DWord); +procedure WriteMSOSpContainer(AStream: TStream; ASize: DWord); +procedure WriteMSOSpGrContainer(AStream: TStream; ASize: DWord); + +implementation + +uses + fpsutils; + +type + TsMSOHeader = packed record + Version_Instance: Word; + RecordType: Word; + RecordSize: DWord; + end; + +{@@ ---------------------------------------------------------------------------- + Writes an OfficeArtClientAnchorSheet record to a stream. + + The OfficeArtClientAnchorSheet structure specifies the anchor position of + a drawing object embedded in a sheet. + + Ref: [MS-XLS].pdf +-------------------------------------------------------------------------------} +procedure WriteMSOClientAnchorSheetRecord(AStream: TStream; + ATopRow, ALeftCol, ABottomRow, ARightCol, + ALeftMargin, ARightMargin, ATopMargin, ABottomMargin: Word; + AMoveIntact, AResizeIntact: Boolean); +const + fMOVE = $0001; // specifies whether the shape will be kept intact when the cells are moved. + fSIZE = $0002; // specifies whether the shape will be kept intact when the cells are resized. +var + flags: Word; +begin + WriteMSOHeader(AStream, MSO_ID_OFFICEART_CLIENTANCHORSHEET, 0, 0, 18); + + flags := 0; + if AMoveIntact then begin + AResizeIntact := true; + flags := flags or fMOVE; + end; + if AResizeIntact then + flags := flags or fSIZE; + AStream.WriteWord(WordToLE(flags)); + + // Column of the cell under the top left corner of the bounding rectangle of the shape. + AStream.WriteWord(WordLEToN(ALeftCol)); + + // x coordinate of the top left corner of the bounding rectangle relative to + // the corner of the underlying cell. + // The value is expressed as 1024th’s of that cell’s width. + AStream.WriteWord(WordLEToN(ALeftMargin)); + + // Row of the cell under the top left corner of the bounding rectangle of the shape. + AStream.WriteWord(WordLEToN(ATopRow)); + + // y coordinate of the top left corner of the bounding rectangle relative to + // the corner of the underlying cell. + // The value is expressed as 256th’s of that cell’s height. + AStream.WriteWord(WordLEToN(ATopMargin)); + + // Column of the cell under the bottom right corner of the bounding rectangle + // of the shape. + AStream.WriteWord(WordToLE(ARightCol)); + + // x coordinate of the bottom right corner of the bounding rectangle relative + // to the corner of the underlying cell. + // The value is expressed as 1024th’s of that cell’s width. + AStream.WriteWord(WordToLE(ARightMargin)); + + // Row of the cell under the bottom right corner of the bounding rectangle + // of the shape. + AStream.WriteWord(WordToLE(ABottomRow)); + + // y coordinate of the bottom right corner of the bounding rectangle relative + // to the corner of the underlying cell. + // The value is expressed as 256th’s of that cell’s height. + AStream.WriteWord(WordToLE(ABottomMargin)); +end; + +{@@ ---------------------------------------------------------------------------- + Writes an OfficeArtClientData record to a stream + + The OfficeArtClientData structure specifies the client data of a drawing + object. + + MUST be the last structure of the rgChildRec field of the current + MSODRAWING BIFF record. + + The next record MUST be OBJ which contains the detailed data information + about this drawing object. +-------------------------------------------------------------------------------} +procedure WriteMSOClientDataRecord(AStream: TStream); +begin + WriteMSOHeader(AStream, MSO_ID_OFFICEART_CLIENTDATA, 0, 0, 0); +end; + +{@@ ---------------------------------------------------------------------------- + Writes an OfficeArtClientTextbox record to a stream +-------------------------------------------------------------------------------} +procedure WriteMSOClientTextboxRecord(AStream: TStream); +begin + WriteMSOHeader(AStream, MSO_ID_OFFICEART_CLIENTTEXTBOX, 0, 0, 0); +end; + +{@@ ---------------------------------------------------------------------------- + Writes an OfficeArtDgContainer record to a stream. + + The OfficeArtDgContainer record specifies the container for all file records + for the objects in an MSO drawing. +-------------------------------------------------------------------------------} +procedure WriteMSODgContainer(AStream: TStream; ASize: DWord); +begin + WriteMSOHeader(AStream, MSO_ID_OFFICEART_DG_CONTAINER, MSO_VER_CONTAINER, 0, ASize); +end; + +{@@ ---------------------------------------------------------------------------- + Writes an OfficeArt FDG record to a stream. + + The OfficeArtFDG record specifies the number of shapes, the drawing identifier, + and the shape identifier of the last shape in a drawing. +-------------------------------------------------------------------------------} +procedure WriteMSOFdgRecord(AStream: TStream; ANumShapes, ADrawingID, ALastObjID: Word); +begin + if ADrawingID > $0FFE then + raise Exception.CreateFmt('[WriteMSOFdgRecord] Invalid drawing identifier $%.4x', [ADrawingID]); + WriteMSOHeader(AStream, MSO_ID_OFFICEART_FDG, 0, ADrawingID, 8); + AStream.WriteDWord(DWordToLE(ANumShapes)); + AStream.WriteDWord(DWordToLE(ALastObjID)); +end; + +{@@ ---------------------------------------------------------------------------- + Writes an OfficeArtFOpt as it is used for a cell comment record to the stream + + The OfficeArtFOPT record specifies a table of OfficeArtRGFOPTE records, + + The OfficeArtRGFOPTE record specifies a property table, which consists of an + array of fixed-size property table entries, followed by a variable-length + field of complex data. +-------------------------------------------------------------------------------} +procedure WriteMSOFOptRecord_Comment(AStream: TStream); +const + NUM_PROPERTIES = 13; +begin + // Escher header + WriteMSOHeader(AStream, MSO_ID_OFFICEART_FOPT, 3, NUM_PROPERTIES, NUM_PROPERTIES*6); + + // TextID + WriteMSOProperty(AStream, MSO_FOPT_ID_TEXTID, 0); + + // Text direction + WriteMSOProperty(AStream, MSO_FOPT_ID_TEXTDIRECTION, 2); // 2 = "determined from text string" + + // Boolean properties of text in a shape + WriteMSOProperty(AStream, MSO_FOPT_ID_TEXTBOOL, $00080008); + + // Type of connection point + WriteMSOProperty(AStream, MSO_FOPT_ID_CONNECTIONPOINTTYPE, 0); + + // Fill color + WriteMSOProperty(AStream, MSO_FOPT_ID_FILLCOLOR, $00E1FFFF); + + // Background color of fill + WriteMSOProperty(AStream, MSO_FOPT_ID_FILLBACKGROUNDCOLOR, $00E1FFFF); + + // Foreground color of fill + WriteMSOProperty(AStream, MSO_FOPT_ID_FILLFOREGROUNDCOLOR, $100000F4); + + // Fill style boolean properties + WriteMSOProperty(AStream, MSO_FOPT_ID_FILLBOOL, $00100010); + + // Line foreground color for black-and-white mode + WriteMSOProperty(AStream, $01C3, $100000F4); + + // Shadow color + WriteMSOProperty(AStream, MSO_FOPT_ID_SHADOWCOLOR, 0); + + // Shadow color primary color modifier if in black-and-white mode + WriteMSOProperty(AStream, $0203, $100000F4); + + // Shadow style boolean properties + WriteMSOProperty(AStream, MSO_FOPT_ID_SHADOWBOOL, $00030003); + + // Group shape boolean properties + WriteMSOProperty(AStream, MSO_FOPT_ID_GROUPBOOL, $00020002); +end; + +{@@ ---------------------------------------------------------------------------- + Writes a property of the FOPT array +-------------------------------------------------------------------------------} +procedure WriteMSOProperty(AStream: TStream; APropertyID: Word; + AValue: DWord); +begin + AStream.WriteWord(WordToLE(APropertyID)); + AStream.WriteDWord(DWordToLE(AValue)); +end; + +{@@ ---------------------------------------------------------------------------- + Writes an OfficeArtFSp record to the stream + + The OfficeArtFSP record specifies an instance of a shape. + The record header contains the shape type, and the record itself contains + the shape identifier and a set of bits that further define the shape. +-------------------------------------------------------------------------------} +procedure WriteMSOFSpRecord(AStream: TStream; AShapeID: DWord; + AShapeType: Word; ABits: DWord); +begin + WriteMSOHeader(AStream, MSO_ID_OFFICEART_FSP, 2, AShapeType, 8); + AStream.WriteDWord(DWordToLE(AShapeID)); + AStream.WriteDWord(DWordToLE(ABits)); +end; + +{@@ ---------------------------------------------------------------------------- + Writes an OfficeArtFSpGr record to the stream. + + The OfficeArtFSPGR record specifies the coordinate system of the group shape + that the anchors of the child shape are expressed in. + This record is present only for group shapes. +-------------------------------------------------------------------------------} +procedure WriteMSOFSpGrRecord(AStream: TStream; ALeft, ATop, ARight, ABottom: DWord); +begin + WriteMSOHeader(AStream, MSO_ID_OFFICEART_FSPGR, 1, 0, 16); + AStream.WriteDWord(DWordToLE(ALeft)); + AStream.WriteDWord(DWordToLE(ATop)); + AStream.WriteDWord(DWordToLE(ARight)); + AStream.WriteDWord(DWordToLE(ABottom)); +end; + +{ Writes the header of an MSO subrecord used internally by MSODRAWING records } +procedure WriteMSOHeader(AStream: TStream; AType, AVersion, AInstance: Word; + ARecSize: DWord); +var + rec: TsMSOHeader; +begin + rec.Version_Instance := WordToLE((AVersion and $000F) + AInstance shl 4); //and $FFF0) shr 4); + // To do: How to handle Version_Instance on big-endian machines? + // Version_Instance combines bits 0..3 for "version" and 4..15 for "instance" + rec.RecordType := WordToLE(AType); + rec.RecordSize := DWordToLE(ARecSize); + AStream.WriteBuffer(rec, SizeOf(rec)); +end; + +{@@ ---------------------------------------------------------------------------- + Writes an OffcieARtSpContainer record to the stream. + The OfficeArtSpContainer record specifies a shape container. +-------------------------------------------------------------------------------} +procedure WriteMSOSpContainer(AStream: TStream; ASize: DWord); +begin + WriteMSOHeader(AStream, MSO_ID_OFFICEART_SP_CONTAINER, MSO_VER_CONTAINER, 0, ASize); +end; + +{@@ ---------------------------------------------------------------------------- + Writes an OfficeArtSpGrContainer record to a stream. + The OfficeArtSpgrContainer record specifies a container for groups of shapes. + The group container contains a variable number of shape containers and other + group containers. Each group is a shape. The first container MUST be an + OfficeArtSpContainer record, which MUST contain shape information for the + group. +-------------------------------------------------------------------------------} +procedure WriteMSOSpGrContainer(AStream: TStream; ASize: DWord); +begin + WriteMSOHeader(AStream, MSO_ID_OFFICEART_SPGR_CONTAINER, MSO_VER_CONTAINER, 0, ASize); +end; + + +end. +