Skip to content

Commit

Permalink
Move handlers to separate classes
Browse files Browse the repository at this point in the history
  • Loading branch information
KeterSCP committed Dec 15, 2023
1 parent 911e1c7 commit 2104b29
Show file tree
Hide file tree
Showing 11 changed files with 370 additions and 264 deletions.
282 changes: 19 additions & 263 deletions src/SharpEmf.Svg/EmfSvgWriter.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using System.Text;
using SharpEmf.Enums;
using SharpEmf.Records.Control.Header;
using SharpEmf.Records.Drawing;
using SharpEmf.Records.ObjectCreation;
using SharpEmf.Records.ObjectManipulation;
using SharpEmf.Records.PathBracket;
using SharpEmf.Records.State;
using SharpEmf.Svg.RecordsProcessing;

namespace SharpEmf.Svg;

Expand All @@ -16,62 +15,62 @@ public static string ConvertToSvg(EnhancedMetafile emf)
var sb = new StringBuilder();
var state = new EmfState();

HandleHeaderRecord(sb, state, emf.Header);
EmfControlRecordsHandlers.HandleHeaderRecord(sb, state, emf.Header);

foreach (var record in emf.Records)
{
switch (record)
{
case EmrSetMapMode setMapMode:
HandleSetMapModeRecord(state, setMapMode);
EmfStateRecordsHandlers.HandleSetMapModeRecord(state, setMapMode);
break;
case EmrSetBkMode setBkMode:
HandleSetBkModeRecord(state, setBkMode);
EmfStateRecordsHandlers.HandleSetBkModeRecord(state, setBkMode);
break;
case EmrSetWindowOrgEx setWindowOrgEx:
HandleSetWindowOrgExRecord(state, setWindowOrgEx);
EmfStateRecordsHandlers.HandleSetWindowOrgExRecord(state, setWindowOrgEx);
break;
case EmrSetViewportOrgEx setViewportOrgEx:
HandleSetViewportOrgExRecord(state, setViewportOrgEx);
EmfStateRecordsHandlers.HandleSetViewportOrgExRecord(state, setViewportOrgEx);
break;
case EmrSetWindowExtEx setWindowExtEx:
HandleSetWindowExtExRecord(state, setWindowExtEx);
EmfStateRecordsHandlers.HandleSetWindowExtExRecord(state, setWindowExtEx);
break;
case EmrSetViewportExtEx setViewportExtEx:
HandleSetViewportExtExRecord(state, setViewportExtEx);
EmfStateRecordsHandlers.HandleSetViewportExtExRecord(state, setViewportExtEx);
break;
case EmrSetPolyfillMode setPolyfillMode:
HandleSetPolyfillMode(state, setPolyfillMode);
EmfStateRecordsHandlers.HandleSetPolyfillMode(state, setPolyfillMode);
break;
case EmrCreateBrushIndirect createBrushIndirect:
HandleCreateBrushIndirect(state, createBrushIndirect);
EmfObjectCreationRecordsHandlers.HandleCreateBrushIndirect(state, createBrushIndirect);
break;
case EmrSelectObject selectObject:
HandleSelectObject(state, selectObject);
EmfObjectManipulationRecordsHandlers.HandleSelectObject(state, selectObject);
break;
case EmrDeleteObject deleteObject:
HandleDeleteObject(state, deleteObject);
EmfObjectManipulationRecordsHandlers.HandleDeleteObject(state, deleteObject);
break;
case EmrExtCreatePen extCreatePen:
HandleExtCreatePen(state, extCreatePen);
EmfObjectCreationRecordsHandlers.HandleExtCreatePen(state, extCreatePen);
break;
case EmrPolyPolygon16 polyPolygon16:
HandlePolyPolygon16(sb, state, polyPolygon16);
EmfDrawingRecordsHandlers.HandlePolyPolygon16(sb, state, polyPolygon16);
break;
case EmrBeginPath:
HandleBeginPath(sb, state);
EmfPathBracketRecordsHandlers.HandleBeginPath(sb, state);
break;
case EmrEndPath:
HandleEndPath(sb, state);
EmfPathBracketRecordsHandlers.HandleEndPath(sb, state);
break;
case EmrMoveToEx moveToEx:
HandleMoveToEx(sb, state, moveToEx);
EmfStateRecordsHandlers.HandleMoveToEx(sb, state, moveToEx);
break;
case EmrPolyBezierTo16 polyBezierTo16:
HandlePolybezierTo16(sb, state, polyBezierTo16);
EmfDrawingRecordsHandlers.HandlePolybezierTo16(sb, state, polyBezierTo16);
break;
case EmrCloseFigure:
sb.Append("Z ");
EmfPathBracketRecordsHandlers.HandleCloseFigure(sb);
break;
}
}
Expand All @@ -80,247 +79,4 @@ public static string ConvertToSvg(EnhancedMetafile emf)
sb.AppendLine("</svg>");
return sb.ToString();
}

private static void HandleHeaderRecord(StringBuilder svgSb, EmfState state, EmfMetafileHeader header)
{
var width = header.Bounds.Right - header.Bounds.Left;
var height = header.Bounds.Bottom - header.Bounds.Top;
var gTransform = $"translate({-header.Bounds.Left},{-header.Bounds.Top})";

state.Scaling = width / MathF.Abs(header.Bounds.Right - header.Bounds.Left);

// TODO: +1 is a hack to make the object table start at index 1
state.ObjectTable = new GraphicsObject[header.Handles + 1];

svgSb.AppendLine(
$"""
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="{width + 1}" height="{height + 1}">
<g transform="{gTransform}">
""");
}

private static void HandleSetMapModeRecord(EmfState state, EmrSetMapMode setMapMode)
{
state.MapMode = setMapMode.MapMode;
}

private static void HandleSetBkModeRecord(EmfState state, EmrSetBkMode setBkMode)
{
state.CurrentPlaybackDeviceContext.BkMode = setBkMode.BackgroundMode;
}

private static void HandleSetWindowOrgExRecord(EmfState state, EmrSetWindowOrgEx setWindowOrgEx)
{
state.WindowOrigin = setWindowOrgEx.Origin;
}

private static void HandleSetViewportOrgExRecord(EmfState state, EmrSetViewportOrgEx setViewportOrgEx)
{
state.ViewportOrigin = setViewportOrgEx.Origin;
}

private static void HandleSetWindowExtExRecord(EmfState state, EmrSetWindowExtEx setWindowExtEx)
{
state.WindowExtent = setWindowExtEx.Extent;
}

private static void HandleSetViewportExtExRecord(EmfState state, EmrSetViewportExtEx setViewportExtEx)
{
state.ViewportExtent = setViewportExtEx.Extent;
}

private static void HandleSetPolyfillMode(EmfState state, EmrSetPolyfillMode setPolyfillMode)
{
state.CurrentPlaybackDeviceContext.PolyFillMode = setPolyfillMode.PolygonFillMode;
}

private static void HandleCreateBrushIndirect(EmfState state, EmrCreateBrushIndirect createBrushIndirect)
{
var index = createBrushIndirect.IHBrush;

state.ObjectTable[index].LogBrush = createBrushIndirect.LogBrush;
state.ObjectTable[index].Type = GraphicsObjectType.Brush;
}

private static void HandleSelectObject(EmfState state, EmrSelectObject selectObject)
{
var index = selectObject.IHObject;

// TODO: Handle stock objects

var graphicsObject = state.ObjectTable[index];

if (graphicsObject.Type is GraphicsObjectType.Pen)
{
state.CurrentPlaybackDeviceContext.SelectedPen = graphicsObject.LogPen;
}
else if (graphicsObject.Type is GraphicsObjectType.Brush)
{
state.CurrentPlaybackDeviceContext.SelectedBrush = graphicsObject.LogBrush;
}
else if (graphicsObject.Type is GraphicsObjectType.Unknown)
{
Console.WriteLine("Warning: Unknown object type selected");
}
}

private static void HandleDeleteObject(EmfState state, EmrDeleteObject deleteObject)
{
var index = deleteObject.IHObject;
state.ObjectTable[index] = default;
}

private static void HandleExtCreatePen(EmfState state, EmrExtCreatePen extCreatePen)
{
var index = extCreatePen.IHPen;

state.ObjectTable[index].LogPen = extCreatePen.Elp;
state.ObjectTable[index].Type = GraphicsObjectType.Pen;
}

private static void HandlePolyPolygon16(StringBuilder svgSb, EmfState state, EmrPolyPolygon16 polyPolygon16)
{
var points = polyPolygon16.APoints;
var polygonPointCounts = polyPolygon16.PolygonPointCount;

var scalingForMapMode = state.GetScalingForCurrentMapMode();
var scaleMatrix = $"matrix({scalingForMapMode.X},0,0,{scalingForMapMode.Y},0,0)";

svgSb.Append($"<path transform=\"{scaleMatrix}\" d=\"");

int totalPointsProcessed = 0;
foreach (var pointCount in polygonPointCounts)
{
var polygonPoints = points.Skip(totalPointsProcessed).Take((int)pointCount).ToList();

var scaledPoint = (polygonPoints[0].X, polygonPoints[0].Y);
svgSb.Append($"M {scaledPoint.X} {scaledPoint.Y} ");

for (var j = 1; j < polygonPoints.Count; j++)
{
scaledPoint = (polygonPoints[j].X, polygonPoints[j].Y);
svgSb.Append($"L {scaledPoint.X} {scaledPoint.Y} ");
}

svgSb.Append('Z');

totalPointsProcessed += (int)pointCount;
}

svgSb.Append("\" ");

AppendFill(svgSb, state);
AppendStroke(svgSb, state);

svgSb.AppendLine(" />");
}

private static void HandleBeginPath(StringBuilder svgSb, EmfState state)
{
state.InPath = true;

var scalingForMapMode = state.GetScalingForCurrentMapMode();
var scaleMatrix = $"matrix({scalingForMapMode.X},0,0,{scalingForMapMode.Y},0,0)";

svgSb.Append($"<path transform=\"{scaleMatrix}\" d=\"");
}

private static void HandleMoveToEx(StringBuilder svgSb, EmfState state, EmrMoveToEx moveToEx)
{
if (state.InPath)
{
svgSb.Append($"M {moveToEx.Offset.X} {moveToEx.Offset.Y} ");
}
}

private static void HandlePolybezierTo16(StringBuilder svgSb, EmfState state, EmrPolyBezierTo16 polyBezierTo16)
{
if (!state.InPath)
{
return;
}

var currentPointCounter = 0;

foreach (var point in polyBezierTo16.APoints)
{
if (currentPointCounter % 3 == 0)
{
svgSb.Append("C ");
}
svgSb.Append($"{point.X} {point.Y} ");

currentPointCounter++;
}
}

private static void HandleEndPath(StringBuilder svgSb, EmfState state)
{
state.InPath = false;
svgSb.Append("\" ");

AppendFill(svgSb, state);
AppendStroke(svgSb, state);

svgSb.AppendLine(" />");
}

private static void AppendFill(StringBuilder svgSb, EmfState state)
{
var fillRuleStr = state.CurrentPlaybackDeviceContext.PolyFillMode switch
{
PolygonFillMode.WINDING => "fill-rule=\"nonzero\" ",
PolygonFillMode.ALTERNATE => "fill-rule=\"evenodd\" ",
_ => ""
};

switch (state.CurrentPlaybackDeviceContext.SelectedBrush.BrushStyle)
{
case BrushStyle.BS_NULL:
svgSb.Append("fill=\"none\" ");
break;
case BrushStyle.BS_SOLID:
svgSb.Append(fillRuleStr);

svgSb.Append($"fill=\"#{state.CurrentPlaybackDeviceContext.SelectedBrush.Color.Red:X2}");
svgSb.Append($"{state.CurrentPlaybackDeviceContext.SelectedBrush.Color.Green:X2}");
svgSb.Append($"{state.CurrentPlaybackDeviceContext.SelectedBrush.Color.Blue:X2}\" ");
break;
case BrushStyle.BS_HATCHED:
case BrushStyle.BS_PATTERN:
case BrushStyle.BS_INDEXED:
case BrushStyle.BS_DIBPATTERN:
case BrushStyle.BS_DIBPATTERNPT:
case BrushStyle.BS_PATTERN8X8:
case BrushStyle.BS_DIBPATTERN8X8:
default:
svgSb.Append($"fill=\"#{state.CurrentPlaybackDeviceContext.SelectedBrush.Color.Red:X2}");
svgSb.Append($"{state.CurrentPlaybackDeviceContext.SelectedBrush.Color.Green:X2}");
svgSb.Append($"{state.CurrentPlaybackDeviceContext.SelectedBrush.Color.Blue:X2}\" ");
break;
}
}

private static void AppendStroke(StringBuilder svgSb, EmfState state)
{
// TODO: use scaling from state
var selectedPen = state.CurrentPlaybackDeviceContext.SelectedPen;
if (selectedPen.PenStyle is PenStyle.PS_NULL)
{
svgSb.Append("stroke=\"none\" ");
}

// switch (state.CurrentPlaybackDeviceContext.SelectedGraphicsObject.LogPen.PenStyle)
// {
// case PenStyle.PS_COSMETIC:
// sb.Append($"stroke-width=\"{stroke}\"");
// break;
// case PenStyle.PS_GEOMETRIC:
// sb.Append($"stroke-width=\"{stroke}\"");
// break;
// }

// TODO: handle other pen styles
}
}
3 changes: 2 additions & 1 deletion src/SharpEmf.Svg/GraphicsObjectType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ internal enum GraphicsObjectType
{
Unknown,
Brush,
Pen
Pen,
// TODO: add more stock object (font, color space, palette)
}
26 changes: 26 additions & 0 deletions src/SharpEmf.Svg/RecordsProcessing/EmfControlRecordsHandlers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Text;
using SharpEmf.Records.Control.Header;

namespace SharpEmf.Svg.RecordsProcessing;

internal static class EmfControlRecordsHandlers
{
public static void HandleHeaderRecord(StringBuilder svgSb, EmfState state, EmfMetafileHeader header)
{
var width = header.Bounds.Right - header.Bounds.Left;
var height = header.Bounds.Bottom - header.Bounds.Top;
var gTransform = $"translate({-header.Bounds.Left},{-header.Bounds.Top})";

state.Scaling = width / MathF.Abs(header.Bounds.Right - header.Bounds.Left);

// TODO: +1 is a hack to make the object table start at index 1
state.ObjectTable = new GraphicsObject[header.Handles + 1];

svgSb.AppendLine(
$"""
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="{width + 1}" height="{height + 1}">
<g transform="{gTransform}">
""");
}
}
Loading

0 comments on commit 2104b29

Please sign in to comment.