mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-05 13:58:04 +02:00
1417 lines
39 KiB
ObjectPascal
1417 lines
39 KiB
ObjectPascal
{ A fpvectorial reader for wmf files.
|
|
|
|
Documentation used:
|
|
- http://msdn.microsoft.com/en-us/library/cc250370.aspx
|
|
- http://wvware.sourceforge.net/caolan/ora-wmf.html
|
|
- http://www.symantec.com/avcenter/reference/inside.the.windows.meta.file.format.pdf
|
|
|
|
These functions are not supported:
|
|
- see the empty case items in "TWMFVectorialReader.ReadRecords"
|
|
|
|
Issues:
|
|
- Text often truncated ( -- fixed)
|
|
- last character missing (-- fixed)
|
|
- Background color not applied
|
|
|
|
Author: Werner Pamler
|
|
}
|
|
|
|
{.$DEFINE WMF_DEBUG}
|
|
|
|
unit wmfvectorialreader;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils,
|
|
FPImage, FPCanvas,
|
|
fpvectorial;
|
|
|
|
type
|
|
TParamArray = array of word;
|
|
|
|
TWMFObjList = class(TFPList)
|
|
public
|
|
function Add(AData: Pointer): Integer;
|
|
end;
|
|
|
|
{ TvWMFVectorialReader }
|
|
|
|
TvWMFVectorialReader = class(TvCustomVectorialReader)
|
|
private
|
|
// list for WMF Objects
|
|
FObjList: TWMFObjList;
|
|
// info from header
|
|
FBBox: TRect; // in metafile units as specified by UnitsPerInch. NOTE: "logical" units can be different!
|
|
FUnitsPerInch: Integer;
|
|
FHasPlaceableMetaHeader: Boolean;
|
|
// state
|
|
FCurrPen: TvPen;
|
|
FCurrBrush: TvBrush;
|
|
FCurrFont: TvFont;
|
|
FCurrPalette: TFPPalette;
|
|
FCurrBkColor: TFPColor;
|
|
FCurrTextColor: TFPColor;
|
|
FCurrTextAlign: Word;
|
|
FCurrBkMode: Word;
|
|
FCurrPolyFillMode: Word;
|
|
FCurrRawFontHeight: Integer;
|
|
FMapMode: Word;
|
|
FWindowOrigin: TPoint;
|
|
FWindowExtent: TPoint;
|
|
FRecordStartPos: Int64;
|
|
FScalingFactorX: Double;
|
|
FScalingFactorY: Double;
|
|
FPageWidth: Double;
|
|
FPageHeight: Double;
|
|
FErrMsg: TStrings;
|
|
|
|
procedure ClearObjList;
|
|
|
|
function CreateBrush(const AParams: TParamArray): Integer;
|
|
function CreateFont(const AParams: TParamArray): Integer;
|
|
function CreatePalette(const AParams: TParamArray): Integer;
|
|
function CreatePatternBrush(const {%H-}AParams: TParamArray): Integer;
|
|
function CreatePen(const AParams: TParamArray): Integer;
|
|
function CreateRegion(const {%H-}AParams: TParamArray): Integer;
|
|
procedure DeleteObj(const AParams: TParamArray);
|
|
function DIBCreatePatternBrush(const AParams: TParamArray): Integer;
|
|
procedure ReadArc(APage: TvVectorialpage; const AParams: TParamArray);
|
|
procedure ReadBkColor({%H-}APage: TvVectorialPage; const AParams: TParamArray);
|
|
procedure ReadBkMode({%H-}APage: TvVectorialPage; const AValue: Word);
|
|
procedure ReadBkMode(APage: TvVectorialPage; const AParams: TParamArray);
|
|
procedure ReadChord(APage: TvVectorialpage; const AParams: TParamArray);
|
|
function ReadColor(const AParams: TParamArray; AIndex: Integer): TFPColor;
|
|
procedure ReadExtTextOut(APage: TvVectorialPage; const AParams: TParamArray);
|
|
procedure ReadEllipse(APage: TvVectorialPage; const AParams: TParamArray);
|
|
function ReadImage(const AParams: TParamArray; AIndex: Integer;
|
|
AImage: TFPCustomImage): Boolean;
|
|
procedure ReadLine(APage: TvVectorialPage; P1X, P1Y, P2X, P2Y: SmallInt);
|
|
procedure ReadMapMode(const AParams: TParamArray);
|
|
procedure ReadOffsetWindowOrg(const AParams: TParamArray);
|
|
procedure ReadPie(APage: TvVectorialPage; const AParams: TParamArray);
|
|
procedure ReadPolyFillMode(const AValue: Word);
|
|
procedure ReadPolygon(APage: TvVectorialPage; const AParams: TParamArray;
|
|
AFilled: boolean);
|
|
procedure ReadPolyPolygon(APage: TvVectorialPage; const AParams: TParamArray);
|
|
procedure ReadRectangle(APage: TvVectorialPage; const AParams: TParamArray;
|
|
IsRounded: Boolean);
|
|
procedure ReadStretchDIB({%H-}AStream: TStream; APage: TvVectorialPage;
|
|
const AParams: TParamArray);
|
|
function ReadString(const AParams: TParamArray;
|
|
AStartIndex, ALength: Integer): String;
|
|
procedure ReadTextAlign(const AParams: TParamArray);
|
|
procedure ReadTextColor(const AParams: TParamArray);
|
|
procedure ReadTextOut(APage: TvVectorialPage; const AParams: TParamArray);
|
|
procedure ReadWindowExt(const AParams: TParamArray);
|
|
procedure ReadWindowOrg(const AParams: TParamArray);
|
|
procedure SelectObj(const AIndex: Integer);
|
|
procedure SelectPalette(const AIndex: Integer);
|
|
|
|
protected
|
|
procedure ReadHeader(AStream: TStream);
|
|
procedure ReadRecords(AStream: TStream; AData: TvVectorialDocument);
|
|
|
|
procedure LogError(AMsg: String);
|
|
|
|
procedure CalcScalingFactors(out fx, fy: Double);
|
|
function ScaleX(x: Integer): Double;
|
|
function ScaleY(y: Integer): Double;
|
|
function ScaleSizeX(x: Integer): Double;
|
|
function ScaleSizeY(y: Integer): Double;
|
|
|
|
public
|
|
constructor Create; override;
|
|
destructor Destroy; override;
|
|
procedure ReadFromStream(AStream: TStream; AData: TvVectorialDocument); override;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
FPReadBMP, BMPcomn,
|
|
LConvEncoding, Math,
|
|
fpvUtils, fpvWMF;
|
|
|
|
const
|
|
INCH2MM = 25.4; // 1 inch = 25.4 mm
|
|
MM2INCH = 1.0/INCH2MM;
|
|
DEFAULT_SIZE = 100; // size of image (in mm) if scaling info is not available
|
|
SIZE_OF_WORD = 2;
|
|
FPV_UNIT: (fuPX, fuMM) = fuPX;
|
|
|
|
type
|
|
TWMFFont = class
|
|
Font: TvFont;
|
|
RawHeight: Integer;
|
|
end;
|
|
|
|
TWMFBrush = class
|
|
Brush: TvBrush;
|
|
end;
|
|
|
|
TWMFPen = class
|
|
Pen: TvPen;
|
|
RawPenWidth: Integer;
|
|
end;
|
|
|
|
TWMFPalette = {%H-}class
|
|
// not used, just needed as a filler in the ObjList
|
|
end;
|
|
|
|
TWMFRegion = class
|
|
// not used, just needed as a filler in the ObjList
|
|
end;
|
|
|
|
|
|
{ TWMFObjList }
|
|
|
|
function TWMFObjList.Add(AData: Pointer): Integer;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
// Fill empty items first
|
|
for i := 0 to Count-1 do
|
|
if Items[i] = nil then begin
|
|
Items[i] := AData;
|
|
Result := i;
|
|
exit;
|
|
end;
|
|
Result := inherited Add(AData);
|
|
end;
|
|
|
|
|
|
{ TvWMFVectorialReader }
|
|
|
|
constructor TvWMFVectorialReader.Create;
|
|
begin
|
|
inherited;
|
|
FErrMsg := TStringList.Create;
|
|
FObjList := TWMFObjList.Create;
|
|
with FCurrPen do begin
|
|
Style := psSolid;
|
|
Color := colBlack;
|
|
Width := 1;
|
|
end;
|
|
with FCurrBrush do begin
|
|
Style := bsClear; //Solid;
|
|
Color := colBlack;
|
|
end;
|
|
with FCurrFont do begin
|
|
Color := colBlack;
|
|
Size := 10;
|
|
Name := 'Arial';
|
|
Orientation := 0;
|
|
Bold := false;
|
|
Italic := False;
|
|
Underline := false;
|
|
StrikeThrough := false;
|
|
end;
|
|
FCurrBkColor := colWhite;
|
|
FCurrTextColor := colBlack;
|
|
FCurrTextAlign := 0; // Left + Top
|
|
FCurrPolyFillMode := ALTERNATE;
|
|
FMapMode := MM_ANISOTROPIC;
|
|
FUnitsPerInch := 96;
|
|
end;
|
|
|
|
destructor TvWMFVectorialReader.Destroy;
|
|
begin
|
|
ClearObjList;
|
|
FObjList.Free;
|
|
FErrMsg.Free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ClearObjList;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=0 to FObjList.Count-1 do
|
|
TObject(FObjList[i]).Free;
|
|
FObjList.Clear;
|
|
end;
|
|
|
|
function TvWMFVectorialReader.CreateBrush(const AParams: TParamArray): Integer;
|
|
var
|
|
brushRec: PWMFBrushRecord;
|
|
wmfBrush: TWMFBrush;
|
|
begin
|
|
wmfBrush := TWMFBrush.Create;
|
|
brushRec := PWMFBrushRecord(@AParams[0]);
|
|
|
|
// brush style
|
|
case brushRec^.Style of
|
|
BS_SOLID:
|
|
wmfBrush.Brush.Style := bsSolid;
|
|
BS_NULL:
|
|
wmfBrush.brush.Style := bsClear;
|
|
BS_HATCHED:
|
|
case brushRec^.Hatch of
|
|
HS_HORIZONTAL : wmfBrush.brush.Style := bsHorizontal;
|
|
HS_VERTICAL : wmfBrush.brush.Style := bsVertical;
|
|
HS_FDIAGONAL : wmfBrush.brush.Style := bsFDiagonal;
|
|
HS_BDIAGONAL : wmfBrush.brush.Style := bsBDiagonal;
|
|
HS_CROSS : wmfBrush.brush.Style := bsCross;
|
|
HS_DIAGCROSS : wmfBrush.brush.Style := bsDiagCross;
|
|
end;
|
|
{ --- not supported at the moment ...
|
|
BS_PATTERN = $0003;
|
|
BS_INDEXED = $0004;
|
|
BS_DIBPATTERN = $0005;
|
|
BS_DIBPATTERNPT = $0006;
|
|
BS_PATTERN8X8 = $0007;
|
|
BS_DIBPATTERN8X8 = $0008;
|
|
BS_MONOPATTERN = $0009; }
|
|
else
|
|
wmfBrush.brush.Style := bsSolid;
|
|
end;
|
|
|
|
// brush color
|
|
wmfBrush.brush.Color.Red := brushRec^.ColorRED shl 8;
|
|
wmfBrush.brush.Color.Green := brushRec^.ColorGREEN shl 8;
|
|
wmfBrush.brush.Color.Blue := brushRec^.ColorBLUE shl 8;
|
|
|
|
// add to WMF object list
|
|
Result := FObjList.Add(wmfBrush);
|
|
end;
|
|
|
|
function TvWMFVectorialReader.DIBCreatePatternBrush(const AParams: TParamArray): Integer;
|
|
var
|
|
wmfBrush: TWMFBrush;
|
|
memImg: TFPMemoryImage = nil;
|
|
{%H-}style: Word;
|
|
{%H-}colorUsage: Word;
|
|
begin
|
|
wmfBrush := TWMFBrush.Create;
|
|
|
|
style := AParams[0];
|
|
colorUsage := AParams[1];
|
|
|
|
memImg := TFPMemoryImage.Create(0, 0);
|
|
try
|
|
if ReadImage(AParams, 2, memImg) then begin
|
|
wmfBrush.Brush.Image := memImg;
|
|
wmfBrush.Brush.Style := bsImage;
|
|
end;
|
|
except
|
|
on E:Exception do begin
|
|
FreeAndNil(memImg);
|
|
LogError('Image reading error: ' + E.Message);
|
|
end;
|
|
end;
|
|
|
|
// Add to WMF object list
|
|
Result := FObjList.Add(wmfBrush);
|
|
end;
|
|
|
|
function TvWMFVectorialReader.CreateFont(const AParams: TParamArray): Integer;
|
|
var
|
|
wmfFont: TWMFFont;
|
|
fontRec: PWMFFontRecord;
|
|
fntName: String = '';
|
|
idx: Integer;
|
|
begin
|
|
idx := Length(Aparams);
|
|
wmfFont := TWMFFont.Create;
|
|
fontRec := PWMFFontRecord(@AParams[0]);
|
|
|
|
// Get font name
|
|
SetLength(fntName, 32);
|
|
idx := SizeOf(TWMFFontRecord) div SIZE_OF_WORD;
|
|
fntname := StrPas(PChar(@AParams[idx]));
|
|
|
|
wmfFont.Font.Name := ISO_8859_1ToUTF8(fntName);
|
|
wmfFont.Font.Size := round(ScaleSizeY(fontRec^.Height));
|
|
wmfFont.Font.Color := colBlack; // to be replaced by FCurrTextColor
|
|
wmfFont.Font.Bold := fontRec^.Weight >= 700;
|
|
wmfFont.Font.Italic := fontRec^.Italic <> 0;
|
|
wmfFont.Font.Underline := fontRec^.UnderLine <> 0;
|
|
wmfFont.Font.StrikeThrough := fontRec^.Strikeout <> 0;
|
|
wmfFont.Font.Orientation := fontRec^.Escapement div 10;
|
|
wmfFont.RawHeight := fontRec^.Height; //* 6 div 5; // Rough correction for y position
|
|
|
|
// add to WMF object list
|
|
Result := FObjList.Add(wmfFont);
|
|
end;
|
|
|
|
// to do: implement read palette
|
|
function TvWMFVectorialReader.CreatePalette(const AParams: TParamArray): Integer;
|
|
var
|
|
pal: TFPPalette;
|
|
col: TFPColor;
|
|
colRec: PWMFPaletteColorRecord;
|
|
i, n: Integer;
|
|
begin
|
|
// start := AParams[0];
|
|
n := AParams[1];
|
|
pal := TFPPalette.Create(n);
|
|
for i:=0 to n-1 do begin
|
|
colRec := PWMFPaletteColorRecord(@AParams[2 + i*4]);
|
|
col.Red := colRec^.ColorRED shl 8;
|
|
col.Green := colRec^.ColorGREEN shl 8;
|
|
col.Blue := colRec^.ColorBLUE shl 8;
|
|
pal.Add(col);
|
|
end;
|
|
Result := FObjList.Add(pal);
|
|
end;
|
|
|
|
function TvWMFVectorialReader.CreatePatternBrush(const AParams: TParamArray): Integer;
|
|
var
|
|
wmfBrush: TWMFBrush;
|
|
begin
|
|
wmfBrush := TWMFBrush.Create;
|
|
|
|
// Add to WMF object list;
|
|
Result := FObjList.Add(wmfBrush);
|
|
end;
|
|
|
|
function TvWMFVectorialReader.CreatePen(const AParams: TParamArray): Integer;
|
|
var
|
|
penRec: PWMFPenRecord;
|
|
wmfPen: TWMFPen;
|
|
begin
|
|
wmfPen := TWMFPen.Create;
|
|
penRec := PWMFPenRecord(@AParams[0]);
|
|
|
|
// pen style
|
|
case penRec^.Style and $000F of
|
|
PS_DASH : wmfPen.pen.Style := psDash;
|
|
PS_DOT : wmfPen.pen.Style := psDot;
|
|
PS_DASHDOT : wmfPen.pen.Style := psDashDot;
|
|
PS_DASHDOTDOT : wmfPen.pen.Style := psDashDotDot;
|
|
PS_NULL : wmfPen.pen.Style := psClear;
|
|
PS_INSIDEFRAME: wmfPen.pen.Style := psInsideFrame;
|
|
else wmfPen.pen.Style := psSolid;
|
|
end;
|
|
{ -- this is not yet supported by fpvectorial
|
|
case penRec^.Style and $0F00 of
|
|
PS_ENDCAP_SQUARE: wmfPen.pen.Endcap := pseSquare;
|
|
PS_ENDCAP_FLAT : wmfPen.pen.EndCap := pseFlat;
|
|
else wmfPen.pen.EndCap := pseRound;
|
|
end;
|
|
case penRec^.Style and $1000 of
|
|
PS_JOIN_BEVEL : wmfPen.pen.JoinStyle := pjsBevel;
|
|
PS_JOIN_MITER : wmfPen.pen.JoinStyle := pjsMiter;
|
|
else wmfPen.pen.JoinStyle := pjsRound;
|
|
end; }
|
|
|
|
// pen width
|
|
wmfPen.pen.Width := round(ScaleSizeX(penRec^.Width));
|
|
if penRec^.Width = 0 then
|
|
wmfPen.pen.Width := 1;
|
|
|
|
if wmfPen.pen.Style = psClear
|
|
then wmfPen.RawPenWidth := 0
|
|
else wmfPen.RawPenWidth := penRec^.Width;
|
|
|
|
// pen color
|
|
wmfPen.pen.Color.Red := penRec^.ColorRED shl 8;
|
|
wmfPen.pen.Color.Green := penRec^.ColorGREEN shl 8;
|
|
wmfPen.pen.Color.Blue := penRec^.ColorBLUE shl 8;
|
|
|
|
// Add to WMF object list
|
|
Result := FObjList.Add(wmfPen);
|
|
end;
|
|
|
|
// todo: implement region
|
|
function TvWMFVectorialReader.CreateRegion(const AParams: TParamArray): Integer;
|
|
var
|
|
wmfReg: TWMFRegion;
|
|
begin
|
|
wmfReg := TWMFRegion.Create;
|
|
Result := FObjList.Add(wmfReg);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.DeleteObj(const AParams: TParamArray);
|
|
var
|
|
obj: TObject;
|
|
idx: Integer;
|
|
begin
|
|
idx := AParams[0];
|
|
if idx < FObjList.Count then begin
|
|
obj := TObject(FObjList[idx]);
|
|
TObject(obj).Free;
|
|
FObjList[idx] := nil;
|
|
// Do not delete from list because this will confuse the obj indexes.
|
|
// Only mark the deleted obj item as nil so that the index can be re-used.
|
|
end;
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.LogError(AMsg: String);
|
|
begin
|
|
FErrMsg.Add(AMsg);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadArc(APage: TvVectorialPage;
|
|
const AParams: TParamArray);
|
|
var
|
|
path: TPath;
|
|
arcRec: PWMFArcRecord;
|
|
begin
|
|
arcRec := PWMFArcRecord(@AParams[0]);
|
|
|
|
APage.StartPath(ScaleX(arcRec^.XStartArc), ScaleY(arcRec^.YStartArc));
|
|
APage.AddEllipticalArcWithCenterToPath(
|
|
ScaleX(arcRec^.Right - arcRec^.Left) / 2,
|
|
ScaleY(abs(arcRec^.Bottom - arcRec^.Top)) / 2,
|
|
0.0,
|
|
ScaleX(arcRec^.XEndArc),
|
|
ScaleY(arcrec^.YEndArc),
|
|
ScaleX(arcRec^.Left + arcRec^.Right) / 2,
|
|
ScaleY(abs(arcRec^.Top + arcRec^.Bottom)) / 2,
|
|
false
|
|
);
|
|
path := APage.EndPath;
|
|
path.Pen := FCurrPen;
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadBkColor(APage: TvVectorialPage;
|
|
const AParams: TParamArray);
|
|
begin
|
|
FCurrBkColor := ReadColor(AParams, 0);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadBkMode(APage: TvVectorialPage;
|
|
const AValue: Word);
|
|
begin
|
|
FCurrBkMode := AValue;
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadBkMode(APage: TvVectorialPage;
|
|
const AParams: TParamArray);
|
|
begin
|
|
ReadBkMode(APage, AParams[0]);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadChord(APage: TvVectorialPage;
|
|
const AParams: TParamArray);
|
|
var
|
|
path: TPath;
|
|
arcRec: PWMFArcRecord;
|
|
p1, p2: T3dPoint;
|
|
begin
|
|
arcRec := PWMFArcRecord(@AParams[0]);
|
|
|
|
p1 := Make3DPoint(ScaleX(arcRec^.XStartArc), ScaleY(arcRec^.YStartArc));
|
|
p2 := Make3DPoint(ScaleX(arcRec^.XEndArc), ScaleY(arcRec^.YEndArc));
|
|
|
|
APage.StartPath(p1.x, p1.y);
|
|
APage.AddEllipticalArcWithCenterToPath(
|
|
ScaleX(arcRec^.Right - arcRec^.Left) / 2,
|
|
ScaleY(abs(arcRec^.Bottom - arcRec^.Top)) / 2,
|
|
0.0,
|
|
p2.x,
|
|
p2.y,
|
|
ScaleX(arcRec^.Left + arcRec^.Right) / 2,
|
|
ScaleY(abs(arcRec^.Top + arcRec^.Bottom)) / 2,
|
|
false
|
|
);
|
|
APage.AddLineToPath(p1.x, p1.y);
|
|
path := APage.EndPath;
|
|
path.Pen := FCurrPen;
|
|
path.Brush := FCurrBrush;
|
|
end;
|
|
|
|
function TvWMFVectorialReader.ReadColor(const AParams: TParamArray;
|
|
AIndex: Integer): TFPColor;
|
|
var
|
|
colorRec: PWMFColorRecord;
|
|
begin
|
|
colorRec := PWMFColorRecord(@AParams[AIndex]);
|
|
Result.Red := colorRec^.ColorRED shl 8;
|
|
Result.Green := colorRec^.ColorGREEN shl 8;
|
|
Result.Blue := colorRec^.ColorBLUE shl 8;
|
|
Result.Alpha := alphaOpaque;
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadExtTextOut(APage: TvVectorialPage;
|
|
const AParams: TParamArray);
|
|
var
|
|
x, y, len, opts: Integer;
|
|
offs: TPoint;
|
|
{%H-}R: TRect;
|
|
s: String;
|
|
txt: TvText;
|
|
angle: Double;
|
|
begin
|
|
y := SmallInt(AParams[0]); // signed int
|
|
x := SmallInt(AParams[1]);
|
|
len := SmallInt(AParams[2]);
|
|
opts := AParams[3]; // unsigned int
|
|
if opts <> 0 then begin
|
|
R.Bottom := SmallInt(AParams[4]);
|
|
R.Right := SmallInt(AParams[5]);
|
|
R.Top := SmallInt(AParams[6]);
|
|
R.Left := SmallInt(AParams[7]);
|
|
s := ReadString(AParams, 8, len);
|
|
end else
|
|
s := ReadString(AParams, 4, len);
|
|
// We ignore the Dx fields
|
|
|
|
// Correct text position which is at baseline in case of fpvectorial, but
|
|
// may be different depending on bits in the CurrTextAlign value.
|
|
|
|
// TO DO: More testing of text positioning
|
|
angle := DegToRad(FCurrFont.Orientation);
|
|
case FCurrTextAlign and $0018 of
|
|
0:
|
|
offs := Point(0, 0); //Rotate2DPoint(Point(0, +FCurrRawFontHeight), Point(0, 0), angle); // wp --- the TA_BASELINE case seems to be correct, this one must be wrong...
|
|
TA_BASELINE:
|
|
offs := Point(0, 0); // wp: was Rotate2DPoint(Point(0, +FCurrRawFontHeight), Point(0, 0), angle);
|
|
TA_BOTTOM:
|
|
offs := Rotate2DPoint(Point(0, -FCurrRawFontHeight*7 div 5), Point(0, 0), angle);
|
|
end;
|
|
|
|
// Pass text to fpvectorial
|
|
txt := APage.AddText(ScaleX(x + offs.X), ScaleY(y + offs.Y), s);
|
|
// Select the font
|
|
txt.Font := FCurrFont;
|
|
txt.Font.Color := FCurrTextColor;
|
|
// Set horizontal text alignment.
|
|
case FCurrTextAlign and (TA_RIGHT or TA_CENTER) of
|
|
TA_RIGHT : txt.TextAnchor := vtaEnd;
|
|
TA_CENTER : txt.TextAnchor := vtaMiddle;
|
|
else txt.TextAnchor := vtaStart;
|
|
end;
|
|
|
|
// Opaque flag
|
|
if opts and ETO_OPAQUE <> 0 then
|
|
begin
|
|
txt.Brush.Style := bsSolid;
|
|
txt.Brush.Color := FCurrBkColor;
|
|
end
|
|
else
|
|
txt.Brush.Style := bsClear;
|
|
|
|
// to do: take care of clipping (if opts and ETO_CLIPPED <> 0)
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadEllipse(APage: TvVectorialPage;
|
|
const AParams: TParamArray);
|
|
var
|
|
rectRec: PWMFRectRecord; // coordinates are SmallInt.
|
|
ellipse: TvEllipse;
|
|
begin
|
|
rectRec := PWMFRectRecord(@AParams[0]);
|
|
|
|
ellipse := TvEllipse.Create(APage);
|
|
ellipse.X := (ScaleX(rectRec^.Left) + ScaleX(rectRec^.Right)) / 2;
|
|
ellipse.Y := (ScaleY(rectRec^.Top) + ScaleY(rectRec^.Bottom)) / 2;
|
|
ellipse.HorzHalfAxis := abs(ScaleX(rectRec^.Right - rectRec^.Left) / 2);
|
|
ellipse.VertHalfAxis := abs(ScaleSizeY(rectRec^.Bottom - rectRec^.Top) / 2);
|
|
|
|
ellipse.Pen := FCurrPen;
|
|
ellipse.Brush := FCurrBrush;
|
|
|
|
APage.AddEntity(ellipse);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadFromStream(AStream: TStream;
|
|
AData: TvVectorialDocument);
|
|
begin
|
|
ClearObjList;
|
|
FErrMsg.Clear;
|
|
|
|
ReadHeader(AStream);
|
|
ReadRecords(AStream, AData);
|
|
|
|
if FErrMsg.Count > 0 then
|
|
raise Exception.Create(FErrMsg.Text);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadHeader(AStream: TStream);
|
|
var
|
|
buf: array[0..80] of byte;
|
|
placeableMetaHdr: TPlaceableMetaHeader absolute buf;
|
|
wmfHdr: TWMFHeader absolute buf;
|
|
n: Integer;
|
|
begin
|
|
AStream.Position := 0;
|
|
|
|
// Test if file begins with a placeable meta file header
|
|
FHasPlaceableMetaHeader := false;
|
|
n := AStream.Read(buf{%H-}, SizeOf(TPlaceableMetaHeader));
|
|
if n <> SizeOf(TPlaceableMetaHeader) then
|
|
begin
|
|
LogError('Error reading the wmf file header.');
|
|
exit;
|
|
end;
|
|
|
|
if placeableMetaHdr.Key = WMF_MAGIC_NUMBER then begin // yes!
|
|
FHasPlaceableMetaHeader := true;
|
|
FBBox.Left := placeableMetaHdr.Left;
|
|
FBBox.Top := placeableMetaHdr.Top;
|
|
FBBox.Right := placeableMetaHdr.Right;
|
|
FBBox.Bottom := placeableMetaHdr.Bottom;
|
|
FUnitsPerInch := placeableMetaHdr.Inch;
|
|
end else
|
|
begin
|
|
// Is it the wmf header?
|
|
if not ((wmfHdr.FileType in [0, 1]) and (wmfHdr.HeaderSize = 9)) then begin
|
|
// No - then it is not a wmf format.
|
|
LogError('This is not a WMF file.');
|
|
exit;
|
|
end;
|
|
// Rewind stream
|
|
AStream.Position := 0;
|
|
end;
|
|
|
|
// Read the wmf header
|
|
AStream.ReadBuffer(buf, SizeOf(TWMFHeader));
|
|
// FNumObj := wmfHdr.NumOfObjects;
|
|
// FMaxRecordSize := wmfHdr.MaxRecordSize; // words
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadLine(APage: TvVectorialPage;
|
|
P1X, P1Y, P2X, P2Y: SmallInt);
|
|
var
|
|
path: TPath;
|
|
begin
|
|
APage.StartPath(ScaleX(P1X), ScaleY(P1Y));
|
|
APage.AddLineToPath(ScaleX(P2X), ScaleY(P2Y));
|
|
path := APage.EndPath;
|
|
path.Pen := FCurrPen;
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadMapMode(const AParams: TParamArray);
|
|
begin
|
|
FMapMode := AParams[0];
|
|
CalcScalingFactors(FScalingFactorX, FScalingFactorY);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadOffsetWindowOrg(const AParams: TParamArray);
|
|
begin
|
|
FWindowOrigin.Y := FWindowOrigin.Y + SmallInt(AParams[0]);
|
|
FWindowOrigin.X := FWindowOrigin.X + SmallInt(AParams[1]);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadPie(APage: TvVectorialpage;
|
|
const AParams: TParamArray);
|
|
var
|
|
path: TPath;
|
|
arcRec: PWMFArcRecord;
|
|
p1, p2, ctr: T3dPoint;
|
|
begin
|
|
arcRec := PWMFArcRecord(@AParams[0]);
|
|
|
|
p1 := Make3DPoint(ScaleX(arcRec^.XStartArc), ScaleY(arcRec^.YStartArc));
|
|
p2 := Make3DPoint(ScaleX(arcRec^.XEndArc), ScaleY(arcRec^.YEndArc));
|
|
ctr := Make3DPoint(ScaleX(arcRec^.Left + arcRec^.Right)/2, ScaleY(arcRec^.Top + arcRec^.Bottom)/2);
|
|
|
|
APage.StartPath(p1.x, p1.y);
|
|
APage.AddEllipticalArcWithCenterToPath(
|
|
ScaleX(arcRec^.Right - arcRec^.Left) / 2,
|
|
ScaleY(abs(arcRec^.Bottom - arcRec^.Top)) / 2,
|
|
0.0,
|
|
p2.x, p2.y,
|
|
ctr.x, ctr.y,
|
|
false
|
|
);
|
|
APage.AddLineToPath(ctr.x, ctr.y);
|
|
APage.AddLineToPath(p1.x, p1.y);
|
|
path := APage.EndPath;
|
|
path.Pen := FCurrPen;
|
|
path.Brush := FCurrBrush;
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadPolyFillMode(const AValue: Word);
|
|
begin
|
|
FCurrPolyFillMode := AValue;
|
|
end;
|
|
|
|
{ AParams[0] ... number of points
|
|
AParams[1] ... x value of 1st point
|
|
AParams[2] ... y value of 1st point
|
|
etc }
|
|
procedure TvWMFVectorialReader.ReadPolygon(APage: TvVectorialPage;
|
|
const AParams: TParamArray; AFilled: boolean);
|
|
const
|
|
EPS = 1E-6;
|
|
var
|
|
n: Integer;
|
|
i, j: Integer;
|
|
pts: Array of T3DPoint = nil;
|
|
path: TPath;
|
|
begin
|
|
n := AParams[0];
|
|
SetLength(pts, n);
|
|
j := 1;
|
|
for i:= 0 to n-1 do begin
|
|
pts[i] := Make3DPoint(ScaleX(SmallInt(AParams[j])), ScaleY(SmallInt(AParams[j+1])));
|
|
inc(j, 2);
|
|
end;
|
|
// Automatically close polygon (but not polyline)
|
|
if AFilled and not SamePoint(pts[0], pts[n-1], EPS) then begin
|
|
SetLength(pts, n+1);
|
|
pts[n] := pts[0];
|
|
end;
|
|
|
|
APage.StartPath(pts[0].X, pts[0].Y);
|
|
for i:=1 to n-1 do
|
|
APage.AddLineToPath(pts[i].x, pts[i].y);
|
|
path := APage.EndPath;
|
|
path.Pen := FCurrPen;
|
|
if AFilled then
|
|
path.Brush := FCurrBrush
|
|
else begin
|
|
path.Brush.Style := bsClear;
|
|
path.Brush.Kind := bkSimpleBrush;
|
|
end;
|
|
case FCurrPolyFillMode of
|
|
ALTERNATE : path.WindingRule := vcmEvenOddRule;
|
|
WINDING : path.WindingRule := vcmNonZeroWindingRule;
|
|
end;
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadPolyPolygon(APage: TvVectorialPage;
|
|
const AParams: TParamArray);
|
|
const
|
|
EPS = 1E-6;
|
|
var
|
|
nPoly: Integer;
|
|
nPts: array of Integer = nil;
|
|
i, j, k: Integer;
|
|
path: TPath;
|
|
P: T3DPoint;
|
|
Pstart: T3DPoint;
|
|
begin
|
|
k := 0;
|
|
nPoly := AParams[k];
|
|
inc(k);
|
|
SetLength(nPts, nPoly);
|
|
for i:=0 to nPoly-1 do begin
|
|
nPts[i] := AParams[k];
|
|
inc(k);
|
|
end;
|
|
|
|
APage.StartPath;
|
|
for j := 0 to nPoly-1 do begin
|
|
PStart := Make3DPoint(ScaleX(SmallInt(AParams[k])), ScaleY(SmallInt(AParams[k+1])));
|
|
inc(k, 2);
|
|
APage.AddMoveToPath(PStart.X, PStart.Y);
|
|
for i := 1 to nPts[j]-1 do begin
|
|
P := Make3DPoint(ScaleX(SmallInt(AParams[k])), ScaleY(SmallInt(AParams[k+1])));
|
|
inc(k, 2);
|
|
APage.AddLineToPath(P.X, P.Y);
|
|
end;
|
|
// Close polygon
|
|
if not SamePoint(P, PStart, EPS) then
|
|
APage.AddLineToPath(PStart.X, PStart.Y);
|
|
end;
|
|
path := APage.EndPath;
|
|
path.Pen := FCurrPen;
|
|
path.Brush := FCurrBrush;
|
|
case FCurrPolyFillMode of
|
|
ALTERNATE : path.WindingRule := vcmEvenOddRule;
|
|
WINDING : path.WindingRule := vcmNonZeroWindingRule;
|
|
end;
|
|
|
|
// No need to add path to page explicity
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadRecords(AStream: TStream; AData: TvVectorialDocument);
|
|
var
|
|
wmfRec: TWMFRecord;
|
|
params: TParamArray = nil;
|
|
page: TvVectorialPage;
|
|
prevX, prevY: Word;
|
|
n: Integer;
|
|
begin
|
|
page := AData.AddPage(not (vrf_UseBottomLeftCoords in Settings.VecReaderFlags));
|
|
page.BackgroundColor := colWhite;
|
|
|
|
while AStream.Position < AStream.Size do begin
|
|
// Store the stream position where the current record begins
|
|
FRecordStartPos := AStream.Position;
|
|
|
|
// Read record size and function code
|
|
n := AStream.Read(wmfRec{%H-}, SizeOf(TWMFRecord));
|
|
if n <> SizeOf(TWMFRecord) then
|
|
raise Exception.Create('Record size error.');
|
|
|
|
{$IFDEF WMF_DEBUG}
|
|
writeLn(Format('Record position: %0:d / Record size: %1:d words / Record type: %2:d ($%2:x): %3:s',
|
|
[FRecordStartPos, wmfRec.Size, wmfRec.Func, WMF_GetRecordTypeName(wmfRec.Func)]));
|
|
{$ENDIF}
|
|
|
|
// End of file?
|
|
if wmfRec.Func = META_EOF then
|
|
break;
|
|
|
|
// Obviously invalid record?
|
|
if wmfRec.Size < 3 then begin
|
|
LogError(Format('Record size error at position %d', [FRecordStartPos]));
|
|
exit;
|
|
end;
|
|
|
|
// Read parameters
|
|
SetLength(params, wmfRec.Size - 3);
|
|
AStream.ReadBuffer(params[0], (wmfRec.Size - 3)*SIZE_OF_WORD);
|
|
|
|
// Process record, depending on function code
|
|
case wmfRec.Func of
|
|
{ *** Bitmap record types *** }
|
|
META_BITBLT:
|
|
;
|
|
META_DIBBITBLT:
|
|
;
|
|
META_DIBSTRETCHBLT:
|
|
;
|
|
META_SETDIBTODEV:
|
|
;
|
|
META_STRETCHBLT:
|
|
;
|
|
META_STRETCHDIB:
|
|
ReadStretchDIB(AStream, page, params);
|
|
|
|
{ *** Drawing records *** }
|
|
META_ARC:
|
|
ReadArc(page, params);
|
|
META_CHORD:
|
|
ReadChord(page, params);
|
|
META_ELLIPSE:
|
|
ReadEllipse(page, params);
|
|
META_EXTFLOODFILL:
|
|
;
|
|
META_EXTTEXTOUT:
|
|
ReadExtTextOut(page, params);
|
|
META_FILLREGION:
|
|
;
|
|
META_FLOODFILL:
|
|
;
|
|
META_FRAMEREGION:
|
|
;
|
|
META_INVERTREGION:
|
|
;
|
|
META_MOVETO:
|
|
begin
|
|
prevX := params[1];
|
|
prevY := params[0];
|
|
end;
|
|
META_LINETO:
|
|
begin
|
|
ReadLine(page, prevX, prevY, params[1], params[0]);
|
|
prevX := params[1];
|
|
prevY := params[0];
|
|
end;
|
|
META_PAINTREGION:
|
|
;
|
|
META_PATBLT:
|
|
;
|
|
META_PIE:
|
|
ReadPie(page, params);
|
|
META_POLYGON:
|
|
ReadPolygon(page, params, true);
|
|
META_POLYLINE:
|
|
ReadPolygon(page, params, false);
|
|
META_POLYPOLYGON:
|
|
ReadPolyPolygon(page, params);
|
|
META_RECTANGLE:
|
|
ReadRectangle(page, params, false);
|
|
META_ROUNDRECT:
|
|
ReadRectangle(page, params, true);
|
|
META_SETPIXEL:
|
|
;
|
|
META_TEXTOUT:
|
|
ReadTextOut(page, params);
|
|
|
|
{ *** WMF Object records *** }
|
|
META_CREATEBRUSHINDIRECT:
|
|
CreateBrush(params);
|
|
META_CREATEFONTINDIRECT:
|
|
CreateFont(params);
|
|
META_CREATEPALETTE:
|
|
CreatePalette(params);
|
|
META_CREATEPATTERNBRUSH:
|
|
CreatePatternBrush(params);
|
|
META_CREATEPENINDIRECT:
|
|
CreatePen(params);
|
|
META_CREATEREGION:
|
|
CreateRegion(params);
|
|
META_DIBCREATEPATTERNBRUSH:
|
|
DIBCreatePatternBrush(params);
|
|
META_DELETEOBJECT:
|
|
DeleteObj(params);
|
|
META_SELECTCLIPREGION:
|
|
;
|
|
META_SELECTOBJECT:
|
|
SelectObj(params[0]);
|
|
META_SELECTPALETTE:
|
|
SelectPalette(params[0]);
|
|
|
|
{ *** State records *** }
|
|
META_ANIMATEPALETTE:
|
|
;
|
|
META_EXCLUDECLIPRECT:
|
|
;
|
|
META_INTERSECTCLIPRECT:
|
|
;
|
|
META_OFFSETCLIPRGN:
|
|
;
|
|
META_OFFSETVIEWPORTORG:
|
|
;
|
|
META_OFFSETWiNDOWORG:
|
|
ReadOffsetWindowOrg(params);
|
|
META_REALIZEPALETTE:
|
|
;
|
|
META_RESIZEPALETTE:
|
|
;
|
|
META_RESTOREDC:
|
|
;
|
|
META_SAVEDC:
|
|
;
|
|
META_SCALEVIEWPORTEXT:
|
|
;
|
|
META_SCALEWINDOWEXT:
|
|
;
|
|
META_SETBKCOLOR:
|
|
ReadBkColor(page, params);
|
|
META_SETBKMODE:
|
|
ReadBkMode(page, params);
|
|
META_SETLAYOUT:
|
|
;
|
|
META_SETMAPMODE:
|
|
ReadMapMode(params);
|
|
META_SETMAPPERFLAGS:
|
|
;
|
|
META_SETPALENTRIES:
|
|
;
|
|
META_SETPOLYFILLMODE:
|
|
ReadPolyFillMode(params[0]);
|
|
META_SETRELABS:
|
|
;
|
|
META_SETROP2:
|
|
;
|
|
META_SETSTRETCHBLTMODE:
|
|
;
|
|
META_SETTEXTALIGN:
|
|
ReadTextAlign(params);
|
|
META_SETTEXTCHAREXTRA:
|
|
;
|
|
META_SETTEXTCOLOR:
|
|
ReadTextColor(params);
|
|
META_SETVIEWPORTEXT:
|
|
;
|
|
META_SETVIEWPORTORG:
|
|
;
|
|
META_SETWINDOWEXT:
|
|
ReadWindowExt(params);
|
|
META_SETWINDOWORG:
|
|
ReadWindowOrg(params);
|
|
|
|
{ *** ESCAPE records *** }
|
|
// None of them implemented
|
|
end;
|
|
|
|
AStream.Position := FRecordStartPos + Int64(wmfRec.Size) * SIZE_OF_WORD;
|
|
end;
|
|
|
|
if FHasPlaceableMetaHeader then begin
|
|
page.Width := FPageWidth;
|
|
page.Height := FPageHeight;
|
|
end else begin
|
|
page.Width := ScaleSizeX(FWindowExtent.X);
|
|
page.Height := ScaleSizeY(FWindowExtent.Y);
|
|
end;
|
|
AData.Width := page.Width;
|
|
AData.Height := page.Height;
|
|
|
|
SetLength(params, 0);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadRectangle(APage: TvVectorialPage;
|
|
const AParams: TParamArray; IsRounded: Boolean);
|
|
var
|
|
rectRec: PWMFRectRecord; // coordinates are SmallInt
|
|
rx, ry: SmallInt;
|
|
rect: TvRectangle;
|
|
begin
|
|
if IsRounded then begin
|
|
ry := AParams[0];
|
|
rx := AParams[1];
|
|
rectRec := PWMFRectRecord(@AParams[2]);
|
|
end else begin
|
|
rectRec := PWMFRectRecord(@AParams[0]);
|
|
rx := 0;
|
|
ry := 0;
|
|
end;
|
|
|
|
rect := TvRectangle.Create(APage);
|
|
rect.X := ScaleX(rectRec^.Left);
|
|
rect.Y := ScaleY(rectRec^.Top);
|
|
rect.CX := ScaleSizeX(abs(rectRec^.Right - rectRec^.Left));
|
|
rect.CY := ScaleSizeY(abs(rectRec^.Bottom - rectRec^.Top));
|
|
rect.RX := ScaleSizeX(rx);
|
|
rect.RY := ScaleSizeY(ry);
|
|
rect.Pen := FCurrPen;
|
|
rect.Brush := FCurrBrush;
|
|
|
|
APage.AddEntity(rect);
|
|
end;
|
|
|
|
function TvWMFVectorialReader.ReadImage(const AParams: TParamArray;
|
|
AIndex: Integer; AImage: TFPCustomImage): Boolean;
|
|
var
|
|
bmpCoreHdr: PWMFBitmapCoreHeader = nil;
|
|
bmpInfoHdr: PWMFBitmapInfoHeader = nil;
|
|
hasCoreHdr: Boolean;
|
|
bmpFileHdr: TBitmapFileHeader;
|
|
w, h: Integer;
|
|
memstream: TMemoryStream;
|
|
imgSize: Int64;
|
|
dataSize: Integer;
|
|
reader: TFPCustomImageReader;
|
|
begin
|
|
Result := false;
|
|
|
|
bmpCoreHdr := PWMFBitmapCoreHeader(@AParams[AIndex]);
|
|
bmpInfoHdr := PWMFBitmapInfoHeader(@AParams[AIndex]);
|
|
hasCoreHdr := bmpInfoHdr^.HeaderSize = SizeOf(TWMFBitmapCoreHeader);
|
|
if hasCoreHdr then
|
|
exit;
|
|
|
|
w := bmpInfoHdr^.Width;
|
|
h := bmpInfoHdr^.Height;
|
|
if (w = 0) or (h = 0) then
|
|
exit;
|
|
|
|
memStream := TMemoryStream.Create;
|
|
try
|
|
datasize := (Length(AParams) - AIndex) * SIZE_OF_WORD;
|
|
|
|
// Put a bitmap file header in front of the bitmap info header and the data
|
|
bmpFileHdr.bfType := BMmagic;
|
|
bmpFileHdr.bfSize := SizeOf(bmpFileHdr) + datasize;
|
|
if bmpInfoHdr^.Compression in [BI_RGB, BI_BITFIELDS, BI_CMYK] then
|
|
imgSize := (w + Int64(bmpInfoHdr^.Planes) * bmpInfoHdr^.BitCount + 31) div 32 * abs(h)
|
|
else
|
|
imgSize := bmpInfoHdr^.ImageSize;
|
|
bmpFileHdr.bfOffset := bmpFileHdr.bfSize - imgSize;
|
|
bmpFileHdr.bfReserved := 0;
|
|
memstream.WriteBuffer(bmpFileHdr, SizeOf(bmpFileHdr));
|
|
memstream.WriteBuffer(AParams[AIndex], (Length(AParams) - AIndex) * SIZE_OF_WORD);
|
|
|
|
// Read bitmap to image using the standard bitmap reader.
|
|
reader := TFPReaderBMP.Create;
|
|
try
|
|
memstream.Position := 0;
|
|
AImage.LoadfromStream(memStream, reader);
|
|
Result := true;
|
|
finally
|
|
reader.Free;
|
|
end;
|
|
finally
|
|
memstream.Free;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ Tested: embedded bmp, png and jpeg in Inkscape, saved as wmf.
|
|
Other tests are missing due to lack of well-defined test files. }
|
|
procedure TvWMFVectorialReader.ReadStretchDIB(AStream: TStream;
|
|
APage: TvVectorialPage; const AParams: TParamArray);
|
|
var
|
|
rasterImg: TvRasterImage = nil;
|
|
memImg: TFPMemoryImage = nil;
|
|
dibRec: PWMFStretchDIBRecord;
|
|
begin
|
|
dibRec := PWMFStretchDIBRecord(@AParams[0]);
|
|
memImg := TFPMemoryImage.Create(0, 0); //w, h);
|
|
try
|
|
if not ReadImage(AParams, SizeOf(TWMFStretchDIBRecord) div SIZE_OF_WORD, memImg) then
|
|
exit;
|
|
|
|
// Pass bitmap to fpvectorial
|
|
rasterImg := TvRasterImage.Create(APage);
|
|
rasterImg.RasterImage := memImg;
|
|
rasterImg.x := ScaleX(dibRec^.DestX);
|
|
rasterImg.y := ScaleY(dibRec^.DestY);
|
|
rasterImg.Width := ScaleSizeX(dibRec^.DestWidth);
|
|
rasterImg.Height := ScaleSizeY(dibRec^.DestHeight);
|
|
APage.AddEntity(rasterImg);
|
|
except
|
|
on E:Exception do begin
|
|
FreeAndNil(rasterImg);
|
|
FreeAndNil(memImg);
|
|
LogError('Image reading error: ' + E.Message);
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
(*
|
|
w := bmpInfoHdr^.Width;
|
|
h := bmpInfoHdr^.Height;
|
|
if (w = 0) or (h = 0) then
|
|
exit;
|
|
memStream := TMemoryStream.Create;
|
|
try
|
|
datasize := Length(AParams) * SizeOf(word) - SizeOf(TWMFStretchDIBRecord);
|
|
// Put a bitmap file header before the bitmap info header and the data
|
|
bmpFileHdr.bfType := BMmagic;
|
|
bmpFileHdr.bfSize:= SizeOf(bmpFileHdr) + datasize;
|
|
if bmpInfoHdr^.Compression in [BI_RGB, BI_BITFIELDS, BI_CMYK] then
|
|
imgSize := (w + bmpInfoHdr^.Planes * bmpInfoHdr^.BitCount + 31) div 32 * abs(h)
|
|
else
|
|
imgSize := bmpInfoHdr^.ImageSize;
|
|
bmpFileHdr.bfOffset := bmpFileHdr.bfSize - imgSize;
|
|
bmpFileHdr.bfReserved := 0;
|
|
memstream.WriteBuffer(bmpFileHdr, SizeOf(bmpFileHdr));
|
|
AStream.Position := FRecordStartPos + 3*SizeOf(Word) + SizeOf(TWMFStretchDIBRecord);
|
|
memstream.CopyFrom(AStream, Length(AParams) * SizeOf(Word) - SizeOf(TWMFStretchDIBRecord));
|
|
memstream.Position := 0;
|
|
try
|
|
// Read bitmap
|
|
memImg := TFPMemoryImage.Create(w, h);
|
|
reader := TFPReaderBMP.Create;
|
|
try
|
|
memImg.LoadfromStream(memStream, reader);
|
|
finally
|
|
reader.Free;
|
|
end;
|
|
// Pass bitmap to fpvectorial
|
|
rasterImg := TvRasterImage.Create(APage);
|
|
rasterImg.RasterImage := memImg;
|
|
rasterImg.x := ScaleX(dibRec^.DestX);
|
|
rasterImg.y := ScaleY(dibRec^.DestY);
|
|
rasterImg.Width := ScaleSizeX(dibRec^.DestWidth);
|
|
rasterImg.Height := ScaleSizeY(dibRec^.DestHeight);
|
|
APage.AddEntity(rasterImg);
|
|
except
|
|
on E:Exception do begin
|
|
memImg.Free;
|
|
rasterImg.Free;
|
|
LogError('Image reading error: ' + E.Message);
|
|
exit;
|
|
end;
|
|
end;
|
|
finally
|
|
memstream.Free;
|
|
end;
|
|
end;
|
|
|
|
// Restore original stream position
|
|
AStream.Position := savedPos;
|
|
end;
|
|
*)
|
|
function TvWMFVectorialReader.ReadString(const AParams: TParamArray;
|
|
AStartIndex, ALength: Integer): String;
|
|
var
|
|
s: ansistring = '';
|
|
begin
|
|
SetLength(s, ALength);
|
|
Move(AParams[AStartIndex], s[1], ALength);
|
|
// Note: ALength is the true string length. No need to remove the padding byte added to odd-length strings.
|
|
Result := ISO_8859_1ToUTF8(s);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadTextAlign(const AParams: TParamArray);
|
|
begin
|
|
FCurrTextAlign := AParams[0];
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadTextColor(const AParams: TParamArray);
|
|
begin
|
|
FCurrTextColor := ReadColor(AParams, 0);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadTextOut(APage: TvVectorialPage;
|
|
const AParams: TParamArray);
|
|
var
|
|
x, y, len, i: Integer;
|
|
s: String;
|
|
txt: TvText;
|
|
offs: TPoint;
|
|
begin
|
|
{ Record layout:
|
|
word - String length
|
|
even number of bytes - String, no trailing zero, but padded to even length
|
|
smallInt - yStart
|
|
smallInt - xStart }
|
|
|
|
len := AParams[0];
|
|
i := 1;
|
|
s := ReadString(AParams, i, len);
|
|
if odd(len) then inc(len);
|
|
inc(i, len div 2);
|
|
y := SmallInt(AParams[i]); // signed int!
|
|
x := SmallInt(AParams[i + 1]);
|
|
|
|
// Correct text position which is at baseline in case of fpvectorial, but
|
|
// may be different depending on bits in the CurrTextAlign value.
|
|
|
|
// TO DO: More testing of text positioning.
|
|
case FCurrTextAlign and $0018 of
|
|
0:
|
|
offs := Point(0, 0); // TA_BASELINE seems to be correct (2023-01-11) --> case 0 must be wrong...
|
|
TA_BASELINE:
|
|
offs := Point(0, 0); //wp: was Rotate2DPoint(Point(0, FCurrRawFontHeight), Point(0, 0), DegToRad(FCurrFont.Orientation));
|
|
TA_BOTTOM:
|
|
offs := Rotate2DPoint(Point(0, -FCurrRawFontHeight*7 div 5), Point(0, 0), DegToRad(FCurrFont.Orientation));
|
|
end;
|
|
|
|
// Pass the text to fpvectorial
|
|
txt := APage.AddText(ScaleX(x + offs.x), ScaleY(y - offs.y), s);
|
|
// Select the font
|
|
txt.Font := FCurrFont;
|
|
// Font color
|
|
txt.Font.Color := FCurrTextColor;
|
|
// Set horizontal text alignment.
|
|
case FCurrTextAlign and (TA_RIGHT or TA_CENTER) of
|
|
TA_RIGHT : txt.TextAnchor := vtaEnd;
|
|
TA_CENTER : txt.TextAnchor := vtaMiddle;
|
|
else txt.TextAnchor := vtaStart;
|
|
end;
|
|
// Set background style
|
|
if FCurrBkMode = BM_OPAQUE then
|
|
begin
|
|
txt.Brush.Style := bsSolid;
|
|
txt.Brush.Color := FCurrBkColor;
|
|
end else
|
|
txt.Brush.Style := bsClear;
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadWindowExt(const AParams: TParamArray);
|
|
begin
|
|
FWindowExtent.Y := SmallInt(AParams[0]); // signed int
|
|
FWindowExtent.X := SmallInt(AParams[1]);
|
|
CalcScalingFactors(FScalingFactorX, FScalingFactorY);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.ReadWindowOrg(const AParams: TParamArray);
|
|
begin
|
|
FWindowOrigin.Y := SmallInt(AParams[0]); // signed int, probably not relevant here.
|
|
FWindowOrigin.X := SmallInt(AParams[1]);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.CalcScalingFactors(out fx, fy: Double);
|
|
begin
|
|
// Convert to pixels
|
|
case FMapMode of
|
|
MM_TEXT: // 1 log unit = 1 pixel
|
|
begin
|
|
fx := 1.0;
|
|
fy := 1.0;
|
|
end;
|
|
MM_LOMETRIC: // 1 log unit = 1/10 mm
|
|
begin
|
|
fx := 0.1 * MM2INCH * ScreenDpiX;
|
|
fy := 0.1 * MM2INCH * ScreenDpiY;
|
|
end;
|
|
MM_HIMETRIC: // 1 log unit = 1/100 mm
|
|
begin
|
|
fx := 0.01 * MM2INCH * ScreenDpiX;
|
|
fy := 0.01 * MM2INCH * ScreenDpiY;
|
|
end;
|
|
MM_LOENGLISH: // 1 log unit = 1/100"
|
|
begin
|
|
fx := 0.01 * ScreenDpiX;
|
|
fy := 0.01 * ScreenDpiY;
|
|
end;
|
|
MM_HIENGLISH: // 1 log unit = 1/1000"
|
|
begin
|
|
fx := 0.001 * ScreenDpiX;
|
|
fy := 0.001 * ScreenDpiY;
|
|
end;
|
|
MM_TWIPS: // 1 log unit = 1 twip = 1/1440 inch
|
|
begin
|
|
fx := 1.0 / 1440 * INCH2MM;
|
|
fy := fx;
|
|
end;
|
|
else
|
|
if (FWindowExtent.X = 0) or (FWindowExtent.Y = 0) then
|
|
exit;
|
|
if FHasPlaceableMetaHeader then begin
|
|
FPageWidth := (FBBox.Right - FBBox.Left) / FUnitsPerInch * ScreenDpiX;
|
|
FPageHeight := (FBBox.Bottom - FBBox.Top) / FUnitsPerInch * ScreenDpiY;
|
|
end else
|
|
if FWindowExtent.X > FWindowExtent.Y then begin
|
|
FPageWidth := DEFAULT_SIZE * MM2INCH * ScreenDpiX;
|
|
FPageHeight := FPageWidth * FWindowExtent.Y / FWindowExtent.X;
|
|
end else begin
|
|
FPageHeight := DEFAULT_SIZE * MM2INCH * ScreenDpiY;
|
|
FPageWidth := FPageHeight * FWindowExtent.X / FWindowExtent.Y;
|
|
end;
|
|
fx := FPageWidth / FWindowExtent.X;
|
|
fy := FPageHeight / FWindowExtent.Y;
|
|
end;
|
|
|
|
// If required convert to mm
|
|
// The nominal fpv units are mm, but the svg reader converts to pixels.
|
|
if FPV_UNIT = fuMM then begin
|
|
fx := fx / ScreenDpiX * INCH2MM;
|
|
fy := fy / ScreenDpiY * INCH2MM;
|
|
if FMapMode in [MM_ISOTROPIC, MM_ANISOTROPIC] then begin
|
|
FPageWidth := FPageWidth / ScreenDpiX * INCH2MM;
|
|
FPageHeight := FPageHeight / ScreenDpiY * INCH2MM;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ Scale horizontal logical units (x) to millimeters }
|
|
function TvWMFVectorialReader.ScaleX(x: Integer): Double;
|
|
begin
|
|
Result := ScaleSizeX(x - FWindowOrigin.X);
|
|
end;
|
|
|
|
{ Scale vertical logical units (y) to millimeters.
|
|
Coordinates will be increasing downwards, like in SVG }
|
|
function TvWMFVectorialReader.ScaleY(y: Integer): Double;
|
|
begin
|
|
// Result := ScaleSizeY(y - FWindowOrigin.Y); // there is probably an issue with y direction
|
|
|
|
if (vrf_UseBottomLeftCoords in Settings.VecReaderFlags) then
|
|
Result := FPageHeight - ScaleSizeY(y)
|
|
else
|
|
Result := ScaleSizeY(y - FWindowOrigin.Y);
|
|
|
|
// Result := FPageHeight - ScaleSizeY(y);
|
|
end;
|
|
|
|
function TvWMFVectorialReader.ScaleSizeX(x: Integer): Double;
|
|
begin
|
|
Result := FScalingFactorX * x;
|
|
end;
|
|
|
|
function TvWMFVectorialReader.ScaleSizeY(y: Integer): Double;
|
|
begin
|
|
Result := FScalingFactorY * y;
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.SelectObj(const AIndex: Integer);
|
|
var
|
|
obj: TObject;
|
|
begin
|
|
obj := TObject(FObjList[AIndex]);
|
|
if obj = nil then
|
|
exit;
|
|
if obj is TWMFPen then begin
|
|
FCurrPen := TWMFPen(obj).Pen;
|
|
end else
|
|
if obj is TWMFBrush then
|
|
FCurrBrush := TWMFBrush(obj).Brush
|
|
else
|
|
if obj is TWMFFont then begin
|
|
FCurrFont := TWMFFont(obj).Font;
|
|
FCurrRawFontHeight := TWMFFont(obj).RawHeight;
|
|
end else
|
|
if obj is TFPPalette then
|
|
FCurrPalette := TFPPalette(obj);
|
|
end;
|
|
|
|
procedure TvWMFVectorialReader.SelectPalette(const AIndex: Integer);
|
|
begin
|
|
SelectObj(AIndex);
|
|
end;
|
|
|
|
|
|
initialization
|
|
RegisterVectorialReader(TvWMFVectorialReader, vfWindowsMetafileWMF);
|
|
|
|
end.
|
|
|