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

