bug 637481 - add vhea/vmtx support to OTS for CJK webfonts. r=jfkthame
authorMasatoshi Kimura <VYV03354@nifty.ne.jp>
Thu, 24 Mar 2011 20:11:38 +0000
changeset 63900 60bfc795e935d8cc06025251e51f6af82907adcd
parent 63899 8887ebd9a6c86542499b8952bba1287f60d5d281
child 63901 b8fd81fa5a4b6fe29799e11d71f4c9e12741f83b
push idunknown
push userunknown
push dateunknown
reviewersjfkthame
bugs637481
milestone2.2a1pre
bug 637481 - add vhea/vmtx support to OTS for CJK webfonts. r=jfkthame
gfx/ots/src/hhea.cc
gfx/ots/src/hhea.h
gfx/ots/src/hmtx.cc
gfx/ots/src/ots.cc
gfx/ots/src/ots.h
gfx/thebes/gfxFontUtils.cpp
gfx/thebes/gfxFontUtils.h
gfx/thebes/gfxGDIFontList.cpp
--- a/gfx/ots/src/hhea.cc
+++ b/gfx/ots/src/hhea.cc
@@ -4,29 +4,32 @@
 
 #include "hhea.h"
 
 #include "head.h"
 #include "maxp.h"
 
 // hhea - Horizontal Header
 // http://www.microsoft.com/opentype/otspec/hhea.htm
+// vhea - Vertical Header
+// http://www.microsoft.com/opentype/otspec/vhea.htm
+// This file is used for both tables because they share the same structures.
 
 namespace ots {
 
-bool ots_hhea_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
+bool ots_Xhea_parse(OpenTypeFile *file, const uint8_t *data, size_t length,
+                    OpenTypeHHEA **out_hhea) {
   Buffer table(data, length);
   OpenTypeHHEA *hhea = new OpenTypeHHEA;
-  file->hhea = hhea;
+  *out_hhea = hhea;
 
-  uint32_t version = 0;
-  if (!table.ReadU32(&version)) {
+  if (!table.ReadU32(&hhea->version)) {
     return OTS_FAILURE();
   }
-  if (version >> 16 != 1) {
+  if (hhea->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) ||
@@ -81,25 +84,34 @@ bool ots_hhea_parse(OpenTypeFile *file, 
 
   if (hhea->num_hmetrics > file->maxp->num_glyphs) {
     return OTS_FAILURE();
   }
 
   return true;
 }
 
+bool ots_hhea_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
+  return ots_Xhea_parse(file, data, length, &file->hhea);
+}
+
+bool ots_vhea_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
+  return ots_Xhea_parse(file, data, length, &file->vhea);
+}
+
 bool ots_hhea_should_serialise(OpenTypeFile *file) {
   return file->hhea;
 }
 
-bool ots_hhea_serialise(OTSStream *out, OpenTypeFile *file) {
-  const OpenTypeHHEA *hhea = file->hhea;
+bool ots_vhea_should_serialise(OpenTypeFile *file) {
+  return file->preserve_otl && file->vhea;
+}
 
-  if (!out->WriteU32(0x00010000) ||
-      !out->WriteS16(hhea->ascent) ||
+bool ots_Xhea_serialise(OTSStream *out, OpenTypeFile *file, const OpenTypeHHEA *hhea) {
+  if (!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) ||
@@ -108,13 +120,31 @@ bool ots_hhea_serialise(OTSStream *out, 
       !out->WriteS16(0) ||  // metric data format
       !out->WriteU16(hhea->num_hmetrics)) {
     return OTS_FAILURE();
   }
 
   return true;
 }
 
+bool ots_hhea_serialise(OTSStream *out, OpenTypeFile *file) {
+  if (!out->WriteU32(0x00010000)) {
+    return OTS_FAILURE();
+  }
+  return ots_Xhea_serialise(out, file, file->hhea);
+}
+
+bool ots_vhea_serialise(OTSStream *out, OpenTypeFile *file) {
+  if (!out->WriteU32(file->vhea->version)) {
+    return OTS_FAILURE();
+  }
+  return ots_Xhea_serialise(out, file, file->vhea);
+}
+
 void ots_hhea_free(OpenTypeFile *file) {
   delete file->hhea;
 }
 
+void ots_vhea_free(OpenTypeFile *file) {
+  delete file->vhea;
+}
+
 }  // namespace ots
--- a/gfx/ots/src/hhea.h
+++ b/gfx/ots/src/hhea.h
@@ -5,16 +5,17 @@
 #ifndef OTS_HHEA_H_
 #define OTS_HHEA_H_
 
 #include "ots.h"
 
 namespace ots {
 
 struct OpenTypeHHEA {
+  uint32_t version;
   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;
--- a/gfx/ots/src/hmtx.cc
+++ b/gfx/ots/src/hmtx.cc
@@ -5,31 +5,35 @@
 #include <new>
 #include "hmtx.h"
 
 #include "hhea.h"
 #include "maxp.h"
 
 // hmtx - Horizontal Metrics
 // http://www.microsoft.com/opentype/otspec/hmtx.htm
+// vmtx - Vertical Metrics
+// http://www.microsoft.com/opentype/otspec/vmtx.htm
+// This file is used for both tables because they share the same structures.
 
 namespace ots {
 
-bool ots_hmtx_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
+bool ots_Xmtx_parse(OpenTypeFile *file, const uint8_t *data, size_t length,
+                    const OpenTypeHHEA *hhea, OpenTypeHMTX **out_hmtx) {
   Buffer table(data, length);
   OpenTypeHMTX *hmtx = new OpenTypeHMTX;
-  file->hmtx = hmtx;
+  *out_hmtx = hmtx;
 
-  if (!file->hhea || !file->maxp) {
+  if (!hhea || !file->maxp) {
     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;
+  const unsigned num_hmetrics = hhea->num_hmetrics;
 
   if (num_hmetrics > file->maxp->num_glyphs) {
     return OTS_FAILURE();
   }
   if (!num_hmetrics) {
     return OTS_FAILURE();
   }
   const unsigned num_lsbs = file->maxp->num_glyphs - num_hmetrics;
@@ -40,69 +44,91 @@ bool ots_hmtx_parse(OpenTypeFile *file, 
     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 (adv > hhea->adv_width_max) {
+      OTS_WARNING("bad adv: %u > %u", adv, hhea->adv_width_max);
+      adv = 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;
+    if (lsb < hhea->min_lsb) {
+      OTS_WARNING("bad lsb: %d < %d", lsb, hhea->min_lsb);
+      lsb = 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) {
+    if (lsb < 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;
+      OTS_WARNING("bad lsb: %d < %d", lsb, hhea->min_lsb);
+      lsb = hhea->min_lsb;
     }
 
     hmtx->lsbs.push_back(lsb);
   }
 
   return true;
 }
 
+bool ots_hmtx_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
+  return ots_Xmtx_parse(file, data, length, file->hhea, &file->hmtx);
+}
+
+bool ots_vmtx_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
+  return ots_Xmtx_parse(file, data, length, file->vhea, &file->vmtx);
+}
+
 bool ots_hmtx_should_serialise(OpenTypeFile *file) {
   return file->hmtx;
 }
 
-bool ots_hmtx_serialise(OTSStream *out, OpenTypeFile *file) {
-  const OpenTypeHMTX *hmtx = file->hmtx;
+bool ots_vmtx_should_serialise(OpenTypeFile *file) {
+  return file->preserve_otl && file->vmtx;
+}
 
+bool ots_Xmtx_serialise(OTSStream *out, OpenTypeFile *file, const OpenTypeHMTX *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();
     }
   }
 
   for (unsigned i = 0; i < hmtx->lsbs.size(); ++i) {
     if (!out->WriteS16(hmtx->lsbs[i])) {
       return OTS_FAILURE();
     }
   }
 
   return true;
 }
 
+bool ots_hmtx_serialise(OTSStream *out, OpenTypeFile *file) {
+  return ots_Xmtx_serialise(out, file, file->hmtx);
+}
+
+bool ots_vmtx_serialise(OTSStream *out, OpenTypeFile *file) {
+  return ots_Xmtx_serialise(out, file, file->vmtx);
+}
+
 void ots_hmtx_free(OpenTypeFile *file) {
   delete file->hmtx;
 }
 
+void ots_vmtx_free(OpenTypeFile *file) {
+  delete file->vmtx;
+}
+
 }  // namespace ots
--- a/gfx/ots/src/ots.cc
+++ b/gfx/ots/src/ots.cc
@@ -137,16 +137,20 @@ const struct {
   { Tag("prep"), ots::ots_prep_parse, ots::ots_prep_serialise,
     ots::ots_prep_should_serialise, ots::ots_prep_free, false },
   { Tag("LTSH"), ots::ots_ltsh_parse, ots::ots_ltsh_serialise,
     ots::ots_ltsh_should_serialise, ots::ots_ltsh_free, false },
   { Tag("VORG"), ots::ots_vorg_parse, ots::ots_vorg_serialise,
     ots::ots_vorg_should_serialise, ots::ots_vorg_free, false },
   { Tag("kern"), ots::ots_kern_parse, ots::ots_kern_serialise,
     ots::ots_kern_should_serialise, ots::ots_kern_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 },
   { Tag("GDEF"), ots::ots_gdef_parse, ots::ots_gdef_serialise,
     ots::ots_gdef_should_serialise, ots::ots_gdef_free, false },
   { Tag("GPOS"), ots::ots_gpos_parse, ots::ots_gpos_serialise,
     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 },
   // TODO(yusukes): Support GDEF, GPOS, GSUB, mort, base, and jstf tables.
   { 0, NULL, NULL, NULL, NULL, false }
--- a/gfx/ots/src/ots.h
+++ b/gfx/ots/src/ots.h
@@ -172,25 +172,30 @@ class Buffer {
   F(loca, LOCA) \
   F(ltsh, LTSH) \
   F(maxp, MAXP) \
   F(name, NAME) \
   F(os2, OS2) \
   F(post, POST) \
   F(prep, PREP) \
   F(vdmx, VDMX) \
+  F(vhea, VHEA) \
+  F(vmtx, VMTX) \
   F(vorg, VORG) \
   F(gdef, GDEF) \
   F(gpos, GPOS) \
   F(gsub, GSUB)
 
 #define F(name, capname) struct OpenType##capname;
 FOR_EACH_TABLE_TYPE
 #undef F
 
+#define OpenTypeVHEA OpenTypeHHEA
+#define OpenTypeVMTX OpenTypeHMTX
+
 struct OpenTypeFile {
   OpenTypeFile() {
 #define F(name, capname) name = NULL;
     FOR_EACH_TABLE_TYPE
 #undef F
   }
 
   uint32_t version;
--- a/gfx/thebes/gfxFontUtils.cpp
+++ b/gfx/thebes/gfxFontUtils.cpp
@@ -2156,17 +2156,30 @@ gfxFontUtils::MakeEOTHeader(const PRUint
 
     // DumpEOTHeader(aHeader->Elements(), aHeader->Length());
 
     return NS_OK;
 }
 
 /* static */
 PRBool
-gfxFontUtils::IsCffFont(const PRUint8* aFontData)
+gfxFontUtils::IsCffFont(const PRUint8* aFontData, PRBool& hasVertical)
 {
     // this is only called after aFontData has passed basic validation,
     // so we know there is enough data present to allow us to read the version!
     const SFNTHeader *sfntHeader = reinterpret_cast<const SFNTHeader*>(aFontData);
+
+    PRUint32 i;
+    PRUint32 numTables = sfntHeader->numTables;
+    const TableDirEntry *dirEntry = 
+        reinterpret_cast<const TableDirEntry*>(aFontData + sizeof(SFNTHeader));
+    hasVertical = PR_FALSE;
+    for (i = 0; i < numTables; i++, dirEntry++) {
+        if (dirEntry->tag == TRUETYPE_TAG('v','h','e','a')) {
+            hasVertical = PR_TRUE;
+            break;
+        }
+    }
+
     return (sfntHeader->sfntVersion == TRUETYPE_TAG('O','T','T','O'));
 }
 
 #endif
--- a/gfx/thebes/gfxFontUtils.h
+++ b/gfx/thebes/gfxFontUtils.h
@@ -691,17 +691,17 @@ public:
     // EOT header on output
     static nsresult
     MakeEOTHeader(const PRUint8 *aFontData, PRUint32 aFontDataLength,
                   FallibleTArray<PRUint8> *aHeader, FontDataOverlay *aOverlay);
 
     // determine whether a font (which has already passed ValidateSFNTHeaders)
     // is CFF format rather than TrueType
     static PRBool
-    IsCffFont(const PRUint8* aFontData);
+    IsCffFont(const PRUint8* aFontData, PRBool& hasVertical);
 
 #endif
 
     // determine the format of font data
     static gfxUserFontType
     DetermineFontDataType(const PRUint8 *aFontData, PRUint32 aFontDataLength);
 
     // checks for valid SFNT table structure, returns true if valid
--- a/gfx/thebes/gfxGDIFontList.cpp
+++ b/gfx/thebes/gfxGDIFontList.cpp
@@ -875,18 +875,19 @@ gfxGDIFontList::MakePlatformFont(const g
         const PRUint8 *mFontData;
     };
     FontDataDeleter autoDelete(aFontData);
 
     // if calls aren't available, bail
     if (!TTLoadEmbeddedFontPtr || !TTDeleteEmbeddedFontPtr)
         return nsnull;
 
-    PRBool isCFF = gfxFontUtils::IsCffFont(aFontData);
-        
+    PRBool hasVertical;
+    PRBool isCFF = gfxFontUtils::IsCffFont(aFontData, hasVertical);
+
     nsresult rv;
     HANDLE fontRef = nsnull;
     PRBool isEmbedded = PR_FALSE;
 
     nsAutoString uniqueName;
     rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName);
     if (NS_FAILED(rv))
         return nsnull;
@@ -952,17 +953,19 @@ gfxGDIFontList::MakePlatformFont(const g
         // "A font that is added by AddFontMemResourceEx is always private 
         //  to the process that made the call and is not enumerable."
         fontRef = AddFontMemResourceEx(fontData, fontLength, 
                                        0 /* reserved */, &numFonts);
         if (!fontRef)
             return nsnull;
 
         // only load fonts with a single face contained in the data
-        if (fontRef && numFonts != 1) {
+        // AddFontMemResourceEx generates an additional face name for
+        // vertical text if the font supports vertical writing
+        if (fontRef && numFonts != 1 + !!hasVertical) {
             RemoveFontMemResourceEx(fontRef);
             return nsnull;
         }
     }
 
     // make a new font entry using the unique name
     WinUserFontData *winUserFontData = new WinUserFontData(fontRef, isEmbedded);
     PRUint16 w = (aProxyEntry->mWeight == 0 ? 400 : aProxyEntry->mWeight);