Skip to content

Commit 839048f

Browse files
committed
完成基础搭建逻辑
1 parent 15371c4 commit 839048f

5 files changed

Lines changed: 335 additions & 11 deletions

File tree

Workbench/Wmf/SkiaWmfRenderer/SkiaWmfRenderer.sln

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,12 @@ VisualStudioVersion = 17.0.31903.59
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}"
77
EndProject
8-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sample", "samples\sample\sample.csproj", "{82A7729F-37E7-459A-880D-B369D002D159}"
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "samples\sample\Sample.csproj", "{82A7729F-37E7-459A-880D-B369D002D159}"
99
EndProject
1010
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
1111
EndProject
1212
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "wieslawsoltes-wmf", "wieslawsoltes-wmf", "{D144BB0A-C7AF-AD14-AAB6-277D09411108}"
1313
EndProject
14-
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3BE548B1-DF76-A83D-2DD1-A4902A7471C0}"
15-
EndProject
16-
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "library", "library", "{B9ACF88E-1B48-4C66-8D28-E5AA88E66DAE}"
17-
EndProject
1814
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Library", "src\wieslawsoltes-wmf\src\library\Library.csproj", "{37C31B24-6793-4298-B68F-7A9E94932DD9}"
1915
EndProject
2016
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkiaWmfRenderer", "src\SkiaWmfRenderer\SkiaWmfRenderer.csproj", "{34500DA8-76D4-4CE5-ADA3-9482FECF7DEC}"
@@ -72,9 +68,10 @@ Global
7268
GlobalSection(NestedProjects) = preSolution
7369
{82A7729F-37E7-459A-880D-B369D002D159} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
7470
{D144BB0A-C7AF-AD14-AAB6-277D09411108} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
75-
{3BE548B1-DF76-A83D-2DD1-A4902A7471C0} = {D144BB0A-C7AF-AD14-AAB6-277D09411108}
76-
{B9ACF88E-1B48-4C66-8D28-E5AA88E66DAE} = {3BE548B1-DF76-A83D-2DD1-A4902A7471C0}
77-
{37C31B24-6793-4298-B68F-7A9E94932DD9} = {B9ACF88E-1B48-4C66-8D28-E5AA88E66DAE}
71+
{37C31B24-6793-4298-B68F-7A9E94932DD9} = {D144BB0A-C7AF-AD14-AAB6-277D09411108}
7872
{34500DA8-76D4-4CE5-ADA3-9482FECF7DEC} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
7973
EndGlobalSection
74+
GlobalSection(ExtensibilityGlobals) = postSolution
75+
SolutionGuid = {CC7896A5-C341-469C-8996-F6E2BA9724FE}
76+
EndGlobalSection
8077
EndGlobal
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,318 @@
11
// See https://aka.ms/new-console-template for more information
2+
3+
using System.Diagnostics;
4+
using Oxage.Wmf;
5+
using System.Drawing;
6+
using System.Drawing.Imaging;
7+
using System.Text;
8+
using Oxage.Wmf.Records;
9+
using SkiaSharp;
10+
11+
var file = @"C:\lindexi\wmf公式\image17.wmf";
12+
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
13+
14+
var image = Image.FromFile(file);
15+
var imageWidth = image.Width;
16+
var imageHeight = image.Height;
17+
var imagePhysicalDimension = image.PhysicalDimension;
18+
19+
using var fileStream = File.OpenRead(file);
20+
var wmfDocument = new WmfDocument();
21+
wmfDocument.Load(fileStream);
22+
23+
24+
var format = wmfDocument.Format;
25+
Console.WriteLine(format.Dump());
26+
27+
// Left: 61528
28+
// Top: 62158
29+
// Right: 4008
30+
// Bottom: 3378
31+
// Unit: 1000
32+
// Checksum: 21749
33+
34+
var x = Math.Min(format.Left, format.Right);
35+
var y = Math.Min(format.Top, format.Bottom);
36+
37+
var width = Math.Abs(format.Right - format.Left);
38+
var height = Math.Abs(format.Bottom - format.Top);
39+
40+
var inchUnit = format.Unit;
41+
var pixelWidth = (double)width / inchUnit * 96;
42+
var pixelHeight = (double)height / inchUnit * 96;
43+
44+
var skBitmap = new SKBitmap(width, height, SKColorType.Bgra8888, SKAlphaType.Premul);
45+
SKCanvas canvas = new SKCanvas(skBitmap);
46+
47+
var currentPenColor = SKColors.Empty;
48+
var currentPenThickness = 0;
49+
50+
float currentX = x;
51+
float currentY = y;
52+
53+
var currentTextColor = SKColors.Black;
54+
55+
using var paint = new SKPaint();
56+
paint.IsAntialias = true;
57+
58+
using var skFont = new SKFont();
59+
float currentFontSize = 0;
60+
61+
string? currentFontName = null;
62+
CharacterSet currentCharacterSet = CharacterSet.ANSI_CHARSET;
63+
Encoding currentEncoding = Encoding.GetEncoding(1252);
64+
65+
Encoding CharacterSetToEncoding(CharacterSet characterSet)
66+
{
67+
var codePageId = characterSet switch
68+
{
69+
CharacterSet.ANSI_CHARSET
70+
// DEFAULT_CHARSET: Specifies a character set based on the current system locale; for example, when the system locale is United States English, the default character set is ANSI_CHARSET.
71+
or CharacterSet.DEFAULT_CHARSET => 1252,
72+
CharacterSet.OEM_CHARSET => 437,
73+
CharacterSet.SHIFTJIS_CHARSET => 932,
74+
CharacterSet.HANGUL_CHARSET => 949,
75+
CharacterSet.JOHAB_CHARSET => 1361,
76+
CharacterSet.GB2312_CHARSET => 936,
77+
CharacterSet.CHINESEBIG5_CHARSET => 950,
78+
CharacterSet.HEBREW_CHARSET => 1255,
79+
CharacterSet.ARABIC_CHARSET => 1256,
80+
CharacterSet.GREEK_CHARSET => 1253,
81+
CharacterSet.TURKISH_CHARSET => 1254,
82+
CharacterSet.BALTIC_CHARSET => 1257,
83+
CharacterSet.EASTEUROPE_CHARSET => 1250,
84+
CharacterSet.RUSSIAN_CHARSET => 1251,
85+
CharacterSet.THAI_CHARSET => 874,
86+
CharacterSet.VIETNAMESE_CHARSET => 1258,
87+
CharacterSet.SYMBOL_CHARSET => 42, // Symbol font is not a code page, but 42 is often used for Symbol font
88+
_ => 1252,
89+
};
90+
91+
return Encoding.GetEncoding(codePageId);
92+
}
93+
94+
float lastDxOffset = 0;
95+
96+
bool isItalic = false;
97+
int fontWeight = 400;
98+
99+
for (var i = 0; i < wmfDocument.Records.Count; i++)
100+
{
101+
var wmfDocumentRecord = wmfDocument.Records[i];
102+
switch (wmfDocumentRecord)
103+
{
104+
// The META_SETBKMODE Record defines the background raster operation mix mode in the playback device context. The background mix mode is the mode for combining pens, text, hatched brushes, and interiors of filled objects with background colors on the output surface.
105+
case WmfSetBkModeRecord setBkModeRecord:
106+
{
107+
// RecordFunction (2 bytes): A 16-bit unsigned integer that defines this WMF record type. The lower byte MUST match the lower byte of the RecordType Enumeration (section 2.1.1.1) table value META_SETBKMODE.
108+
// BkMode (2 bytes): A 16-bit unsigned integer that defines background mix mode. This MUST be one of the values in the MixMode Enumeration (section 2.1.1.20).
109+
110+
break;
111+
}
112+
case WmfSetTextAlignRecord setTextAlignRecord:
113+
{
114+
// RecordFunction (2 bytes): A 16-bit unsigned integer that defines this WMF record type. The lower byte MUST match the lower byte of the RecordType Enumeration (section 2.1.1.1) table value META_SETTEXTALIGN.
115+
// TextAlignmentMode (2 bytes): A 16-bit unsigned integer that defines text alignment. This value MUST be a combination of one or more TextAlignmentMode Flags (section 2.1.2.3) for text with a horizontal baseline, and VerticalTextAlignmentMode Flags (section 2.1.2.4) for text with a vertical baseline.
116+
117+
break;
118+
}
119+
case WmfCreatePenIndirectRecord createPenIndirectRecord:
120+
{
121+
Color color = createPenIndirectRecord.Color;
122+
currentPenColor = new SKColor(color.R, color.G, color.B, color.A);
123+
currentPenThickness = Math.Max(createPenIndirectRecord.Width.X, createPenIndirectRecord.Width.Y);
124+
break;
125+
}
126+
// - [11] {== WmfMoveToRecord ==
127+
// RecordSize: 5 words = 10 bytes
128+
// RecordType: 0x0214 (RecordType.META_MOVETO)
129+
// X: 1372Y: 865} Oxage.Wmf.IBinaryRecord {Oxage.Wmf.Records.WmfMoveToRecord}
130+
//
131+
case WmfMoveToRecord moveToRecord:
132+
{
133+
currentX = moveToRecord.X;
134+
currentY = moveToRecord.Y;
135+
lastDxOffset = 0;
136+
break;
137+
}
138+
// - [12] {== WmfLineToRecord ==
139+
// RecordSize: 5 words = 10 bytes
140+
// RecordType: 0x0213 (RecordType.META_LINETO)
141+
// X: 1840Y: 865} Oxage.Wmf.IBinaryRecord {Oxage.Wmf.Records.WmfLineToRecord}
142+
//
143+
case WmfLineToRecord lineToRecord:
144+
{
145+
paint.IsStroke = true;
146+
paint.Color = currentPenColor;
147+
paint.StrokeWidth = currentPenThickness;
148+
149+
canvas.DrawLine(currentX, currentY, lineToRecord.X, lineToRecord.Y, paint);
150+
151+
break;
152+
}
153+
// - [13] {== WmfSetTextColorRecord ==
154+
// RecordSize: 5 words = 10 bytes
155+
// RecordType: 0x0209 (RecordType.META_SETTEXTCOLOR)
156+
// ColorRef: Color [A=255, R=0, G=0, B=0]
157+
// } Oxage.Wmf.IBinaryRecord {Oxage.Wmf.Records.WmfSetTextColorRecord}
158+
//
159+
case WmfSetTextColorRecord setTextColorRecord:
160+
{
161+
currentTextColor = ToSKColor(setTextColorRecord.Color);
162+
163+
break;
164+
}
165+
// - [15] {== WmfCreateFontIndirectRecord ==
166+
// RecordSize: 28 words = 56 bytes
167+
// RecordType: 0x02fb (RecordType.META_CREATEFONTINDIRECT)
168+
// Height: -636
169+
// Width: 0
170+
// Escapement: 0
171+
// Orientation: 0
172+
// Weight: 400
173+
// Italic: False
174+
// Underline: False
175+
// StrikeOut: False
176+
// CharSet: 0x0000 (CharacterSet.ANSI_CHARSET)
177+
// OutPrecision: 0x00 (OutPrecision.OUT_DEFAULT_PRECIS)
178+
// ClipPrecision: 0x02 (ClipPrecision.CLIP_STROKE_PRECIS)
179+
// Quality: 0x00 (FontQuality.DEFAULT_QUALITY)
180+
// Pitch: 0x00 (PitchFont.DEFAULT_PITCH)
181+
// Family: 0x00 (FamilyFont.FF_DONTCARE)
182+
// Facename: Times New Roman Oxage.Wmf.IBinaryRecord {Oxage.Wmf.Records.WmfCreateFontIndirectRecord}
183+
//
184+
case WmfCreateFontIndirectRecord createFontIndirectRecord:
185+
{
186+
// "Times New Roman\0è±ñwñ±ñw @ów³\u0011fÁ"
187+
currentFontName = createFontIndirectRecord.FaceName.ToString();
188+
currentCharacterSet = createFontIndirectRecord.CharSet;
189+
currentEncoding = CharacterSetToEncoding(currentCharacterSet);
190+
191+
// Width (2 bytes): A 16-bit signed integer that defines the average width, in logical units, of characters in the font. If Width is 0x0000, the aspect ratio of the device SHOULD be matched against the digitization aspect ratio of the available fonts to find the closest match, determined by the absolute value of the difference.
192+
var fontWidth = createFontIndirectRecord.Width;
193+
194+
// Height (2 bytes): A 16-bit signed integer that specifies the height, in logical units, of the font's character cell. The character height is computed as the character cell height minus the internal leading. The font mapper SHOULD interpret the height as follows.
195+
// value < 0x0000 The font mapper SHOULD transform this value into device units and match its absolute value against the character height of available fonts.
196+
// 0x0000 A default height value MUST be used when creating a physical font.
197+
// 0x0000 < value The font mapper SHOULD transform this value into device units and match it against the cell height of available fonts.
198+
// For all height comparisons, the font mapper SHOULD find the largest physical font that does not exceed the requested size.<40>
199+
// <40> Section 2.2.1.2: All Windows versions: mapping the logical font size to the available physical fonts occurs the first time the logical font needs to be used in a drawing operation.
200+
// For the MM_TEXT mapping mode, the following formula can be used to compute the height of a font with a specified point size.
201+
// Height = -MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);
202+
//
203+
var fontHeight = createFontIndirectRecord.Height;
204+
205+
fontWidth = Math.Abs(fontWidth);
206+
fontHeight = Math.Abs(fontHeight);
207+
208+
currentFontSize = Math.Max(fontWidth, fontHeight);
209+
210+
isItalic = createFontIndirectRecord.Italic;
211+
fontWeight = createFontIndirectRecord.Weight;
212+
213+
break;
214+
}
215+
case WmfUnknownRecord unknownRecord:
216+
{
217+
switch (unknownRecord.RecordType)
218+
{
219+
case RecordType.META_EXTTEXTOUT:
220+
{
221+
// 关于字间距的规则:
222+
// 1. 如果两个 META_EXTTEXTOUT 相邻,中间没有 MoveTo 之类
223+
// 则第二个 META_EXTTEXTOUT 将需要使用前一个 META_EXTTEXTOUT 的 dx 末项
224+
// 2. 可选的 dx 是存放在字符串末尾的可选项,从文档 2.3.3.5 上可见 dx 是顶格写的,这就意味着这个值是一定对齐整数倍的。由于 dx 是放在数据末尾,可通过减法算出 dx 长度,即数据总长度减去所有已知字段的长度加上字符串长度,剩余的就是 dx 长度。如果计算返回的 dx 长度是奇数,则首个 byte 是需要跳过的,如此就能确保在 16bit 下的 wmf 格式里面,读取的 dx 是从整数倍开始读取
225+
// 参考 https://learn.microsoft.com/zh-cn/windows/win32/api/wingdi/nf-wingdi-exttextoutw
226+
227+
// 测试 17 项
228+
var memoryStream = new MemoryStream(unknownRecord.Data);
229+
var binaryReader = new BinaryReader(memoryStream);
230+
var ty = binaryReader.ReadUInt16();
231+
var tx = binaryReader.ReadUInt16();
232+
var stringLength = binaryReader.ReadUInt16();
233+
var fwOpts = (ExtTextOutOptions)binaryReader.ReadUInt16();
234+
// Rectangle (8 bytes): An optional 8-byte Rect Object (section 2.2.2.18).) When either ETO_CLIPPED, ETO_OPAQUE, or both are specified, the rectangle defines the dimensions, in logical coordinates, used for clipping, opaquing, or both. When neither ETO_CLIPPED nor ETO_OPAQUE is specified, the coordinates in Rectangle are ignored.
235+
var st = 8; /*2byte ofr ty tx stringLength fwOpts*/
236+
if (fwOpts is ExtTextOutOptions.ETO_CLIPPED or ExtTextOutOptions.ETO_OPAQUE)
237+
{
238+
// 此时才有 Rectangle 的值
239+
binaryReader.ReadBytes(8);
240+
st += 8;
241+
}
242+
243+
st += stringLength;
244+
245+
// String (variable): A variable-length string that specifies the text to be drawn. The string does not need to be null-terminated, because StringLength specifies the length of the string. If the length is odd, an extra byte is placed after it so that the following member (optional Dx) is aligned on a 16-bit boundary. The string will be decoded based on the font object currently selected into the playback device context. If a font matching the font object’s specification is not found, the decoding is undefined. If a matching font is found that matches the charset specified in the font object, the string should be decoded with the codepages in the following table.
246+
var stringBuffer = binaryReader.ReadBytes(stringLength);
247+
var text = currentEncoding.GetString(stringBuffer);
248+
249+
// Dx (variable): An optional array of 16-bit signed integers that indicate the distance between origins of adjacent character cells. For example, Dx[i] logical units separate the origins of character cell i and character cell i + 1. If this field is present, there MUST be the same number of values as there are characters in the string.
250+
//Debug.Assert(st == unknownRecord.RecordSize);
251+
var dxLength = unknownRecord.Data.Length - st;
252+
253+
skFont.Size = currentFontSize;
254+
skFont.Typeface = SKTypeface.FromFamilyName(currentFontName, (SKFontStyleWeight)fontWeight,
255+
SKFontStyleWidth.Normal, isItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright);
256+
257+
paint.Style = SKPaintStyle.Fill;
258+
paint.Color = currentTextColor;
259+
260+
var currentXOffset = currentX + tx + lastDxOffset;
261+
262+
if (dxLength == 0)
263+
{
264+
canvas.DrawText(text, currentXOffset, currentY + ty, skFont, paint);
265+
}
266+
else
267+
{
268+
// 如果这里计算出来不是偶数,则首个需要跳过。这是经过测试验证的。~~但没有相关说明内容。且跳过的 byte 是有内容的~~ String (variable): If the length is odd, an extra byte is placed after it so that the following member (optional Dx) is aligned on a 16-bit boundary.
269+
// 如果字符串的长度是奇数,则在字符串后面放置一个额外的字节,以便下一个成员(可选的 Dx)对齐到 16 位边界。
270+
if (dxLength > ((dxLength / sizeof(UInt16)) * sizeof(UInt16)))
271+
{
272+
// 读取掉这个额外的字节,以便 Dx 对齐到 16 位边界
273+
var r = binaryReader.ReadByte();
274+
_ = r;
275+
}
276+
277+
UInt16[] dxArray = new UInt16[dxLength / sizeof(UInt16)];
278+
for (var t = 0; t < dxArray.Length; t++)
279+
{
280+
dxArray[t] = binaryReader.ReadUInt16();
281+
}
282+
283+
if (dxArray.Length != text.Length)
284+
{
285+
continue;
286+
}
287+
288+
for (var textIndex = 0; textIndex < text.Length; textIndex++)
289+
{
290+
canvas.DrawText(text[textIndex].ToString(), currentXOffset, currentY + ty, skFont, paint);
291+
292+
currentXOffset += dxArray[textIndex];
293+
}
294+
295+
lastDxOffset = dxArray[^1];
296+
}
297+
298+
break;
299+
}
300+
}
301+
302+
break;
303+
}
304+
}
305+
}
306+
307+
var outputFile = "1.png";
308+
using (var outputStream = File.OpenWrite(outputFile))
309+
{
310+
skBitmap.Encode(outputStream, SKEncodedImageFormat.Png, 100);
311+
}
312+
2313
Console.WriteLine("Hello, World!");
314+
315+
SKColor ToSKColor(Color color)
316+
{
317+
return new SKColor(color.R, color.G, color.B, color.A);
318+
}

Workbench/Wmf/SkiaWmfRenderer/src/SkiaWmfRenderer/Program.cs

Lines changed: 0 additions & 2 deletions
This file was deleted.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace SkiaWmfRenderer;
8+
public static class SkiaWmfRenderHelper
9+
{
10+
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<OutputType>Exe</OutputType>
54
<TargetFramework>net9.0</TargetFramework>
65
<ImplicitUsings>enable</ImplicitUsings>
76
<Nullable>enable</Nullable>
87
</PropertyGroup>
98

9+
<ItemGroup>
10+
<ProjectReference Include="..\wieslawsoltes-wmf\src\library\Library.csproj" />
11+
</ItemGroup>
12+
1013
</Project>

0 commit comments

Comments
 (0)