在Swift 3中读取TrueType'cmap'Format 4 Subtable

时间:2016-11-29 13:41:49

标签: c swift pointers character-encoding true-type-fonts

如何在Swift中编写以下C代码?

glyphIndex = *(&idRangeOffset[i] + idRangeOffset[i] / 2 + (c - startCode[i]))

我正在尝试使用简单的小二进制数据读取器读取Format 4 TrueType字符映射表。在涉及指针操作之前,一切都很好,因为在C中工作时我几乎无法做出指针的正面或反面,更不用说伪装成附加Unsafe前缀的伪装了。

我尝试了很多东西,但似乎没有什么工作做得很好。我想我只是不确定如何在Swift中使用指针“地址”。

例如,这里有一个更完整的概念我在哪里:

// The following variables are all [UInt16]:
// - startCodes
// - endCodes
// - idDeltas
// - idRangeOffsets

var gids = [Int]()

// Iterate segments, skipping the last character code (0xFFFF)
for i in 0 ..< segCount - 1 {

    let start = startCodes[i]
    let end = endCodes[i]
    let delta = idDeltas[i]
    let rangeOffset = idRangeOffsets[i]
    let charRange = start ..< (end + 1)

    if rangeOffset == 0 {
        gids.append(contentsOf: charRange.map { charCode in
            return (charCode + delta) & 0xFFFF
        })
    }
    else {
        for charCode in charRange {
            // ???
        }
    }
}

在上面的代码中,您会注意到???。这是我使用上面提到的奇怪的C指针 - 地址 - 指针 - 技巧来检索字形索引的地方。问题是,我无法弄明白。替换我实际理解的变量,这就是我所拥有的:

for charCode in charRange {
        Not too sure about this   Actual value of idRangeOffset[i]
                 |                          |
                 v                          v
    glyphIndex = *(&idRangeOffset[i] + rangeOffset / 2 + (charCode - start))
                   ^
                   |
                Or this
}

那里有没有Swift 3指针专家可以带领我走向启蒙之路?任何帮助将不胜感激!

2 个答案:

答案 0 :(得分:1)

如果我将你的伪C代码逐字翻译成Swift,那就是这样的:

//"May not work" example, do no use this
glyphIndex = withUnsafePointer(to: &idRangeOffset[i]) {idRangeOffsetPointer in
//In some cases `idRangeOffsetPointer` can work as `&idRangeOffset[i]` in C...
    (idRangeOffsetPointer + Int(rangeOffset) / 2 + Int(charCode - start)).pointee
    //To make pointer operation in Swift, all integers need to be `Int`,
    //And to match the C-rule of integer operation, you need to cast each portion to `Int` before adding operation
    //The equivalent to C's dereferencing operator `*` in Swift is `pointee` property
}

但由于Swift的inout参数的 copy-in / copy-out语义,这可能无法正常工作。 Swift可以创建一个包含单个元素idRangeOffset[i]的时间区域,并将其地址传递给idRangeOffsetPointer,因此指针操作的结果可能指向某个时间区域附近,这是完全无用的

如果您想从指针操作中获得有意义的结果,您可能需要在保证数组的所有元素都放在连续区域中的上下文中工作。

你也应该知道C语句:

glyphIndex = *(&idRangeOffset[i] + idRangeOffset[i] / 2 + (c - startCode[i]))

基于这样一个事实,即整个idRangeOffsetglyphIdArray被放置在一个没有任何间隙或填充的连续区域中。 (我假设您对格式4了解得很清楚。)

因此,如果您的idRangeOffset仅包含segCount元素,则以下代码将无效。

//"Should work" in a certain condition
glyphIndex = idRangeOffset.withUnsafeBufferPointer{idRangeOffsetBufferPointer in
    let idRangeOffsetPointer = idRangeOffsetBufferPointer.baseAddress! + i
    //`idRangeOffsetPointer` is equivalent to `&idRangeOffset[i]` in C inside this closure
    return (idRangeOffsetPointer + Int(rangeOffset) / 2 + Int(charCode - start)).pointee
}

但考虑到C中的指针和数组语义,上面的代码等同于:

glyphIndex = idRangeOffset[i + Int(rangeOffset) / 2 + Int(charCode - start)]

//`*(&arr[i] + n)` is equivalent to `arr[i + n]` in C

我再说一遍,数组idRangeOffset需要包含idRangeOffset和glyphIdArray 的全部内容。

答案 1 :(得分:0)

要添加@OOPer所说的内容,我建议将整个cmap或感兴趣的子表读入内存,并使用文档作为指南在Swift中使用它们;例如,请参阅https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html。您也可以使用C实现作为参考,但这不是一个好的路径,除非您是一位经验丰富的C程序员:C中有太多的细微之处可以并且会咬你。在C中,在指针和偏移方面使用cmap非常方便。因为Swift不是指针友好的,所以最好只在表中使用偏移量。您可能会遇到将地图的不同部分解释为不同类型的值的问题,但至少您不必处理指针魔法。