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