1128 lines
22 KiB
ObjectPascal
1128 lines
22 KiB
ObjectPascal
|
|
{******************************************}
|
|
{ }
|
|
{ FastReport VCL }
|
|
{ Escher Stream Writing API }
|
|
{ }
|
|
{ Copyright (c) 1998-2021 }
|
|
{ by Fast Reports Inc. }
|
|
{ }
|
|
{******************************************}
|
|
|
|
{ This module provides API for writing Escher
|
|
streams. This format is documented in MSDN
|
|
[MS-ODRAW] section. }
|
|
|
|
unit frxEscher;
|
|
|
|
interface
|
|
|
|
{$I frx.inc}
|
|
|
|
uses
|
|
Classes, Windows;
|
|
|
|
const
|
|
|
|
//
|
|
// Record types
|
|
//
|
|
|
|
EscherRkBse = $f007;
|
|
EscherRkBlip = $f018;
|
|
EscherRkDggc = $f000;
|
|
EscherRkDgg = $f006;
|
|
EscherRkBstore = $f001;
|
|
EscherRkSpCont = $f004;
|
|
EscherRkSp = $f00a;
|
|
EscherRkOpts = $f00b;
|
|
EscherRkAnchor = $f00e;
|
|
EscherRkDgCont = $f002;
|
|
EscherRkDg = $f008;
|
|
EscherRkSpgrCont= $f003;
|
|
EscherRkSpgr = $f009;
|
|
EscherRkSMC = $f11e;
|
|
EscherRkCData = $f011;
|
|
EscherRkCAnchor = $f010;
|
|
|
|
//
|
|
// BLIP kinds
|
|
//
|
|
|
|
EscherBkEMF = 2;
|
|
EscherBkWMF = 3;
|
|
EscherBkJPEG = 5;
|
|
EscherBkPNG = 6;
|
|
EscherBkDIB = 7;
|
|
EscherBkTIFF = 8;
|
|
|
|
//
|
|
// BLIP signatures
|
|
// Actually these values are stored
|
|
// in 12-bit fields.
|
|
//
|
|
|
|
EscherBsUnknown = $000;
|
|
EscherBsWMF = $216;
|
|
EscherBsEMF = $3d4;
|
|
EscherBsPNG = $6e0;
|
|
EscherBsJPEG = $46a;
|
|
EscherBsDIB = $7a8;
|
|
EscherBsTIFF = $6e4;
|
|
|
|
//
|
|
// BLIP usage
|
|
//
|
|
|
|
EscherBuDefault = 0;
|
|
EscherBuTexture = 1;
|
|
|
|
//
|
|
// Metafile compression
|
|
//
|
|
|
|
EscherMcDeflate = 0;
|
|
EscherMcNone = $fe;
|
|
|
|
//
|
|
// Shape flags.
|
|
// They are specified in Sp record.
|
|
//
|
|
|
|
EscherSfGroup = $001;
|
|
EscherSfChild = $002;
|
|
EscherSfRoot = $004;
|
|
EscherSfDeleted = $008;
|
|
EscherSfOle = $010;
|
|
EscherSfMaster = $020;
|
|
EscherSfFlipHor = $040;
|
|
EscherSfFlipVer = $080;
|
|
EscherSfConn = $100;
|
|
EscherSfAnchor = $200;
|
|
EscherSfBg = $400;
|
|
EscherSfShape = $800;
|
|
|
|
//
|
|
// Shape types.
|
|
//
|
|
// These values are written in
|
|
// the instance field of the Sp
|
|
// record.
|
|
//
|
|
// The complete list of 204
|
|
// values can be found in the
|
|
// documentation.
|
|
//
|
|
|
|
EscherStNone = 0;
|
|
EscherStPictureFrame = 75;
|
|
|
|
//
|
|
// Each shape in an Escher stream has an
|
|
// unique id, named "shape id". Shapes are
|
|
// grouped into "drawing groups". Each drawing
|
|
// group can contain up to a limited number
|
|
// of shapes. This is not a restriction of
|
|
// the Escher format, but this limit allow
|
|
// to simplify generating shape id values.
|
|
//
|
|
|
|
EscherGroupLimit = 1024;
|
|
|
|
type
|
|
|
|
{ Escher stream consists of consequent records.
|
|
Each record has a fixed header and a variable
|
|
length body. The body of an Escher record can
|
|
be another Escher stream.
|
|
|
|
When TEscherStream serializes its data,
|
|
it looks over all its TEscherRec items and
|
|
serializes them consequently.
|
|
|
|
When TEscherRec serializes its data,
|
|
it does that in three steps:
|
|
|
|
- it writes the fixed header of 8 bytes
|
|
|
|
- it serializes the own data, due to it is
|
|
derived from TMemoryStream
|
|
|
|
- it serializes all subrecords, due to TEscherRec
|
|
is derived from TEscherStream }
|
|
|
|
TEscherRec = class;
|
|
|
|
TEscherStream = class(TMemoryStream)
|
|
private
|
|
|
|
Records: TList; // List of TEscherRec
|
|
|
|
public
|
|
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
procedure Flush(Stream: TStream); virtual;
|
|
procedure WriteVal(Value: LongWord; Count: LongWord);
|
|
|
|
function Add: TEscherRec; overload;
|
|
function Add(Version: Byte; Instance: Word; Kind: Word): TEscherRec; overload;
|
|
function AddCont(Instance: Word; Kind: Word): TEscherRec; overload;
|
|
|
|
end;
|
|
|
|
TEscherRec = class(TEscherStream)
|
|
public
|
|
|
|
Version: Byte;
|
|
Instance: Word;
|
|
Kind: Word;
|
|
|
|
procedure Flush(Stream: TStream); override;
|
|
function GetESize: LongWord;
|
|
|
|
end;
|
|
|
|
{ There are two general kinds of drawings in Escher stream:
|
|
|
|
- Metafiles (they are: EMF, WMF and PICT)
|
|
- Bitmaps (they are: PNG, JPEG and DIB)
|
|
|
|
The only difference is that metafiles start with one header
|
|
and bitmaps start with another header. The actual data of images
|
|
is not interesting for the Escher stream }
|
|
|
|
TEscherBlipKind = Byte; // See EscherBk values
|
|
TEscherBlipSign = Word; // See EscherBs values. Actually it's 12-bit length.
|
|
|
|
TEscherPicture = class(TMemoryStream)
|
|
public
|
|
|
|
Kind: TEscherBlipKind;
|
|
|
|
function GetESize: LongWord; virtual; abstract;
|
|
procedure Flush(Stream: TStream); virtual; abstract;
|
|
|
|
end;
|
|
|
|
TEscherBitmap = class(TEscherPicture)
|
|
public
|
|
|
|
function GetESize: LongWord; override;
|
|
procedure Flush(Stream: TStream); override;
|
|
|
|
end;
|
|
|
|
TEscherMetafile = class(TEscherPicture)
|
|
public
|
|
|
|
Bounds: TRect; // Boundary for metafile commands
|
|
MFSize: TPoint; // Size of the metafile in EMUs (360000 EMU = 1 cm)
|
|
Compr: Boolean; // Entire metafile can be LZ compressed
|
|
|
|
function GetESize: LongWord; override;
|
|
procedure Flush(Stream: TStream); override;
|
|
|
|
end;
|
|
|
|
TEscherBlipHash = array[1..16] of Byte;
|
|
|
|
TEscherBlip = class
|
|
public
|
|
|
|
Hash: TEscherBlipHash;
|
|
RefCount: LongWord;
|
|
Pict: TEscherPicture;
|
|
Index: LongWord; // Index to this BLIP in the array of images
|
|
|
|
constructor Create(Pict: TEscherPicture);
|
|
destructor Destroy; override;
|
|
procedure Flush(Stream: TEscherStream);
|
|
|
|
end;
|
|
|
|
{ Properties of drawings are stored in a separate
|
|
record. The contents of this record is an array
|
|
of fixed size entries followed by an array of
|
|
variable length elements such as Unicode strings }
|
|
|
|
TEscherProp = class
|
|
public
|
|
|
|
Id: Word;
|
|
Blip: Boolean;
|
|
Complex: Boolean;
|
|
Value: LongInt;
|
|
|
|
constructor Create(Id: Word);
|
|
|
|
end;
|
|
|
|
TEscherPropList = class
|
|
private
|
|
|
|
FS: TList; // List of TEscherProp
|
|
|
|
public
|
|
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
function Add(Id: Word): TEscherProp;
|
|
procedure Flush(Stream: TEscherStream);
|
|
|
|
end;
|
|
|
|
{ Shape location.
|
|
Location of a shape in an excel sheet can be
|
|
specified relative to data cells. The left side
|
|
of the sahpe is specified with two parameters:
|
|
|
|
- Left - index to an excel data cell where
|
|
the left side of the shape is located
|
|
|
|
- LeftOffset - a distance between the left side
|
|
of the data cell and the left side of the shape
|
|
(this distance is measured in excel-specific
|
|
units of length)
|
|
|
|
The same rule is applicable to the other three
|
|
sides of the shape. }
|
|
|
|
TEscherShapePos = class
|
|
public
|
|
|
|
Left: LongInt;
|
|
Top: LongInt;
|
|
Right: LongInt;
|
|
Bottom: LongInt;
|
|
|
|
LeftOffset: LongInt;
|
|
TopOffset: LongInt;
|
|
RightOffset: LongInt;
|
|
BottomOffset: LongInt;
|
|
|
|
procedure Flush(Stream: TEscherStream);
|
|
|
|
end;
|
|
|
|
{ Drawing group is a list of shapes.
|
|
Each shape has:
|
|
|
|
- shape id that is unique within a document
|
|
- reference to an image object, such as a metafile
|
|
- some options, such as a placemenet }
|
|
|
|
TEscherShape = class
|
|
public
|
|
|
|
Id: LongWord; // Shape Id that's unique within a document
|
|
Flags: LongWord; // Combination of EsherSf flags
|
|
Image: LongWord; // Reference to an image object.
|
|
Pos: TEscherShapePos;
|
|
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Flush(Stream: TEscherStream);
|
|
|
|
end;
|
|
|
|
TEscherStorage = class;
|
|
|
|
TEscherGroup = class
|
|
private
|
|
|
|
SId: LongWord; // Used for generating unique Shape Ids.
|
|
Shapes: TList; // List of TEscherShape
|
|
|
|
function GetShape(Index: LongInt): TEscherShape;
|
|
function GetSId: LongWord;
|
|
function GetRId: LongWord;
|
|
|
|
public
|
|
|
|
Id: LongWord; // Drawing Group Id that is unique within a document
|
|
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
procedure Flush(Stream: TEscherStream);
|
|
function Count: LongWord;
|
|
function Add: TEscherShape;
|
|
function GetMaxSId: LongWord;
|
|
|
|
property Items[Index: LongInt]: TEscherShape read GetShape; default;
|
|
|
|
end;
|
|
|
|
{ Escher Storage is a global per a document Escher stream
|
|
that contains images. The storage consists of:
|
|
|
|
- array of drawing groups (TEscherGroup)
|
|
- array of images (TEscherBlip)
|
|
- array of properties (TEscherPropList) }
|
|
|
|
TEscherStorage = class
|
|
private
|
|
|
|
Groups: TList; // List of TEscherGroup
|
|
Images: TList; // List of TEscherBlip
|
|
|
|
function GetGroup(Index: LongInt): TEscherGroup;
|
|
function GetImage(Index: LongInt): TEscherBlip;
|
|
|
|
public
|
|
|
|
Props: TEscherPropList;
|
|
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
function AddGroup: TEscherGroup;
|
|
function AddImage(Pict: TEscherPicture): TEscherBlip;
|
|
|
|
function Empty: Boolean;
|
|
|
|
procedure Flush(Stream: TEscherStream);
|
|
|
|
property Image[Index: LongInt]: TEscherBlip read GetImage;
|
|
property Group[Index: LongInt]: TEscherGroup read GetGroup;
|
|
|
|
end;
|
|
|
|
{ Returns a BLIP signature corresponding to a BLIP type }
|
|
|
|
function EscherGetBlipSign(Kind: TEscherBlipKind): TEscherBlipSign;
|
|
|
|
{ Computes a 16-bytes hash value for a drawing }
|
|
|
|
procedure EscherGetBlipHash(out Hash: TEscherBlipHash; Blip: TMemoryStream);
|
|
|
|
implementation
|
|
|
|
uses
|
|
frxCrypto;
|
|
|
|
type
|
|
|
|
{ Shape placement
|
|
18 bytes }
|
|
|
|
TEscherPlacement = packed record
|
|
|
|
Unknown: Word; // Content of this field is unknown
|
|
LCol: Word;
|
|
LOff: Word;
|
|
TRow: Word;
|
|
TOff: Word;
|
|
RCol: Word;
|
|
ROff: Word;
|
|
BRow: Word;
|
|
BOff: Word;
|
|
|
|
end;
|
|
|
|
{ Types.TRect is declared without "packed"
|
|
modifier }
|
|
|
|
TPackedRect = packed record
|
|
|
|
Left: LongInt;
|
|
Top: LongInt;
|
|
Right: LongInt;
|
|
Bottom: LongInt;
|
|
|
|
end;
|
|
|
|
{ Windows.TSize is declared without "packed"
|
|
modifier }
|
|
|
|
TPackedSize = packed record
|
|
|
|
Width: LongWord;
|
|
Height: LongWord;
|
|
|
|
end;
|
|
|
|
{ Escher record header
|
|
8 bytes
|
|
Starts each record in an Escher stream }
|
|
|
|
TEscherRecHeader = packed record
|
|
|
|
VerInst: Word;
|
|
Kind: Word;
|
|
Length: LongWord;
|
|
|
|
end;
|
|
|
|
{ BLIP header
|
|
36 bytes
|
|
Followed by an Escher stream with the BLIP data }
|
|
|
|
TEscherBlipHeader = packed record
|
|
|
|
Win32: Byte; // BLIP type. See EscherBk values.
|
|
MacOS: Byte; // BLIP type. See EscherBk values.
|
|
Id: TGUID; // Unique identifier.
|
|
Unused1: Word;
|
|
Size: LongWord; // Picture size in the stream.
|
|
RefCount: LongWord; // Number of references to this BLIP.
|
|
Offset: LongWord; // Offset to the delay stream. Can be zero.
|
|
Usage: Byte; // See EscherBu values.
|
|
NameLen: Byte; // Length of a Unicode name of this BLIP. Can be zero.
|
|
Unused2: Word;
|
|
|
|
end;
|
|
|
|
{ Metafile header
|
|
34 bytes
|
|
Followed by metafile data, probably LZ compressed }
|
|
|
|
TEscherMetafileHeader = packed record
|
|
|
|
MFSize: LongWord; // Metafile size
|
|
Bounds: TPackedRect; // Bounds for metafile commands
|
|
Size: TPackedSize; // Width and height of the metafile in EMUs
|
|
PackedSize: LongWord; // Size of packed data that follows this header
|
|
Compr: Byte; // See EscherMc values
|
|
Filter: Byte; // Always $fe
|
|
|
|
end;
|
|
|
|
{ Property header
|
|
6 bytes }
|
|
|
|
TEscherPropHeader = packed record
|
|
|
|
Id: Word;
|
|
Value: LongInt;
|
|
|
|
end;
|
|
|
|
procedure DestroyList(List: TList);
|
|
var
|
|
i: LongInt;
|
|
begin
|
|
for i := 0 to List.Count - 1 do
|
|
TObject(List[i]).Free;
|
|
|
|
List.Free;
|
|
end;
|
|
|
|
function EscherGetBlipSign(Kind: TEscherBlipKind): TEscherBlipSign;
|
|
begin
|
|
case Kind of
|
|
EscherBkEMF: Result := EscherBsEMF;
|
|
EscherBkWMF: Result := EscherBsWMF;
|
|
EscherBkJPEG: Result := EscherBsJPEG;
|
|
EscherBkPNG: Result := EscherBsPNG;
|
|
EscherBkDIB: Result := EscherBsDIB;
|
|
EscherBkTIFF: Result := EscherBsTIFF;
|
|
else Result := EscherBsUnknown;
|
|
end;
|
|
end;
|
|
|
|
procedure EscherGetBlipHash(out Hash: TEscherBlipHash; Blip: TMemoryStream);
|
|
begin
|
|
TCryptoHash.Hash('MD5', Hash, Length(Hash), Blip.Memory^, Blip.Size);
|
|
end;
|
|
|
|
//
|
|
// TEscherRec
|
|
//
|
|
|
|
function TEscherRec.GetESize: LongWord;
|
|
var
|
|
i: LongInt;
|
|
begin
|
|
Result := Size;
|
|
for i := 0 to Records.Count - 1 do
|
|
Inc(Result, 8 + TEscherRec(Records[i]).GetESize);
|
|
end;
|
|
|
|
procedure TEscherRec.Flush(Stream: TStream);
|
|
var
|
|
h: TEscherRecHeader;
|
|
begin
|
|
//
|
|
// 1. Fixed size header
|
|
//
|
|
|
|
ZeroMemory(@h, SizeOf(h));
|
|
|
|
h.VerInst :=
|
|
(Version and $f) or
|
|
(Instance and $fff) shl 4;
|
|
|
|
h.Kind := Kind;
|
|
h.Length := GetESize;
|
|
|
|
Stream.Write(h, Sizeof(h));
|
|
|
|
//
|
|
// 2. Own data
|
|
//
|
|
|
|
SaveToStream(Stream);
|
|
|
|
//
|
|
// 3. All subrecords
|
|
//
|
|
|
|
inherited Flush(Stream);
|
|
end;
|
|
|
|
//
|
|
// TEscherStream
|
|
//
|
|
|
|
constructor TEscherStream.Create;
|
|
begin
|
|
Records := TList.Create;
|
|
end;
|
|
|
|
destructor TEscherStream.Destroy;
|
|
begin
|
|
DestroyList(Records);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TEscherStream.Flush(Stream: TStream);
|
|
var
|
|
i: LongInt;
|
|
begin
|
|
for i := 0 to Records.Count - 1 do
|
|
TEscherRec(Records[i]).Flush(Stream);
|
|
end;
|
|
|
|
function TEscherStream.Add: TEscherRec;
|
|
begin
|
|
Result := TEscherRec.Create;
|
|
Records.Add(Result);
|
|
end;
|
|
|
|
function TEscherStream.AddCont(Instance: Word; Kind: Word): TEscherRec;
|
|
begin
|
|
Result := Add($f, Instance, Kind);
|
|
end;
|
|
|
|
function TEscherStream.Add(Version: Byte; Instance: Word; Kind: Word): TEscherRec;
|
|
begin
|
|
Result := Add;
|
|
|
|
Result.Version := Version;
|
|
Result.Instance := Instance;
|
|
Result.Kind := Kind;
|
|
end;
|
|
|
|
procedure TEscherStream.WriteVal(Value: LongWord; Count: LongWord);
|
|
begin
|
|
Write(Value, Count);
|
|
end;
|
|
|
|
//
|
|
// TEscherBitmap
|
|
//
|
|
|
|
procedure TEscherBitmap.Flush(Stream: TStream);
|
|
var
|
|
Tag: Byte;
|
|
begin
|
|
Tag := $ff;
|
|
Stream.Write(Tag, 1);
|
|
SaveToStream(Stream);
|
|
end;
|
|
|
|
function TEscherBitmap.GetESize: LongWord;
|
|
begin
|
|
Result := 1 + Size;
|
|
end;
|
|
|
|
//
|
|
// TEscherMetafile
|
|
//
|
|
|
|
procedure TEscherMetafile.Flush(Stream: TStream);
|
|
var
|
|
h: TEscherMetafileHeader;
|
|
begin
|
|
// todo -cFeature: TEscherMetafile doesn't support compression.
|
|
|
|
ZeroMemory(@h, SizeOf(h));
|
|
|
|
h.MFSize := Size;
|
|
h.PackedSize := Size;
|
|
h.Filter := $fe;
|
|
h.Compr := EscherMcNone;
|
|
|
|
with h.Size do
|
|
begin
|
|
Width := MFSize.X;
|
|
Height := MFSize.Y;
|
|
end;
|
|
|
|
with h.Bounds do
|
|
begin
|
|
Left := Bounds.Left;
|
|
Top := Bounds.Top;
|
|
Right := Bounds.Right;
|
|
Bottom := bounds.Bottom;
|
|
end;
|
|
|
|
Stream.Write(h, SizeOf(h));
|
|
SaveToStream(Stream);
|
|
end;
|
|
|
|
function TEscherMetafile.GetESize: LongWord;
|
|
begin
|
|
Result := SizeOf(TEscherMetafileHeader) + Size;
|
|
end;
|
|
|
|
//
|
|
// TEscherBlip
|
|
//
|
|
|
|
constructor TEscherBlip.Create(Pict: TEscherPicture);
|
|
begin
|
|
Self.Pict := Pict;
|
|
EscherGetBlipHash(Hash, Pict);
|
|
end;
|
|
|
|
destructor TEscherBlip.Destroy;
|
|
begin
|
|
Pict.Free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TEscherBlip.Flush(Stream: TEscherStream);
|
|
var
|
|
h: TEscherBlipHeader;
|
|
r: TEscherRec;
|
|
begin
|
|
|
|
{ Blip structure is as follows:
|
|
|
|
- BSE record
|
|
- BSE header (includes the BLIP signature)
|
|
- Escher record
|
|
- BLIP signature (matches the signature in the BSE header)
|
|
- BLIP header (metafile, bitmap)
|
|
- BLIP data (bitmap pixels, compressed jpeg, etc.) }
|
|
|
|
ZeroMemory(@h, SizeOf(h));
|
|
|
|
h.Win32 := Pict.Kind;
|
|
h.MacOS := Pict.Kind;
|
|
h.Size := 8 + SizeOf(Hash) + Pict.GetESize;
|
|
h.RefCount := RefCount;
|
|
h.Offset := 0;
|
|
h.Usage := EscherBuDefault;
|
|
|
|
CopyMemory(@h.Id, @Hash, 16);
|
|
|
|
with Stream.Add do
|
|
begin
|
|
Version := 2;
|
|
Instance := Pict.Kind;
|
|
Kind := EscherRkBse;
|
|
|
|
Write(h, SizeOf(h));
|
|
r := Add;
|
|
|
|
with r do
|
|
begin
|
|
Version := 0;
|
|
Instance := EscherGetBlipSign(Pict.Kind);
|
|
Kind := EscherRkBlip + Pict.Kind;
|
|
|
|
Write(Hash, SizeOf(Hash));
|
|
end;
|
|
|
|
Pict.Flush(r);
|
|
end;
|
|
end;
|
|
|
|
//
|
|
// TEscherProp
|
|
//
|
|
|
|
constructor TEscherProp.Create(Id: Word);
|
|
begin
|
|
Self.Id := Id;
|
|
Blip := False;
|
|
Complex := False;
|
|
Value := 0;
|
|
end;
|
|
|
|
//
|
|
// TEscherPropList
|
|
//
|
|
|
|
constructor TEscherPropList.Create;
|
|
begin
|
|
FS := TList.Create;
|
|
end;
|
|
|
|
destructor TEscherPropList.Destroy;
|
|
begin
|
|
DestroyList(FS);
|
|
end;
|
|
|
|
function TEscherPropList.Add(Id: Word): TEscherProp;
|
|
var
|
|
i: LongInt;
|
|
begin
|
|
Result := TEscherProp.Create(Id);
|
|
|
|
if FS.Count = 0 then
|
|
begin
|
|
FS.Add(Result);
|
|
Exit;
|
|
end;
|
|
|
|
for i := 0 to FS.Count - 1 do
|
|
if Id < TEscherProp(FS[i]).Id then
|
|
Break;
|
|
|
|
FS.Insert(i, Result);
|
|
end;
|
|
|
|
procedure TEscherPropList.Flush(Stream: TEscherStream);
|
|
var
|
|
i: LongInt;
|
|
h: TEscherPropHeader;
|
|
p: TEscherProp;
|
|
begin
|
|
if FS.Count = 0 then
|
|
Exit;
|
|
|
|
with Stream.Add(3, FS.Count, EscherRkOpts) do
|
|
for i := 0 to FS.Count - 1 do
|
|
begin
|
|
p := TEscherProp(FS[i]);
|
|
|
|
h.Value := p.Value;
|
|
h.Id := $3fff and p.Id;
|
|
|
|
if p.Blip then
|
|
h.Id := h.Id or $4000;
|
|
|
|
if p.Complex then
|
|
h.Id := h.Id or $8000;
|
|
|
|
Write(h, SizeOf(h));
|
|
end;
|
|
end;
|
|
|
|
//
|
|
// TEscherShapePos
|
|
//
|
|
|
|
procedure TEscherShapePos.Flush(Stream: TEscherStream);
|
|
var
|
|
r: TEscherPlacement;
|
|
begin
|
|
ZeroMemory(@r, SizeOf(r));
|
|
|
|
with r do
|
|
begin
|
|
LCol := Left;
|
|
RCol := Right;
|
|
TRow := Top;
|
|
BRow := Bottom;
|
|
|
|
LOff := LeftOffset;
|
|
ROff := RightOffset;
|
|
TOff := TopOffset;
|
|
BOff := BottomOffset;
|
|
end;
|
|
|
|
Stream.Add(0, 0, EscherRkCAnchor).Write(r, SizeOf(r));
|
|
end;
|
|
|
|
//
|
|
// TEscherShape
|
|
//
|
|
|
|
constructor TEscherShape.Create;
|
|
begin
|
|
Flags := EscherSfShape;
|
|
Pos := TEscherShapePos.Create;
|
|
end;
|
|
|
|
destructor TEscherShape.Destroy;
|
|
begin
|
|
Pos.Free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TEscherShape.Flush(Stream: TEscherStream);
|
|
var
|
|
pl: TEscherPropList;
|
|
spc: TEscherStream;
|
|
begin
|
|
spc := Stream.AddCont(0, EscherRkSpCont);
|
|
|
|
with spc do
|
|
begin
|
|
with Add(2, EscherStPictureFrame, EscherRkSp) do
|
|
begin
|
|
WriteVal(Id, 4);
|
|
WriteVal(Flags, 4);
|
|
end;
|
|
|
|
pl := TEscherPropList.Create;
|
|
|
|
with pl.Add(260) do
|
|
begin
|
|
Blip := True;
|
|
Value := Image + 1;
|
|
end;
|
|
|
|
pl.Flush(spc);
|
|
pl.Free;
|
|
Pos.Flush(spc);
|
|
end;
|
|
end;
|
|
|
|
//
|
|
// TEscherGroup
|
|
//
|
|
|
|
constructor TEscherGroup.Create;
|
|
begin
|
|
Shapes := TList.Create;
|
|
SId := 1;
|
|
end;
|
|
|
|
destructor TEscherGroup.Destroy;
|
|
begin
|
|
DestroyList(Shapes);
|
|
end;
|
|
|
|
function TEscherGroup.GetShape(Index: LongInt): TEscherShape;
|
|
begin
|
|
if (Index >= 0) and (Index < Shapes.Count) then
|
|
Result := TEscherShape(Shapes[Index])
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TEscherGroup.Count: LongWord;
|
|
begin
|
|
Result := Shapes.Count;
|
|
end;
|
|
|
|
function TEscherGroup.Add: TEscherShape;
|
|
begin
|
|
Result := TEscherShape.Create;
|
|
Result.Id := GetSId;
|
|
Shapes.Add(Result);
|
|
end;
|
|
|
|
function TEscherGroup.GetSId: LongWord;
|
|
begin
|
|
Result := EscherGroupLimit * Id + SId;
|
|
Inc(SId);
|
|
end;
|
|
|
|
function TEscherGroup.GetRId: LongWord;
|
|
begin
|
|
Result := EscherGroupLimit * Id;
|
|
end;
|
|
|
|
function TEscherGroup.GetMaxSId: LongWord;
|
|
begin
|
|
Result := EscherGroupLimit * Id + SId;
|
|
end;
|
|
|
|
procedure TEscherGroup.Flush(Stream: TEscherStream);
|
|
var
|
|
i: LongInt;
|
|
sc: TEscherStream;
|
|
|
|
begin
|
|
with Stream.AddCont(0, EscherRkDgCont) do
|
|
begin
|
|
with Add(0, Id, EscherRkDg) do
|
|
begin
|
|
WriteVal(Count + 1, 4);
|
|
WriteVal(Items[Count - 1].Id, 4);
|
|
end;
|
|
|
|
sc := AddCont(0, EscherRkSpgrCont);
|
|
with sc do
|
|
begin
|
|
with AddCont(0, EscherRkSpCont) do
|
|
begin
|
|
with Add(1, 0, EscherRkSpgr) do
|
|
begin
|
|
{ todo: actually, Spgr contains
|
|
"bounds". The meaning of these "bounds"
|
|
is unknown. MS Excel writes zeros here }
|
|
|
|
WriteVal(0, 4); // Left
|
|
WriteVal(0, 4); // Top
|
|
WriteVal(0, 4); // Right
|
|
WriteVal(0, 4); // Bottom
|
|
end;
|
|
|
|
with Add(2, EscherStNone, EscherRkSp) do
|
|
begin
|
|
WriteVal(GetRId, 4);
|
|
WriteVal(EscherSfGroup or EscherSfRoot, 4);
|
|
end;
|
|
end;
|
|
|
|
for i := 0 to Count - 1 do
|
|
Items[i].Flush(sc);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//
|
|
// TEscherStorage
|
|
//
|
|
|
|
constructor TEscherStorage.Create;
|
|
begin
|
|
Groups := TList.Create;
|
|
Images := TList.Create;
|
|
Props := TEscherPropList.Create;
|
|
|
|
with Props do
|
|
begin
|
|
Add(191).Value := $00080008;
|
|
Add(385).Value := $08000041;
|
|
Add(448).Value := $08000040;
|
|
end;
|
|
end;
|
|
|
|
destructor TEscherStorage.Destroy;
|
|
begin
|
|
Props.Destroy;
|
|
DestroyList(Images);
|
|
DestroyList(Groups);
|
|
end;
|
|
|
|
function TEscherStorage.AddGroup: TEscherGroup;
|
|
begin
|
|
Result := TEscherGroup.Create;
|
|
Groups.Add(Result);
|
|
Result.Id := Groups.Count;
|
|
end;
|
|
|
|
function TEscherStorage.GetGroup(Index: LongInt): TEscherGroup;
|
|
begin
|
|
if (Index >= 0) and (Index < Groups.Count) then
|
|
Result := TEscherGroup(Groups[Index])
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TEscherStorage.GetImage(Index: LongInt): TEscherBlip;
|
|
begin
|
|
if (Index >= 0) and (Index < Images.Count) then
|
|
Result := TEscherBlip(Images[Index])
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TEscherStorage.AddImage(Pict: TEscherPicture): TEscherBlip;
|
|
label
|
|
done;
|
|
var
|
|
i, j: LongInt;
|
|
b: TEscherBlip;
|
|
e: Boolean;
|
|
h1, h2: TEscherBlipHash;
|
|
begin
|
|
b := TEscherBlip.Create(Pict);
|
|
|
|
for i := 0 to Images.Count - 1 do
|
|
begin
|
|
h1 := Image[i].Hash;
|
|
h2 := b.Hash;
|
|
|
|
{ This algorithm just compares two hashes
|
|
and when the hashes are identical, it assumes
|
|
that the objects are identical. Normally,
|
|
the function should compare the two objects,
|
|
but it's slow }
|
|
|
|
e := True;
|
|
|
|
for j := 1 to 16 do
|
|
e := e and (h1[j] = h2[j]);
|
|
|
|
if e then
|
|
begin
|
|
b.Free;
|
|
Result := Image[i];
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
b.Index := Images.Count;
|
|
Images.Add(b);
|
|
Result := b;
|
|
end;
|
|
|
|
function TEscherStorage.Empty;
|
|
begin
|
|
Result := Groups.Count = 0;
|
|
end;
|
|
|
|
procedure TEscherStorage.Flush(Stream: TEscherStream);
|
|
var
|
|
i: Longint;
|
|
bs: TEscherStream;
|
|
cont: TEscherStream;
|
|
maxId: LongWord;
|
|
sn: LongWord;
|
|
|
|
begin
|
|
maxId := EscherGroupLimit * (1 + Groups.Count) + 1;
|
|
|
|
sn := 0;
|
|
for i := 0 to Groups.Count - 1 do
|
|
Inc(sn, Group[i].Count + 1);
|
|
|
|
cont := Stream.AddCont(0, EscherRkDggc);
|
|
with cont do
|
|
begin
|
|
with Add(0, 0, EscherRkDgg) do
|
|
begin
|
|
WriteVal(maxId, 4);
|
|
WriteVal(Groups.Count + 1, 4);
|
|
WriteVal(sn, 4);
|
|
WriteVal(Groups.Count, 4);
|
|
|
|
for i := 0 to Groups.Count - 1 do
|
|
with Group[i] do
|
|
begin
|
|
WriteVal(Id, 4);
|
|
WriteVal(Count + 1, 4);
|
|
end;
|
|
end;
|
|
|
|
bs := AddCont(Images.Count, EscherRkBstore);
|
|
for i := 0 to Images.Count - 1 do
|
|
Image[i].Flush(bs);
|
|
|
|
Props.Flush(cont);
|
|
|
|
with Add(0, 4, EscherRkSMC) do
|
|
begin
|
|
WriteVal($0800000d, 4);
|
|
WriteVal($0800000c, 4);
|
|
WriteVal($08000017, 4);
|
|
WriteVal($100000f7, 4);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
end.
|