in src/core/cff_parser.js [486:690]
parseCharString(state, data, localSubrIndex, globalSubrIndex) {
if (!data || state.callDepth > MAX_SUBR_NESTING) {
return false;
}
let stackSize = state.stackSize;
const stack = state.stack;
let length = data.length;
for (let j = 0; j < length; ) {
const value = data[j++];
let validationCommand = null;
if (value === 12) {
const q = data[j++];
if (q === 0) {
// The CFF specification state that the 'dotsection' command
// (12, 0) is deprecated and treated as a no-op, but all Type2
// charstrings processors should support them. Unfortunately
// the font sanitizer don't. As a workaround the sequence (12, 0)
// is replaced by a useless (0, hmoveto).
data[j - 2] = 139;
data[j - 1] = 22;
stackSize = 0;
} else {
validationCommand = CharstringValidationData12[q];
}
} else if (value === 28) {
// number (16 bit)
stack[stackSize] = readInt16(data, j);
j += 2;
stackSize++;
} else if (value === 14) {
if (stackSize >= 4) {
stackSize -= 4;
if (this.seacAnalysisEnabled) {
state.seac = stack.slice(stackSize, stackSize + 4);
return false;
}
}
validationCommand = CharstringValidationData[value];
} else if (value >= 32 && value <= 246) {
// number
stack[stackSize] = value - 139;
stackSize++;
} else if (value >= 247 && value <= 254) {
// number (+1 bytes)
stack[stackSize] =
value < 251
? ((value - 247) << 8) + data[j] + 108
: -((value - 251) << 8) - data[j] - 108;
j++;
stackSize++;
} else if (value === 255) {
// number (32 bit)
stack[stackSize] =
((data[j] << 24) |
(data[j + 1] << 16) |
(data[j + 2] << 8) |
data[j + 3]) /
65536;
j += 4;
stackSize++;
} else if (value === 19 || value === 20) {
state.hints += stackSize >> 1;
if (state.hints === 0) {
// Not a valid value (see bug 1529502): just remove it.
data.copyWithin(j - 1, j, -1);
j -= 1;
length -= 1;
continue;
}
// skipping right amount of hints flag data
j += (state.hints + 7) >> 3;
stackSize %= 2;
validationCommand = CharstringValidationData[value];
} else if (value === 10 || value === 29) {
const subrsIndex = value === 10 ? localSubrIndex : globalSubrIndex;
if (!subrsIndex) {
validationCommand = CharstringValidationData[value];
warn("Missing subrsIndex for " + validationCommand.id);
return false;
}
let bias = 32768;
if (subrsIndex.count < 1240) {
bias = 107;
} else if (subrsIndex.count < 33900) {
bias = 1131;
}
const subrNumber = stack[--stackSize] + bias;
if (
subrNumber < 0 ||
subrNumber >= subrsIndex.count ||
isNaN(subrNumber)
) {
validationCommand = CharstringValidationData[value];
warn("Out of bounds subrIndex for " + validationCommand.id);
return false;
}
state.stackSize = stackSize;
state.callDepth++;
const valid = this.parseCharString(
state,
subrsIndex.get(subrNumber),
localSubrIndex,
globalSubrIndex
);
if (!valid) {
return false;
}
state.callDepth--;
stackSize = state.stackSize;
continue;
} else if (value === 11) {
state.stackSize = stackSize;
return true;
} else if (value === 0 && j === data.length) {
// Operator 0 is not used according to the current spec and
// it's the last char and consequently it's likely a terminator.
// So just replace it by endchar command to make OTS happy.
data[j - 1] = 14;
validationCommand = CharstringValidationData[14];
} else if (value === 9) {
// Not a valid value.
data.copyWithin(j - 1, j, -1);
j -= 1;
length -= 1;
continue;
} else {
validationCommand = CharstringValidationData[value];
}
if (validationCommand) {
if (validationCommand.stem) {
state.hints += stackSize >> 1;
if (value === 3 || value === 23) {
// vstem or vstemhm.
state.hasVStems = true;
} else if (state.hasVStems && (value === 1 || value === 18)) {
// Some browsers don't draw glyphs that specify vstems before
// hstems. As a workaround, replace hstem (1) and hstemhm (18)
// with a pointless vstem (3) or vstemhm (23).
warn("CFF stem hints are in wrong order");
data[j - 1] = value === 1 ? 3 : 23;
}
}
if ("min" in validationCommand) {
if (!state.undefStack && stackSize < validationCommand.min) {
warn(
"Not enough parameters for " +
validationCommand.id +
"; actual: " +
stackSize +
", expected: " +
validationCommand.min
);
if (stackSize === 0) {
// Just "fix" the outline in replacing command by a endchar:
// it could lead to wrong rendering of some glyphs or not.
// For example, the pdf in #6132 is well-rendered.
data[j - 1] = 14;
return true;
}
return false;
}
}
if (state.firstStackClearing && validationCommand.stackClearing) {
state.firstStackClearing = false;
// the optional character width can be found before the first
// stack-clearing command arguments
stackSize -= validationCommand.min;
if (stackSize >= 2 && validationCommand.stem) {
// there are even amount of arguments for stem commands
stackSize %= 2;
} else if (stackSize > 1) {
warn("Found too many parameters for stack-clearing command");
}
if (stackSize > 0) {
// Width can be any number since its the difference
// from nominalWidthX.
state.width = stack[stackSize - 1];
}
}
if ("stackDelta" in validationCommand) {
if ("stackFn" in validationCommand) {
validationCommand.stackFn(stack, stackSize);
}
stackSize += validationCommand.stackDelta;
} else if (validationCommand.stackClearing) {
stackSize = 0;
} else if (validationCommand.resetStack) {
stackSize = 0;
state.undefStack = false;
} else if (validationCommand.undefStack) {
stackSize = 0;
state.undefStack = true;
state.firstStackClearing = false;
}
}
}
if (length < data.length) {
data.fill(/* endchar = */ 14, length);
}
state.stackSize = stackSize;
return true;
}