计算Windows中字形的位置

时间:2014-11-19 18:17:33

标签: .net pdf gdi uniscribe

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给出horizo​​ntalOffset和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似乎是唯一的答案。

1 个答案:

答案 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