2021SC@SDUSC
目录
前言:本篇博客继续对Aztec二维码编码进行分析。
一、State
State表示有关生成当前输出所需的序列的所有信息。注意,状态是不可变的。
成员变量
变量名 | 说明 |
---|---|
mode | 编码的当前模式(或者,如果处于二进制移位模式,将返回的模式) |
token | 输出的令牌列表。如果处于二进制移位模式,此令牌列表尚未包含这些字节的令牌 |
binaryShiftByteCount | 如果非零,则表示应以二进制移位模式输出的最新字节数。 |
bitCount | 生成的总位数(包括二进制移位) |
latchAndAppend
创建一个表示此状态的新状态,并将latch转换为(不需要不同的)模式,然后创建一个代码。
State latchAndAppend(int mode, int value) {
// 明确binaryShiftByteCount == 0;
int bitCount = this.bitCount;
Token token = this.token;
if (mode != this.mode) {
int latch = HighLevelEncoder.LATCH_TABLE[this.mode][mode];
token = token.add(latch & 0xFFFF, latch >> 16);
bitCount += latch >> 16;
}
int latchModeBitCount = mode == HighLevelEncoder.MODE_DIGIT ? 4 : 5;
token = token.add(value, latchModeBitCount);
return new State(token, mode, 0, bitCount + latchModeBitCount);
}
shiftAndAppend
创建表示此状态的新状态,临时切换到其他模式以输出单个值。
State shiftAndAppend(int mode, int value) {
// 明确binaryShiftByteCount == 0 && this.mode != mode;
Token token = this.token;
int thisModeBitCount = this.mode == HighLevelEncoder.MODE_DIGIT ? 4 : 5;
// 移位仅存在于UPPER和PUNCT,这两种类型的令牌大小均为5。
token = token.add(HighLevelEncoder.SHIFT_TABLE[this.mode][mode], thisModeBitCount);
token = token.add(value, 5);
return new State(token, this.mode, 0, this.bitCount + thisModeBitCount + 5);
}
addBinaryShiftChar
创建一个表示此状态的新状态,但在二进制移位模式下创建一个附加字符输出。
State addBinaryShiftChar(int index) {
Token token = this.token;
int mode = this.mode;
int bitCount = this.bitCount;
if (this.mode == HighLevelEncoder.MODE_PUNCT || this.mode == HighLevelEncoder.MODE_DIGIT) {
// 明确binaryShiftByteCount == 0;
int latch = HighLevelEncoder.LATCH_TABLE[mode][HighLevelEncoder.MODE_UPPER];
token = token.add(latch & 0xFFFF, latch >> 16);
bitCount += latch >> 16;
mode = HighLevelEncoder.MODE_UPPER;
}
int deltaBitCount =
(binaryShiftByteCount == 0 || binaryShiftByteCount == 31) ? 18 :
(binaryShiftByteCount == 62) ? 9 : 8;
State result = new State(token, mode, binaryShiftByteCount + 1, bitCount + deltaBitCount);
if (result.binaryShiftByteCount == 2047 + 31) {
// 字符串的长度与允许的长度相同。我们应该结束它。
result = result.endBinaryShift(index + 1);
}
return result;
}
endBinaryShift
创建与此相同的状态,但我们不再处于二进制移位模式。
State endBinaryShift(int index) {
if (binaryShiftByteCount == 0) {
return this;
}
Token token = this.token;
token = token.addBinaryShift(index - binaryShiftByteCount, binaryShiftByteCount);
// 明确token.getTotalBitCount() == this.bitCount;
return new State(token, mode, 0, this.bitCount);
}
isBetterThanOrEqualTo
如果在所有可能的情况下“this”状态比“that”状态更好(或相等),则返回true。
boolean isBetterThanOrEqualTo(State other) {
int newModeBitCount = this.bitCount + (HighLevelEncoder.LATCH_TABLE[this.mode][other.mode] >> 16);
if (this.binaryShiftByteCount < other.binaryShiftByteCount) {
// 如果有,添加其他B/S编码成本
newModeBitCount += other.binaryShiftCost - this.binaryShiftCost;
} else if (this.binaryShiftByteCount > other.binaryShiftByteCount && other.binaryShiftByteCount > 0) {
// 最大可能的额外成本(我们最终超过了31字节的边界,其他状态可以保持在该边界之下)
newModeBitCount += 10;
}
return newModeBitCount <= other.bitCount;
}
二、HighLevelEncoder
产生接近最佳的文本编码到Aztec代码使用的第一级编码中。
它使用动态算法。对于字符串的每个前缀,它确定可能导致该前缀的一组编码。我们反复添加一个字符并生成一组新的最佳编码,直到我们看完整个输入。
static final int[][] LATCH_TABLE
LATCH_TABLE显示了每对模式从一种模式转换到另一种模式的最佳方法。在最坏的情况下,这可能高达14位。在最好的情况下,每个条目的高半字表示位数。每个条目的下半个字是需要更改的实际位。
static final int[][] LATCH_TABLE = {
{
0,
(5 << 16) + 28, // UPPER -> LOWER
(5 << 16) + 30, // UPPER -> DIGIT
(5 << 16) + 29, // UPPER -> MIXED
(10 << 16) + (29 << 5) + 30, // UPPER -> MIXED -> PUNCT
},
{
(9 << 16) + (30 << 4) + 14, // LOWER -> DIGIT -> UPPER
0,
(5 << 16) + 30, // LOWER -> DIGIT
(5 << 16) + 29, // LOWER -> MIXED
(10 << 16) + (29 << 5) + 30, // LOWER -> MIXED -> PUNCT
},
{
(4 << 16) + 14, // DIGIT -> UPPER
(9 << 16) + (14 << 5) + 28, // DIGIT -> UPPER -> LOWER
0,
(9 << 16) + (14 << 5) + 29, // DIGIT -> UPPER -> MIXED
(14 << 16) + (14 << 10) + (29 << 5) + 30,
// DIGIT -> UPPER -> MIXED -> PUNCT
},
{
(5 << 16) + 29, // MIXED -> UPPER
(5 << 16) + 28, // MIXED -> LOWER
(10 << 16) + (29 << 5) + 30, // MIXED -> UPPER -> DIGIT
0,
(5 << 16) + 30, // MIXED -> PUNCT
},
{
(5 << 16) + 31, // PUNCT -> UPPER
(10 << 16) + (31 << 5) + 28, // PUNCT -> UPPER -> LOWER
(10 << 16) + (31 << 5) + 30, // PUNCT -> UPPER -> DIGIT
(10 << 16) + (31 << 5) + 29, // PUNCT -> UPPER -> MIXED
0,
},
};
encode
返回由编码为BitArray的编码器表示的文本
public BitArray encode() {
State initialState = State.INITIAL_STATE;
if (charset != null) {
CharacterSetECI eci = CharacterSetECI.getCharacterSetECI(charset);
if (null == eci) {
throw new IllegalArgumentException("No ECI code for character set " + charset);
}
initialState = initialState.appendFLGn(eci.getValue());
}
Collection<State> states = Collections.singletonList(initialState);
for (int index = 0; index < text.length; index++) {
int pairCode;
int nextChar = index + 1 < text.length ? text[index + 1] : 0;
switch (text[index]) {
case '\r':
pairCode = nextChar == '\n' ? 2 : 0;
break;
case '.' :
pairCode = nextChar == ' ' ? 3 : 0;
break;
case ',' :
pairCode = nextChar == ' ' ? 4 : 0;
break;
case ':' :
pairCode = nextChar == ' ' ? 5 : 0;
break;
default:
pairCode = 0;
}
if (pairCode > 0) {
// 有四对特殊的点对中的一对。特别对待他们。
// 获取两个新字符的新状态集。
states = updateStateListForPair(states, index, pairCode);
index++;
} else {
// 获取新角色的一组新状态。
states = updateStateListForChar(states, index);
}
}
// 只剩下一组状态。找一个最短的。
State minState = Collections.min(states, new Comparator<State>() {
@Override
public int compare(State a, State b) {
return a.getBitCount() - b.getBitCount();
}
});
// 将其转换为位数组,然后返回。
return minState.toBitArray(text);
}
updateStateListForChar
通过更新新角色的每个状态、合并结果,然后删除非最佳状态来更新新角色的一组状态。
private Collection<State> updateStateListForChar(Iterable<State> states, int index) {
Collection<State> result = new LinkedList<>();
for (State state : states) {
updateStateForChar(state, index, result);
}
return simplifyStates(result);
}
updateStateForChar
返回一组状态,这些状态表示为下一个字符更新此状态的可能方式。结果状态集将添加到“结果”列表中。
private void updateStateForChar(State state, int index, Collection<State> result) {
char ch = (char) (text[index] & 0xFF);
boolean charInCurrentTable = CHAR_MAP[state.getMode()][ch] > 0;
State stateNoBinary = null;
for (int mode = 0; mode <= MODE_PUNCT; mode++) {
int charInMode = CHAR_MAP[mode][ch];
if (charInMode > 0) {
if (stateNoBinary == null) {
// 仅在第一次需要时才创建stateNoBinary。
stateNoBinary = state.endBinaryShift(index);
}
// 尝试通过锁定其模式来生成角色
if (!charInCurrentTable || mode == state.getMode() || mode == MODE_DIGIT) {
// 如果字符在当前表中,我们不希望锁定到任何其他模式,可能除了数字(仅使用4位)。在*这个字符之后,任何其他锁存都将同样成功,因此不会保存任何位。
State latchState = stateNoBinary.latchAndAppend(mode, charInMode);
result.add(latchState);
}
// 尝试通过切换到角色模式来生成角色。
if (!charInCurrentTable && SHIFT_TABLE[state.getMode()][mode] >= 0) {
// 如果角色存在于当前模式中,则暂时切换到其他模式是没有意义的。这永远也省不了多少钱。
State shiftState = stateNoBinary.shiftAndAppend(mode, charInMode);
result.add(shiftState);
}
}
}
if (state.getBinaryShiftByteCount() > 0 || CHAR_MAP[state.getMode()][ch] == 0) {
// 如果您尚未处于二进制移位模式,并且字符存在于当前模式中,则进入二进制移位模式是不值得的。这永远不会比仅在当前模式下输出字符节省位。
State binaryState = state.addBinaryShiftChar(index);
result.add(binaryState);
}
}
updateStateForPair
private static void updateStateForPair(State state, int index, int pairCode, Collection<State> result) {
State stateNoBinary = state.endBinaryShift(index);
// 可能性1。锁存到MODE_PUNCT,然后附加此代码
result.add(stateNoBinary.latchAndAppend(MODE_PUNCT, pairCode));
if (state.getMode() != MODE_PUNCT) {
// 可能性2.切换到MODE_PUNCT,然后附加此代码。
// 除MODE_PUNCT(如上所述)外的所有状态都可以改变
result.add(stateNoBinary.shiftAndAppend(MODE_PUNCT, pairCode));
}
if (pairCode == 3 || pairCode == 4) {
// 两个字符都是数字。有时最好只加两个数字
State digitState = stateNoBinary
.latchAndAppend(MODE_DIGIT, 16 - pairCode) // 数字中的句点或逗号
.latchAndAppend(MODE_DIGIT, 1); // 数字中的空格
result.add(digitState);
}
if (state.getBinaryShiftByteCount() > 0) {
// 只有当我们已经处于二进制模式时,将字符作为二进制字符才有意义。
State binaryState = state.addBinaryShiftChar(index).addBinaryShiftChar(index + 1);
result.add(binaryState);
}
}