summaryrefslogtreecommitdiffstats
path: root/gfx/ots/src/colr.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /gfx/ots/src/colr.cc
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/ots/src/colr.cc')
-rw-r--r--gfx/ots/src/colr.cc1092
1 files changed, 1092 insertions, 0 deletions
diff --git a/gfx/ots/src/colr.cc b/gfx/ots/src/colr.cc
new file mode 100644
index 0000000000..8931c77c32
--- /dev/null
+++ b/gfx/ots/src/colr.cc
@@ -0,0 +1,1092 @@
+// 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.
+
+#include "colr.h"
+
+#include "cpal.h"
+#include "maxp.h"
+#include "variations.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+// COLR - Color Table
+// http://www.microsoft.com/typography/otspec/colr.htm
+
+#define TABLE_NAME "COLR"
+
+namespace {
+
+// 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;
+
+ std::map<uint16_t, std::pair<const uint8_t*, size_t>> baseGlyphMap;
+ std::vector<std::pair<const uint8_t*, size_t>> layerList;
+
+ // 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();
+}
+
+enum Extend : uint8_t
+{
+ EXTEND_PAD = 0,
+ EXTEND_REPEAT = 1,
+ EXTEND_REFLECT = 2,
+};
+
+bool ParseColorLine(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state, bool var)
+{
+ auto& set = var ? state.varColorLines : state.colorLines;
+ if (setContains(set, data)) {
+ return true;
+ }
+ set.insert(data);
+
+ ots::Buffer subtable(data, length);
+
+ uint8_t extend;
+ uint16_t numColorStops;
+
+ if (!subtable.ReadU8(&extend) ||
+ !subtable.ReadU16(&numColorStops)) {
+ return OTS_FAILURE_MSG("Failed to read [Var]ColorLine");
+ }
+
+ if (extend != EXTEND_PAD && extend != EXTEND_REPEAT && extend != EXTEND_REFLECT) {
+ OTS_WARNING("Unknown color-line extend mode %u", extend);
+ }
+
+ 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");
+ }
+ }
+
+ return true;
+}
+
+// Composition modes
+enum CompositeMode : uint8_t
+{
+ // Porter-Duff modes
+ // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators
+ COMPOSITE_CLEAR = 0, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_clear
+ COMPOSITE_SRC = 1, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_src
+ COMPOSITE_DEST = 2, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dst
+ COMPOSITE_SRC_OVER = 3, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcover
+ COMPOSITE_DEST_OVER = 4, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstover
+ COMPOSITE_SRC_IN = 5, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcin
+ COMPOSITE_DEST_IN = 6, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstin
+ COMPOSITE_SRC_OUT = 7, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcout
+ COMPOSITE_DEST_OUT = 8, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstout
+ COMPOSITE_SRC_ATOP = 9, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcatop
+ COMPOSITE_DEST_ATOP = 10, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstatop
+ COMPOSITE_XOR = 11, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_xor
+ COMPOSITE_PLUS = 12, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_plus
+
+ // Blend modes
+ // https://www.w3.org/TR/compositing-1/#blending
+ COMPOSITE_SCREEN = 13, // https://www.w3.org/TR/compositing-1/#blendingscreen
+ COMPOSITE_OVERLAY = 14, // https://www.w3.org/TR/compositing-1/#blendingoverlay
+ COMPOSITE_DARKEN = 15, // https://www.w3.org/TR/compositing-1/#blendingdarken
+ COMPOSITE_LIGHTEN = 16, // https://www.w3.org/TR/compositing-1/#blendinglighten
+ COMPOSITE_COLOR_DODGE = 17, // https://www.w3.org/TR/compositing-1/#blendingcolordodge
+ COMPOSITE_COLOR_BURN = 18, // https://www.w3.org/TR/compositing-1/#blendingcolorburn
+ COMPOSITE_HARD_LIGHT = 19, // https://www.w3.org/TR/compositing-1/#blendinghardlight
+ COMPOSITE_SOFT_LIGHT = 20, // https://www.w3.org/TR/compositing-1/#blendingsoftlight
+ COMPOSITE_DIFFERENCE = 21, // https://www.w3.org/TR/compositing-1/#blendingdifference
+ COMPOSITE_EXCLUSION = 22, // https://www.w3.org/TR/compositing-1/#blendingexclusion
+ COMPOSITE_MULTIPLY = 23, // https://www.w3.org/TR/compositing-1/#blendingmultiply
+
+ // Modes that, uniquely, do not operate on components
+ // https://www.w3.org/TR/compositing-1/#blendingnonseparable
+ COMPOSITE_HSL_HUE = 24, // https://www.w3.org/TR/compositing-1/#blendinghue
+ COMPOSITE_HSL_SATURATION = 25, // https://www.w3.org/TR/compositing-1/#blendingsaturation
+ COMPOSITE_HSL_COLOR = 26, // https://www.w3.org/TR/compositing-1/#blendingcolor
+ COMPOSITE_HSL_LUMINOSITY = 27, // https://www.w3.org/TR/compositing-1/#blendingluminosity
+};
+
+bool ParseAffine(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ bool var)
+{
+ ots::Buffer subtable(data, length);
+
+ Fixed xx, yx, xy, yy, dx, dy;
+ VarIdxBase varIndexBase;
+
+ if (!subtable.ReadS32(&xx) ||
+ !subtable.ReadS32(&yx) ||
+ !subtable.ReadS32(&xy) ||
+ !subtable.ReadS32(&yy) ||
+ !subtable.ReadS32(&dx) ||
+ !subtable.ReadS32(&dy) ||
+ (var && !subtable.ReadU32(&varIndexBase))) {
+ return OTS_FAILURE_MSG("Failed to read [Var]Affine2x3");
+ }
+
+ return true;
+}
+
+// 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)) {
+ return OTS_FAILURE_MSG("Cycle detected in PaintColrLayers");
+ }
+ 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");
+ }
+ }
+
+ state.visited.erase(data);
+
+ return true;
+}
+
+bool ParsePaintSolid(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state, bool var)
+{
+ ots::Buffer subtable(data, length);
+
+ uint16_t paletteIndex;
+ F2DOT14 alpha;
+
+ if (!subtable.Skip(1) ||
+ !subtable.ReadU16(&paletteIndex) ||
+ !subtable.ReadS16(&alpha)) {
+ return OTS_FAILURE_MSG("Failed to read PaintSolid");
+ }
+
+ if (paletteIndex >= state.numPaletteEntries && paletteIndex != 0xffffu) {
+ return OTS_FAILURE_MSG("Invalid palette index %u PaintSolid", paletteIndex);
+ }
+
+ if (alpha < 0 || alpha > F2DOT14_one) {
+ OTS_WARNING("Alpha value outside valid range 0.0 - 1.0");
+ }
+
+ if (var) {
+ VarIdxBase varIndexBase;
+ if (!subtable.ReadU32(&varIndexBase)) {
+ return OTS_FAILURE_MSG("Failed to read PaintVarSolid");
+ }
+ }
+
+ return true;
+}
+
+bool ParsePaintLinearGradient(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state, bool var)
+{
+ ots::Buffer subtable(data, length);
+
+ uint32_t colorLineOffset;
+ FWORD x0, y0, x1, y1, x2, y2;
+ VarIdxBase varIndexBase;
+
+ if (!subtable.Skip(1) ||
+ !subtable.ReadU24(&colorLineOffset) ||
+ !subtable.ReadS16(&x0) ||
+ !subtable.ReadS16(&y0) ||
+ !subtable.ReadS16(&x1) ||
+ !subtable.ReadS16(&y1) ||
+ !subtable.ReadS16(&x2) ||
+ !subtable.ReadS16(&y2) ||
+ (var && !subtable.ReadU32(&varIndexBase))) {
+ return OTS_FAILURE_MSG("Failed to read Paint[Var]LinearGradient");
+ }
+
+ if (colorLineOffset >= length) {
+ return OTS_FAILURE_MSG("ColorLine is out of bounds");
+ }
+
+ if (!ParseColorLine(font, data + colorLineOffset, length - colorLineOffset, state, var)) {
+ return OTS_FAILURE_MSG("Failed to parse [Var]ColorLine");
+ }
+
+ return true;
+}
+
+bool ParsePaintRadialGradient(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state, bool var)
+{
+ ots::Buffer subtable(data, length);
+
+ uint32_t colorLineOffset;
+ FWORD x0, y0;
+ UFWORD radius0;
+ FWORD x1, y1;
+ UFWORD radius1;
+ VarIdxBase varIndexBase;
+
+ if (!subtable.Skip(1) ||
+ !subtable.ReadU24(&colorLineOffset) ||
+ !subtable.ReadS16(&x0) ||
+ !subtable.ReadS16(&y0) ||
+ !subtable.ReadU16(&radius0) ||
+ !subtable.ReadS16(&x1) ||
+ !subtable.ReadS16(&y1) ||
+ !subtable.ReadU16(&radius1) ||
+ (var && !subtable.ReadU32(&varIndexBase))) {
+ return OTS_FAILURE_MSG("Failed to read Paint[Var]RadialGradient");
+ }
+
+ if (colorLineOffset >= length) {
+ return OTS_FAILURE_MSG("ColorLine is out of bounds");
+ }
+
+ if (!ParseColorLine(font, data + colorLineOffset, length - colorLineOffset, state, var)) {
+ return OTS_FAILURE_MSG("Failed to parse [Var]ColorLine");
+ }
+
+ return true;
+}
+
+bool ParsePaintSweepGradient(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state, bool var)
+{
+ ots::Buffer subtable(data, length);
+
+ uint32_t colorLineOffset;
+ FWORD centerX, centerY;
+ F2DOT14 startAngle, endAngle;
+ VarIdxBase varIndexBase;
+
+ if (!subtable.Skip(1) ||
+ !subtable.ReadU24(&colorLineOffset) ||
+ !subtable.ReadS16(&centerX) ||
+ !subtable.ReadS16(&centerY) ||
+ !subtable.ReadS16(&startAngle) ||
+ !subtable.ReadS16(&endAngle) ||
+ (var && !subtable.ReadU32(&varIndexBase))) {
+ return OTS_FAILURE_MSG("Failed to read Paint[Var]SweepGradient");
+ }
+
+ if (colorLineOffset >= length) {
+ return OTS_FAILURE_MSG("ColorLine is out of bounds");
+ }
+
+ if (!ParseColorLine(font, data + colorLineOffset, length - colorLineOffset, state, var)) {
+ return OTS_FAILURE_MSG("Failed to parse [Var]ColorLine");
+ }
+
+ return true;
+}
+
+bool ParsePaintGlyph(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state)
+{
+ ots::Buffer subtable(data, length);
+
+ uint32_t paintOffset;
+ uint16_t glyphID;
+
+ if (!subtable.Skip(1) ||
+ !subtable.ReadU24(&paintOffset) ||
+ !subtable.ReadU16(&glyphID)) {
+ return OTS_FAILURE_MSG("Failed to read PaintGlyph");
+ }
+
+ if (!paintOffset || paintOffset >= length) {
+ return OTS_FAILURE_MSG("Invalid paint offset in PaintGlyph");
+ }
+
+ if (glyphID >= state.numGlyphs) {
+ return OTS_FAILURE_MSG("Glyph ID %u out of bounds", glyphID);
+ }
+
+ if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) {
+ return OTS_FAILURE_MSG("Failed to parse paint for PaintGlyph");
+ }
+
+ return true;
+}
+
+bool ParsePaintColrGlyph(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state)
+{
+ if (setContains(state.visited, data)) {
+ return OTS_FAILURE_MSG("Cycle detected in PaintColrGlyph");
+ }
+ state.visited.insert(data);
+
+ ots::Buffer subtable(data, length);
+
+ uint16_t glyphID;
+
+ 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);
+ }
+
+ state.visited.erase(data);
+
+ return true;
+}
+
+bool ParsePaintTransform(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state, bool var)
+{
+ ots::Buffer subtable(data, length);
+
+ uint32_t paintOffset;
+ uint32_t transformOffset;
+
+ if (!subtable.Skip(1) ||
+ !subtable.ReadU24(&paintOffset) ||
+ !subtable.ReadU24(&transformOffset)) {
+ return OTS_FAILURE_MSG("Failed to read Paint[Var]Transform");
+ }
+
+ if (!paintOffset || paintOffset >= length) {
+ return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Transform");
+ }
+ if (transformOffset >= length) {
+ return OTS_FAILURE_MSG("Transform offset out of bounds");
+ }
+
+ if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) {
+ return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Transform");
+ }
+
+ if (!ParseAffine(font, data + transformOffset, length - transformOffset, var)) {
+ return OTS_FAILURE_MSG("Failed to parse affine transform");
+ }
+
+ return true;
+}
+
+bool ParsePaintTranslate(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state, bool var)
+{
+ ots::Buffer subtable(data, length);
+
+ uint32_t paintOffset;
+ FWORD dx, dy;
+ VarIdxBase varIndexBase;
+
+ if (!subtable.Skip(1) ||
+ !subtable.ReadU24(&paintOffset) ||
+ !subtable.ReadS16(&dx) ||
+ !subtable.ReadS16(&dy) ||
+ (var && !subtable.ReadU32(&varIndexBase))) {
+ return OTS_FAILURE_MSG("Failed to read Paint[Var]Translate");
+ }
+
+ if (!paintOffset || paintOffset >= length) {
+ return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Translate");
+ }
+
+ if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) {
+ return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Translate");
+ }
+
+ return true;
+}
+
+bool ParsePaintScale(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state,
+ bool var, bool aroundCenter, bool uniform)
+{
+ ots::Buffer subtable(data, length);
+
+ uint32_t paintOffset;
+ F2DOT14 scaleX, scaleY;
+ FWORD centerX, centerY;
+ VarIdxBase varIndexBase;
+
+ if (!subtable.Skip(1) ||
+ !subtable.ReadU24(&paintOffset) ||
+ !subtable.ReadS16(&scaleX) ||
+ (!uniform && !subtable.ReadS16(&scaleY)) ||
+ (aroundCenter && (!subtable.ReadS16(&centerX) ||
+ !subtable.ReadS16(&centerY))) ||
+ (var && !subtable.ReadU32(&varIndexBase))) {
+ return OTS_FAILURE_MSG("Failed to read Paint[Var]Scale[...]");
+ }
+
+ if (!paintOffset || paintOffset >= length) {
+ return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Scale[...]");
+ }
+
+ if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) {
+ return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Scale[...]");
+ }
+
+ return true;
+}
+
+bool ParsePaintRotate(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state,
+ bool var, bool aroundCenter)
+{
+ ots::Buffer subtable(data, length);
+
+ uint32_t paintOffset;
+ F2DOT14 angle;
+ FWORD centerX, centerY;
+ VarIdxBase varIndexBase;
+
+ if (!subtable.Skip(1) ||
+ !subtable.ReadU24(&paintOffset) ||
+ !subtable.ReadS16(&angle) ||
+ (aroundCenter && (!subtable.ReadS16(&centerX) ||
+ !subtable.ReadS16(&centerY))) ||
+ (var && !subtable.ReadU32(&varIndexBase))) {
+ return OTS_FAILURE_MSG("Failed to read Paint[Var]Rotate[...]");
+ }
+
+ if (!paintOffset || paintOffset >= length) {
+ return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Rotate[...]");
+ }
+
+ if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) {
+ return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Rotate[...]");
+ }
+
+ return true;
+}
+
+bool ParsePaintSkew(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state,
+ bool var, bool aroundCenter)
+{
+ ots::Buffer subtable(data, length);
+
+ uint32_t paintOffset;
+ F2DOT14 xSkewAngle, ySkewAngle;
+ FWORD centerX, centerY;
+ VarIdxBase varIndexBase;
+
+ if (!subtable.Skip(1) ||
+ !subtable.ReadU24(&paintOffset) ||
+ !subtable.ReadS16(&xSkewAngle) ||
+ !subtable.ReadS16(&ySkewAngle) ||
+ (aroundCenter && (!subtable.ReadS16(&centerX) ||
+ !subtable.ReadS16(&centerY))) ||
+ (var && !subtable.ReadU32(&varIndexBase))) {
+ return OTS_FAILURE_MSG("Failed to read Paint[Var]Skew[...]");
+ }
+
+ if (!paintOffset || paintOffset >= length) {
+ return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Skew[...]");
+ }
+
+ if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) {
+ return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Skew[...]");
+ }
+
+ return true;
+}
+
+bool ParsePaintComposite(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state)
+{
+ ots::Buffer subtable(data, length);
+
+ uint32_t sourcePaintOffset;
+ uint8_t compositeMode;
+ uint32_t backdropPaintOffset;
+
+ if (!subtable.Skip(1) ||
+ !subtable.ReadU24(&sourcePaintOffset) ||
+ !subtable.ReadU8(&compositeMode) ||
+ !subtable.ReadU24(&backdropPaintOffset)) {
+ return OTS_FAILURE_MSG("Failed to read PaintComposite");
+ }
+ if (compositeMode > COMPOSITE_HSL_LUMINOSITY) {
+ OTS_WARNING("Unknown composite mode %u\n", compositeMode);
+ }
+
+ if (!sourcePaintOffset || sourcePaintOffset >= length) {
+ return OTS_FAILURE_MSG("Invalid source paint offset");
+ }
+ if (!ParsePaint(font, data + sourcePaintOffset, length - sourcePaintOffset, state)) {
+ return OTS_FAILURE_MSG("Failed to parse source paint");
+ }
+
+ if (!backdropPaintOffset || backdropPaintOffset >= length) {
+ return OTS_FAILURE_MSG("Invalid backdrop paint offset");
+ }
+ if (!ParsePaint(font, data + backdropPaintOffset, length - backdropPaintOffset, state)) {
+ return OTS_FAILURE_MSG("Failed to parse backdrop paint");
+ }
+
+ return true;
+}
+
+bool ParsePaint(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state)
+{
+ if (setContains(state.paints, data)) {
+ return true;
+ }
+
+ ots::Buffer subtable(data, length);
+
+ uint8_t format;
+
+ if (!subtable.ReadU8(&format)) {
+ return OTS_FAILURE_MSG("Failed to read paint record format");
+ }
+
+ bool ok = true;
+ switch (format) {
+ case 1: ok = ParsePaintColrLayers(font, data, length, state); break;
+ case 2: ok = ParsePaintSolid(font, data, length, state, false); break;
+ case 3: ok = ParsePaintSolid(font, data, length, state, true); break;
+ case 4: ok = ParsePaintLinearGradient(font, data, length, state, false); break;
+ case 5: ok = ParsePaintLinearGradient(font, data, length, state, true); break;
+ case 6: ok = ParsePaintRadialGradient(font, data, length, state, false); break;
+ case 7: ok = ParsePaintRadialGradient(font, data, length, state, true); break;
+ case 8: ok = ParsePaintSweepGradient(font, data, length, state, false); break;
+ case 9: ok = ParsePaintSweepGradient(font, data, length, state, true); break;
+ case 10: ok = ParsePaintGlyph(font, data, length, state); break;
+ case 11: ok = ParsePaintColrGlyph(font, data, length, state); break;
+ case 12: ok = ParsePaintTransform(font, data, length, state, false); break;
+ case 13: ok = ParsePaintTransform(font, data, length, state, true); break;
+ case 14: ok = ParsePaintTranslate(font, data, length, state, false); break;
+ case 15: ok = ParsePaintTranslate(font, data, length, state, true); break;
+ case 16: ok = ParsePaintScale(font, data, length, state, false, false, false); break; // Scale
+ case 17: ok = ParsePaintScale(font, data, length, state, true, false, false); break; // VarScale
+ case 18: ok = ParsePaintScale(font, data, length, state, false, true, false); break; // ScaleAroundCenter
+ case 19: ok = ParsePaintScale(font, data, length, state, true, true, false); break; // VarScaleAroundCenter
+ case 20: ok = ParsePaintScale(font, data, length, state, false, false, true); break; // ScaleUniform
+ case 21: ok = ParsePaintScale(font, data, length, state, true, false, true); break; // VarScaleUniform
+ case 22: ok = ParsePaintScale(font, data, length, state, false, true, true); break; // ScaleUniformAroundCenter
+ case 23: ok = ParsePaintScale(font, data, length, state, true, true, true); break; // VarScaleUniformAroundCenter
+ case 24: ok = ParsePaintRotate(font, data, length, state, false, false); break; // Rotate
+ case 25: ok = ParsePaintRotate(font, data, length, state, true, false); break; // VarRotate
+ case 26: ok = ParsePaintRotate(font, data, length, state, false, true); break; // RotateAroundCenter
+ case 27: ok = ParsePaintRotate(font, data, length, state, true, true); break; // VarRotateAroundCenter
+ case 28: ok = ParsePaintSkew(font, data, length, state, false, false); break; // Skew
+ case 29: ok = ParsePaintSkew(font, data, length, state, true, false); break; // VarSkew
+ case 30: ok = ParsePaintSkew(font, data, length, state, false, true); break; // SkewAroundCenter
+ case 31: ok = ParsePaintSkew(font, data, length, state, true, true); break; // VarSkewAroundCenter
+ case 32: ok = ParsePaintComposite(font, data, length, state); break;
+ default:
+ // Clients are supposed to ignore unknown paint types.
+ OTS_WARNING("Unknown paint type %u", format);
+ break;
+ }
+
+ state.paints.insert(data);
+
+ return ok;
+}
+
+#pragma pack(1)
+struct COLRv1
+{
+ // Version-0 fields
+ uint16_t version;
+ uint16_t numBaseGlyphRecords;
+ uint32_t baseGlyphRecordsOffset;
+ uint32_t layerRecordsOffset;
+ uint16_t numLayerRecords;
+ // Version-1 additions
+ uint32_t baseGlyphListOffset;
+ uint32_t layerListOffset;
+ uint32_t clipListOffset;
+ uint32_t varIdxMapOffset;
+ uint32_t varStoreOffset;
+};
+#pragma pack()
+
+bool ParseBaseGlyphRecords(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ uint32_t numBaseGlyphRecords,
+ uint32_t numLayerRecords,
+ colrState& state)
+{
+ ots::Buffer subtable(data, length);
+
+ 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");
+ }
+
+ prevGlyphID = glyphID;
+ }
+
+ return true;
+}
+
+bool ParseLayerRecords(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ uint32_t numLayerRecords,
+ colrState& state)
+{
+ ots::Buffer subtable(data, length);
+
+ for (auto i = 0u; i < numLayerRecords; ++i) {
+ uint16_t glyphID,
+ paletteIndex;
+
+ if (!subtable.ReadU16(&glyphID) ||
+ !subtable.ReadU16(&paletteIndex)) {
+ return OTS_FAILURE_MSG("Failed to read layer record");
+ }
+
+ if (glyphID >= int32_t(state.numGlyphs)) {
+ return OTS_FAILURE_MSG("Layer record glyph ID %u out of bounds", glyphID);
+ }
+
+ if (paletteIndex >= state.numPaletteEntries && paletteIndex != 0xffffu) {
+ return OTS_FAILURE_MSG("Invalid palette index %u in layer record", paletteIndex);
+ }
+ }
+
+ return true;
+}
+
+bool ParseBaseGlyphList(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state)
+{
+ ots::Buffer subtable(data, length);
+
+ uint32_t numBaseGlyphPaintRecords;
+
+ 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());
+ }
+
+ return true;
+}
+
+// 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");
+ }
+
+ state.layerList.push_back(std::pair<const uint8_t*, size_t>(data + paintOffset, length - paintOffset));
+ }
+
+ return true;
+}
+
+bool ParseClipBox(const ots::Font* font,
+ const uint8_t* data, size_t length)
+{
+ ots::Buffer subtable(data, length);
+
+ uint8_t format;
+ FWORD xMin, yMin, xMax, yMax;
+
+ if (!subtable.ReadU8(&format) ||
+ !subtable.ReadS16(&xMin) ||
+ !subtable.ReadS16(&yMin) ||
+ !subtable.ReadS16(&xMax) ||
+ !subtable.ReadS16(&yMax)) {
+ return OTS_FAILURE_MSG("Failed to read clip box");
+ }
+
+ switch (format) {
+ case 1:
+ break;
+ case 2: {
+ uint32_t varIndexBase;
+ if (!subtable.ReadU32(&varIndexBase)) {
+ return OTS_FAILURE_MSG("Failed to read clip box");
+ }
+ break;
+ }
+ default:
+ return OTS_FAILURE_MSG("Invalid clip box format: %u", format);
+ }
+
+ if (xMin > xMax || yMin > yMax) {
+ return OTS_FAILURE_MSG("Invalid clip box bounds");
+ }
+
+ return true;
+}
+
+bool ParseClipList(const ots::Font* font,
+ const uint8_t* data, size_t length,
+ colrState& state)
+{
+ ots::Buffer subtable(data, length);
+
+ uint8_t format;
+ uint32_t numClipRecords;
+
+ if (!subtable.ReadU8(&format) ||
+ !subtable.ReadU32(&numClipRecords)) {
+ return OTS_FAILURE_MSG("Failed to read clip list");
+ }
+
+ if (format != 1) {
+ return OTS_FAILURE_MSG("Unknown clip list format: %u", format);
+ }
+
+ int32_t prevEndGlyphID = -1;
+ for (auto i = 0u; i < numClipRecords; ++i) {
+ uint16_t startGlyphID,
+ endGlyphID;
+ uint32_t clipBoxOffset;
+
+ if (!subtable.ReadU16(&startGlyphID) ||
+ !subtable.ReadU16(&endGlyphID) ||
+ !subtable.ReadU24(&clipBoxOffset)) {
+ return OTS_FAILURE_MSG("Failed to read clip list");
+ }
+
+ if (int32_t(startGlyphID) <= prevEndGlyphID ||
+ endGlyphID < startGlyphID ||
+ endGlyphID >= int32_t(state.numGlyphs)) {
+ return OTS_FAILURE_MSG("Bad or out-of-order glyph ID range %u-%u in clip list", startGlyphID, endGlyphID);
+ }
+
+ if (clipBoxOffset >= length) {
+ return OTS_FAILURE_MSG("Clip box offset out of bounds for glyphs %u-%u", startGlyphID, endGlyphID);
+ }
+
+ if (!ParseClipBox(font, data + clipBoxOffset, length - clipBoxOffset)) {
+ return OTS_FAILURE_MSG("Failed to parse clip box for glyphs %u-%u", startGlyphID, endGlyphID);
+ }
+
+ prevEndGlyphID = endGlyphID;
+ }
+
+ return true;
+}
+
+} // namespace
+
+namespace ots {
+
+bool OpenTypeCOLR::Parse(const uint8_t *data, size_t length) {
+ // Parsing COLR table requires |maxp->num_glyphs| and |cpal->num_palette_entries|.
+ Font *font = GetFont();
+ Buffer table(data, length);
+
+ uint32_t headerSize = offsetof(COLRv1, baseGlyphListOffset);
+
+ // Version 0 header fields.
+ uint16_t version = 0;
+ uint16_t numBaseGlyphRecords = 0;
+ uint32_t offsetBaseGlyphRecords = 0;
+ uint32_t offsetLayerRecords = 0;
+ uint16_t numLayerRecords = 0;
+ if (!table.ReadU16(&version) ||
+ !table.ReadU16(&numBaseGlyphRecords) ||
+ !table.ReadU32(&offsetBaseGlyphRecords) ||
+ !table.ReadU32(&offsetLayerRecords) ||
+ !table.ReadU16(&numLayerRecords)) {
+ return Error("Incomplete table");
+ }
+
+ if (version > 1) {
+ return Error("Unknown COLR table version %u", version);
+ }
+
+ // Additional header fields for Version 1.
+ uint32_t offsetBaseGlyphList = 0;
+ uint32_t offsetLayerList = 0;
+ uint32_t offsetClipList = 0;
+ uint32_t offsetVarIdxMap = 0;
+ uint32_t offsetItemVariationStore = 0;
+
+ if (version == 1) {
+ if (!table.ReadU32(&offsetBaseGlyphList) ||
+ !table.ReadU32(&offsetLayerList) ||
+ !table.ReadU32(&offsetClipList) ||
+ !table.ReadU32(&offsetVarIdxMap) ||
+ !table.ReadU32(&offsetItemVariationStore)) {
+ return Error("Incomplete v.1 table");
+ }
+ headerSize = sizeof(COLRv1);
+ }
+
+ colrState state;
+
+ auto* maxp = static_cast<ots::OpenTypeMAXP*>(font->GetTypedTable(OTS_TAG_MAXP));
+ if (!maxp) {
+ return OTS_FAILURE_MSG("Required maxp table missing");
+ }
+ state.numGlyphs = maxp->num_glyphs;
+
+ 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");
+ }
+ }
+
+ this->m_data = data;
+ this->m_length = length;
+ return true;
+}
+
+bool OpenTypeCOLR::Serialize(OTSStream *out) {
+ if (!out->Write(this->m_data, this->m_length)) {
+ return Error("Failed to write COLR table");
+ }
+
+ return true;
+}
+
+} // namespace ots
+
+#undef TABLE_NAME