dd vhea/vmtx table support.

- Added vhea.{cc,h} and vmtx.{cc,h}.
- Added metrics.{cc,h} to integrate common functions of hhea/hmtx and vhea/vmtx.
- Moved functions in hhea.cc to metrics.cc.

BUG=77386
TEST=http://code.google.com/p/ots/wiki/HowToTestOts (verified with 3500+ font files)
TEST=test/table_dependencies_test.cc


git-svn-id: http://ots.googlecode.com/svn/trunk@61 a4e77c2c-9104-11de-800e-5b313e0d2bf3
diff --git a/ots-common.gypi b/ots-common.gypi
index f023de3..0432b4a 100644
--- a/ots-common.gypi
+++ b/ots-common.gypi
@@ -45,6 +45,8 @@
       'src/ltsh.h',
       'src/maxp.cc',
       'src/maxp.h',
+      'src/metrics.cc',
+      'src/metrics.h',
       'src/name.cc',
       'src/os2.cc',
       'src/os2.h',
@@ -56,6 +58,10 @@
       'src/prep.h',
       'src/vdmx.cc',
       'src/vdmx.h',
+      'src/vhea.cc',
+      'src/vhea.h',
+      'src/vmtx.cc',
+      'src/vmtx.h',
       'src/vorg.cc',
       'src/vorg.h',
     ],
diff --git a/src/hhea.cc b/src/hhea.cc
index f9f81b5..0342ba5 100644
--- a/src/hhea.cc
+++ b/src/hhea.cc
@@ -17,69 +17,14 @@
   OpenTypeHHEA *hhea = new OpenTypeHHEA;
   file->hhea = hhea;
 
-  uint32_t version = 0;
-  if (!table.ReadU32(&version)) {
+  if (!table.ReadU32(&hhea->header.version)) {
     return OTS_FAILURE();
   }
-  if (version >> 16 != 1) {
+  if (hhea->header.version >> 16 != 1) {
     return OTS_FAILURE();
   }
 
-  if (!table.ReadS16(&hhea->ascent) ||
-      !table.ReadS16(&hhea->descent) ||
-      !table.ReadS16(&hhea->linegap) ||
-      !table.ReadU16(&hhea->adv_width_max) ||
-      !table.ReadS16(&hhea->min_lsb) ||
-      !table.ReadS16(&hhea->min_rsb) ||
-      !table.ReadS16(&hhea->x_max_extent) ||
-      !table.ReadS16(&hhea->caret_slope_rise) ||
-      !table.ReadS16(&hhea->caret_slope_run) ||
-      !table.ReadS16(&hhea->caret_offset)) {
-    return OTS_FAILURE();
-  }
-
-  if (hhea->ascent < 0) {
-    OTS_WARNING("bad ascent: %d", hhea->ascent);
-    hhea->ascent = 0;
-  }
-  if (hhea->linegap < 0) {
-    OTS_WARNING("bad linegap: %d", hhea->linegap);
-    hhea->linegap = 0;
-  }
-
-  if (!file->head) {
-    return OTS_FAILURE();
-  }
-
-  // if the font is non-slanted, caret_offset should be zero.
-  if (!(file->head->mac_style & 2) &&
-      (hhea->caret_offset != 0)) {
-    OTS_WARNING("bad caret offset: %d", hhea->caret_offset);
-    hhea->caret_offset = 0;
-  }
-
-  // skip the reserved bytes
-  if (!table.Skip(8)) {
-    return OTS_FAILURE();
-  }
-
-  int16_t data_format;
-  if (!table.ReadS16(&data_format)) {
-    return OTS_FAILURE();
-  }
-  if (data_format) {
-    return OTS_FAILURE();
-  }
-
-  if (!table.ReadU16(&hhea->num_hmetrics)) {
-    return OTS_FAILURE();
-  }
-
-  if (!file->maxp) {
-    return OTS_FAILURE();
-  }
-
-  if (hhea->num_hmetrics > file->maxp->num_glyphs) {
+  if (!ParseMetricsHeader(file, &table, &hhea->header)) {
     return OTS_FAILURE();
   }
 
@@ -91,25 +36,9 @@
 }
 
 bool ots_hhea_serialise(OTSStream *out, OpenTypeFile *file) {
-  const OpenTypeHHEA *hhea = file->hhea;
-
-  if (!out->WriteU32(0x00010000) ||
-      !out->WriteS16(hhea->ascent) ||
-      !out->WriteS16(hhea->descent) ||
-      !out->WriteS16(hhea->linegap) ||
-      !out->WriteU16(hhea->adv_width_max) ||
-      !out->WriteS16(hhea->min_lsb) ||
-      !out->WriteS16(hhea->min_rsb) ||
-      !out->WriteS16(hhea->x_max_extent) ||
-      !out->WriteS16(hhea->caret_slope_rise) ||
-      !out->WriteS16(hhea->caret_slope_run) ||
-      !out->WriteS16(hhea->caret_offset) ||
-      !out->WriteR64(0) ||  // reserved
-      !out->WriteS16(0) ||  // metric data format
-      !out->WriteU16(hhea->num_hmetrics)) {
+  if (!SerialiseMetricsHeader(out, &file->hhea->header)) {
     return OTS_FAILURE();
   }
-
   return true;
 }
 
diff --git a/src/hhea.h b/src/hhea.h
index 99aafde..bdea9aa 100644
--- a/src/hhea.h
+++ b/src/hhea.h
@@ -5,22 +5,13 @@
 #ifndef OTS_HHEA_H_
 #define OTS_HHEA_H_
 
+#include "metrics.h"
 #include "ots.h"
 
 namespace ots {
 
 struct OpenTypeHHEA {
-  int16_t ascent;
-  int16_t descent;
-  int16_t linegap;
-  uint16_t adv_width_max;
-  int16_t min_lsb;
-  int16_t min_rsb;
-  int16_t x_max_extent;
-  int16_t caret_slope_rise;
-  int16_t caret_slope_run;
-  int16_t caret_offset;
-  uint16_t num_hmetrics;
+  OpenTypeMetricsHeader header;
 };
 
 }  // namespace ots
diff --git a/src/hmtx.cc b/src/hmtx.cc
index 3dcaccd..ea7491d 100644
--- a/src/hmtx.cc
+++ b/src/hmtx.cc
@@ -21,58 +21,10 @@
     return OTS_FAILURE();
   }
 
-  // |num_hmetrics| is a uint16_t, so it's bounded < 65536. This limits that
-  // amount of memory that we'll allocate for this to a sane amount.
-  const unsigned num_hmetrics = file->hhea->num_hmetrics;
-
-  if (num_hmetrics > file->maxp->num_glyphs) {
+  if (!ParseMetricsTable(&table, file->maxp->num_glyphs,
+                         &file->hhea->header, &hmtx->metrics)) {
     return OTS_FAILURE();
   }
-  if (!num_hmetrics) {
-    return OTS_FAILURE();
-  }
-  const unsigned num_lsbs = file->maxp->num_glyphs - num_hmetrics;
-
-  hmtx->metrics.reserve(num_hmetrics);
-  for (unsigned i = 0; i < num_hmetrics; ++i) {
-    uint16_t adv = 0;
-    int16_t lsb = 0;
-    if (!table.ReadU16(&adv) || !table.ReadS16(&lsb)) {
-      return OTS_FAILURE();
-    }
-
-    // Since so many fonts don't have proper value on |adv| and |lsb|,
-    // we should not call ots_failure() here. For example, about 20% of fonts
-    // in http://www.princexml.com/fonts/ (200+ fonts) fails these tests.
-    if (adv > file->hhea->adv_width_max) {
-      OTS_WARNING("bad adv: %u > %u", adv, file->hhea->adv_width_max);
-      adv = file->hhea->adv_width_max;
-    }
-    if (lsb < file->hhea->min_lsb) {
-      OTS_WARNING("bad lsb: %d < %d", lsb, file->hhea->min_lsb);
-      lsb = file->hhea->min_lsb;
-    }
-
-    hmtx->metrics.push_back(std::make_pair(adv, lsb));
-  }
-
-  hmtx->lsbs.reserve(num_lsbs);
-  for (unsigned i = 0; i < num_lsbs; ++i) {
-    int16_t lsb;
-    if (!table.ReadS16(&lsb)) {
-      // Some Japanese fonts (e.g., mona.ttf) fail this test.
-      return OTS_FAILURE();
-    }
-
-    if (lsb < file->hhea->min_lsb) {
-      // The same as above. Three fonts in http://www.fontsquirrel.com/fontface
-      // (e.g., Notice2Std.otf) have weird lsb values.
-      OTS_WARNING("bad lsb: %d < %d", lsb, file->hhea->min_lsb);
-      lsb = file->hhea->min_lsb;
-    }
-
-    hmtx->lsbs.push_back(lsb);
-  }
 
   return true;
 }
@@ -82,21 +34,9 @@
 }
 
 bool ots_hmtx_serialise(OTSStream *out, OpenTypeFile *file) {
-  const OpenTypeHMTX *hmtx = file->hmtx;
-
-  for (unsigned i = 0; i < hmtx->metrics.size(); ++i) {
-    if (!out->WriteU16(hmtx->metrics[i].first) ||
-        !out->WriteS16(hmtx->metrics[i].second)) {
-      return OTS_FAILURE();
-    }
+  if (!SerialiseMetricsTable(out, &file->hmtx->metrics)) {
+    return OTS_FAILURE();
   }
-
-  for (unsigned i = 0; i < hmtx->lsbs.size(); ++i) {
-    if (!out->WriteS16(hmtx->lsbs[i])) {
-      return OTS_FAILURE();
-    }
-  }
-
   return true;
 }
 
diff --git a/src/hmtx.h b/src/hmtx.h
index 79a9cb6..435949c 100644
--- a/src/hmtx.h
+++ b/src/hmtx.h
@@ -5,16 +5,13 @@
 #ifndef OTS_HMTX_H_
 #define OTS_HMTX_H_
 
-#include <utility>  // std::pair
-#include <vector>
-
+#include "metrics.h"
 #include "ots.h"
 
 namespace ots {
 
 struct OpenTypeHMTX {
-  std::vector<std::pair<uint16_t, int16_t> > metrics;
-  std::vector<int16_t> lsbs;
+  OpenTypeMetricsTable metrics;
 };
 
 }  // namespace ots
diff --git a/src/metrics.cc b/src/metrics.cc
new file mode 100644
index 0000000..2e9190f
--- /dev/null
+++ b/src/metrics.cc
@@ -0,0 +1,181 @@
+// Copyright (c) 2011 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.
+
+#include "metrics.h"
+
+#include "head.h"
+#include "maxp.h"
+
+// OpenType horizontal and vertical common header format
+// http://www.microsoft.com/opentype/otspec/hhea.htm
+// http://www.microsoft.com/opentype/otspec/vhea.htm
+
+namespace ots {
+
+bool ParseMetricsHeader(OpenTypeFile *file, Buffer *table,
+                        OpenTypeMetricsHeader *header) {
+  if (!table->ReadS16(&header->ascent) ||
+      !table->ReadS16(&header->descent) ||
+      !table->ReadS16(&header->linegap) ||
+      !table->ReadU16(&header->adv_width_max) ||
+      !table->ReadS16(&header->min_sb1) ||
+      !table->ReadS16(&header->min_sb2) ||
+      !table->ReadS16(&header->max_extent) ||
+      !table->ReadS16(&header->caret_slope_rise) ||
+      !table->ReadS16(&header->caret_slope_run) ||
+      !table->ReadS16(&header->caret_offset)) {
+    return OTS_FAILURE();
+  }
+
+  if (header->ascent < 0) {
+    OTS_WARNING("bad ascent: %d", header->ascent);
+    header->ascent = 0;
+  }
+  if (header->linegap < 0) {
+    OTS_WARNING("bad linegap: %d", header->linegap);
+    header->linegap = 0;
+  }
+
+  if (!file->head) {
+    return OTS_FAILURE();
+  }
+
+  // if the font is non-slanted, caret_offset should be zero.
+  if (!(file->head->mac_style & 2) &&
+      (header->caret_offset != 0)) {
+    OTS_WARNING("bad caret offset: %d", header->caret_offset);
+    header->caret_offset = 0;
+  }
+
+  // skip the reserved bytes
+  if (!table->Skip(8)) {
+    return OTS_FAILURE();
+  }
+
+  int16_t data_format;
+  if (!table->ReadS16(&data_format)) {
+    return OTS_FAILURE();
+  }
+  if (data_format) {
+    return OTS_FAILURE();
+  }
+
+  if (!table->ReadU16(&header->num_metrics)) {
+    return OTS_FAILURE();
+  }
+
+  if (!file->maxp) {
+    return OTS_FAILURE();
+  }
+
+  if (header->num_metrics > file->maxp->num_glyphs) {
+    return OTS_FAILURE();
+  }
+
+  return true;
+}
+
+bool SerialiseMetricsHeader(OTSStream *out,
+                            const OpenTypeMetricsHeader *header) {
+  if (!out->WriteU32(header->version) ||
+      !out->WriteS16(header->ascent) ||
+      !out->WriteS16(header->descent) ||
+      !out->WriteS16(header->linegap) ||
+      !out->WriteU16(header->adv_width_max) ||
+      !out->WriteS16(header->min_sb1) ||
+      !out->WriteS16(header->min_sb2) ||
+      !out->WriteS16(header->max_extent) ||
+      !out->WriteS16(header->caret_slope_rise) ||
+      !out->WriteS16(header->caret_slope_run) ||
+      !out->WriteS16(header->caret_offset) ||
+      !out->WriteR64(0) ||  // reserved
+      !out->WriteS16(0) ||  // metric data format
+      !out->WriteU16(header->num_metrics)) {
+    return OTS_FAILURE();
+  }
+
+  return true;
+}
+
+bool ParseMetricsTable(Buffer *table,
+                       const uint16_t num_glyphs,
+                       const OpenTypeMetricsHeader *header,
+                       OpenTypeMetricsTable *metrics) {
+  // |num_metrics| is a uint16_t, so it's bounded < 65536. This limits that
+  // amount of memory that we'll allocate for this to a sane amount.
+  const unsigned num_metrics = header->num_metrics;
+
+  if (num_metrics > num_glyphs) {
+    return OTS_FAILURE();
+  }
+  if (!num_metrics) {
+    return OTS_FAILURE();
+  }
+  const unsigned num_sbs = num_glyphs - num_metrics;
+
+  metrics->entries.reserve(num_metrics);
+  for (unsigned i = 0; i < num_metrics; ++i) {
+    uint16_t adv = 0;
+    int16_t sb = 0;
+    if (!table->ReadU16(&adv) || !table->ReadS16(&sb)) {
+      return OTS_FAILURE();
+    }
+
+    // Since so many fonts don't have proper value on |adv| and |sb|,
+    // we should not call ots_failure() here. For example, about 20% of fonts
+    // in http://www.princexml.com/fonts/ (200+ fonts) fails these tests.
+    if (adv > header->adv_width_max) {
+      OTS_WARNING("bad adv: %u > %u", adv, header->adv_width_max);
+      adv = header->adv_width_max;
+    }
+
+    if (sb < header->min_sb1) {
+      OTS_WARNING("bad sb: %d < %d", sb, header->min_sb1);
+      sb = header->min_sb1;
+    }
+
+    metrics->entries.push_back(std::make_pair(adv, sb));
+  }
+
+  metrics->sbs.reserve(num_sbs);
+  for (unsigned i = 0; i < num_sbs; ++i) {
+    int16_t sb;
+    if (!table->ReadS16(&sb)) {
+      // Some Japanese fonts (e.g., mona.ttf) fail this test.
+      return OTS_FAILURE();
+    }
+
+    if (sb < header->min_sb1) {
+      // The same as above. Three fonts in http://www.fontsquirrel.com/fontface
+      // (e.g., Notice2Std.otf) have weird lsb values.
+      OTS_WARNING("bad lsb: %d < %d", sb, header->min_sb1);
+      sb = header->min_sb1;
+    }
+
+    metrics->sbs.push_back(sb);
+  }
+
+  return true;
+}
+
+bool SerialiseMetricsTable(OTSStream *out,
+                           const OpenTypeMetricsTable *metrics) {
+  for (unsigned i = 0; i < metrics->entries.size(); ++i) {
+    if (!out->WriteU16(metrics->entries[i].first) ||
+        !out->WriteS16(metrics->entries[i].second)) {
+      return OTS_FAILURE();
+    }
+  }
+
+  for (unsigned i = 0; i < metrics->sbs.size(); ++i) {
+    if (!out->WriteS16(metrics->sbs[i])) {
+      return OTS_FAILURE();
+    }
+  }
+
+  return true;
+}
+
+}  // namespace ots
+
diff --git a/src/metrics.h b/src/metrics.h
new file mode 100644
index 0000000..46559e1
--- /dev/null
+++ b/src/metrics.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2011 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.
+
+#ifndef OTS_METRICS_H_
+#define OTS_METRICS_H_
+
+#include <utility>  // std::pair
+#include <vector>
+
+#include "ots.h"
+
+namespace ots {
+
+struct OpenTypeMetricsHeader {
+  uint32_t version;
+  int16_t ascent;
+  int16_t descent;
+  int16_t linegap;
+  uint16_t adv_width_max;
+  int16_t min_sb1;
+  int16_t min_sb2;
+  int16_t max_extent;
+  int16_t caret_slope_rise;
+  int16_t caret_slope_run;
+  int16_t caret_offset;
+  uint16_t num_metrics;
+};
+
+struct OpenTypeMetricsTable {
+  std::vector<std::pair<uint16_t, int16_t> > entries;
+  std::vector<int16_t> sbs;
+};
+
+bool ParseMetricsHeader(OpenTypeFile *file, Buffer *table,
+                        OpenTypeMetricsHeader *header);
+bool SerialiseMetricsHeader(OTSStream *out,
+                            const OpenTypeMetricsHeader *header);
+
+bool ParseMetricsTable(Buffer *table,
+                       const uint16_t num_glyphs,
+                       const OpenTypeMetricsHeader *header,
+                       OpenTypeMetricsTable *metrics);
+bool SerialiseMetricsTable(OTSStream *out,
+                           const OpenTypeMetricsTable *metrics);
+
+}  // namespace ots
+
+#endif  // OTS_METRICS_H_
+
diff --git a/src/ots.cc b/src/ots.cc
index 75d9a35..ff0ee76 100644
--- a/src/ots.cc
+++ b/src/ots.cc
@@ -16,17 +16,6 @@
 // The OpenType Font File
 // http://www.microsoft.com/typography/otspec/cmap.htm
 
-#define F(name, capname) \
-  namespace ots { \
-  bool ots_##name##_parse(OpenTypeFile *f, const uint8_t *d, size_t l); \
-  bool ots_##name##_should_serialise(OpenTypeFile *f); \
-  bool ots_##name##_serialise(OTSStream *s, OpenTypeFile *f); \
-  void ots_##name##_free(OpenTypeFile *f); \
-  }
-  // TODO(yusukes): change these function names to follow Chromium coding rule.
-FOR_EACH_TABLE_TYPE
-#undef F
-
 namespace {
 
 bool g_debug_output = true;
@@ -150,6 +139,10 @@
     ots::ots_gpos_should_serialise, ots::ots_gpos_free, false },
   { Tag("GSUB"), ots::ots_gsub_parse, ots::ots_gsub_serialise,
     ots::ots_gsub_should_serialise, ots::ots_gsub_free, false },
+  { Tag("vhea"), ots::ots_vhea_parse, ots::ots_vhea_serialise,
+    ots::ots_vhea_should_serialise, ots::ots_vhea_free, false },
+  { Tag("vmtx"), ots::ots_vmtx_parse, ots::ots_vmtx_serialise,
+    ots::ots_vmtx_should_serialise, ots::ots_vmtx_free, false },
   // TODO(bashi): Support mort, base, and jstf tables.
   { 0, NULL, NULL, NULL, NULL, false },
 };
diff --git a/src/ots.h b/src/ots.h
index 8ba30b3..5acb81f 100644
--- a/src/ots.h
+++ b/src/ots.h
@@ -179,7 +179,9 @@
   F(post, POST) \
   F(prep, PREP) \
   F(vdmx, VDMX) \
-  F(vorg, VORG)
+  F(vorg, VORG) \
+  F(vhea, VHEA) \
+  F(vmtx, VMTX)
 
 #define F(name, capname) struct OpenType##capname;
 FOR_EACH_TABLE_TYPE
@@ -203,6 +205,15 @@
 #undef F
 };
 
+#define F(name, capname) \
+bool ots_##name##_parse(OpenTypeFile *f, const uint8_t *d, size_t l); \
+bool ots_##name##_should_serialise(OpenTypeFile *f); \
+bool ots_##name##_serialise(OTSStream *s, OpenTypeFile *f); \
+void ots_##name##_free(OpenTypeFile *f);
+// TODO(yusukes): change these function names to follow Chromium coding rule.
+FOR_EACH_TABLE_TYPE
+#undef F
+
 }  // namespace ots
 
 #endif  // OTS_H_
diff --git a/src/vhea.cc b/src/vhea.cc
new file mode 100644
index 0000000..b37b73a
--- /dev/null
+++ b/src/vhea.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2011 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.
+
+#include "vhea.h"
+
+#include "gsub.h"
+#include "head.h"
+#include "maxp.h"
+
+// vhea - Vertical Header Table
+// http://www.microsoft.com/opentype/otspec/vhea.htm
+
+namespace ots {
+
+bool ots_vhea_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
+  Buffer table(data, length);
+  OpenTypeVHEA *vhea = new OpenTypeVHEA;
+  file->vhea = vhea;
+
+  if (!table.ReadU32(&vhea->header.version)) {
+    return OTS_FAILURE();
+  }
+  if (vhea->header.version != 0x00010000 &&
+      vhea->header.version != 0x00011000) {
+    return OTS_FAILURE();
+  }
+
+  if (!ParseMetricsHeader(file, &table, &vhea->header)) {
+    return OTS_FAILURE();
+  }
+
+  return true;
+}
+
+bool ots_vhea_should_serialise(OpenTypeFile *file) {
+  // vhea should'nt serialise when vmtx doesn't exist.
+  // Firefox developer pointed out that vhea/vmtx should serialise iff GSUB is
+  // preserved. See http://crbug.com/77386
+  return file->vhea != NULL && file->vmtx != NULL &&
+      ots_gsub_should_serialise(file);
+}
+
+bool ots_vhea_serialise(OTSStream *out, OpenTypeFile *file) {
+  if (!SerialiseMetricsHeader(out, &file->vhea->header)) {
+    return OTS_FAILURE();
+  }
+  return true;
+}
+
+void ots_vhea_free(OpenTypeFile *file) {
+  delete file->vhea;
+}
+
+}  // namespace ots
+
diff --git a/src/vhea.h b/src/vhea.h
new file mode 100644
index 0000000..f8efde7
--- /dev/null
+++ b/src/vhea.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2011 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.
+
+#ifndef OTS_VHEA_H_
+#define OTS_VHEA_H_
+
+#include "metrics.h"
+#include "ots.h"
+
+namespace ots {
+
+struct OpenTypeVHEA {
+  OpenTypeMetricsHeader header;
+};
+
+}  // namespace ots
+
+#endif  // OTS_VHEA_H_
+
diff --git a/src/vmtx.cc b/src/vmtx.cc
new file mode 100644
index 0000000..04761ab
--- /dev/null
+++ b/src/vmtx.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 2011 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.
+
+#include "vmtx.h"
+
+#include "gsub.h"
+#include "maxp.h"
+#include "vhea.h"
+
+// vmtx - Vertical Metrics Table
+// http://www.microsoft.com/opentype/otspec/vmtx.htm
+
+namespace ots {
+
+bool ots_vmtx_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
+  Buffer table(data, length);
+  OpenTypeVMTX *vmtx = new OpenTypeVMTX;
+  file->vmtx = vmtx;
+
+  if (!file->vhea || !file->maxp) {
+    return OTS_FAILURE();
+  }
+
+  if (!ParseMetricsTable(&table, file->maxp->num_glyphs,
+                         &file->vhea->header, &vmtx->metrics)) {
+    return OTS_FAILURE();
+  }
+
+  return true;
+}
+
+bool ots_vmtx_should_serialise(OpenTypeFile *file) {
+  // vmtx should serialise when vhea and GSUB are preserved.
+  // See the comment in ots_vhea_should_serialise().
+  return file->vmtx != NULL && file->vhea != NULL &&
+      ots_gsub_should_serialise(file);
+}
+
+bool ots_vmtx_serialise(OTSStream *out, OpenTypeFile *file) {
+  if (!SerialiseMetricsTable(out, &file->vmtx->metrics)) {
+    return OTS_FAILURE();
+  }
+  return true;
+}
+
+void ots_vmtx_free(OpenTypeFile *file) {
+  delete file->vmtx;
+}
+
+}  // namespace ots
+
diff --git a/src/vmtx.h b/src/vmtx.h
new file mode 100644
index 0000000..061dc73
--- /dev/null
+++ b/src/vmtx.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2011 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.
+
+#ifndef OTS_VMTX_H_
+#define OTS_VMTX_H_
+
+#include "metrics.h"
+#include "ots.h"
+
+namespace ots {
+
+struct OpenTypeVMTX {
+  OpenTypeMetricsTable metrics;
+};
+
+}  // namespace ots
+
+#endif  // OTS_VMTX_H_
+
diff --git a/test/SConstruct b/test/SConstruct
index 907dd91..157cec8 100644
--- a/test/SConstruct
+++ b/test/SConstruct
@@ -31,17 +31,21 @@
 	    '../src/loca.cc',
 	    '../src/ltsh.cc',
 	    '../src/maxp.cc',
+	    '../src/metrics.cc',
 	    '../src/name.cc',
 	    '../src/os2.cc',
 	    '../src/ots.cc',
 	    '../src/post.cc',
 	    '../src/prep.cc',
 	    '../src/vdmx.cc',
+	    '../src/vhea.cc',
+	    '../src/vmtx.cc',
 	    '../src/vorg.cc'
             ])
 
 env.Program('../test/cff_type2_charstring_test.cc', LIBS = ['ots', 'gtest_main'], LIBPATH = '../src')
 env.Program('../test/layout_common_table_test.cc', LIBS = ['ots', 'gtest_main'], LIBPATH = '../src')
+env.Program('../test/table_dependencies_test.cc', LIBS = ['ots', 'gtest_main'], LIBPATH = '../src')
 env.Program('../test/ot-sanitise.cc', LIBS = ['ots'], LIBPATH='../src')
 env.Program('../test/idempotent.cc', LIBS = ['ots', 'freetype', 'z', 'm'], LIBPATH='../src')
 env.Program('../test/perf.cc', LIBS = ['ots'], LIBPATH='../src')
diff --git a/test/table_dependencies_test.cc b/test/table_dependencies_test.cc
new file mode 100644
index 0000000..bbaaa30
--- /dev/null
+++ b/test/table_dependencies_test.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2011 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.
+
+#include <gtest/gtest.h>
+
+#include "gsub.h"
+#include "ots.h"
+#include "ots-memory-stream.h"
+#include "vhea.h"
+#include "vmtx.h"
+
+#define SET_TABLE(name, capname) \
+  do { file.name = new ots::OpenType##capname; } while (0)
+#define SET_LAYOUT_TABLE(name, capname)                    \
+  do {                                                     \
+    if (!file.name) {                                      \
+      SET_TABLE(name, capname);                            \
+    }                                                      \
+    file.name->data = reinterpret_cast<const uint8_t*>(1); \
+    file.name->length = 1;                                 \
+  } while (0)
+#define DROP_TABLE(name) \
+  do { delete file.name; file.name = NULL; } while (0)
+#define DROP_LAYOUT_TABLE(name) \
+  do { file.name->data = NULL; file.name->length = 0; } while (0)
+
+namespace {
+
+class TableDependenciesTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    SET_LAYOUT_TABLE(gsub, GSUB);
+    SET_TABLE(vhea, VHEA);
+    SET_TABLE(vmtx, VMTX);
+  }
+
+  virtual void TearDown() {
+    DROP_TABLE(gsub);
+    DROP_TABLE(vhea);
+    DROP_TABLE(vmtx);
+  }
+  ots::OpenTypeFile file;
+};
+}  // namespace
+
+TEST_F(TableDependenciesTest, TestVhea) {
+  EXPECT_TRUE(ots::ots_vhea_should_serialise(&file));
+}
+
+TEST_F(TableDependenciesTest, TestVmtx) {
+  EXPECT_TRUE(ots::ots_vmtx_should_serialise(&file));
+}
+
+TEST_F(TableDependenciesTest, TestVheaVmtx) {
+  DROP_TABLE(vmtx);
+  EXPECT_FALSE(ots::ots_vhea_should_serialise(&file));
+}
+
+TEST_F(TableDependenciesTest, TestVmtxVhea) {
+  DROP_TABLE(vhea);
+  EXPECT_FALSE(ots::ots_vmtx_should_serialise(&file));
+}
+
+TEST_F(TableDependenciesTest, TestVheaGsub) {
+  DROP_LAYOUT_TABLE(gsub);
+  EXPECT_FALSE(ots::ots_vhea_should_serialise(&file));
+  DROP_TABLE(gsub);
+  EXPECT_FALSE(ots::ots_vhea_should_serialise(&file));
+}
+
+TEST_F(TableDependenciesTest, TestVmtxGsub) {
+  DROP_LAYOUT_TABLE(gsub);
+  EXPECT_FALSE(ots::ots_vmtx_should_serialise(&file));
+  DROP_TABLE(gsub);
+  EXPECT_FALSE(ots::ots_vmtx_should_serialise(&file));
+}
+