{ A fpvectorial writer 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 Coordinates: - wmf has y=0 at top, y grows downward (like with standard canvas). - fpv has y=0 at bottom and y grows upwards if page.UseTopLeftCoordinates is false or like wmf otherwise. Issues: - Text background is opaque although it should be transparent. - IrfanView cannot open the files written. - Text positioning incorrect due to positive/negative font heights. Author: Werner Pamler } {.$DEFINE WMF_DEBUG} unit wmfvectorialwriter; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FPImage, FPCanvas, fpvectorial, fpvWMF; type TParamArray = array of word; TWMFObjList = class(TFPList) public function Add(AData: Pointer): Integer; function FindBrush(ABrush: TvBrush): Word; function FindFont(AFont: TvFont): Word; function FindPen(APen: TvPen): Word; end; { TvWMFVectorialWriter } TvWMFVectorialWriter = class(TvCustomVectorialWriter) private // Headers FWMFHeader: TWMFHeader; FPlaceableHeader: TPlaceableMetaHeader; // list for WMF Objects FObjList: TWMFObjList; // FBBox: TRect; // in metafile units as specified by UnitsPerInch. NOTE: "logical" units can be different! FLogicalMaxX: Word; // Max x coordinate used for scaling FLogicalMaxY: Word; // Max y coordinate used for scaling FLogicalBounds: TRect; // Enclosing boundary rectangle in logical units FScalingFactor: Double; // Conversion fpvectorial units to logical units FMaxRecordSize: Int64; FCurrFont: TvFont; FCurrBrush: TvBrush; FCurrPen: TvPen; FCurrTextColor: TFPColor; FCurrTextAnchor: TvTextAnchor; FCurrBkMode: Word; FCurrPolyFillMode: Word; FUseTopLeftCoordinates: Boolean; FErrMsg: TStrings; function CalcChecksum: Word; procedure ClearObjList; function MakeWMFColorRecord(AColor: TFPColor): TWMFColorRecord; procedure PrepareScaling(APage: TvVectorialPage); function ScaleX(x: Double): Integer; function ScaleY(y: Double): Integer; function ScaleSizeX(x: Double): Integer; function ScaleSizeY(y: Double): Integer; procedure UpdateBounds(x, y: Integer); procedure WriteBkColor(AStream: TStream; APage: TvVectorialPage); procedure WriteBkMode(AStream: TStream; AMode: Word); procedure WriteBrush(AStream: TStream; ABrush: TvBrush); procedure WriteCircle(AStream: TStream; ACircle: TvCircle); procedure WriteEllipse(AStream: TStream; AEllipse: TvEllipse); procedure WriteEOF(AStream: TStream); procedure WriteExtText(AStream: TStream; AText: TvText); procedure WriteFont(AStream: TStream; AFont: TvFont); procedure WriteLayer(AStream: TStream; ALayer: TvLayer); procedure WriteMapMode(AStream: TStream); procedure WritePageEntities(AStream: TStream; APage: TvVectorialPage); procedure WritePath(AStream: TStream; APath: TPath); procedure WritePen(AStream: TStream; APen: TvPen); procedure WritePolyFillMode(AStream: TStream; AValue: Word); procedure WritePolygon(AStream: TStream; APolygon: TvPolygon); procedure WriteRectangle(AStream: TStream; ARectangle: TvRectangle); procedure WriteText(AStream: TStream; AText: TvText); procedure WriteTextAlign(AStream: TStream; AAlign: Word); procedure WriteTextAnchor(AStream: TStream; AAnchor: TvTextAnchor); procedure WriteTextColor(AStream: TStream; AColor: TFPColor); procedure WriteWindowExt(AStream: TStream); procedure WriteWindowOrg(AStream: TStream); procedure WriteEntity(AStream: TStream; AEntity: TvEntity); procedure WriteWMFRecord(AStream: TStream; AFunc: word; ASize: Int64); overload; procedure WriteWMFRecord(AStream: TStream; AFunc: Word; const AParams; ASize: Int64); procedure WriteWMFParams(AStream: TStream; const AParams; ASize: Int64); protected procedure WritePage(AStream: TStream; AData: TvVectorialDocument; APage: TvVectorialPage); procedure LogError(AMsg: String); public constructor Create; override; destructor Destroy; override; procedure WriteToStream(AStream: TStream; AData: TvVectorialDocument); override; end; var // Settings gWMFVecReader_UseTopLeftCoords: Boolean = True; implementation uses Types, LazUTF8, LConvEncoding, Math, fpvUtils; const ONE_INCH = 25.4; // 1 inch = 25.4 mm DEFAULT_SIZE = 100; // size of image if scaling info is not available SIZE_OF_WORD = 2; type TWMFFont = class Font: TvFont; end; TWMFBrush = class Brush: TvBrush; end; TWMFPen = class Pen: TvPen; end; TWMFPalette = 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; function SameBrush(ABrush1, ABrush2: TvBrush): Boolean; begin Result := (ABrush1.Color.Red = ABrush2.Color.Red) and (ABrush1.Color.Green = ABrush2.Color.Green) and (ABrush1.Color.Blue = ABrush2.Color.Blue) and (ABrush1.Style = ABrush2.Style); end; function SameFont(AFont1, AFont2: TvFont): Boolean; const EPS = 1E-3; begin Result := {(AFont1.Color.Red = AFont2.Color.Red) and (AFont1.Color.Green = AFont2.Color.Green) and (AFont1.Color.Blue = AFont2.Color.Blue) and } (AFont1.Size = AFont2.Size) and (UTF8Lowercase(AFont1.Name) = UTF8Lowercase(AFont2.Name)) and SameValue(AFont1.Orientation, AFont2.Orientation, EPS) and (AFont1.Bold = AFont2.Bold) and (AFont1.Italic = AFont2.Italic) and (AFont1.Underline = AFont2.Underline) and (AFont1.StrikeThrough = AFont2.StrikeThrough); end; function SamePen(APen1, APen2: TvPen): Boolean; var i: Integer; begin Result := (APen1.Color.Red = APen2.Color.Red) and (APen1.Color.Green = APen2.Color.Green) and (APen1.Color.Blue = APen2.Color.Blue) and (APen1.Style = APen2.Style) and (APen1.Width = APen2.Width) and (High(APen1.Pattern) = High(APen2.Pattern)); if Result then for i:=0 to Length(APen1.Pattern) - 1 do if APen1.Pattern[i] <> APen2.Pattern[i] then begin Result := false; exit; end; end; function SamePoint(P1, P2: TWMFPointXYRecord): Boolean; begin Result := (P1.X = P2.X) and (P1.Y = P2.Y); 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; function TWMFObjList.FindBrush(ABrush: TvBrush): Word; var i: Integer; begin for i:=0 to Count-1 do if (TObject(Items[i]) is TWMFBrush) and SameBrush(ABrush, TWMFBrush(Items[i]).Brush) then begin Result := i; exit; end; Result := Word(-1); end; function TWMFObjList.FindFont(AFont: TvFont): Word; var i: Integer; begin for i:=0 to Count-1 do if (TObject(Items[i]) is TWMFFont) and SameFont(AFont, TWMFFont(Items[i]).Font) then begin Result := i; exit; end; Result := Word(-1); end; function TWMFObjList.FindPen(APen: TvPen): Word; var i: Integer; begin for i:=0 to Count-1 do if (TObject(Items[i]) is TWMFPen) and SamePen(APen, TWMFPen(Items[i]).Pen) then begin Result := i; exit; end; Result := Word(-1); end; { TvWMFVectorialWriter } constructor TvWMFVectorialWriter.Create; begin inherited; FErrMsg := TStringList.Create; FObjList := TWMFObjList.Create; FCurrTextColor := colBlack; FCurrTextAnchor := vtaStart; with FCurrPen do begin Style := TFPPenStyle(-1); Color := colBlack; Width := -1; end; with FCurrBrush do begin Style := TFPBrushStyle(-1); Color := colBlack; end; with FCurrFont do begin Color := colBlack; Size := -1; Name := ''; Orientation := 0; Bold := false; Italic := False; Underline := false; StrikeThrough := false; end; end; destructor TvWMFVectorialWriter.Destroy; begin ClearObjList; FObjList.Free; FErrMsg.Free; inherited; end; { Calculate the checksum of the PlaceableHeader (without the Checksum field) } function TvWMFVectorialWriter.CalcChecksum: Word; var P: ^word; n: Integer; begin Result := 0; P := @FPlaceableHeader; n := 0; while n < SizeOf(FPlaceableHeader) do begin Result := Result xor P^; inc(P); inc(n, SIZE_OF_WORD); end; end; procedure TvWMFVectorialWriter.ClearObjList; var i: Integer; begin for i:=0 to FObjList.Count-1 do TObject(FObjList[i]).Free; FObjList.Clear; end; procedure TvWMFVectorialWriter.LogError(AMsg: String); begin FErrMsg.Add(AMsg); end; function TvWMFVectorialWriter.MakeWMFColorRecord(AColor: TFPColor): TWMFColorRecord; begin Result.ColorRED := AColor.Red shr 8; Result.ColorGREEN := AColor.Green shr 8; Result.ColorBLUE := AColor.Blue shr 8; Result.Reserved := 0; end; procedure TvWMFVectorialWriter.PrepareScaling(APage: TvVectorialPage); begin FScalingFactor := round(ONE_INCH * 100); // 1 logical unit is 1/100 mm = 10 µm FLogicalMaxX := trunc(APage.Width * FScalingFactor); FLogicalMaxY := trunc(APage.Height * FScalingFactor); // wmf is 16 bit only! --> reduce magnification if numbers get too big if Max(FLogicalMaxX, FLogicalMaxY) > $7FFF then begin FScalingFactor := trunc($7FFF / Max(APage.Width, APage.Height)); FLogicalMaxX := trunc(APage.Width * FScalingFactor); FLogicalMaxY := trunc(APage.Height * FScalingFactor); end; end; function TvWMFVectorialWriter.ScaleSizeX(x: Double): Integer; begin Result := Round(x * FScalingFactor); end; function TvWMFVectorialWriter.ScaleSizeY(y: Double): Integer; begin Result := Round(y * FScalingFactor); end; function TvWMFVectorialWriter.ScaleX(x: Double): Integer; begin Result := ScaleSizeX(x); end; function TvWMFVectorialWriter.ScaleY(y: Double): Integer; begin if FUseTopLeftCoordinates then Result := ScaleSizeY(y) else Result := FLogicalMaxY - ScaleSizeY(y); end; procedure TvWMFVectorialWriter.UpdateBounds(x, y: Integer); begin FLogicalBounds.Left := Min(X, FLogicalBounds.Left); FLogicalBounds.Top := Min(Y, FLogicalBounds.Top); FLogicalBounds.Right := Max(X, FLogicalBounds.Right); FLogicalBounds.Bottom := Max(Y, FLogicalBounds.Bottom); end; procedure TvWMFVectorialWriter.WriteBkColor(AStream: TStream; APage: TvVectorialPage); var rec: TWMFColorRecord; begin rec := MakeWMFColorRecord(APage.BackgroundColor); WriteWMFRecord(AStream, META_SETBKCOLOR, rec, SizeOf(rec)); end; procedure TvWMFVectorialWriter.WriteBkMode(AStream: TStream; AMode: Word); var mode: DWord; begin FCurrBkMode := AMode; if AMode in [BM_TRANSPARENT, BM_OPAQUE] then begin mode := AMode; WriteWMFRecord(AStream, META_SETBKMODE, mode, SizeOf(mode)); end; end; procedure TvWMFVectorialWriter.WriteBrush(AStream: TStream; ABrush: TvBrush); var rec: TWMFBrushRecord; idx: Word; wmfbrush: TWMFBrush; brushstyle: TFPBrushStyle; begin if SameBrush(ABrush, FCurrBrush) then exit; idx := FObjList.FindBrush(ABrush); if idx = Word(-1) then begin // No gradient support by wmf --> use a clear brush instead. if ABrush.Kind <> bkSimpleBrush then brushstyle := bsClear else brushstyle := ABrush.Style; case brushstyle of bsClear : rec.Style := BS_NULL; bsSolid : rec.Style := BS_SOLID; bsHorizontal : begin rec.Style := BS_HATCHED; rec.Hatch := HS_HORIZONTAL; end; bsVertical : begin rec.Style := BS_HATCHED; rec.Hatch := HS_VERTICAL; end; bsFDiagonal : begin rec.Style := BS_HATCHED; rec.Hatch := HS_FDIAGONAL; end; bsBDiagonal : begin rec.Style := BS_HATCHED; rec.Hatch := HS_BDIAGONAL; end; bsCross : begin rec.Style := BS_HATCHED; rec.Hatch := HS_CROSS; end; bsDiagCross : begin rec.Style := BS_HATCHED; rec.Hatch := HS_DIAGCROSS; end; { not supported BS_PATTERN = $0003; BS_INDEXED = $0004; BS_DIBPATTERN = $0005; BS_DIBPATTERNPT = $0006; BS_PATTERN8X8 = $0007; BS_DIBPATTERN8X8 = $0008; BS_MONOPATTERN = $0009; } else rec.Style := BS_SOLID; end; rec.ColorRED := ABrush.Color.Red shr 8; rec.ColorGREEN := ABrush.Color.Green shr 8; rec.ColorBLUE := ABrush.Color.Blue shr 8; rec.Reserved := 0; wmfBrush := TWMFBrush.Create; wmfBrush.Brush := ABrush; idx := FObjList.Add(wmfBrush); WriteWMFRecord(AStream, META_CREATEBRUSHINDIRECT, rec, SizeOf(rec)); end; WriteWMFRecord(AStream, META_SELECTOBJECT, idx, SizeOf(idx)); FCurrBrush := ABrush; end; procedure TvWMFVectorialWriter.WriteCircle(AStream: TStream; ACircle: TvCircle); var rec: TWMFRectRecord; c, r: TPoint; begin WritePen(AStream, ACircle.Pen); WriteBrush(AStream, ACircle.Brush); c.x := ScaleX(ACircle.X); c.y := ScaleY(ACircle.Y); r.x := ScaleSizeX(ACircle.Radius); r.y := ScaleSizeY(ACircle.Radius); rec.Left := c.x - r.x; rec.Right := c.x + r.x; if FUseTopLeftCoordinates then begin rec.Top := c.y - r.y; rec.Bottom := c.y + r.y; end else begin rec.Top := c.y + r.y; reC.Bottom := c.y - r.y; end; UpdateBounds(rec.Left, rec.Top); UpdateBounds(rec.Right, rec.Bottom); // WMF record header + parameters WriteWMFRecord(AStream, META_ELLIPSE, rec, SizeOf(TWMFRectRecord)); end; procedure TvWMFVectorialWriter.WriteEllipse(AStream: TStream; AEllipse: TvEllipse); var r: TWMFRectRecord; begin WritePen(AStream, AEllipse.Pen); WriteBrush(AStream, AEllipse.Brush); r.Left := ScaleX(AEllipse.X - AEllipse.HorzHalfAxis); r.Top := ScaleY(AEllipse.Y + AEllipse.VertHalfAxis); r.Right := ScaleX(AEllipse.X + AEllipse.HorzHalfAxis); r.Bottom := ScaleY(AEllipse.Y - AEllipse.VertHalfAxis); UpdateBounds(r.Left, r.Top); UpdateBounds(r.Right, r.Bottom); // WMF record header + parameters WriteWMFRecord(AStream, META_ELLIPSE, r, SizeOf(TWMFRectRecord)); end; procedure TvWMFVectorialWriter.WriteEntity(AStream: TStream; AEntity: TvEntity); begin if AEntity is TvPolygon then WritePolygon(AStream, TvPolygon(AEntity)) else if AEntity is TvRectangle then WriteRectangle(AStream, TvRectangle(AEntity)) else if AEntity is TvCircle then WriteCircle(AStream, TvCircle(AEntity)) else if AEntity is TvEllipse then WriteEllipse(AStream, TvEllipse(AEntity)) else if AEntity is TvText then WriteText(AStream, TvText(AEntity)) else if AEntity is TPath then WritePath(AStream, TPath(AEntity)); end; procedure TvWMFVectorialWriter.WriteEOF(AStream: TStream); begin WriteWMFRecord(AStream, META_EOF, 0); end; procedure TvWMFVectorialWriter.WriteExtText(AStream: TStream; AText: TvText); var s: String; rec: TWMFExtTextRecord; i, n: Integer; P: TPoint; offs: TPoint; brush: TvBrush; begin brush := AText.Brush; if (brush.Style = bsClear) and (FCurrBkMode = BM_OPAQUE) then WriteBkMode(AStream, BM_TRANSPARENT) else begin if FCurrBkMode = BM_TRANSPARENT then WriteBkMode(AStream, BM_OPAQUE); WriteBrush(AStream, AText.Brush); end; WriteFont(AStream, AText.Font); if (AText.TextAnchor <> FCurrTextAnchor) then WriteTextAnchor(AStream, AText.TextAnchor); s := UTF8ToISO_8859_1(AText.Value.Text); n := SizeOf(TWMFExtTextRecord) + Length(s); if odd(n) then begin inc(n); s := s + #0; end; rec.X := ScaleX(AText.X); rec.Y := ScaleY(AText.Y); // No vertical offset required because text alignment is TA_BASELINE. rec.Options := 0; // no clipping, no background rec.Len := UTF8Length(s); WriteWMFRecord(AStream, META_EXTTEXTOUT, rec, n); AStream.Position := AStream.Position - Length(s); WriteWMFParams(AStream, s[1], Length(s)); end; procedure TvWMFVectorialWriter.WriteFont(AStream: TStream; AFont: TvFont); var rec: TWMFFontRecord; idx: Word; wmfFont: TWMFFont; fntName: String; i, n: Integer; begin if (AFont.Color.Red <> FCurrTextColor.Red) or (AFont.Color.Green <> FCurrTextColor.Green) or (AFont.Color.Blue <> FCurrTextColor.Blue) then WriteTextColor(AStream, AFont.Color); if SameFont(AFont, FCurrFont) then exit; idx := FObjList.FindFont(AFont); if idx = Word(-1) then begin fntName := UTF8ToISO_8859_1(AFont.Name) + #0; if odd(UTF8Length(fntName)) then fntName := fntName + #0; if Length(fntName) > 32 then begin Delete(fntName, 31, MaxInt); fntName := fntName + #0; end; n := SizeOf(TWMFFontRecord) + Length(fntName); rec.Height := ScaleSizeY(AFont.Size); rec.Width := 0; rec.Orientation := round(AFont.Orientation * 10); rec.Escapement := round(AFont.Orientation * 10); // 0; // strange: must use "Escapement" here, not "Orientation". // Otherwise MS software will not show the rotated font. rec.Weight := IfThen(AFont.Bold, 700, 400); rec.Italic := IfThen(AFont.Italic, 1, 0); rec.Underline := IfThen(AFont.Underline, 1, 0); rec.Strikeout := IfThen(AFont.StrikeThrough, 1, 0); rec.Charset := DEFAULT_CHARSET; rec.OutPrecision := 0; // default rec.ClipPrecision := 0; // default rec.Quality := 0; // default rec.PitchAndFamily := 0; // don't care / default WriteWMFRecord(AStream, META_CREATEFONTINDIRECT, rec, n); AStream.Position := AStream.Position - Length(fntName); WriteWMFParams(AStream, fntName[1], Length(fntName)); wmfFont := TWMFFont.Create; wmfFont.Font := AFont; idx := FObjList.Add(wmfFont); end; WriteWMFRecord(AStream, META_SELECTOBJECT, idx, SizeOf(idx)); FCurrFont := AFont; end; procedure TvWMFVectorialWriter.WriteLayer(AStream: TStream; ALayer: TvLayer); var entity: TvEntity; i: Integer; begin for i := 0 to ALayer.GetEntitiesCount - 1 do begin entity := ALayer.GetEntity(i); WriteEntity(AStream, entity); end; end; procedure TvWMFVectorialWriter.WriteMapMode(AStream: TStream); var mode: Word; begin mode := MM_ANISOTROPIC; WriteWMFRecord(AStream, META_SETMAPMODE, mode, SizeOf(mode)); end; procedure TvWMFVectorialWriter.WritePage(AStream: TStream; AData: TvVectorialDocument; APage: TvVectorialPage); begin WriteWindowExt(AStream); WriteWindowOrg(AStream); WriteMapMode(AStream); WriteBkColor(AStream, APage); WriteBkMode(AStream, BM_TRANSPARENT); WriteTextAlign(AStream, TA_BASELINE or TA_LEFT); WritePageEntities(AStream, APage); WriteEOF(AStream); end; procedure TvWMFVectorialWriter.WritePageEntities(AStream: TStream; APage: TvVectorialPage); var entity: TvEntity; i: Integer; begin for i := 0 to APage.GetEntitiesCount - 1 do begin entity := APage.GetEntity(i); WriteEntity(AStream, entity); end; end; procedure TvWMFVectorialWriter.WritePath(AStream: TStream; APath: TPath); var points: TPointsArray; // array of TPoint pts: array of TWMFPointXYRecord; polystarts: TIntegerDynArray; allclosed: boolean; isClosed: Boolean; i, len: Word; first, last: Integer; p, npoly, npts: Integer; begin WritePen(AStream, APath.Pen); WriteBrush(AStream, APath.Brush); ConvertPathToPolygons(APath, 0, 0, FScalingFactor, FScalingFactor, points, polystarts); SetLength(pts, Length(points)); for i:=0 to High(points) do begin pts[i].X := points[i].X; if FUseTopLeftCoordinates then pts[i].Y := points[i].Y else pts[i].Y := FLogicalMaxY - points[i].Y; end; allClosed := true; p := 0; while p < Length(polystarts) do begin first := polystarts[p]; last := IfThen(p = High(polystarts), High(pts), polystarts[p+1]-1); isClosed := SamePoint(pts[first], pts[last]); if not isClosed then begin allClosed := false; break; end; inc(p); end; npoly := Length(polystarts); if allClosed and (Length(polystarts) > 1) then begin // "POLY-POLYGON" WriteWMFRecord(AStream, META_POLYPOLYGON, // Prepare memory for ... SIZE_OF_WORD + // ... polygon count Length(polystarts) * SIZE_OF_WORD + // ... point count per polygon Length(pts) * SizeOf(TWMFPointXYRecord) // ... points ); // Write polgon count WriteWMFParams(AStream, npoly, SIZE_OF_WORD); // Write number of points per polygon p := 0; while p < Length(polystarts) do begin first := polystarts[p]; last := IfThen(p = High(polystarts), High(pts), polystarts[p+1]-1); npts := last - first + 1; WriteWMFParams(AStream, npts, SIZE_OF_WORD); inc(p); end; // Write points of each polygon p := 0; while p < Length(polystarts) do begin first := polystarts[p]; last := IfThen(p = High(polystarts), High(pts), polystarts[p+1]-1); npts := last - first + 1; WriteWMFParams(AStream, pts[first], npts*SizeOf(TWMFPointXYRecord)); inc(p); end; end else begin p := 0; while p < Length(polystarts) do begin first := polystarts[p]; last := IfThen(p = High(polystarts), High(pts), polystarts[p+1]-1); len := last - first + 1; isClosed := SamePoint(pts[first], pts[last]); if isClosed and (APath.Brush.Kind = bkSimpleBrush) and (APath.Brush.Style <> bsClear) then WriteWMFRecord(AStream, META_POLYGON, SIZE_OF_WORD + len * SizeOf(TWMFPointXYRecord)) else WriteWMFRecord(AStream, META_POLYLINE, SIZE_OF_WORD + len * SizeOf(TWMFPointXYRecord)); WriteWMFParams(AStream, len, SIZE_OF_WORD); WriteWMFParams(AStream, pts[first], len * SizeOf(TWMFPointXYRecord)); inc(p); end; end; end; procedure TvWMFVectorialWriter.WritePen(AStream: TStream; APen: TvPen); var rec: TWMFPenRecord; idx: Word; wmfpen: TWMFPen; begin if SamePen(APen, FCurrPen) then exit; idx := FObjList.FindPen(APen); if idx = Word(-1) then begin case APen.Style of psDash : rec.Style := PS_DASH; psDot : rec.Style := PS_DOT; psDashDot : rec.Style := PS_DASHDOT; psDashDotDot : rec.Style := PS_DASHDOTDOT; psClear : rec.Style := PS_NULL; psInsideFrame: rec.Style := PS_INSIDEFRAME; else rec.Style := PS_SOLID; end; rec.Width := ScaleSizeX(APen.Width); rec.Ignored1 := 0; rec.ColorRED := APen.Color.Red shr 8; rec.ColorGREEN := APen.Color.Green shr 8; rec.ColorBLUE := APen.Color.Blue shr 8; rec.Ignored2 := 0; wmfPen := TWMFPen.Create; wmfPen.Pen := APen; idx := FObjList.Add(wmfPen); WriteWMFRecord(AStream, META_CREATEPENINDIRECT, rec, SizeOf(rec)); end; WriteWMFRecord(AStream, META_SELECTOBJECT, idx, SizeOf(idx)); FCurrPen := APen; end; procedure TvWMFVectorialWriter.WritePolyFillMode(AStream: TStream; AValue: Word); begin WriteWMFRecord(AStream, META_SETPOLYFILLMODE, AValue, SizeOf(AValue)); end; procedure TvWMFVectorialWriter.WritePolygon(AStream: TStream; APolygon: TvPolygon); var pts: array of TWMFPointXYRecord; i: Integer; w: Word; begin case APolygon.WindingRule of vcmEvenOddRule : WritePolyFillMode(AStream, ALTERNATE); vcmNonzeroWindingRule : WritePolyFillMode(AStream, WINDING); end; WritePen(AStream, APolygon.Pen); WriteBrush(AStream, APolygon.Brush); SetLength(pts, Length(APolygon.Points)); for i:=0 to High(APolygon.Points) do begin pts[i].X := ScaleX(APolygon.Points[i].X); pts[i].Y := ScaleY(APolygon.Points[i].Y); UpdateBounds(pts[i].X, pts[i].Y); end; // WMF Record header if (APolygon.Brush.Kind = bkSimpleBrush) and (APolygon.Brush.Style = bsClear) then WriteWMFRecord(AStream, META_POLYLINE, Length(pts) * SizeOf(TWMFPointXYRecord) + SIZE_OF_WORD) else WriteWMFRecord(AStream, META_POLYGON, Length(pts) * SizeOf(TWMFPointXYRecord) + SIZE_OF_WORD); // Number of points in polygon w := Length(APolygon.Points); WriteWMFParams(AStream, w, SIZE_OF_WORD); // Polygon points WriteWMFParams(AStream, pts[0], Length(pts) * SizeOf(TWMFPointXYRecord)); end; procedure TvWMFVectorialWriter.WriteRectangle(AStream: TStream; ARectangle: TvRectangle); var r: TWMFRectRecord; p: TWMFPointRecord; begin WritePen(AStream, ARectangle.Pen); WriteBrush(AStream, ARectangle.Brush); r.Left := ScaleX(ARectangle.X); r.Top := ScaleY(ARectangle.Y); r.Right := ScaleX(ARectangle.X + ARectangle.CX); r.Bottom := ScaleY(ARectangle.Y - ARectangle.CY); UpdateBounds(r.Left, r.Top); UpdateBounds(r.Right, r.Bottom); // WMF record header + parameters if (ARectangle.RX = 0) or (ARectangle.RY = 0) then // "normal" rectangle WriteWMFRecord(AStream, META_RECTANGLE, r, SizeOf(TWMFRectRecord)) else begin // rounded rectangle p.X := ScaleSizeX(ARectangle.RX); p.Y := ScaleSizeY(ARectangle.RY); WriteWMFRecord(AStream, META_ROUNDRECT, SizeOf(p) + SizeOf(r)); WriteWMFParams(AStream, p, SizeOf(p)); WriteWMFParams(AStream, r, SizeOf(r)); end; end; procedure TvWMFVectorialWriter.WriteText(AStream: TStream; AText: TvText); var s: String; n: Integer; len: SmallInt; rec: TWMFPointRecord; offs: TPoint; P: TPoint; brush: TvBrush; begin brush := AText.Brush; if (brush.Style = bsClear) and (FCurrBkMode = BM_OPAQUE) then WriteBkMode(AStream, BM_TRANSPARENT) else begin if FCurrBkMode = BM_TRANSPARENT then WriteBkMode(AStream, BM_OPAQUE); WriteBrush(AStream, AText.Brush); end; WriteFont(AStream, AText.Font); if (AText.TextAnchor <> FCurrTextAnchor) then WriteTextAnchor(AStream, AText.TextAnchor); s := UTF8ToISO_8859_1(AText.Value.Text); len := Length(s); if odd(len) then begin s := s + #0; inc(len); end; rec.X := ScaleX(AText.X); rec.Y := ScaleY(AText.Y); // No vertical font height offset required because text alignment is TA_BASELINE { The record structure is - TWMFRecord - Stringlength (SmallInt) - String, no trailing zero - y - x } WriteWMFRecord(AStream, META_TEXTOUT, SizeOf(len) + len + SizeOf(TWMFPointRecord)); WriteWMFParams(AStream, len, SizeOf(len)); WriteWMFParams(AStream, s[1], Length(s)); WriteWMFParams(AStream, rec, SizeOf(rec)); end; procedure TvWMFVectorialWriter.WriteTextAlign(AStream: TStream; AAlign: word); begin WriteWMFRecord(AStream, META_SETTEXTALIGN, AAlign, SizeOf(AAlign)); end; procedure TvWMFVectorialWriter.WriteTextAnchor(AStream: TStream; AAnchor: TvTextAnchor); var align: DWord; begin case AAnchor of vtaStart : align := TA_LEFT; vtaMiddle : align := TA_CENTER; vtaEnd : align := TA_RIGHT; end; align := align or TA_BASELINE; WriteTextAlign(AStream, align); FCurrTextAnchor := AAnchor; end; procedure TvWMFVectorialWriter.WriteTextColor(AStream: TStream; AColor: TFPColor); var rec: TWMFColorRecord; begin rec := MakeWMFColorRecord(AColor); WriteWMFRecord(AStream, META_SETTEXTCOLOR, rec, SizeOf(rec)); FCurrTextColor := AColor; end; procedure TvWMFVectorialWriter.WriteToStream(AStream: TStream; AData: TvVectorialDocument); const PAGE_INDEX = 0; var page: TvVectorialPage; begin // Initialize ClearObjList; FErrMsg.Clear; FWMFHeader.MaxRecordSize := 0; FBBox := Rect(0, 0, 0, 0); page := AData.GetPageAsVectorial(PAGE_INDEX); FUseTopLeftCoordinates := page.UseTopLeftCoordinates; // Prepare scaling PrepareScaling(page); FLogicalBounds := Rect(LongInt($7FFFFFFF), LongInt($7FFFFFFF), LongInt($80000000), LongInt($80000000)); // Write placeholder for WMF header and placeable header, // will be rewritten with correct values later AStream.Write(FWMFHeader, SizeOf(TWMFHeader)); AStream.Write(FPlaceableHeader, SizeOf(TPlaceableMetaHeader)); // Write the specified page of the document WritePage(AStream, AData, page); // Go back to the beginning of the file and write the headers. Use correct // header fields now. with FPlaceableHeader do begin Key := WMF_MAGIC_NUMBER; Handle := 0; Reserved := 0; Inch := ScaleX(ONE_INCH); Left := 0; Top := 0; Right := ScaleSizeX(page.Width); Bottom := ScaleSizeX(page.Height); Checksum := CalcChecksum; end; AStream.Position := 0; AStream.WriteBuffer(FPlaceableHeader, SizeOf(TPlaceableMetaHeader)); with FWMFHeader do begin FileType := 1; HeaderSize := 9; Version := $0300; NumOfObjects := FObjList.Count; MaxRecordSize := FMaxRecordSize; FileSize := AStream.Size div SIZE_OF_WORD; NumOfParams := 0; end; AStream.WriteBuffer(FWMFHeader, SizeOf(TWMFHeader)); if FErrMsg.Count > 0 then raise Exception.Create(FErrMsg.Text); end; procedure TvWMFVectorialWriter.WriteWindowExt(AStream: TStream); var params: Array[0..1] of word; begin params[0] := FLogicalMaxY; params[1] := FLogicalMaxX; WriteWMFRecord(AStream, META_SETWINDOWEXT, params, SizeOf(params)); end; procedure TvWMFVectorialWriter.WriteWindowOrg(AStream: TStream); var params: Array[0..1] of word; begin params[0] := 0; params[1] := 0; WriteWMFRecord(AStream, META_SETWINDOWORG, params, Sizeof(params)); end; { ASize is in bytes } procedure TvWMFVectorialWriter.WriteWMFRecord(AStream: TStream; AFunc: Word; ASize: Int64); var rec: TWMFRecord; begin rec.Size := (SizeOf(TWMFRecord) + ASize) div SIZE_OF_WORD; rec.Func := AFunc; AStream.WriteBuffer(rec, SizeOf(TWMFRecord)); FMaxRecordSize := Max(FMaxRecordSize, rec.Size); end; { ASize is the size of the parameter part, in bytes } procedure TvWMFVectorialWriter.WriteWMFRecord(AStream: TStream; AFunc: Word; const AParams; ASize: Int64); var rec: TWMFRecord; begin rec.Size := (SizeOf(TWMFRecord) + ASize) div SIZE_OF_WORD; rec.Func := AFunc; AStream.WriteBuffer(rec, SizeOf(TWMFRecord)); AStream.WriteBuffer(AParams, ASize); end; { ASize is in bytes } procedure TvWMFVectorialWriter.WriteWMFParams(AStream: TStream; const AParams; ASize: Int64); begin AStream.WriteBuffer(AParams, ASize); end; initialization RegisterVectorialWriter(TvWMFVectorialWriter, vfWindowsMetafileWMF); end.