199 lines
7.1 KiB
Swift
199 lines
7.1 KiB
Swift
// MARK: - DataType
|
|
|
|
/// Type of the data value.
|
|
public enum DataType : UInt8 {
|
|
/// `data` is either `0` or `1`, specifying this resource is either undefined or empty, respectively.
|
|
case Null = 0x00
|
|
/// `data` holds a `TblTableRef`, a reference to another resource table entry.
|
|
case Reference = 0x01
|
|
/// `data` holds an attribute resource identifier.
|
|
case Attribute = 0x02
|
|
/// `data` holds an index into the containing resource table's global value string pool.
|
|
case String = 0x03
|
|
/// `data` holds a single-precision floating point number.
|
|
case Float = 0x04
|
|
/// `data` holds a complex number encoding a dimension value, such as `100in`.
|
|
case Dimension = 0x05
|
|
/// `data` holds a complex number encoding a fraction of a container.
|
|
case Fraction = 0x06
|
|
/// `data` holds a dynamic `TblTableRef`, which needs to be resolved before it can be used like a `.Reference`.
|
|
case DynamicReference = 0x07
|
|
/// `data` holds an attribute resource identifier, which needs to be resolved before it can be used like a `.Attribute`.
|
|
case DynamicAttribute = 0x08
|
|
/// `data` is a raw integer value of the form `n..n`.
|
|
case IntDec = 0x10
|
|
/// `data` is a raw integer value of the form `0xn..n`.
|
|
case IntHex = 0x11
|
|
/// `data` is either `0` or `1`, for input `false` or `true` respectively.
|
|
case IntBoolean = 0x12
|
|
/// `data` is a raw integer value of the form `#aarrggbb`.
|
|
case IntColorARGB8 = 0x1c
|
|
/// `data` is a raw integer value of the form `#rrggbb`.
|
|
case IntColorRGB8 = 0x1d
|
|
/// `data` is a raw integer value of the form `#argb`.
|
|
case IntColorARGB4 = 0x1e
|
|
/// `data` is a raw integer value of the form `#rgb`.
|
|
case IntColorRGB4 = 0x1f
|
|
|
|
var isColor: Bool {
|
|
self == .IntColorARGB8 || self == .IntColorRGB8 || self == .IntColorARGB4 || self == .IntColorRGB4
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: - ResValue
|
|
|
|
/**
|
|
* Representation of a value in a resource, supplying type information.
|
|
*/
|
|
/// Size: `8 Bytes`
|
|
public struct ResValue {
|
|
/// Number of bytes in this structure.
|
|
let size: UInt16
|
|
/// Always set to 0.
|
|
let res0: UInt8
|
|
/// Type of the data value.
|
|
public let dataType: DataType // UInt8
|
|
/// The data for this item, as interpreted according to dataType.
|
|
let data: UInt32
|
|
|
|
init(type: DataType, data rawData: UInt32) {
|
|
size = 8
|
|
res0 = 0
|
|
dataType = type
|
|
data = rawData
|
|
}
|
|
|
|
init(_ br: inout RawBytes.Reader) throws {
|
|
size = br.read16()
|
|
res0 = br.read8()
|
|
let rawType: UInt8 = br.read8()
|
|
guard let typ = DataType(rawValue: rawType) else {
|
|
throw AXMLError("Unknown ResValue data-type \(rawType) (offset: \(br.index - 1))")
|
|
}
|
|
dataType = typ
|
|
data = br.read32()
|
|
assert(dataType != .DynamicAttribute, "TODO: .DynamicAttribute data type")
|
|
assert(dataType != .DynamicReference, "TODO: .DynamicReference data type")
|
|
}
|
|
|
|
/// Convenience getter for `DataType.String`
|
|
public var asStringRef: StringPoolRef { data }
|
|
/// Convenience getter for `DataType.Reference` and `DataType.Attribute`
|
|
public var asTableRef: TblTableRef { TblTableRef(data) }
|
|
/// Convenience getter for `DataType.Float`
|
|
public var asFloat: Float { Float(bitPattern: data) }
|
|
/// Convenience getter for `DataType.Dimension`
|
|
public var asDimension: ComplexType { ComplexType(data, isFraction: false) }
|
|
/// Convenience getter for `DataType.Fraction`
|
|
public var asFraction: ComplexType { ComplexType(data, isFraction: true) }
|
|
/// Convenience getter for `DataType.IntDec`
|
|
public var asIntDec: Int { Int(Int32(truncatingIfNeeded: data)) }
|
|
/// Convenience getter for `DataType.IntHex` with format `00AA22CC`
|
|
public var asIntHex: String { data.hex(8) }
|
|
/// Convenience getter for `DataType.IntBoolean` (value `> 0`)
|
|
public var asBoolean: Bool { data > 0 }
|
|
/// Convenience getter for `DataType.IntColor*`
|
|
public var asColor: ColorComponents { ColorComponents(data) }
|
|
|
|
// see: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/libs/androidfw/ResourceTypes.cpp#7637
|
|
/// Use meta data to display value according to type.
|
|
/// `StringPool` only required for type `.String`
|
|
public func resolve(_ pool: StringPool?) -> String {
|
|
switch dataType {
|
|
case .Null:
|
|
// switch data {
|
|
// case 0: return "(null)" // not defined
|
|
// case 1: return "(null empty)" // explicitly defined as empty
|
|
// default: return String(format: "(null) 0x%08x\n", data) // should never happen
|
|
// }
|
|
return ""
|
|
case .DynamicReference:
|
|
// TODO: can we treat them as their non-dynamic counterpart?
|
|
fallthrough
|
|
case .Reference: return ((data >> 24) & 0xFF == 1 ? "@android:" : "@") + data.hex(8)
|
|
case .DynamicAttribute:
|
|
fallthrough
|
|
case .Attribute: return ((data >> 24) & 0xFF == 1 ? "?android:" : "?") + data.hex(8)
|
|
case .String: return pool!.getString(data)
|
|
case .Float: return "\(asFloat)"
|
|
case .Dimension: return asDimension.asString()
|
|
case .Fraction: return asFraction.asString()
|
|
case .IntDec: return "\(asIntDec)"
|
|
case .IntHex: return "0x" + data.hex(8)
|
|
case .IntBoolean: return data > 0 ? "true" : "false"
|
|
case .IntColorARGB8: return asColor.argb8()
|
|
case .IntColorRGB8: return asColor.rgb8()
|
|
case .IntColorARGB4: return asColor.argb4()
|
|
case .IntColorRGB4: return asColor.rgb4()
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: - ComplexType
|
|
|
|
/**
|
|
* Structure of complex data values (`DataType.Dimension` and `DataType.Fraction`)
|
|
*
|
|
* `MANTISSA_MASK = 0xFFFFFF00`
|
|
*
|
|
* Where the actual value is.
|
|
* This gives us 23 bits of precision.
|
|
* The top bit is the sign.
|
|
*
|
|
* `RADIX_MASK = 0x00000030`
|
|
*
|
|
* Where the radix information is, telling where the decimal place appears in the mantissa.
|
|
* This give us 4 possible fixed point representations as defined below.
|
|
*
|
|
* `UNIT_MASK = 0x0000000F`
|
|
*
|
|
* Where the unit type information is.
|
|
* This gives us 16 possible types, as defined below.
|
|
*/
|
|
public struct ComplexType {
|
|
public let value: Float
|
|
public let unit: String
|
|
|
|
init(_ complex: UInt32, isFraction: Bool) {
|
|
// UNIT
|
|
var scale: Float = 1.0
|
|
if (isFraction) {
|
|
scale = 100
|
|
switch (complex & 0xF) {
|
|
case 0: unit = "%" // A basic fraction of the overall size.
|
|
case 1: unit = "%p" // A fraction of the parent size.
|
|
default: unit = ""
|
|
}
|
|
} else {
|
|
switch (complex & 0xF) {
|
|
case 0: unit = "px" // raw pixels
|
|
case 1: unit = "dp" // Device Independent Pixels
|
|
case 2: unit = "sp" // Scaled device independent Pixels
|
|
case 3: unit = "pt" // points
|
|
case 4: unit = "in" // inches
|
|
case 5: unit = "mm" // millimeters
|
|
default: unit = ""
|
|
}
|
|
}
|
|
// VALUE
|
|
let neg = (complex & 0x80000000) != 0
|
|
let mantissa = Float((complex & 0x7FFFFF00) >> 8) - (neg ? Float(0x7FFFFF) : 0)
|
|
let val = mantissa / (radix[Int(complex & 0x30) >> 4] / scale)
|
|
// for `.Fraction`, round to nearest integral number (if equivalent)
|
|
let isIntegral = isFraction && abs(val.rounded() - val) < 1e-05
|
|
value = isIntegral ? val.rounded() : val
|
|
}
|
|
|
|
public func asString() -> String {
|
|
value.rounded() == value ? "\(Int(value))\(unit)" : "\(value)\(unit)"
|
|
}
|
|
}
|
|
|
|
// 23p0 - The mantissa is an integral number -- i.e., 0xnnnnnn.0
|
|
// 16p7 - The mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn
|
|
// 8p15 - The mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn
|
|
// 0p23 - The mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn
|
|
private let radix: [Float] = [1, 0x80, 0x8000, 0x800000]
|