// Copyright (c) 2022 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file.
// Some typedefs so that our local variables will more closely parallel the spec. typedef int16_t F2DOT14; // 2.14 fixed-point typedef int32_t Fixed; // 16.16 fixed-point typedef int16_t FWORD; // A 16-bit integer value in font design units typedef uint16_t UFWORD; typedef uint32_t VarIdxBase;
constexpr F2DOT14 F2DOT14_one = 0x4000;
struct colrState
{ // We track addresses of structs that we have already seen and checked, // because fonts may share these among multiple glyph descriptions. // (We only do this for color lines, which may be large, depending on the // number of color stops, and for paints, which may refer to an extensive // sub-graph; for small records like ClipBox and Affine2x3, we just read // them directly whenever encountered.)
std::set<const uint8_t*> colorLines;
std::set<const uint8_t*> varColorLines;
std::set<const uint8_t*> paints;
// Set of visited records, for cycle detection. // We don't actually need to track every paint table here, as most of the // paint-offsets that create the graph can only point forwards; // only PaintColrLayers and PaintColrGlyph can cause a backward jump // and hence a potential cycle.
std::set<const uint8_t*> visited;
uint16_t numGlyphs; // from maxp
uint16_t numPaletteEntries; // from CPAL
};
// std::set<T>::contains isn't available until C++20, so we use this instead // for better compatibility with old compilers. template <typename T> bool setContains(const std::set<T>& set, T item)
{ return set.find(item) != set.end();
}
for (auto i = 0u; i < numColorStops; ++i) {
F2DOT14 stopOffset;
uint16_t paletteIndex;
F2DOT14 alpha;
VarIdxBase varIndexBase;
if (!subtable.ReadS16(&stopOffset) ||
!subtable.ReadU16(&paletteIndex) ||
!subtable.ReadS16(&alpha) ||
(var && !subtable.ReadU32(&varIndexBase))) { return OTS_FAILURE_MSG("Failed to read [Var]ColorStop");
}
if (paletteIndex >= state.numPaletteEntries && paletteIndex != 0xffffu) { return OTS_FAILURE_MSG("Invalid palette index %u in color stop", paletteIndex);
}
if (alpha < 0 || alpha > F2DOT14_one) {
OTS_WARNING("Alpha value outside valid range 0.0 - 1.0");
}
}
// Paint-record dispatch function that reads the format byte and then dispatches // to one of the record-specific helpers. bool ParsePaint(const ots::Font* font, const uint8_t* data, size_t length, colrState& state);
// All these paint record parsers start with Skip(1) to ignore the format field, // which the caller has already read in order to dispatch here.
bool ParsePaintColrLayers(const ots::Font* font, const uint8_t* data, size_t length,
colrState& state)
{ if (setContains(state.visited, data)) { #ifdef OTS_COLR_CYCLE_CHECK // A cycle would imply an infinite loop during painting, unless the renderer // detects and breaks it. To be safe, reject the table. return OTS_FAILURE_MSG("Cycle detected in PaintColrLayers"); #else // Just issue a warning and return (as we've already checked this subgraph).
OTS_WARNING("Cycle detected in COLRv1 glyph paint graph (PaintColrLayers)\n"); returntrue; #endif
}
state.visited.insert(data);
ots::Buffer subtable(data, length);
uint8_t numLayers;
uint32_t firstLayerIndex;
if (!subtable.Skip(1) ||
!subtable.ReadU8(&numLayers) ||
!subtable.ReadU32(&firstLayerIndex)) { return OTS_FAILURE_MSG("Failed to read PaintColrLayers record");
}
if (uint64_t(firstLayerIndex) + numLayers > state.layerList.size()) { return OTS_FAILURE_MSG("PaintColrLayers exceeds bounds of layer list");
}
for (auto i = firstLayerIndex; i < firstLayerIndex + numLayers; ++i) { auto layer = state.layerList[i]; if (!ParsePaint(font, layer.first, layer.second, state)) { return OTS_FAILURE_MSG("Failed to parse layer");
}
}
if (!subtable.Skip(1) ||
!subtable.ReadU16(&glyphID)) { return OTS_FAILURE_MSG("Failed to read PaintColrGlyph");
}
auto baseGlyph = state.baseGlyphMap.find(glyphID); if (baseGlyph == state.baseGlyphMap.end()) { return OTS_FAILURE_MSG("Glyph ID %u not found in BaseGlyphList", glyphID);
}
if (!ParsePaint(font, baseGlyph->second.first, baseGlyph->second.second, state)) { return OTS_FAILURE_MSG("Failed to parse referenced color glyph %u", glyphID);
}
int32_t prevGlyphID = -1; for (auto i = 0u; i < numBaseGlyphRecords; ++i) {
uint16_t glyphID,
firstLayerIndex,
numLayers;
if (!subtable.ReadU16(&glyphID) ||
!subtable.ReadU16(&firstLayerIndex) ||
!subtable.ReadU16(&numLayers)) { return OTS_FAILURE_MSG("Failed to read base glyph record");
}
if (glyphID >= int32_t(state.numGlyphs)) { return OTS_FAILURE_MSG("Base glyph record glyph ID %u out of bounds", glyphID);
}
if (int32_t(glyphID) <= prevGlyphID) { return OTS_FAILURE_MSG("Base glyph record for glyph ID %u out of order", glyphID);
}
if (uint32_t(firstLayerIndex) + uint32_t(numLayers) > numLayerRecords) { return OTS_FAILURE_MSG("Layer index out of bounds");
}
if (!subtable.ReadU32(&numBaseGlyphPaintRecords)) { return OTS_FAILURE_MSG("Failed to read base glyph list");
}
int32_t prevGlyphID = -1; // We loop over the list twice, first to collect all the glyph IDs present, // and then to check they can be parsed.
size_t saveOffset = subtable.offset(); for (auto i = 0u; i < numBaseGlyphPaintRecords; ++i) {
uint16_t glyphID;
uint32_t paintOffset;
if (!subtable.ReadU16(&glyphID) ||
!subtable.ReadU32(&paintOffset)) { return OTS_FAILURE_MSG("Failed to read base glyph list");
}
if (glyphID >= int32_t(state.numGlyphs)) { return OTS_FAILURE_MSG("Base glyph list glyph ID %u out of bounds", glyphID);
}
if (int32_t(glyphID) <= prevGlyphID) { return OTS_FAILURE_MSG("Base glyph list record for glyph ID %u out of order", glyphID);
}
if (!paintOffset || paintOffset >= length) { return OTS_FAILURE_MSG("Invalid paint offset for base glyph ID %u", glyphID);
}
// Record the base glyph list records so that we can follow them when processing // PaintColrGlyph records.
state.baseGlyphMap[glyphID] = std::pair<const uint8_t*, size_t>(data + paintOffset, length - paintOffset);
prevGlyphID = glyphID;
}
subtable.set_offset(saveOffset); for (auto i = 0u; i < numBaseGlyphPaintRecords; ++i) {
uint16_t glyphID;
uint32_t paintOffset;
if (!subtable.ReadU16(&glyphID) ||
!subtable.ReadU32(&paintOffset)) { return OTS_FAILURE_MSG("Failed to read base glyph list");
}
if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) { return OTS_FAILURE_MSG("Failed to parse paint for base glyph ID %u", glyphID);
}
// After each base glyph record is fully processed, the visited set should be clear; // otherwise, we have a bug in the cycle-detection logic.
assert(state.visited.empty());
}
returntrue;
}
// We call this twice: first with parsePaints = false, to just get the number of layers, // and then with parsePaints = true to actually descend the paint graphs. bool ParseLayerList(const ots::Font* font, const uint8_t* data, size_t length,
colrState& state)
{
ots::Buffer subtable(data, length);
uint32_t numLayers; if (!subtable.ReadU32(&numLayers)) { return OTS_FAILURE_MSG("Failed to read layer list");
}
for (auto i = 0u; i < numLayers; ++i) {
uint32_t paintOffset;
if (!subtable.ReadU32(&paintOffset)) { return OTS_FAILURE_MSG("Failed to read layer list");
}
if (!paintOffset || paintOffset >= length) { return OTS_FAILURE_MSG("Invalid paint offset in layer list");
}
auto *cpal = static_cast<ots::OpenTypeCPAL*>(font->GetTypedTable(OTS_TAG_CPAL)); if (!cpal) { return OTS_FAILURE_MSG("Required cpal table missing");
}
state.numPaletteEntries = cpal->num_palette_entries;
if (numBaseGlyphRecords) { if (offsetBaseGlyphRecords < headerSize || offsetBaseGlyphRecords >= length) { return Error("Bad base glyph records offset in table header");
} if (!ParseBaseGlyphRecords(font, data + offsetBaseGlyphRecords, length - offsetBaseGlyphRecords,
numBaseGlyphRecords, numLayerRecords, state)) { return Error("Failed to parse base glyph records");
}
}
if (numLayerRecords) { if (offsetLayerRecords < headerSize || offsetLayerRecords >= length) { return Error("Bad layer records offset in table header");
} if (!ParseLayerRecords(font, data + offsetLayerRecords, length - offsetLayerRecords, numLayerRecords,
state)) { return Error("Failed to parse layer records");
}
}
if (offsetLayerList) { if (offsetLayerList < headerSize || offsetLayerList >= length) { return Error("Bad layer list offset in table header");
} // This reads the layer list into our state.layerList vector, but does not parse the // paint graphs within it; these will be checked when visited via PaintColrLayers. if (!ParseLayerList(font, data + offsetLayerList, length - offsetLayerList, state)) { return Error("Failed to parse layer list");
}
}
if (offsetBaseGlyphList) { if (offsetBaseGlyphList < headerSize || offsetBaseGlyphList >= length) { return Error("Bad base glyph list offset in table header");
} // Here, we recursively check the paint graph starting at each base glyph record. if (!ParseBaseGlyphList(font, data + offsetBaseGlyphList, length - offsetBaseGlyphList,
state)) { return Error("Failed to parse base glyph list");
}
}
if (offsetClipList) { if (offsetClipList < headerSize || offsetClipList >= length) { return Error("Bad clip list offset in table header");
} if (!ParseClipList(font, data + offsetClipList, length - offsetClipList, state)) { return Error("Failed to parse clip list");
}
}
if (offsetVarIdxMap) { if (offsetVarIdxMap < headerSize || offsetVarIdxMap >= length) { return Error("Bad delta set index offset in table header");
} if (!ParseDeltaSetIndexMap(font, data + offsetVarIdxMap, length - offsetVarIdxMap)) { return Error("Failed to parse delta set index map");
}
}
if (offsetItemVariationStore) { if (offsetItemVariationStore < headerSize || offsetItemVariationStore >= length) { return Error("Bad item variation store offset in table header");
} if (!ParseItemVariationStore(font, data + offsetItemVariationStore, length - offsetItemVariationStore)) { return Error("Failed to parse item variation store");
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.