问题
Is there any simple and compatible GDI or .NET accessible subsystem of Windows that will give glyph position characters. The task here is the combining symbols such as those in Arabic which sometimes have chains of multiple combining symbols stacking on top of each others such as Arabic Fatha + Arabic Letter Superscript Alef + Arabic Maddah Above. The trouble is that though the X positions can be determined precisely with GDI GetCharacterPlacement, the Y position calculations which are derived from OpenType or TrueType font tables and anchors and a complex set of rules, is not available. Ultimately, to generate a PDF with Arabic properly formatted, the Y positions are needed and precisely. Studying Microsoft Word 2013's save as PDF feature, it is clear they have a way of properly gathering this data as studying the PDF details shows that each character is displayed at its precise position including the combining symbols.
WPF may contain some functions to do this in the GlyphRun class property GlyphOffsets. DirectWrite has the IDWriteTextAnalyzer interface which method GetGlyphPlacements can return DWRITE_GLYPH_OFFSETs and many other complex script information. Looking at the GDI Display and Printer Drive functions, STROBJ_bEnumPositionsOnly seems to return a set of GLYPHPOS structures with this information. GDI certainly renders this correctly under all circumstances if you send the full text to render but not if you want to do it glyph by glyph.
IXpsOMGlyphs in the XPS Object Model allows for a GetGlyphIndices call returning a set of XPS_GLYPH_INDEX gives horizontalOffset and verticalOffset though this library is hardly appropriate.
In the end the only appropriate library looks to be Uniscribe which is complex to use but supported since Internet Explorer 5 and Windows 2000 as opposed to all the other discussions beyond GDI which are generally Vista and later or requiring special dependencies. ScriptItemize returns an array of SCRIPT_STRING_ANALYSIS which can be passed to ScriptShape then ScriptPlace returning an array o GOFFSETs. In fact Uniscribe will given information about word breaks, diacritics, directional flow and many other aspects of what is happening in a complex script. I just wanted to know if there is a simpler method or if this is the minimum required and exact appropriate for such a task since Uniscribe appears to be extremely difficult to use directly from .NET and reasonably would need a C++ wrapper since there are a great deal of structures and pointers.
Update and answer: Uniscribe will not work for PDF purposes as it uses integers in GDI device units thus the accuracy is greatly compromised. Probably why Microsoft Word 2013 finally supports native PDF conversion support because ultimately DirectWrite seems to be relied upon. As mentioned below I posted both code solutions in .NET as tips on CodeProject. DirectWrite seems to be the only answer beyond designing a custom font shaping and calculation engine.
回答1:
Sample Uniscribe code in .NET since it is not available on the web currently:
_ Public Structure GCP_RESULTS Public StructSize As UInteger _ Public OutString As String Public Order As IntPtr Public Dx As IntPtr Public CaretPos As IntPtr Public [Class] As IntPtr Public Glyphs As IntPtr Public GlyphCount As UInteger Public MaxFit As Integer End Structure _ Public Structure SCRIPT_CONTROL Public ScriptControlFlags As UInteger End Structure _ Public Structure SCRIPT_STATE Public ScriptStateFlags As UShort End Structure _ Public Structure SCRIPT_ANALYSIS Public ScriptAnalysisFlags As UShort Public s As SCRIPT_STATE End Structure _ Public Structure SCRIPT_VISATTR Public ScriptVisAttrFlags As UShort End Structure _ Public Structure SCRIPT_ITEM Public iCharPos As Integer Public a As SCRIPT_ANALYSIS End Structure _ Public Structure GOFFSET Public du As Integer Public dv As Integer End Structure _ Public Structure ABC Public abcA As Integer Public abcB As UInteger Public abcC As Integer End Structure Public Const E_OUTOFMEMORY As Integer = &H8007000E Public Const E_PENDING As Integer = &H8000000A Public Const USP_E_SCRIPT_NOT_IN_FONT As Integer = &H80040200 _ Public Shared Function GetCharacterPlacement(hdc As IntPtr, lpString As String, nCount As Integer, nMaxExtent As Integer, ByRef lpResults As GCP_RESULTS, dwFlags As UInteger) As UInteger End Function _ Public Shared Function ScriptItemize( wcInChars As String, cInChars As Integer, cMaxItems As Integer, psControl As SCRIPT_CONTROL, psState As SCRIPT_STATE, pItems() As SCRIPT_ITEM, ByRef pcItems As Integer) As Integer End Function _ Public Shared Function ScriptShape(hdc As IntPtr, ByRef psc As IntPtr, wcChars As String, cChars As Integer, cMaxGlyphs As Integer, ByRef psa As SCRIPT_ANALYSIS, wOutGlyphs() As UShort, wLogClust() As UShort, psva() As SCRIPT_VISATTR, ByRef cGlyphs As Integer) As Integer End Function _ Public Shared Function ScriptPlace(hdc As IntPtr, ByRef psc As IntPtr, wGlyphs() As UShort, cGlyphs As Integer, psva() As SCRIPT_VISATTR, ByRef psa As SCRIPT_ANALYSIS, iAdvance() As Integer, pGoffset() As GOFFSET, ByRef pABC As ABC) As Integer End Function _ Public Shared Function ScriptFreeCache(ByRef psc As IntPtr) As Integer End Function _ Public Shared Function GetDC(hWnd As IntPtr) As IntPtr End Function _ Public Shared Function ReleaseDC(hWnd As IntPtr, hdc As IntPtr) As Integer End Function _ Private Shared Function SelectObject(ByVal hdc As IntPtr, ByVal hObject As IntPtr) As IntPtr End Function Structure CharPosInfo Public Index As Integer Public Width As Integer Public PriorWidth As Integer Public X As Integer Public Y As Integer End Structure Public Shared Function GetWordDiacriticPositions(Str As String, useFont As Font) As CharPosInfo() Dim hdc As IntPtr Dim CharPosInfos As New List(Of CharPosInfo) hdc = GetDC(IntPtr.Zero) 'desktop device context Dim oldFont As IntPtr = SelectObject(hdc, useFont.ToHfont()) Dim MaxItems As Integer = 16 Dim Control As New SCRIPT_CONTROL With {.ScriptControlFlags = 0} Dim State As New SCRIPT_STATE With {.ScriptStateFlags = 1} '0 LTR, 1 RTL Dim Items() As SCRIPT_ITEM = Nothing Dim ItemCount As Integer Dim Result As Integer Do ReDim Items(MaxItems - 1) Result = ScriptItemize(Str, Str.Length, MaxItems, Control, State, Items, ItemCount) If Result = 0 Then ReDim Preserve Items(ItemCount) 'there is a dummy last item so adding one here Exit Do ElseIf Result = E_OUTOFMEMORY Then End If MaxItems *= 2 Loop While True If Result = 0 Then 'last item is dummy item pointing to end of string Dim Cache As IntPtr = IntPtr.Zero For Count = 0 To ItemCount - 2 Dim Logs() As UShort = Nothing Dim Glyphs() As UShort = Nothing Dim VisAttrs() As SCRIPT_VISATTR = Nothing ReDim Glyphs((Items(Count + 1).iCharPos - Items(Count).iCharPos) * 3 \ 2 + 16 - 1) ReDim VisAttrs((Items(Count + 1).iCharPos - Items(Count).iCharPos) * 3 \ 2 + 16 - 1) ReDim Logs(Items(Count + 1).iCharPos - Items(Count).iCharPos - 1) Dim dc As IntPtr = IntPtr.Zero Do Dim GlyphsUsed As Integer Result = ScriptShape(dc, Cache, Str.Substring(Items(Count).iCharPos), Items(Count + 1).iCharPos - Items(Count).iCharPos, Glyphs.Length, Items(Count).a, Glyphs, Logs, VisAttrs, GlyphsUsed) If Result = 0 Then ReDim Preserve Glyphs(GlyphsUsed - 1) ReDim Preserve VisAttrs(GlyphsUsed - 1) Exit Do ElseIf Result = E_PENDING Then dc = hdc ElseIf Result = E_OUTOFMEMORY Then ReDim Glyphs(Glyphs.Length * 2 - 1) ReDim VisAttrs(VisAttrs.Length * 2 - 1) ElseIf Result = USP_E_SCRIPT_NOT_IN_FONT Then Else End If Loop While True If Result = 0 Then Dim Advances(Glyphs.Length - 1) As Integer Dim Offsets(Glyphs.Length - 1) As GOFFSET Dim abc As New ABC With {.abcA = 0, .abcB = 0, .abcC = 0} dc = IntPtr.Zero Do Result = ScriptPlace(dc, Cache, Glyphs, Glyphs.Length, VisAttrs, Items(Count).a, Advances, Offsets, abc) If Result E_PENDING Then Exit Do dc = hdc Loop While True If Result = 0 Then Dim LastPriorWidth As Integer = 0 Dim RunStart As Integer = 0 For CharCount = 0 To Logs.Length - 1 Dim PriorWidth As Integer = 0 Dim RunCount As Integer = 0 For ResCount As Integer = Logs(CharCount) To If(CharCount = Logs.Length - 1, 0, Logs(CharCount + 1)) Step -1 'fDiacritic or fZeroWidth If (VisAttrs(ResCount).ScriptVisAttrFlags And (32 Or 64)) 0 Then CharPosInfos.Add(New CharPosInfo With {.Index = RunStart + RunCount, .PriorWidth = LastPriorWidth, .Width = Advances(ResCount), .X = Offsets(ResCount).du, .Y = Offsets(ResCount).dv}) End If If CharCount = Logs.Length - 1 OrElse Logs(CharCount) Logs(CharCount + 1) Then PriorWidth += Advances(ResCount) RunCount += 1 End If Next LastPriorWidth += PriorWidth If CharCount = Logs.Length - 1 OrElse Logs(CharCount) Logs(CharCount + 1) Then RunStart = CharCount + 1 End If Next End If End If Next ScriptFreeCache(Cache) End If SelectObject(hdc, oldFont) ReleaseDC(IntPtr.Zero, hdc) Return CharPosInfos.ToArray() End Function
来源:https://stackoverflow.com/questions/27024074/calculating-the-positions-of-glyphs-in-windows