parseCharString()

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;
  }