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.
+