Open JeremyKuhne opened 3 years ago
https://github.com/dotnet/winforms/issues/4238 can get a regression test when this is feature is moved forward.
I'm interested in seeing how this will work. I'm learning about enhanced metafiles now and was able to save the DropDownHolder
in #4238 with the fixed resize grip to a disk-based metafile using CreateEnhMetaFile
and can open the metafile in Paint. I can kind of see how the validation will work by enumerating the records.
@willibrandon Enumerating the top-level records in EMF+ is relatively easy as GDI+ gives you a record enumerator (exposed in System.Drawing.Imaging.Metafile
). Getting the details out of each record takes a little bit of work to get started as you have to sequentially scan as there aren't offsets to let you skip to the data you care about. I started playing with this in the WInterop project I linked above (the tests in the link above show how the data presents itself when viewed as a standard EMF or through the EMF+ enumerator). It can be used freely as a base/reference to doing a more complete parser. I intend to keep expanding the implementation there, but I don't have an expected timeframe.
@JeremyKuhne Thank you, taking a look at Winterop now and looking at the EMF+ specification. I can see how the record types are easily validated in the tests you linked due to the System.Drawing.Imaging.Metafile
record enumerator. Am I correct that the details for each record will need to be parsed out of IntPtr data
in the callback?
Am I correct that the details for each record will need to be parsed out of IntPtr data in the callback?
I believe so. I'm not positive where the data pointer starts, was just about to that point in my code. EMF+ records are stored in regular EMF comment records, which I was starting to tear apart in the linked test. Enumerating the same data via the equivalent callback that I have and comparing offsets would validate exactly where data
points in the record.
The process is a little slow as it involves multiple jumps around the specification. :)
Making some progress. I can now take the MetafilePlusObject in your code and tear the Pen object out of it and inspect its EmfPlusPen Object properties. I can inspect the Version, Type, PenData, and BrushObject.
For example, if I fiddle with the Pen and specify a GpUnit
of UnitInch
like using Pen pen = new(Color.Purple, 1, GpUnit.UnitInch)
, I can then validate the PenUnit like so:
@object->Pen.PenData.PenUnit.Should().Be(UnitType.UnitTypeInch);
Now working on accessing the DrawLine record which is eluding me at the moment.
Making more progress. I can now traverse from the EmfPlusObject record to the EmfPlusDrawLines record.
public static unsafe MetafilePlusRecord* GetNextEmfPlusRecord(MetafilePlusRecord* record)
{
return (MetafilePlusRecord*)((byte*)record + record->Size);
}
...
record = MetafilePlusRecord.GetNextEmfPlusRecord(record);
record->Type.Should().Be(RecordType.EmfPlusDrawLines);
MetafilePlusDrawLines* @drawLines = (MetafilePlusDrawLines*)record;
@drawLines->CompressedData.Should().Be(true);
@drawLines->ExtraLine.Should().Be(false);
@drawLines->RelativeLocation.Should().Be(false);
@drawLines->ObjectId.Should().Be(0); // The index of an EmfPlusPen object in the EMF+ Object Table to draw the lines.
Now working on tearing the DrawLine record apart.
I can now inspect the PointData in the EmfPlusDrawLines record. I understand what you mean about the process being slow.
@drawLines->Count.Should().Be(2); // Number of points
MetafilePlusPoint from = @drawLines->GetPoint(0);
MetafilePlusPoint to = @drawLines->GetPoint(1);
from.X.Should().Be(1);
from.Y.Should().Be(1);
to.X.Should().Be(3);
to.Y.Should().Be(5);
There are two components to this. The first is relatively easy- we need helpers and some examples to view GDI calls generated when rendering to a
Graphics
object. The second is more complicated- we need to be able to look at GDI+ records.Recording and playing back GDI records from a Graphics object can be done in a few ways:
Enumerating the GDI+ records is more complicated as the record headers and parsing need to be created from scratch by following the EMF+ specification.
I've started fiddling with the lower level APIs with this here: https://github.com/JeremyKuhne/WInterop/blob/main/src/Tests/WInterop.Tests/GdiPlus/Metafiles.cs#L65 This code can be used to find the equivalent System.Drawing entry points.
The EMF+ specification: https://docs.microsoft.com/openspecs/windows_protocols/ms-emfplus/5f92c789-64f2-46b5-9ed4-15a9bb0946c6