Windows是否有任何简单且兼容的GDI或.NET可访问子系统,它们将提供字形位置字符。这里的任务是组合符号,例如阿拉伯语中的符号,有时具有多个组合符号的链,堆叠在彼此之上,例如阿拉伯语Fatha + Arabic Letter Superscript Alef + Arabic Maddah Above。麻烦的是,尽管可以使用GDI GetCharacterPlacement精确确定X位置,但是从OpenType或TrueType字体表和锚点以及一组复杂规则派生的Y位置计算不可用。最终,要生成具有正确格式化阿拉伯语的PDF,需要并精确地定位Y位置。学习Microsoft Word 2013保存为PDF功能,很明显他们有一种正确收集这些数据的方法,因为研究PDF细节显示每个字符都显示在其精确位置,包括组合符号。
WPF可能包含一些在GlyphRun类属性GlyphOffsets中执行此操作的函数。 DirectWrite具有IDWriteTextAnalyzer接口,GetGlyphPlacements方法可以返回DWRITE_GLYPH_OFFSETs和许多其他复杂的脚本信息。查看GDI显示和打印机驱动器功能,STROBJ_bEnumPositionsOnly似乎返回一组带有此信息的GLYPHPOS结构。如果你发送全文进行渲染,GDI肯定会在所有情况下都正确渲染,但如果你想用字形进行字形渲染,则无法正确渲染。
XPS对象模型中的IXpsOMGlyphs允许GetGlyphIndices调用返回一组XPS_GLYPH_INDEX给出horizontalOffset和verticalOffset,尽管这个库很难用。
最后,唯一合适的库看起来是Uniscribe,它使用起来很复杂,但是从Internet Explorer 5和Windows 2000开始支持,而不是GDI之外的所有其他讨论,通常是Vista及更高版本或需要特殊依赖。 ScriptItemize返回一个SCRIPT_STRING_ANALYSIS数组,该数组可以传递给ScriptShape,然后ScriptPlace返回一个数组o GOFFSETs。事实上,Uniscribe将提供有关单词中断,变音符号,方向流以及复杂脚本中发生的许多其他方面的信息。我只是想知道是否有一个更简单的方法,或者这是否是最低要求并且完全适合这样的任务,因为Uniscribe似乎非常难以直接从.NET使用,并且合理地需要一个C ++包装器,因为它有一个很好的处理结构和指针。
更新和回答:Uniscribe不能用于PDF目的,因为它在GDI设备单元中使用整数,因此准确性大大降低。可能是为什么Microsoft Word 2013最终支持本机PDF转换支持,因为最终似乎依赖于DirectWrite。如下所述,我在.NET中发布了两个代码解决方案,作为CodeProject的提示。除了设计自定义字体整形和计算引擎外,DirectWrite似乎是唯一的答案。
答案 0 :(得分:1)
.NET中的示例Uniscribe代码,因为它目前在网上不可用:
_ 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