Index: src/math.cc |
diff --git a/src/math.cc b/src/math.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..06e1361c27a58706073c0ecdf0e329fbb71f87a8 |
--- /dev/null |
+++ b/src/math.cc |
@@ -0,0 +1,594 @@ |
+// Copyright (c) 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+// We use an underscore to avoid confusion with the standard math.h library. |
+#include "math_.h" |
+ |
+#include <limits> |
+#include <vector> |
+ |
+#include "layout.h" |
+#include "maxp.h" |
+ |
+// MATH - The MATH Table |
+// The specification is not yet public but has been submitted to the MPEG group |
+// in response to the 'Call for Proposals for ISO/IEC 14496-22 "Open Font |
+// Format" Color Font Technology and MATH layout support'. Meanwhile, you can |
+// contact Microsoft's engineer Murray Sargent to obtain a copy. |
+ |
+namespace { |
+ |
+// The size of MATH header. |
+// Version |
+// MathConstants |
+// MathGlyphInfo |
+// MathVariants |
+const unsigned kMathHeaderSize = 4 + 3 * 2; |
+ |
+// The size of the MathGlyphInfo header. |
+// MathItalicsCorrectionInfo |
+// MathTopAccentAttachment |
+// ExtendedShapeCoverage |
+// MathKernInfo |
+const unsigned kMathGlyphInfoHeaderSize = 4 * 2; |
+ |
+// The size of the MathValueRecord. |
+// Value |
+// DeviceTable |
+const unsigned kMathValueRecordSize = 2 * 2; |
+ |
+// The size of the GlyphPartRecord. |
+// glyph |
+// StartConnectorLength |
+// EndConnectorLength |
+// FullAdvance |
+// PartFlags |
+const unsigned kGlyphPartRecordSize = 5 * 2; |
+ |
+// Shared Table: MathValueRecord |
+ |
+bool ParseMathValueRecord(ots::Buffer* subtable, const uint8_t *data, |
+ const size_t length) { |
+ // Check the Value field. |
+ if (!subtable->Skip(2)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ // Check the offset to device table. |
+ uint16_t offset = 0; |
+ if (!subtable->ReadU16(&offset)) { |
+ return OTS_FAILURE(); |
+ } |
+ if (offset) { |
+ if (offset >= length) { |
+ return OTS_FAILURE(); |
+ } |
+ if (!ots::ParseDeviceTable(data + offset, length - offset)) { |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+bool ParseMathConstantsTable(const uint8_t *data, size_t length) { |
+ ots::Buffer subtable(data, length); |
+ |
+ // Part 1: int16 or uint16 constants. |
+ // ScriptPercentScaleDown |
+ // ScriptScriptPercentScaleDown |
+ // DelimitedSubFormulaMinHeight |
+ // DisplayOperatorMinHeight |
+ if (!subtable.Skip(4 * 2)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ // Part 2: MathValueRecord constants. |
+ // MathLeading |
+ // AxisHeight |
+ // AccentBaseHeight |
+ // FlattenedAccentBaseHeight |
+ // SubscriptShiftDown |
+ // SubscriptTopMax |
+ // SubscriptBaselineDropMin |
+ // SuperscriptShiftUp |
+ // SuperscriptShiftUpCramped |
+ // SuperscriptBottomMin |
+ // |
+ // SuperscriptBaselineDropMax |
+ // SubSuperscriptGapMin |
+ // SuperscriptBottomMaxWithSubscript |
+ // SpaceAfterScript |
+ // UpperLimitGapMin |
+ // UpperLimitBaselineRiseMin |
+ // LowerLimitGapMin |
+ // LowerLimitBaselineDropMin |
+ // StackTopShiftUp |
+ // StackTopDisplayStyleShiftUp |
+ // |
+ // StackBottomShiftDown |
+ // StackBottomDisplayStyleShiftDown |
+ // StackGapMin |
+ // StackDisplayStyleGapMin |
+ // StretchStackTopShiftUp |
+ // StretchStackBottomShiftDown |
+ // StretchStackGapAboveMin |
+ // StretchStackGapBelowMin |
+ // FractionNumeratorShiftUp |
+ // FractionNumeratorDisplayStyleShiftUp |
+ // |
+ // FractionDenominatorShiftDown |
+ // FractionDenominatorDisplayStyleShiftDown |
+ // FractionNumeratorGapMin |
+ // FractionNumDisplayStyleGapMin |
+ // FractionRuleThickness |
+ // FractionDenominatorGapMin |
+ // FractionDenomDisplayStyleGapMin |
+ // SkewedFractionHorizontalGap |
+ // SkewedFractionVerticalGap |
+ // OverbarVerticalGap |
+ // |
+ // OverbarRuleThickness |
+ // OverbarExtraAscender |
+ // UnderbarVerticalGap |
+ // UnderbarRuleThickness |
+ // UnderbarExtraDescender |
+ // RadicalVerticalGap |
+ // RadicalDisplayStyleVerticalGap |
+ // RadicalRuleThickness |
+ // RadicalExtraAscender |
+ // RadicalKernBeforeDegree |
+ // |
+ // RadicalKernAfterDegree |
+ for (unsigned i = 0; i < static_cast<unsigned>(51); ++i) { |
+ if (!ParseMathValueRecord(&subtable, data, length)) { |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ |
+ // Part 3: uint16 constant |
+ // RadicalDegreeBottomRaisePercent |
+ if (!subtable.Skip(2)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ return true; |
+} |
+ |
+bool ParseMathValueRecordSequenceForGlyphs(ots::Buffer* subtable, |
+ const uint8_t *data, |
+ const size_t length, |
+ const uint16_t num_glyphs) { |
+ // Check the header. |
+ uint16_t offset_coverage = 0; |
+ uint16_t sequence_count = 0; |
+ if (!subtable->ReadU16(&offset_coverage) || |
+ !subtable->ReadU16(&sequence_count)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ const unsigned sequence_end = static_cast<unsigned>(2 * 2) + |
+ sequence_count * kMathValueRecordSize; |
+ if (sequence_end > std::numeric_limits<uint16_t>::max()) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ // Check coverage table. |
+ if (offset_coverage < sequence_end || offset_coverage >= length) { |
+ return OTS_FAILURE(); |
+ } |
+ if (!ots::ParseCoverageTable(data + offset_coverage, |
+ length - offset_coverage, |
+ num_glyphs, sequence_count)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ // Check sequence. |
+ for (unsigned i = 0; i < sequence_count; ++i) { |
+ if (!ParseMathValueRecord(subtable, data, length)) { |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+bool ParseMathItalicsCorrectionInfoTable(const uint8_t *data, |
+ size_t length, |
+ const uint16_t num_glyphs) { |
+ ots::Buffer subtable(data, length); |
+ return ParseMathValueRecordSequenceForGlyphs(&subtable, data, length, |
+ num_glyphs); |
+} |
+ |
+bool ParseMathTopAccentAttachmentTable(const uint8_t *data, |
+ size_t length, |
+ const uint16_t num_glyphs) { |
+ ots::Buffer subtable(data, length); |
+ return ParseMathValueRecordSequenceForGlyphs(&subtable, data, length, |
+ num_glyphs); |
+} |
+ |
+bool ParseMathKernTable(const uint8_t *data, size_t length) { |
+ ots::Buffer subtable(data, length); |
+ |
+ // Check the Height count. |
+ uint16_t height_count = 0; |
+ if (!subtable.ReadU16(&height_count)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ // Check the Correction Heights. |
+ for (unsigned i = 0; i < height_count; ++i) { |
+ if (!ParseMathValueRecord(&subtable, data, length)) { |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ |
+ // Check the Kern Values. |
+ for (unsigned i = 0; i <= height_count; ++i) { |
+ if (!ParseMathValueRecord(&subtable, data, length)) { |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+bool ParseMathKernInfoTable(const uint8_t *data, size_t length, |
+ const uint16_t num_glyphs) { |
+ ots::Buffer subtable(data, length); |
+ |
+ // Check the header. |
+ uint16_t offset_coverage = 0; |
+ uint16_t sequence_count = 0; |
+ if (!subtable.ReadU16(&offset_coverage) || |
+ !subtable.ReadU16(&sequence_count)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ const unsigned sequence_end = static_cast<unsigned>(2 * 2) + |
+ sequence_count * 4 * 2; |
+ if (sequence_end > std::numeric_limits<uint16_t>::max()) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ // Check coverage table. |
+ if (offset_coverage < sequence_end || offset_coverage >= length) { |
+ return OTS_FAILURE(); |
+ } |
+ if (!ots::ParseCoverageTable(data + offset_coverage, length - offset_coverage, |
+ num_glyphs, sequence_count)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ // Check sequence of MathKernInfoRecord |
+ for (unsigned i = 0; i < sequence_count; ++i) { |
+ // Check TopRight, TopLeft, BottomRight and BottomLeft Math Kern. |
+ for (unsigned j = 0; j < 4; ++j) { |
+ uint16_t offset_math_kern = 0; |
+ if (!subtable.ReadU16(&offset_math_kern)) { |
+ return OTS_FAILURE(); |
+ } |
+ if (offset_math_kern) { |
+ if (offset_math_kern < sequence_end || offset_math_kern >= length || |
+ !ParseMathKernTable(data + offset_math_kern, |
+ length - offset_math_kern)) { |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+bool ParseMathGlyphInfoTable(const uint8_t *data, size_t length, |
+ const uint16_t num_glyphs) { |
+ ots::Buffer subtable(data, length); |
+ |
+ // Check Header. |
+ uint16_t offset_math_italics_correction_info = 0; |
+ uint16_t offset_math_top_accent_attachment = 0; |
+ uint16_t offset_extended_shaped_coverage = 0; |
+ uint16_t offset_math_kern_info = 0; |
+ if (!subtable.ReadU16(&offset_math_italics_correction_info) || |
+ !subtable.ReadU16(&offset_math_top_accent_attachment) || |
+ !subtable.ReadU16(&offset_extended_shaped_coverage) || |
+ !subtable.ReadU16(&offset_math_kern_info)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ // Check subtables. |
+ // The specification does not say whether the offsets for |
+ // MathItalicsCorrectionInfo, MathTopAccentAttachment and MathKernInfo may |
+ // be NULL, but that's the case in some fonts (e.g STIX) so we accept that. |
+ if (offset_math_italics_correction_info) { |
+ if (offset_math_italics_correction_info >= length || |
+ offset_math_italics_correction_info < kMathGlyphInfoHeaderSize || |
+ !ParseMathItalicsCorrectionInfoTable( |
+ data + offset_math_italics_correction_info, |
+ length - offset_math_italics_correction_info, |
+ num_glyphs)) { |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ if (offset_math_top_accent_attachment) { |
+ if (offset_math_top_accent_attachment >= length || |
+ offset_math_top_accent_attachment < kMathGlyphInfoHeaderSize || |
+ !ParseMathTopAccentAttachmentTable(data + |
+ offset_math_top_accent_attachment, |
+ length - |
+ offset_math_top_accent_attachment, |
+ num_glyphs)) { |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ if (offset_extended_shaped_coverage) { |
+ if (offset_extended_shaped_coverage >= length || |
+ offset_extended_shaped_coverage < kMathGlyphInfoHeaderSize || |
+ !ots::ParseCoverageTable(data + offset_extended_shaped_coverage, |
+ length - offset_extended_shaped_coverage, |
+ num_glyphs)) { |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ if (offset_math_kern_info) { |
+ if (offset_math_kern_info >= length || |
+ offset_math_kern_info < kMathGlyphInfoHeaderSize || |
+ !ParseMathKernInfoTable(data + offset_math_kern_info, |
+ length - offset_math_kern_info, num_glyphs)) { |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+bool ParseGlyphAssemblyTable(const uint8_t *data, |
+ size_t length, const uint16_t num_glyphs) { |
+ ots::Buffer subtable(data, length); |
+ |
+ // Check the header. |
+ uint16_t part_count = 0; |
+ if (!ParseMathValueRecord(&subtable, data, length) || |
+ !subtable.ReadU16(&part_count)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ const unsigned sequence_end = kMathValueRecordSize + |
+ static_cast<unsigned>(2) + part_count * kGlyphPartRecordSize; |
+ if (sequence_end > std::numeric_limits<uint16_t>::max()) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ // Check the sequence of GlyphPartRecord. |
+ for (unsigned i = 0; i < part_count; ++i) { |
+ uint16_t glyph = 0; |
+ uint16_t part_flags = 0; |
+ if (!subtable.ReadU16(&glyph) || |
+ !subtable.Skip(2 * 3) || |
+ !subtable.ReadU16(&part_flags)) { |
+ return OTS_FAILURE(); |
+ } |
+ if (glyph >= num_glyphs) { |
+ OTS_WARNING("bad glyph ID: %u", glyph); |
+ return OTS_FAILURE(); |
+ } |
+ if (part_flags & ~0x00000001) { |
+ OTS_WARNING("unknown part flag: %u", part_flags); |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+bool ParseMathGlyphConstructionTable(const uint8_t *data, |
+ size_t length, const uint16_t num_glyphs) { |
+ ots::Buffer subtable(data, length); |
+ |
+ // Check the header. |
+ uint16_t offset_glyph_assembly = 0; |
+ uint16_t variant_count = 0; |
+ if (!subtable.ReadU16(&offset_glyph_assembly) || |
+ !subtable.ReadU16(&variant_count)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ const unsigned sequence_end = static_cast<unsigned>(2 * 2) + |
+ variant_count * 2 * 2; |
+ if (sequence_end > std::numeric_limits<uint16_t>::max()) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ // Check the GlyphAssembly offset. |
+ if (offset_glyph_assembly) { |
+ if (offset_glyph_assembly >= length || |
+ offset_glyph_assembly < sequence_end) { |
+ return OTS_FAILURE(); |
+ } |
+ if (!ParseGlyphAssemblyTable(data + offset_glyph_assembly, |
+ length - offset_glyph_assembly, num_glyphs)) { |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ |
+ // Check the sequence of MathGlyphVariantRecord. |
+ for (unsigned i = 0; i < variant_count; ++i) { |
+ uint16_t glyph = 0; |
+ if (!subtable.ReadU16(&glyph) || |
+ !subtable.Skip(2)) { |
+ return OTS_FAILURE(); |
+ } |
+ if (glyph >= num_glyphs) { |
+ OTS_WARNING("bad glyph ID: %u", glyph); |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+bool ParseMathGlyphConstructionSequence(ots::Buffer* subtable, |
+ const uint8_t *data, |
+ size_t length, |
+ const uint16_t num_glyphs, |
+ uint16_t offset_coverage, |
+ uint16_t glyph_count, |
+ const unsigned sequence_end) { |
+ // Check coverage table. |
+ if (offset_coverage < sequence_end || offset_coverage >= length) { |
+ return OTS_FAILURE(); |
+ } |
+ if (!ots::ParseCoverageTable(data + offset_coverage, |
+ length - offset_coverage, |
+ num_glyphs, glyph_count)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ // Check sequence of MathGlyphConstruction. |
+ for (unsigned i = 0; i < glyph_count; ++i) { |
+ uint16_t offset_glyph_construction = 0; |
+ if (!subtable->ReadU16(&offset_glyph_construction)) { |
+ return OTS_FAILURE(); |
+ } |
+ if (offset_glyph_construction < sequence_end || |
+ offset_glyph_construction >= length || |
+ !ParseMathGlyphConstructionTable(data + offset_glyph_construction, |
+ length - offset_glyph_construction, |
+ num_glyphs)) { |
+ return OTS_FAILURE(); |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+bool ParseMathVariantsTable(const uint8_t *data, |
+ size_t length, const uint16_t num_glyphs) { |
+ ots::Buffer subtable(data, length); |
+ |
+ // Check the header. |
+ uint16_t offset_vert_glyph_coverage = 0; |
+ uint16_t offset_horiz_glyph_coverage = 0; |
+ uint16_t vert_glyph_count = 0; |
+ uint16_t horiz_glyph_count = 0; |
+ if (!subtable.Skip(2) || // MinConnectorOverlap |
+ !subtable.ReadU16(&offset_vert_glyph_coverage) || |
+ !subtable.ReadU16(&offset_horiz_glyph_coverage) || |
+ !subtable.ReadU16(&vert_glyph_count) || |
+ !subtable.ReadU16(&horiz_glyph_count)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ const unsigned sequence_end = 5 * 2 + vert_glyph_count * 2 + |
+ horiz_glyph_count * 2; |
+ if (sequence_end > std::numeric_limits<uint16_t>::max()) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ if (!ParseMathGlyphConstructionSequence(&subtable, data, length, num_glyphs, |
+ offset_vert_glyph_coverage, |
+ vert_glyph_count, |
+ sequence_end) || |
+ !ParseMathGlyphConstructionSequence(&subtable, data, length, num_glyphs, |
+ offset_horiz_glyph_coverage, |
+ horiz_glyph_count, |
+ sequence_end)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ return true; |
+} |
+ |
+} // namespace |
+ |
+#define DROP_THIS_TABLE \ |
+ do { file->math->data = 0; file->math->length = 0; } while (0) |
+ |
+namespace ots { |
+ |
+bool ots_math_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { |
+ // Grab the number of glyphs in the file from the maxp table to check |
+ // GlyphIDs in MATH table. |
+ if (!file->maxp) { |
+ return OTS_FAILURE(); |
+ } |
+ const uint16_t num_glyphs = file->maxp->num_glyphs; |
+ |
+ Buffer table(data, length); |
+ |
+ OpenTypeMATH* math = new OpenTypeMATH; |
+ file->math = math; |
+ |
+ uint32_t version = 0; |
+ if (!table.ReadU32(&version)) { |
+ return OTS_FAILURE(); |
+ } |
+ if (version != 0x00010000) { |
+ OTS_WARNING("bad MATH version"); |
+ DROP_THIS_TABLE; |
+ return true; |
+ } |
+ |
+ uint16_t offset_math_constants = 0; |
+ uint16_t offset_math_glyph_info = 0; |
+ uint16_t offset_math_variants = 0; |
+ if (!table.ReadU16(&offset_math_constants) || |
+ !table.ReadU16(&offset_math_glyph_info) || |
+ !table.ReadU16(&offset_math_variants)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ if (offset_math_constants >= length || |
+ offset_math_constants < kMathHeaderSize || |
+ offset_math_glyph_info >= length || |
+ offset_math_glyph_info < kMathHeaderSize || |
+ offset_math_variants >= length || |
+ offset_math_variants < kMathHeaderSize) { |
+ OTS_WARNING("bad offset in MATH header"); |
+ DROP_THIS_TABLE; |
+ return true; |
+ } |
+ |
+ if (!ParseMathConstantsTable(data + offset_math_constants, |
+ length - offset_math_constants)) { |
+ DROP_THIS_TABLE; |
+ return true; |
+ } |
+ if (!ParseMathGlyphInfoTable(data + offset_math_glyph_info, |
+ length - offset_math_glyph_info, num_glyphs)) { |
+ DROP_THIS_TABLE; |
+ return true; |
+ } |
+ if (!ParseMathVariantsTable(data + offset_math_variants, |
+ length - offset_math_variants, num_glyphs)) { |
+ DROP_THIS_TABLE; |
+ return true; |
+ } |
+ |
+ math->data = data; |
+ math->length = length; |
+ return true; |
+} |
+ |
+bool ots_math_should_serialise(OpenTypeFile *file) { |
+ return file->math != NULL && file->math->data != NULL; |
+} |
+ |
+bool ots_math_serialise(OTSStream *out, OpenTypeFile *file) { |
+ if (!out->Write(file->math->data, file->math->length)) { |
+ return OTS_FAILURE(); |
+ } |
+ |
+ return true; |
+} |
+ |
+void ots_math_free(OpenTypeFile *file) { |
+ delete file->math; |
+} |
+ |
+} // namespace ots |
+ |