Browse Source

generate cheesy accents if font doesn't have them

David Rose 23 years ago
parent
commit
5b7526d3a0

+ 7 - 4
panda/src/text/Sources.pp

@@ -23,7 +23,8 @@
     stringDecoder.I stringDecoder.h \
     textFont.I textFont.h \
     textGlyph.I textGlyph.h \
-    textNode.I textNode.h textNode.cxx
+    textNode.I textNode.h textNode.cxx \
+    unicodeLatinMap.h
 
   #define INCLUDED_SOURCES \
     config_text.cxx \
@@ -35,7 +36,8 @@
     geomTextGlyph.cxx \
     stringDecoder.cxx \
     staticTextFont.cxx \
-    textFont.cxx textGlyph.cxx
+    textFont.cxx textGlyph.cxx \
+    unicodeLatinMap.cxx
 
   #define INSTALL_HEADERS \
     config_text.h \
@@ -48,9 +50,10 @@
     stringDecoder.I stringDecoder.h \
     textFont.I textFont.h \
     textGlyph.I textGlyph.h \
-    textNode.I textNode.h
+    textNode.I textNode.h \
+    unicodeLatinMap.h
+
 
   #define IGATESCAN all
 
 #end lib_target
-

+ 1 - 0
panda/src/text/config_text.cxx

@@ -23,6 +23,7 @@
 #include "dynamicTextFont.h"
 #include "dynamicTextPage.h"
 #include "geomTextGlyph.h"
+#include "unicodeLatinMap.h"
 
 #include "dconfig.h"
 

+ 0 - 61
panda/src/text/dynamicTextFont.I

@@ -118,67 +118,6 @@ get_scale_factor() const {
   return _scale_factor;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: DynamicTextFont::set_small_caps
-//       Access: Published
-//  Description: Sets the small_caps flag.  When this is set,
-//               lowercase letters are generated as scaled-down
-//               versions of their uppercase equivalents.  This is
-//               particularly useful to set for fonts that do not have
-//               lowercase letters.
-//
-//               It is also a good idea to set this for a font that
-//               has already implemented lowercase letters as
-//               scaled-down versions of their uppercase equivalents,
-//               since without this flag the texture memory may
-//               needlessly duplicate equivalent glyphs for upper and
-//               lowercase letters.  Setting this flag causes the
-//               texture memory to share the mixed-case letters.
-//
-//               The amount by which the lowercase letters are scaled
-//               is specified by set_small_caps_scale().
-////////////////////////////////////////////////////////////////////
-INLINE void DynamicTextFont::
-set_small_caps(bool small_caps) {
-  _small_caps = small_caps;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: DynamicTextFont::get_small_caps
-//       Access: Published
-//  Description: Returns the small_caps flag.  See set_small_caps().
-////////////////////////////////////////////////////////////////////
-INLINE bool DynamicTextFont::
-get_small_caps() const {
-  return _small_caps;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: DynamicTextFont::set_small_caps_scale
-//       Access: Published
-//  Description: Sets the scale factor applied to lowercase letters
-//               from their uppercase equivalents, when the small_caps
-//               flag is in effect.  See set_small_caps().  Normally,
-//               this will be a number less than one.
-////////////////////////////////////////////////////////////////////
-INLINE void DynamicTextFont::
-set_small_caps_scale(float small_caps_scale) {
-  _small_caps_scale = small_caps_scale;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: DynamicTextFont::get_small_caps_scale
-//       Access: Published
-//  Description: Returns the scale factor applied to lowercase letters
-//               from their uppercase equivalents, when the small_caps
-//               flag is in effect.  See set_small_caps() and
-//               set_small_caps_scale().
-////////////////////////////////////////////////////////////////////
-INLINE float DynamicTextFont::
-get_small_caps_scale() const {
-  return _small_caps_scale;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: DynamicTextFont::set_texture_margin
 //       Access: Published

+ 1 - 11
panda/src/text/dynamicTextFont.cxx

@@ -325,20 +325,12 @@ write(ostream &out, int indent_level) const {
 //               printable glyph.
 ////////////////////////////////////////////////////////////////////
 bool DynamicTextFont::
-get_glyph(int character, const TextGlyph *&glyph, float &glyph_scale) {
+get_glyph(int character, const TextGlyph *&glyph) {
   if (!_is_valid) {
     glyph = (TextGlyph *)NULL;
     return false;
   }
 
-  glyph_scale = 1.0f;
-  if (character < 128 && islower(character) && get_small_caps()) {
-    // If we have small_caps on, we implement lowercase letters by
-    // applying a scale to the corresponding uppercase letter.
-    glyph_scale = get_small_caps_scale();
-    character = toupper(character);
-  }
-
   int glyph_index = FT_Get_Char_Index(_face, character);
 
   Cache::iterator ci = _cache.find(glyph_index);
@@ -368,8 +360,6 @@ initialize() {
   _point_size = text_point_size;
   _tex_pixels_per_unit = text_pixels_per_unit;
   _scale_factor = text_scale_factor;
-  _small_caps = text_small_caps;
-  _small_caps_scale = text_small_caps_scale;
 
   // We don't necessarily want to use mipmaps, since we don't want to
   // regenerate those every time the texture changes, but we probably

+ 1 - 9
panda/src/text/dynamicTextFont.h

@@ -57,11 +57,6 @@ PUBLISHED:
   INLINE bool set_scale_factor(float scale_factor);
   INLINE float get_scale_factor() const;
 
-  INLINE void set_small_caps(bool small_caps);
-  INLINE bool get_small_caps() const;
-  INLINE void set_small_caps_scale(float small_caps_scale);
-  INLINE float get_small_caps_scale() const;
-
   INLINE void set_texture_margin(int texture_margin);
   INLINE int get_texture_margin() const;
   INLINE void set_poly_margin(float poly_margin);
@@ -91,8 +86,7 @@ PUBLISHED:
   virtual void write(ostream &out, int indent_level) const;
 
 public:
-  virtual bool get_glyph(int character, const TextGlyph *&glyph,
-                         float &glyph_scale);
+  virtual bool get_glyph(int character, const TextGlyph *&glyph);
 
 private:
   void initialize();
@@ -110,8 +104,6 @@ private:
   float _tex_pixels_per_unit;
   float _scale_factor;
   float _font_pixels_per_unit;
-  bool _small_caps;
-  float _small_caps_scale;
   int _texture_margin;
   float _poly_margin;
   int _page_x_size, _page_y_size;

+ 0 - 16
panda/src/text/dynamicTextGlyph.cxx

@@ -35,22 +35,6 @@ DynamicTextGlyph::
 ~DynamicTextGlyph() {
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: DynamicTextGlyph::get_geom
-//       Access: Public, Virtual
-//  Description: Returns a Geom that renders the particular glyph.
-////////////////////////////////////////////////////////////////////
-PT(Geom) DynamicTextGlyph::
-get_geom() const {
-  if (_geom == (Geom *)NULL) {
-    return _geom;
-  }
-
-  // A DynamicTextGlyph must make a copy of its GeomTextGlyph, so that
-  // it will increase the reference count properly.
-  return _geom->make_copy();
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: DynamicTextGlyph::get_row
 //       Access: Public

+ 0 - 1
panda/src/text/dynamicTextGlyph.h

@@ -45,7 +45,6 @@ private:
 
 public:
   virtual ~DynamicTextGlyph();
-  virtual PT(Geom) get_geom() const;
 
   INLINE bool intersects(int x, int y, int x_size, int y_size) const;
   unsigned char *get_row(int y);

+ 1 - 3
panda/src/text/staticTextFont.cxx

@@ -162,9 +162,7 @@ write(ostream &out, int indent_level) const {
 //               printable glyph.
 ////////////////////////////////////////////////////////////////////
 bool StaticTextFont::
-get_glyph(int character, const TextGlyph *&glyph, float &glyph_scale) {
-  glyph_scale = 1.0f;
-
+get_glyph(int character, const TextGlyph *&glyph) {
   Glyphs::const_iterator gi = _glyphs.find(character);
   if (gi == _glyphs.end()) {
     // No definition for this character.

+ 1 - 2
panda/src/text/staticTextFont.h

@@ -47,8 +47,7 @@ PUBLISHED:
   virtual void write(ostream &out, int indent_level) const;
 
 public:
-  virtual bool get_glyph(int character, const TextGlyph *&glyph,
-                         float &glyph_scale);
+  virtual bool get_glyph(int character, const TextGlyph *&glyph);
 
 private:
   void find_character_gsets(PandaNode *root, Geom *&ch, GeomPoint *&dot,

+ 2 - 3
panda/src/text/textFont.cxx

@@ -78,14 +78,13 @@ calc_width(int character) {
   }
 
   const TextGlyph *glyph;
-  float glyph_scale;
-  get_glyph(character, glyph, glyph_scale);
+  get_glyph(character, glyph);
   if (glyph == (TextGlyph *)NULL) {
     // Unknown character.
     return 0.0f;
   }
 
-  return glyph->get_advance() * glyph_scale;
+  return glyph->get_advance();
 }
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 2
panda/src/text/textFont.h

@@ -65,8 +65,7 @@ public:
   wstring wordwrap_to(const wstring &text, float wordwrap_width,
                      bool preserve_trailing_whitespace);
 
-  virtual bool get_glyph(int character, const TextGlyph *&glyph,
-                         float &glyph_scale)=0;
+  virtual bool get_glyph(int character, const TextGlyph *&glyph)=0;
 
 protected:
   bool _is_valid;

+ 18 - 0
panda/src/text/textGlyph.I

@@ -67,6 +67,24 @@ operator = (const TextGlyph &copy) {
   _advance = copy._advance;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextGlyph::get_geom
+//       Access: Public
+//  Description: Returns a Geom that renders the particular glyph.
+////////////////////////////////////////////////////////////////////
+INLINE PT(Geom) TextGlyph::
+get_geom() const {
+  if (_geom == (Geom *)NULL) {
+    return _geom;
+  }
+
+  // We always return a copy of the geom.  That will allow the caller
+  // to modify its vertices without fear of stomping on other copies;
+  // it is also critical for the DynamicTextGlyph, which depends on
+  // this behavior to properly count references to this glyph.
+  return _geom->make_copy();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextGlyph::get_state
 //       Access: Public

+ 1 - 19
panda/src/text/textGlyph.cxx

@@ -20,27 +20,9 @@
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextGlyph::Destructor
-//       Access: Public, Virtual
+//       Access: Public
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 TextGlyph::
 ~TextGlyph() {
 }
-
-////////////////////////////////////////////////////////////////////
-//     Function: TextGlyph::get_geom
-//       Access: Public, Virtual
-//  Description: Returns a Geom that renders the particular glyph.
-////////////////////////////////////////////////////////////////////
-PT(Geom) TextGlyph::
-get_geom() const {
-  if (_geom == (Geom *)NULL) {
-    return _geom;
-  }
-
-  // We always return a copy of the geom.  That will allow the caller
-  // to modify its vertices without fear of stomping on other copies;
-  // it is also critical for the DynamicTextGlyph, which depends on
-  // this behavior to properly count references to this glyph.
-  return _geom->make_copy();
-}

+ 1 - 1
panda/src/text/textGlyph.h

@@ -39,7 +39,7 @@ public:
   INLINE void operator = (const TextGlyph &copy);
   virtual ~TextGlyph();
 
-  virtual PT(Geom) get_geom() const;
+  INLINE PT(Geom) get_geom() const;
   INLINE const RenderState *get_state() const;
   INLINE float get_advance() const;
 

+ 92 - 0
panda/src/text/textNode.I

@@ -202,6 +202,74 @@ get_line_height() const {
   return 0.0f;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::set_small_caps
+//       Access: Published
+//  Description: Sets the small_caps flag.  When this is set,
+//               lowercase letters are generated as scaled-down
+//               versions of their uppercase equivalents.  This is
+//               particularly useful to set for fonts that do not have
+//               lowercase letters.
+//
+//               It is also a good idea to set this for a (dynamic)
+//               font that has already implemented lowercase letters
+//               as scaled-down versions of their uppercase
+//               equivalents, since without this flag the texture
+//               memory may needlessly duplicate equivalent glyphs for
+//               upper and lowercase letters.  Setting this flag
+//               causes the texture memory to share the mixed-case
+//               letters.
+//
+//               The amount by which the lowercase letters are scaled
+//               is specified by set_small_caps_scale().
+////////////////////////////////////////////////////////////////////
+INLINE void TextNode::
+set_small_caps(bool small_caps) {
+  if (small_caps) {
+    _flags |= F_small_caps;
+  } else {
+    _flags &= ~F_small_caps;
+  }
+  invalidate_with_measure();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::get_small_caps
+//       Access: Published
+//  Description: Returns the small_caps flag.  See set_small_caps().
+////////////////////////////////////////////////////////////////////
+INLINE bool TextNode::
+get_small_caps() const {
+  return (_flags & F_small_caps) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::set_small_caps_scale
+//       Access: Published
+//  Description: Sets the scale factor applied to lowercase letters
+//               from their uppercase equivalents, when the small_caps
+//               flag is in effect.  See set_small_caps().  Normally,
+//               this will be a number less than one.
+////////////////////////////////////////////////////////////////////
+INLINE void TextNode::
+set_small_caps_scale(float small_caps_scale) {
+  _small_caps_scale = small_caps_scale;
+  invalidate_with_measure();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::get_small_caps_scale
+//       Access: Published
+//  Description: Returns the scale factor applied to lowercase letters
+//               from their uppercase equivalents, when the small_caps
+//               flag is in effect.  See set_small_caps() and
+//               set_small_caps_scale().
+////////////////////////////////////////////////////////////////////
+INLINE float TextNode::
+get_small_caps_scale() const {
+  return _small_caps_scale;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_slant
 //       Access: Published
@@ -1156,6 +1224,30 @@ get_char(int index) const {
   return _wtext[index];
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::get_text_as_ascii
+//       Access: Published
+//  Description: Returns the text associated with the node, converted
+//               as nearly as possible to a fully-ASCII
+//               representation.  This means replacing accented
+//               letters with their unaccented ASCII equivalents.
+//
+//               It is possible that some characters in the string
+//               cannot be converted to ASCII.  (The string may
+//               involve symbols like the copyright symbol, for
+//               instance, or it might involve letters in some other
+//               alphabet such as Greek or Cyrillic, or even Latin
+//               letters like thorn or eth that are not part of the
+//               ASCII character set.)  In this case, as much of the
+//               string as possible will be converted to ASCII, and
+//               the nonconvertible characters will remain encoded in
+//               the encoding specified by set_encoding().
+////////////////////////////////////////////////////////////////////
+INLINE string TextNode::
+get_text_as_ascii() const {
+  return encode_wtext(get_wtext_as_ascii());
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::calc_width
 //       Access: Published

+ 509 - 28
panda/src/text/textNode.cxx

@@ -23,6 +23,7 @@
 #include "fontPool.h"
 #include "default_font.h"
 #include "dynamicTextFont.h"
+#include "unicodeLatinMap.h"
 
 #include "compose_matrix.h"
 #include "geom.h"
@@ -54,6 +55,13 @@ PT(TextFont) TextNode::_default_font;
 bool TextNode::_loaded_default_font = false;
 TextNode::Encoding TextNode::_default_encoding;
 
+// This is the factor by which CP_tiny scales the character down.
+static const float tiny_accent_scale = 0.3f;
+
+// This is the factor by which the advance is reduced for the first
+// character of a two-character ligature.
+static const float ligature_advance_scale = 0.6f;
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::Constructor
 //       Access: Published
@@ -71,6 +79,11 @@ TextNode(const string &name) : PandaNode(name) {
   _align = A_left;
   _wordwrap_width = 1.0f;
 
+  if (text_small_caps) {
+    _flags |= F_small_caps;
+  }
+  _small_caps_scale = text_small_caps_scale;
+
   _text_color.set(1.0f, 1.0f, 1.0f, 1.0f);
   _frame_color.set(1.0f, 1.0f, 1.0f, 1.0f);
   _card_color.set(1.0f, 1.0f, 1.0f, 1.0f);
@@ -397,6 +410,49 @@ generate() {
   return root;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::get_wtext_as_ascii
+//       Access: Published
+//  Description: Returns the text associated with the node, converted
+//               as nearly as possible to a fully-ASCII
+//               representation.  This means replacing accented
+//               letters with their unaccented ASCII equivalents.
+//
+//               It is possible that some characters in the string
+//               cannot be converted to ASCII.  (The string may
+//               involve symbols like the copyright symbol, for
+//               instance, or it might involve letters in some other
+//               alphabet such as Greek or Cyrillic, or even Latin
+//               letters like thorn or eth that are not part of the
+//               ASCII character set.)  In this case, as much of the
+//               string as possible will be converted to ASCII, and
+//               the nonconvertible characters will remain in their
+//               original form.
+////////////////////////////////////////////////////////////////////
+wstring TextNode::
+get_wtext_as_ascii() const {
+  get_wtext();
+  wstring result;
+  wstring::const_iterator si;
+  for (si = _wtext.begin(); si != _wtext.end(); ++si) {
+    wchar_t character = (*si);
+
+    const UnicodeLatinMap::Entry *map_entry = 
+      UnicodeLatinMap::look_up(character);
+    if (map_entry != NULL && map_entry->_ascii_equiv != 0) {
+      result += (wchar_t)map_entry->_ascii_equiv;
+      if (map_entry->_ascii_additional != 0) {
+        result += (wchar_t)map_entry->_ascii_additional;
+      }
+
+    } else {
+      result += character;
+    }
+  }
+
+  return result;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::encode_wchar
 //       Access: Public
@@ -870,7 +926,8 @@ do_measure() {
   _ul3d = _ul3d * _transform;
   _lr3d = _lr3d * _transform;
 }
-
+  
+  
 #ifndef CPPPARSER  // interrogate has a bit of trouble with wstring.
 
 ////////////////////////////////////////////////////////////////////
@@ -895,10 +952,18 @@ assemble_row(wstring::iterator &si, const wstring::iterator &send,
 
     } else {
       // A printable character.
-
+      bool got_glyph;
       const TextGlyph *glyph;
+      const TextGlyph *second_glyph;
+      UnicodeLatinMap::AccentType accent_type;
+      int additional_flags;
       float glyph_scale;
-      if (!font->get_glyph(character, glyph, glyph_scale)) {
+      float advance_scale;
+      get_character_glyphs(character, font, 
+                           got_glyph, glyph, second_glyph, accent_type,
+                           additional_flags, glyph_scale, advance_scale);
+
+      if (!got_glyph) {
         text_cat.warning()
           << "No definition in " << font->get_name() 
           << " for character " << character;
@@ -910,35 +975,87 @@ assemble_row(wstring::iterator &si, const wstring::iterator &send,
           << "\n";
       }
 
+      // Build up a temporary array of the Geoms that go into this
+      // character.  Normally, there is only one Geom per character,
+      // but it may involve multiple Geoms if we need to add cheesy
+      // accents or ligatures.
+      static const int max_geoms = 10;
+      Geom *geom_array[max_geoms];
+      int num_geoms = 0;
+      int gi;
+
+      float advance = 0.0f;
+
       if (glyph != (TextGlyph *)NULL) {
         PT(Geom) char_geom = glyph->get_geom();
-        const RenderState *state = glyph->get_state();
-
         if (char_geom != (Geom *)NULL) {
-          LMatrix4f mat2 = LMatrix4f::scale_mat(glyph_scale);
-          mat2.set_row(3, LVector3f(xpos, 0.0f, 0.0f));
-          LMatrix4f xform = mat2 * mat;
-
-          // Transform the vertices of the geom appropriately.  We
-          // assume the geom is non-indexed.
-          PTA_Vertexf coords;
-          PTA_ushort index;
-          char_geom->get_coords(coords, index);
-          PTA_Vertexf new_coords;
-          new_coords.reserve(coords.size());
-          PTA_Vertexf::const_iterator vi;
-          for (vi = coords.begin(); vi != coords.end(); ++vi) {
-            new_coords.push_back((*vi) * xform);
-          }
-          nassertr(new_coords.size() == coords.size(), false);
-          char_geom->set_coords(new_coords);
+          dest->add_geom(char_geom, glyph->get_state());
+          geom_array[num_geoms++] = char_geom;
+        }
+        advance = glyph->get_advance() * advance_scale;
+      }
+      if (second_glyph != (TextGlyph *)NULL) {
+        PT(Geom) second_char_geom = second_glyph->get_geom();
+        if (second_char_geom != (Geom *)NULL) {
+          second_char_geom->transform_vertices(LMatrix4f::translate_mat(advance, 0.0f, 0.0f));
+          dest->add_geom(second_char_geom, second_glyph->get_state());
+          geom_array[num_geoms++] = second_char_geom;
+        }
+        advance += second_glyph->get_advance();
+      }
 
-          // Now add the geom to the destination node.
-          dest->add_geom(char_geom, state);
+      // Now compute the matrix that will transform the glyph (or
+      // glyphs) into position.
+      LMatrix4f glyph_xform = LMatrix4f::scale_mat(glyph_scale);
+
+      if (accent_type != UnicodeLatinMap::AT_none || additional_flags != 0) {
+        // If we have some special handling to perform, do so now.
+        // This will probably require the bounding volume of the
+        // glyph, so go get that.
+        LPoint3f min_vert, max_vert;
+        bool found_any = false;
+        for (gi = 0; gi < num_geoms; gi++) {
+          geom_array[gi]->calc_tight_bounds(min_vert, max_vert, found_any);
         }
 
-        xpos += glyph->get_advance() * glyph_scale;
+        if (found_any) {
+          LPoint3f centroid = (min_vert + max_vert) / 2.0f;
+          tack_on_accent(accent_type, min_vert, max_vert, centroid, 
+                         font, dest, geom_array, num_geoms);
+    
+          if ((additional_flags & UnicodeLatinMap::AF_turned) != 0) {
+            // Invert the character.  Should we also invert the accent
+            // mark, so that an accent that would have been above the
+            // glyph will now be below it?  That's what we do here,
+            // which is probably the right thing to do for n-tilde,
+            // but not for most of the rest of the accent marks.  For
+            // now we'll assume there are no characters with accent
+            // marks that also have the turned flag.
+
+            // We rotate the character around its centroid, which may
+            // not always be the right point, but it's the best we've
+            // got and it's probably pretty close.
+            LMatrix4f rotate =
+              LMatrix4f::translate_mat(-centroid) *
+              LMatrix4f::rotate_mat_normaxis(180.0f, LVecBase3f(0.0f, 1.0f, 0.0f)) *
+              LMatrix4f::translate_mat(centroid);
+            glyph_xform *= rotate;
+          }
+        }
+      }
+
+      glyph_xform(3, 0) += xpos;
+      LMatrix4f net_xform = glyph_xform * mat;
+
+      // Finally, transform all the Geoms for this character into
+      // place.  Again, normally there is only one Geom per character;
+      // there will only be multiple Geoms if we have added accents or
+      // ligatures.
+      for (gi = 0; gi < num_geoms; gi++) {
+        geom_array[gi]->transform_vertices(net_xform);
       }
+      
+      xpos += advance * glyph_scale;
     }
     ++si;
   }
@@ -1035,13 +1152,27 @@ measure_row(wstring::iterator &si, const wstring::iterator &send,
 
     } else {
       // A printable character.
-
+      bool got_glyph;
       const TextGlyph *glyph;
+      const TextGlyph *second_glyph;
+      UnicodeLatinMap::AccentType accent_type;
+      int additional_flags;
       float glyph_scale;
-      font->get_glyph(character, glyph, glyph_scale);
+      float advance_scale;
+      get_character_glyphs(character, font, 
+                           got_glyph, glyph, second_glyph, accent_type,
+                           additional_flags, glyph_scale, advance_scale);
+
+      float advance = 0.0f;
+
       if (glyph != (TextGlyph *)NULL) {
-        xpos += glyph->get_advance() * glyph_scale;
+        advance = glyph->get_advance() * advance_scale;
       }
+      if (second_glyph != (TextGlyph *)NULL) {
+        advance += second_glyph->get_advance();
+      }
+
+      xpos += advance * glyph_scale;
     }
     ++si;
   }
@@ -1092,6 +1223,356 @@ measure_text(wstring::iterator si, const wstring::iterator &send,
 }
 #endif  // CPPPARSER
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::get_character_glyphs
+//       Access: Private
+//  Description: Looks up the glyph(s) from the font for the
+//               appropriate character.  If the desired glyph isn't
+//               available (especially in the case of an accented
+//               letter), tries to find a suitable replacement.
+//               Normally, only one glyph is returned per character,
+//               but in the case we have to simulate a missing
+//               ligature in the font, two glyphs might be returned.
+//
+//               All parameters except the first two are output
+//               parameters.  got_glyph is set true if the glyph (or
+//               an acceptable substitute) is successfully found,
+//               false otherwise; but even if it is false, glyph might
+//               still be non-NULL, indicating a stand-in glyph for a
+//               missing character.
+////////////////////////////////////////////////////////////////////
+void TextNode::
+get_character_glyphs(int character, TextFont *font,
+                     bool &got_glyph, const TextGlyph *&glyph,
+                     const TextGlyph *&second_glyph,
+                     UnicodeLatinMap::AccentType &accent_type,
+                     int &additional_flags,
+                     float &glyph_scale, float &advance_scale) {
+  got_glyph = false;
+  glyph = NULL;
+  second_glyph = NULL;
+  accent_type = UnicodeLatinMap::AT_none;
+  additional_flags = 0;
+  glyph_scale = 1.0f;
+  advance_scale = 1.0f;
+
+  // Maybe we should remap the character to something else--e.g. a
+  // small capital.
+  const UnicodeLatinMap::Entry *map_entry = 
+    UnicodeLatinMap::look_up(character);
+  if (map_entry != NULL) {
+    if (get_small_caps() && map_entry->_toupper_character != character) {
+      character = map_entry->_toupper_character;
+      map_entry = UnicodeLatinMap::look_up(character);
+      glyph_scale = get_small_caps_scale();
+    }
+  }
+  
+  got_glyph = font->get_glyph(character, glyph);
+  if (!got_glyph && map_entry != NULL && map_entry->_ascii_equiv != 0) {
+    // If we couldn't find the Unicode glyph, try the ASCII
+    // equivalent (without the accent marks).
+    got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph);
+    
+    if (!got_glyph && map_entry->_toupper_character != character) {
+      // If we still couldn't find it, try the uppercase
+      // equivalent.
+      character = map_entry->_toupper_character;
+      map_entry = UnicodeLatinMap::look_up(character);
+      if (map_entry != NULL) {
+        got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph);
+      }
+    }
+    
+    if (got_glyph) {
+      accent_type = map_entry->_accent_type;
+      additional_flags = map_entry->_additional_flags;
+      
+      bool got_second_glyph = false;
+      if (map_entry->_ascii_additional != 0) {
+        // There's another character, too--probably a ligature.
+        got_second_glyph = 
+          font->get_glyph(map_entry->_ascii_additional, second_glyph);
+      }
+      
+      if ((additional_flags & UnicodeLatinMap::AF_ligature) != 0 &&
+          got_second_glyph) {
+        // If we have two letters that are supposed to be in a
+        // ligature, just jam them together.
+        additional_flags &= ~UnicodeLatinMap::AF_ligature;
+        advance_scale = ligature_advance_scale;
+      }
+      
+      if ((additional_flags & UnicodeLatinMap::AF_smallcap) != 0) {
+        additional_flags &= ~UnicodeLatinMap::AF_smallcap;
+        glyph_scale = get_small_caps_scale();
+      }
+    }
+  }
+}
+  
+  
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::tack_on_accent
+//       Access: Private
+//  Description: This is a cheesy attempt to tack on an accent to an
+//               ASCII letter for which we don't have the appropriate
+//               already-accented glyph in the font.
+////////////////////////////////////////////////////////////////////
+void TextNode::
+tack_on_accent(UnicodeLatinMap::AccentType accent_type,
+               const LPoint3f &min_vert, const LPoint3f &max_vert,
+               const LPoint3f &centroid,
+               TextFont *font, GeomNode *dest, 
+               Geom *geom_array[], int &num_geoms) {
+  switch (accent_type) {
+  case UnicodeLatinMap::AT_grave:
+    // We use the slash as the grave and acute accents.  ASCII does
+    // have a grave accent character, but a lot of fonts put the
+    // reverse apostrophe there instead.  And some fonts (particularly
+    // fonts from mf) don't even do backslash.
+    tack_on_accent('/', CP_above, CT_tiny_mirror_x, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_acute:
+    tack_on_accent('/', CP_above, CT_tiny, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_breve:
+    tack_on_accent(')', CP_above, CT_tiny_rotate_90, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_inverted_breve:
+    tack_on_accent('(', CP_above, CT_tiny_rotate_90, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_circumflex:
+    tack_on_accent('^', CP_above, CT_none, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_circumflex_below:
+    tack_on_accent('^', CP_below, CT_none, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_caron:
+    tack_on_accent('^', CP_above, CT_mirror_y, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_tilde:
+    tack_on_accent('~', CP_above, CT_none, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_tilde_below:
+    tack_on_accent('~', CP_below, CT_none, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_diaeresis:
+    tack_on_accent(':', CP_above, CT_rotate_90, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_diaeresis_below:
+    tack_on_accent(':', CP_below, CT_rotate_90, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_dot_above:
+    tack_on_accent('.', CP_above, CT_none, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_dot_below:
+    tack_on_accent('.', CP_below, CT_none, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_macron:
+    tack_on_accent('-', CP_above, CT_none, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_line_below:
+    tack_on_accent('-', CP_below, CT_none, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_ring_above:
+    tack_on_accent('o', CP_top, CT_tiny, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_ring_below:
+    tack_on_accent('o', CP_bottom, CT_tiny, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_cedilla:
+    tack_on_accent(',', CP_bottom, CT_none, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_comma_below:
+    tack_on_accent(',', CP_below, CT_none, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_ogonek:
+    tack_on_accent(',', CP_bottom, CT_mirror_x, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  case UnicodeLatinMap::AT_stroke:
+    tack_on_accent('/', CP_within, CT_none, min_vert, max_vert, centroid,
+                   font, dest, geom_array, num_geoms);
+    break;
+
+  default:
+    // There are lots of other crazy kinds of accents.  Forget 'em.
+    break;
+  }    
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::tack_on_accent
+//       Access: Private
+//  Description: Generates a cheesy accent mark above (or below, etc.)
+//               the character.
+////////////////////////////////////////////////////////////////////
+void TextNode::
+tack_on_accent(char accent_mark, TextNode::CheesyPlacement placement,
+               TextNode::CheesyTransform transform,
+               const LPoint3f &min_vert, const LPoint3f &max_vert,
+               const LPoint3f &centroid,
+               TextFont *font, GeomNode *dest, 
+               Geom *geom_array[], int &num_geoms) {
+  
+  TextGlyph *accent_glyph;
+  if (font->get_glyph(accent_mark, accent_glyph)) {
+    PT(Geom) accent_geom = accent_glyph->get_geom();
+    if (accent_geom != (Geom *)NULL) {
+      LPoint3f min_accent, max_accent;
+      bool found_any = false;
+      accent_geom->calc_tight_bounds(min_accent, max_accent, found_any);
+      if (found_any) {
+        float t, u;
+        LMatrix4f accent_mat;
+
+        switch (transform) {
+        case CT_none:
+          accent_mat = LMatrix4f::ident_mat();
+          break;
+
+        case CT_mirror_x:
+          accent_mat = LMatrix4f::scale_mat(-1.0f, 1.0f, 1.0f);
+          
+          t = -min_accent[0];
+          min_accent[0] = -max_accent[0];
+          max_accent[0] = t;
+          break;
+
+        case CT_mirror_y:
+          accent_mat = LMatrix4f::scale_mat(1.0f, -1.0f, 1.0f);
+          
+          t = -min_accent[2];
+          min_accent[2] = -max_accent[2];
+          max_accent[2] = t;
+          break;
+
+        case CT_rotate_90:
+          accent_mat =
+            LMatrix4f::rotate_mat_normaxis(90.0f, LVecBase3f(0.0f, 1.0f, 0.0f));
+          // rotate min, max
+          t = min_accent[0];
+          u = max_accent[0];
+          min_accent[0] = min_accent[2];
+          max_accent[0] = max_accent[2];
+          min_accent[2] = -u;
+          max_accent[2] = -t;
+          break;
+
+        case CT_tiny:
+          accent_mat = LMatrix4f::scale_mat(tiny_accent_scale);
+          min_accent *= tiny_accent_scale;
+          max_accent *= tiny_accent_scale;
+          break;
+
+        case CT_tiny_mirror_x:
+          accent_mat = LMatrix4f::scale_mat(-tiny_accent_scale, 1.0f, tiny_accent_scale);
+          
+          t = -min_accent[0] * tiny_accent_scale;
+          min_accent[0] = -max_accent[0] * tiny_accent_scale;
+          max_accent[0] = t;
+          break;
+
+        case CT_tiny_rotate_90:
+          accent_mat =
+            LMatrix4f::rotate_mat_normaxis(90.0f, LVecBase3f(0.0f, 1.0f, 0.0f)) *
+            LMatrix4f::scale_mat(tiny_accent_scale);
+
+          // rotate min, max
+          t = min_accent[0];
+          u = max_accent[0];
+          min_accent[0] = min_accent[2] * tiny_accent_scale;
+          max_accent[0] = max_accent[2] * tiny_accent_scale;
+          min_accent[2] = -u * tiny_accent_scale;
+          max_accent[2] = -t * tiny_accent_scale;
+          break;
+        }
+
+        LPoint3f accent_centroid = (min_accent + max_accent) / 2.0f;
+        float accent_height = max_accent[2] - min_accent[2];
+        LVector3f trans;
+        switch (placement) {
+        case CP_above:
+          // A little above the character.
+          trans.set(centroid[0] - accent_centroid[0], 0.0f,
+                    max_vert[2] - accent_centroid[2] + accent_height * 0.5);
+          break;
+
+        case CP_below:
+          // A little below the character.
+          trans.set(centroid[0] - accent_centroid[0], 0.0f,
+                    min_vert[2] - accent_centroid[2] - accent_height * 0.5);
+          break;
+
+        case CP_top:
+          // Touching the top of the character.
+          trans.set(centroid[0] - accent_centroid[0], 0.0f,
+                    max_vert[2] - accent_centroid[2]);
+          break;
+
+        case CP_bottom:
+          // Touching the bottom of the character.
+          trans.set(centroid[0] - accent_centroid[0], 0.0f,
+                    min_vert[2] - accent_centroid[2]);
+          break;
+
+        case CP_within:
+          // Centered within the character.
+          trans.set(centroid[0] - accent_centroid[0], 0.0f,
+                    centroid[2] - accent_centroid[2]);
+          break;
+        }
+
+        accent_mat.set_row(3, trans);
+        accent_geom->transform_vertices(accent_mat);
+
+        dest->add_geom(accent_geom, accent_glyph->get_state());
+        geom_array[num_geoms++] = accent_geom;
+      }
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::make_frame
 //       Access: Private

+ 62 - 17
panda/src/text/textNode.h

@@ -23,8 +23,8 @@
 
 #include "config_text.h"
 #include "textFont.h"
+#include "unicodeLatinMap.h"
 #include "pandaNode.h"
-
 #include "luse.h"
 
 class StringDecoder;
@@ -89,6 +89,11 @@ PUBLISHED:
 
   INLINE float get_line_height() const;
 
+  INLINE void set_small_caps(bool small_caps);
+  INLINE bool get_small_caps() const;
+  INLINE void set_small_caps_scale(float small_caps_scale);
+  INLINE float get_small_caps_scale() const;
+
   INLINE void set_slant(float slant);
   INLINE float get_slant() const;
 
@@ -182,6 +187,7 @@ PUBLISHED:
   INLINE void append_char(int character);
   INLINE int get_num_chars() const;
   INLINE int get_char(int index) const;
+  INLINE string get_text_as_ascii() const;
 
   INLINE float calc_width(int character) const;
   INLINE float calc_width(const string &line) const;
@@ -213,6 +219,7 @@ public:
   INLINE void set_wtext(const wstring &wtext);
   INLINE const wstring &get_wtext() const;
   INLINE void append_wtext(const wstring &text);
+  wstring get_wtext_as_ascii() const;
 
   INLINE float calc_width(const wstring &line) const;
   INLINE wstring wordwrap_to(const wstring &wtext, float wordwrap_width,
@@ -262,6 +269,42 @@ private:
                     LVector2f &ul, LVector2f &lr, int &num_rows);
 #endif  // CPPPARSER
 
+  enum CheesyPlacement {
+    CP_above,
+    CP_below,
+    CP_top,
+    CP_bottom,
+    CP_within,
+  };
+  enum CheesyTransform {
+    CT_none,
+    CT_mirror_x,
+    CT_mirror_y,
+    CT_rotate_90,
+    CT_tiny,
+    CT_tiny_mirror_x,
+    CT_tiny_rotate_90,
+  };
+
+  void get_character_glyphs(int character, TextFont *font,
+                            bool &got_glyph, const TextGlyph *&glyph,
+                            const TextGlyph *&second_glyph,
+                            UnicodeLatinMap::AccentType &accent_type,
+                            int &additional_flags,
+                            float &glyph_scale, float &advance_scale);
+
+  void tack_on_accent(UnicodeLatinMap::AccentType accent_type,
+                      const LPoint3f &min_vert, const LPoint3f &max_vert,
+                      const LPoint3f &centroid,
+                      TextFont *font, GeomNode *dest, 
+                      Geom *geom_array[], int &num_geoms);
+  void tack_on_accent(char accent_mark, CheesyPlacement placement,
+                      CheesyTransform transform,
+                      const LPoint3f &min_vert, const LPoint3f &max_vert,
+                      const LPoint3f &centroid,
+                      TextFont *font, GeomNode *dest, 
+                      Geom *geom_array[], int &num_geoms);
+
   PT(PandaNode) make_frame();
   PT(PandaNode) make_card();
   PT(PandaNode) make_card_with_border();
@@ -281,22 +324,23 @@ private:
   Colorf _card_color;
 
   enum Flags {
-    F_has_text_color   =  0x0001,
-    F_has_wordwrap     =  0x0002,
-    F_has_frame        =  0x0004,
-    F_frame_as_margin  =  0x0008,
-    F_has_card         =  0x0010,
-    F_card_as_margin   =  0x0020,
-    F_has_card_texture =  0x0040,
-    F_has_shadow       =  0x0080,
-    F_frame_corners    =  0x0100,
-    F_card_transp      =  0x0200,
-    F_has_card_border  =  0x0400,
-    F_expand_amp       =  0x0800,
-    F_got_text         =  0x1000,
-    F_got_wtext        =  0x2000,
-    F_needs_rebuild    =  0x4000,
-    F_needs_measure    =  0x8000,
+    F_has_text_color   =  0x00000001,
+    F_has_wordwrap     =  0x00000002,
+    F_has_frame        =  0x00000004,
+    F_frame_as_margin  =  0x00000008,
+    F_has_card         =  0x00000010,
+    F_card_as_margin   =  0x00000020,
+    F_has_card_texture =  0x00000040,
+    F_has_shadow       =  0x00000080,
+    F_frame_corners    =  0x00000100,
+    F_card_transp      =  0x00000200,
+    F_has_card_border  =  0x00000400,
+    F_expand_amp       =  0x00000800,
+    F_got_text         =  0x00001000,
+    F_got_wtext        =  0x00002000,
+    F_needs_rebuild    =  0x00004000,
+    F_needs_measure    =  0x00008000,
+    F_small_caps       =  0x00010000,
   };
 
   int _flags;
@@ -305,6 +349,7 @@ private:
   float _frame_width;
   float _card_border_size;
   float _card_border_uv_portion;
+  float _small_caps_scale;
 
   LVector2f _frame_ul, _frame_lr;
   LVector2f _card_ul, _card_lr;

+ 1 - 0
panda/src/text/text_composite1.cxx

@@ -9,3 +9,4 @@
 #include "stringDecoder.cxx"
 #include "textFont.cxx"
 #include "textGlyph.cxx"
+#include "unicodeLatinMap.cxx"

+ 1359 - 0
panda/src/text/unicodeLatinMap.cxx

@@ -0,0 +1,1359 @@
+// Filename: unicodeLatinMap.cxx
+// Created by:  drose (01Feb03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "unicodeLatinMap.h"
+
+bool UnicodeLatinMap::_initialized = false;
+UnicodeLatinMap::ByCharacter UnicodeLatinMap::_by_character;
+const UnicodeLatinMap::Entry *UnicodeLatinMap::_direct_chars[UnicodeLatinMap::max_direct_chars];
+
+static const UnicodeLatinMap::Entry latin_map[] = {
+  { 0x00a1, UnicodeLatinMap::CT_punct, '!', 0, 0x00a1, 0x00a1,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned },
+  { 0x00bf, UnicodeLatinMap::CT_punct, '?', 0, 0x00bf, 0x00bf,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned },
+  { 0x0061, UnicodeLatinMap::CT_lower, 'a', 0, 0x0061, 0x0041,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0041, UnicodeLatinMap::CT_upper, 'A', 0, 0x0061, 0x0041,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x00e1, UnicodeLatinMap::CT_lower, 'a', 0, 0x00e1, 0x00c1,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x00c1, UnicodeLatinMap::CT_upper, 'A', 0, 0x00e1, 0x00c1,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x00e0, UnicodeLatinMap::CT_lower, 'a', 0, 0x00e0, 0x00c0,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x00c0, UnicodeLatinMap::CT_upper, 'A', 0, 0x00e0, 0x00c0,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x0103, UnicodeLatinMap::CT_lower, 'a', 0, 0x0103, 0x0102,
+    UnicodeLatinMap::AT_breve, 0 },
+  { 0x0102, UnicodeLatinMap::CT_upper, 'A', 0, 0x0103, 0x0102,
+    UnicodeLatinMap::AT_breve, 0 },
+  { 0x1eaf, UnicodeLatinMap::CT_lower, 'a', 0, 0x1eaf, 0x1eae,
+    UnicodeLatinMap::AT_breve_and_acute, 0 },
+  { 0x1eae, UnicodeLatinMap::CT_upper, 'A', 0, 0x1eaf, 0x1eae,
+    UnicodeLatinMap::AT_breve_and_acute, 0 },
+  { 0x1eb1, UnicodeLatinMap::CT_lower, 'a', 0, 0x1eb1, 0x1eb0,
+    UnicodeLatinMap::AT_breve_and_grave, 0 },
+  { 0x1eb0, UnicodeLatinMap::CT_upper, 'A', 0, 0x1eb1, 0x1eb0,
+    UnicodeLatinMap::AT_breve_and_grave, 0 },
+  { 0x1eb5, UnicodeLatinMap::CT_lower, 'a', 0, 0x1eb5, 0x1eb4,
+    UnicodeLatinMap::AT_breve_and_tilde, 0 },
+  { 0x1eb4, UnicodeLatinMap::CT_upper, 'A', 0, 0x1eb5, 0x1eb4,
+    UnicodeLatinMap::AT_breve_and_tilde, 0 },
+  { 0x1eb3, UnicodeLatinMap::CT_lower, 'a', 0, 0x1eb3, 0x1eb2,
+    UnicodeLatinMap::AT_breve_and_hook_above, 0 },
+  { 0x1eb2, UnicodeLatinMap::CT_upper, 'A', 0, 0x1eb3, 0x1eb2,
+    UnicodeLatinMap::AT_breve_and_hook_above, 0 },
+  { 0x00e2, UnicodeLatinMap::CT_lower, 'a', 0, 0x00e2, 0x00c2,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x00c2, UnicodeLatinMap::CT_upper, 'A', 0, 0x00e2, 0x00c2,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x1ea5, UnicodeLatinMap::CT_lower, 'a', 0, 0x1ea5, 0x1ea4,
+    UnicodeLatinMap::AT_circumflex_and_acute, 0 },
+  { 0x1ea4, UnicodeLatinMap::CT_upper, 'A', 0, 0x1ea5, 0x1ea4,
+    UnicodeLatinMap::AT_circumflex_and_acute, 0 },
+  { 0x1ea7, UnicodeLatinMap::CT_lower, 'a', 0, 0x1ea7, 0x1ea6,
+    UnicodeLatinMap::AT_circumflex_and_grave, 0 },
+  { 0x1ea6, UnicodeLatinMap::CT_upper, 'A', 0, 0x1ea7, 0x1ea6,
+    UnicodeLatinMap::AT_circumflex_and_grave, 0 },
+  { 0x1eab, UnicodeLatinMap::CT_lower, 'a', 0, 0x1eab, 0x1eaa,
+    UnicodeLatinMap::AT_circumflex_and_tilde, 0 },
+  { 0x1eaa, UnicodeLatinMap::CT_upper, 'A', 0, 0x1eab, 0x1eaa,
+    UnicodeLatinMap::AT_circumflex_and_tilde, 0 },
+  { 0x1ea9, UnicodeLatinMap::CT_lower, 'a', 0, 0x1ea9, 0x1ea8,
+    UnicodeLatinMap::AT_circumflex_and_hook_above, 0 },
+  { 0x1ea8, UnicodeLatinMap::CT_upper, 'A', 0, 0x1ea9, 0x1ea8,
+    UnicodeLatinMap::AT_circumflex_and_hook_above, 0 },
+  { 0x01ce, UnicodeLatinMap::CT_lower, 'a', 0, 0x01ce, 0x01cd,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x01cd, UnicodeLatinMap::CT_upper, 'A', 0, 0x01ce, 0x01cd,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x00e5, UnicodeLatinMap::CT_lower, 'a', 0, 0x00e5, 0x00c5,
+    UnicodeLatinMap::AT_ring_above, 0 },
+  { 0x00c5, UnicodeLatinMap::CT_upper, 'A', 0, 0x00e5, 0x00c5,
+    UnicodeLatinMap::AT_ring_above, 0 },
+  { 0x01fb, UnicodeLatinMap::CT_lower, 'a', 0, 0x01fb, 0x01fa,
+    UnicodeLatinMap::AT_ring_above_and_acute, 0 },
+  { 0x01fa, UnicodeLatinMap::CT_upper, 'A', 0, 0x01fb, 0x01fa,
+    UnicodeLatinMap::AT_ring_above_and_acute, 0 },
+  { 0x00e4, UnicodeLatinMap::CT_lower, 'a', 0, 0x00e4, 0x00c4,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x00c4, UnicodeLatinMap::CT_upper, 'A', 0, 0x00e4, 0x00c4,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x01df, UnicodeLatinMap::CT_lower, 'a', 0, 0x01df, 0x01de,
+    UnicodeLatinMap::AT_diaeresis_and_macron, 0 },
+  { 0x01de, UnicodeLatinMap::CT_upper, 'A', 0, 0x01df, 0x01de,
+    UnicodeLatinMap::AT_diaeresis_and_macron, 0 },
+  { 0x00e3, UnicodeLatinMap::CT_lower, 'a', 0, 0x00e3, 0x00c3,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x00c3, UnicodeLatinMap::CT_upper, 'A', 0, 0x00e3, 0x00c3,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x0227, UnicodeLatinMap::CT_lower, 'a', 0, 0x0227, 0x0226,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x0226, UnicodeLatinMap::CT_upper, 'A', 0, 0x0227, 0x0226,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x01e1, UnicodeLatinMap::CT_lower, 'a', 0, 0x01e1, 0x01e0,
+    UnicodeLatinMap::AT_dot_above_and_macron, 0 },
+  { 0x01e0, UnicodeLatinMap::CT_upper, 'A', 0, 0x01e1, 0x01e0,
+    UnicodeLatinMap::AT_dot_above_and_macron, 0 },
+  { 0x0105, UnicodeLatinMap::CT_lower, 'a', 0, 0x0105, 0x0104,
+    UnicodeLatinMap::AT_ogonek, 0 },
+  { 0x0104, UnicodeLatinMap::CT_upper, 'A', 0, 0x0105, 0x0104,
+    UnicodeLatinMap::AT_ogonek, 0 },
+  { 0x0101, UnicodeLatinMap::CT_lower, 'a', 0, 0x0101, 0x0100,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x0100, UnicodeLatinMap::CT_upper, 'A', 0, 0x0101, 0x0100,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x1ea3, UnicodeLatinMap::CT_lower, 'a', 0, 0x1ea3, 0x1ea2,
+    UnicodeLatinMap::AT_hook_above, 0 },
+  { 0x1ea2, UnicodeLatinMap::CT_upper, 'A', 0, 0x1ea3, 0x1ea2,
+    UnicodeLatinMap::AT_hook_above, 0 },
+  { 0x0201, UnicodeLatinMap::CT_lower, 'a', 0, 0x0201, 0x0200,
+    UnicodeLatinMap::AT_double_grave, 0 },
+  { 0x0200, UnicodeLatinMap::CT_upper, 'A', 0, 0x0201, 0x0200,
+    UnicodeLatinMap::AT_double_grave, 0 },
+  { 0x0203, UnicodeLatinMap::CT_lower, 'a', 0, 0x0203, 0x0202,
+    UnicodeLatinMap::AT_inverted_breve, 0 },
+  { 0x0202, UnicodeLatinMap::CT_upper, 'A', 0, 0x0203, 0x0202,
+    UnicodeLatinMap::AT_inverted_breve, 0 },
+  { 0x1ea1, UnicodeLatinMap::CT_lower, 'a', 0, 0x1ea1, 0x1ea0,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1ea0, UnicodeLatinMap::CT_upper, 'A', 0, 0x1ea1, 0x1ea0,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1eb7, UnicodeLatinMap::CT_lower, 'a', 0, 0x1eb7, 0x1eb6,
+    UnicodeLatinMap::AT_breve_and_dot_below, 0 },
+  { 0x1eb6, UnicodeLatinMap::CT_upper, 'A', 0, 0x1eb7, 0x1eb6,
+    UnicodeLatinMap::AT_breve_and_dot_below, 0 },
+  { 0x1ead, UnicodeLatinMap::CT_lower, 'a', 0, 0x1ead, 0x1eac,
+    UnicodeLatinMap::AT_circumflex_and_dot_below, 0 },
+  { 0x1eac, UnicodeLatinMap::CT_upper, 'A', 0, 0x1ead, 0x1eac,
+    UnicodeLatinMap::AT_circumflex_and_dot_below, 0 },
+  { 0x1e01, UnicodeLatinMap::CT_lower, 'a', 0, 0x1e01, 0x1e00,
+    UnicodeLatinMap::AT_ring_below, 0 },
+  { 0x1e00, UnicodeLatinMap::CT_upper, 'A', 0, 0x1e01, 0x1e00,
+    UnicodeLatinMap::AT_ring_below, 0 },
+  { 0x00e6, UnicodeLatinMap::CT_lower, 'a', 'e', 0x00e6, 0x00c6,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature },
+  { 0x00c6, UnicodeLatinMap::CT_upper, 'A', 'E', 0x00e6, 0x00c6,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature },
+  { 0x01fd, UnicodeLatinMap::CT_lower, 'a', 'e', 0x01fd, 0x01fc,
+    UnicodeLatinMap::AT_acute, UnicodeLatinMap::AF_ligature },
+  { 0x01fc, UnicodeLatinMap::CT_upper, 'A', 'E', 0x01fd, 0x01fc,
+    UnicodeLatinMap::AT_acute, UnicodeLatinMap::AF_ligature },
+  { 0x01e3, UnicodeLatinMap::CT_lower, 'a', 'e', 0x01e3, 0x01e2,
+    UnicodeLatinMap::AT_macron, UnicodeLatinMap::AF_ligature },
+  { 0x01e2, UnicodeLatinMap::CT_upper, 'A', 'E', 0x01e3, 0x01e2,
+    UnicodeLatinMap::AT_macron, UnicodeLatinMap::AF_ligature },
+  { 0x0250, UnicodeLatinMap::CT_lower, 'a', 0, 0x0250, 0x0041,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned },
+  { 0x0062, UnicodeLatinMap::CT_lower, 'b', 0, 0x0062, 0x0042,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0042, UnicodeLatinMap::CT_upper, 'B', 0, 0x0062, 0x0042,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x1e03, UnicodeLatinMap::CT_lower, 'b', 0, 0x1e03, 0x1e02,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e02, UnicodeLatinMap::CT_upper, 'B', 0, 0x1e03, 0x1e02,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e05, UnicodeLatinMap::CT_lower, 'b', 0, 0x1e05, 0x1e04,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e04, UnicodeLatinMap::CT_upper, 'B', 0, 0x1e05, 0x1e04,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e07, UnicodeLatinMap::CT_lower, 'b', 0, 0x1e07, 0x1e06,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x1e06, UnicodeLatinMap::CT_upper, 'B', 0, 0x1e07, 0x1e06,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x0299, UnicodeLatinMap::CT_upper, 'B', 0, 0x0062, 0x0299,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_smallcap },
+  { 0x0180, UnicodeLatinMap::CT_lower, 'b', 0, 0x0180, 0x0042,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x0253, UnicodeLatinMap::CT_lower, 'b', 0, 0x0253, 0x0181,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0181, UnicodeLatinMap::CT_upper, 'B', 0, 0x0253, 0x0181,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0183, UnicodeLatinMap::CT_lower, 'b', 0, 0x0183, 0x0182,
+    UnicodeLatinMap::AT_topbar, 0 },
+  { 0x0182, UnicodeLatinMap::CT_upper, 'B', 0, 0x0183, 0x0182,
+    UnicodeLatinMap::AT_topbar, 0 },
+  { 0x0063, UnicodeLatinMap::CT_lower, 'c', 0, 0x0063, 0x0043,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0043, UnicodeLatinMap::CT_upper, 'C', 0, 0x0063, 0x0043,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0107, UnicodeLatinMap::CT_lower, 'c', 0, 0x0107, 0x0106,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x0106, UnicodeLatinMap::CT_upper, 'C', 0, 0x0107, 0x0106,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x0109, UnicodeLatinMap::CT_lower, 'c', 0, 0x0109, 0x0108,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x0108, UnicodeLatinMap::CT_upper, 'C', 0, 0x0109, 0x0108,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x010d, UnicodeLatinMap::CT_lower, 'c', 0, 0x010d, 0x010c,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x010c, UnicodeLatinMap::CT_upper, 'C', 0, 0x010d, 0x010c,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x010b, UnicodeLatinMap::CT_lower, 'c', 0, 0x010b, 0x010a,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x010a, UnicodeLatinMap::CT_upper, 'C', 0, 0x010b, 0x010a,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x00e7, UnicodeLatinMap::CT_lower, 'c', 0, 0x00e7, 0x00c7,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x00c7, UnicodeLatinMap::CT_upper, 'C', 0, 0x00e7, 0x00c7,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x1e09, UnicodeLatinMap::CT_lower, 'c', 0, 0x1e09, 0x1e08,
+    UnicodeLatinMap::AT_cedilla_and_acute, 0 },
+  { 0x1e08, UnicodeLatinMap::CT_upper, 'C', 0, 0x1e09, 0x1e08,
+    UnicodeLatinMap::AT_cedilla_and_acute, 0 },
+  { 0x0188, UnicodeLatinMap::CT_lower, 'c', 0, 0x0188, 0x0187,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0187, UnicodeLatinMap::CT_upper, 'C', 0, 0x0188, 0x0187,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0255, UnicodeLatinMap::CT_lower, 'c', 0, 0x0255, 0x0043,
+    UnicodeLatinMap::AT_curl, 0 },
+  { 0x0064, UnicodeLatinMap::CT_lower, 'd', 0, 0x0064, 0x0044,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0044, UnicodeLatinMap::CT_upper, 'D', 0, 0x0064, 0x0044,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x010f, UnicodeLatinMap::CT_lower, 'd', 0, 0x010f, 0x010e,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x010e, UnicodeLatinMap::CT_upper, 'D', 0, 0x010f, 0x010e,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x1e0b, UnicodeLatinMap::CT_lower, 'd', 0, 0x1e0b, 0x1e0a,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e0a, UnicodeLatinMap::CT_upper, 'D', 0, 0x1e0b, 0x1e0a,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e11, UnicodeLatinMap::CT_lower, 'd', 0, 0x1e11, 0x1e10,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x1e10, UnicodeLatinMap::CT_upper, 'D', 0, 0x1e11, 0x1e10,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x1e0d, UnicodeLatinMap::CT_lower, 'd', 0, 0x1e0d, 0x1e0c,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e0c, UnicodeLatinMap::CT_upper, 'D', 0, 0x1e0d, 0x1e0c,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e13, UnicodeLatinMap::CT_lower, 'd', 0, 0x1e13, 0x1e12,
+    UnicodeLatinMap::AT_circumflex_below, 0 },
+  { 0x1e12, UnicodeLatinMap::CT_upper, 'D', 0, 0x1e13, 0x1e12,
+    UnicodeLatinMap::AT_circumflex_below, 0 },
+  { 0x1e0f, UnicodeLatinMap::CT_lower, 'd', 0, 0x1e0f, 0x1e0e,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x1e0e, UnicodeLatinMap::CT_upper, 'D', 0, 0x1e0f, 0x1e0e,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x0111, UnicodeLatinMap::CT_lower, 'd', 0, 0x0111, 0x0110,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x0110, UnicodeLatinMap::CT_upper, 'D', 0, 0x0111, 0x0110,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x0257, UnicodeLatinMap::CT_lower, 'd', 0, 0x0257, 0x018a,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x018a, UnicodeLatinMap::CT_upper, 'D', 0, 0x0257, 0x018a,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x018c, UnicodeLatinMap::CT_lower, 'd', 0, 0x018c, 0x018b,
+    UnicodeLatinMap::AT_topbar, 0 },
+  { 0x018b, UnicodeLatinMap::CT_upper, 'D', 0, 0x018c, 0x018b,
+    UnicodeLatinMap::AT_topbar, 0 },
+  { 0x0065, UnicodeLatinMap::CT_lower, 'e', 0, 0x0065, 0x0045,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0045, UnicodeLatinMap::CT_upper, 'E', 0, 0x0065, 0x0045,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x00e9, UnicodeLatinMap::CT_lower, 'e', 0, 0x00e9, 0x00c9,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x00c9, UnicodeLatinMap::CT_upper, 'E', 0, 0x00e9, 0x00c9,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x00e8, UnicodeLatinMap::CT_lower, 'e', 0, 0x00e8, 0x00c8,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x00c8, UnicodeLatinMap::CT_upper, 'E', 0, 0x00e8, 0x00c8,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x0115, UnicodeLatinMap::CT_lower, 'e', 0, 0x0115, 0x0114,
+    UnicodeLatinMap::AT_breve, 0 },
+  { 0x0114, UnicodeLatinMap::CT_upper, 'E', 0, 0x0115, 0x0114,
+    UnicodeLatinMap::AT_breve, 0 },
+  { 0x00ea, UnicodeLatinMap::CT_lower, 'e', 0, 0x00ea, 0x00ca,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x00ca, UnicodeLatinMap::CT_upper, 'E', 0, 0x00ea, 0x00ca,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x1ebf, UnicodeLatinMap::CT_lower, 'e', 0, 0x1ebf, 0x1ebe,
+    UnicodeLatinMap::AT_circumflex_and_acute, 0 },
+  { 0x1ebe, UnicodeLatinMap::CT_upper, 'E', 0, 0x1ebf, 0x1ebe,
+    UnicodeLatinMap::AT_circumflex_and_acute, 0 },
+  { 0x1ec1, UnicodeLatinMap::CT_lower, 'e', 0, 0x1ec1, 0x1ec0,
+    UnicodeLatinMap::AT_circumflex_and_grave, 0 },
+  { 0x1ec0, UnicodeLatinMap::CT_upper, 'E', 0, 0x1ec1, 0x1ec0,
+    UnicodeLatinMap::AT_circumflex_and_grave, 0 },
+  { 0x1ec5, UnicodeLatinMap::CT_lower, 'e', 0, 0x1ec5, 0x1ec4,
+    UnicodeLatinMap::AT_circumflex_and_tilde, 0 },
+  { 0x1ec4, UnicodeLatinMap::CT_upper, 'E', 0, 0x1ec5, 0x1ec4,
+    UnicodeLatinMap::AT_circumflex_and_tilde, 0 },
+  { 0x1ec3, UnicodeLatinMap::CT_lower, 'e', 0, 0x1ec3, 0x1ec2,
+    UnicodeLatinMap::AT_circumflex_and_hook_above, 0 },
+  { 0x1ec2, UnicodeLatinMap::CT_upper, 'E', 0, 0x1ec3, 0x1ec2,
+    UnicodeLatinMap::AT_circumflex_and_hook_above, 0 },
+  { 0x011b, UnicodeLatinMap::CT_lower, 'e', 0, 0x011b, 0x011a,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x011a, UnicodeLatinMap::CT_upper, 'E', 0, 0x011b, 0x011a,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x00eb, UnicodeLatinMap::CT_lower, 'e', 0, 0x00eb, 0x00cb,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x00cb, UnicodeLatinMap::CT_upper, 'E', 0, 0x00eb, 0x00cb,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x1ebd, UnicodeLatinMap::CT_lower, 'e', 0, 0x1ebd, 0x1ebc,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x1ebc, UnicodeLatinMap::CT_upper, 'E', 0, 0x1ebd, 0x1ebc,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x0117, UnicodeLatinMap::CT_lower, 'e', 0, 0x0117, 0x0116,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x0116, UnicodeLatinMap::CT_upper, 'E', 0, 0x0117, 0x0116,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x0229, UnicodeLatinMap::CT_lower, 'e', 0, 0x0229, 0x0228,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x0228, UnicodeLatinMap::CT_upper, 'E', 0, 0x0229, 0x0228,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x1e1d, UnicodeLatinMap::CT_lower, 'e', 0, 0x1e1d, 0x1e1c,
+    UnicodeLatinMap::AT_cedilla_and_breve, 0 },
+  { 0x1e1c, UnicodeLatinMap::CT_upper, 'E', 0, 0x1e1d, 0x1e1c,
+    UnicodeLatinMap::AT_cedilla_and_breve, 0 },
+  { 0x0119, UnicodeLatinMap::CT_lower, 'e', 0, 0x0119, 0x0118,
+    UnicodeLatinMap::AT_ogonek, 0 },
+  { 0x0118, UnicodeLatinMap::CT_upper, 'E', 0, 0x0119, 0x0118,
+    UnicodeLatinMap::AT_ogonek, 0 },
+  { 0x0113, UnicodeLatinMap::CT_lower, 'e', 0, 0x0113, 0x0112,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x0112, UnicodeLatinMap::CT_upper, 'E', 0, 0x0113, 0x0112,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x1e17, UnicodeLatinMap::CT_lower, 'e', 0, 0x1e17, 0x1e16,
+    UnicodeLatinMap::AT_macron_and_acute, 0 },
+  { 0x1e16, UnicodeLatinMap::CT_upper, 'E', 0, 0x1e17, 0x1e16,
+    UnicodeLatinMap::AT_macron_and_acute, 0 },
+  { 0x1e15, UnicodeLatinMap::CT_lower, 'e', 0, 0x1e15, 0x1e14,
+    UnicodeLatinMap::AT_macron_and_grave, 0 },
+  { 0x1e14, UnicodeLatinMap::CT_upper, 'E', 0, 0x1e15, 0x1e14,
+    UnicodeLatinMap::AT_macron_and_grave, 0 },
+  { 0x1ebb, UnicodeLatinMap::CT_lower, 'e', 0, 0x1ebb, 0x1eba,
+    UnicodeLatinMap::AT_hook_above, 0 },
+  { 0x1eba, UnicodeLatinMap::CT_upper, 'E', 0, 0x1ebb, 0x1eba,
+    UnicodeLatinMap::AT_hook_above, 0 },
+  { 0x0205, UnicodeLatinMap::CT_lower, 'e', 0, 0x0205, 0x0204,
+    UnicodeLatinMap::AT_double_grave, 0 },
+  { 0x0204, UnicodeLatinMap::CT_upper, 'E', 0, 0x0205, 0x0204,
+    UnicodeLatinMap::AT_double_grave, 0 },
+  { 0x0207, UnicodeLatinMap::CT_lower, 'e', 0, 0x0207, 0x0206,
+    UnicodeLatinMap::AT_inverted_breve, 0 },
+  { 0x0206, UnicodeLatinMap::CT_upper, 'E', 0, 0x0207, 0x0206,
+    UnicodeLatinMap::AT_inverted_breve, 0 },
+  { 0x1eb9, UnicodeLatinMap::CT_lower, 'e', 0, 0x1eb9, 0x1eb8,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1eb8, UnicodeLatinMap::CT_upper, 'E', 0, 0x1eb9, 0x1eb8,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1ec7, UnicodeLatinMap::CT_lower, 'e', 0, 0x1ec7, 0x1ec6,
+    UnicodeLatinMap::AT_circumflex_and_dot_below, 0 },
+  { 0x1ec6, UnicodeLatinMap::CT_upper, 'E', 0, 0x1ec7, 0x1ec6,
+    UnicodeLatinMap::AT_circumflex_and_dot_below, 0 },
+  { 0x1e19, UnicodeLatinMap::CT_lower, 'e', 0, 0x1e19, 0x1e18,
+    UnicodeLatinMap::AT_circumflex_below, 0 },
+  { 0x1e18, UnicodeLatinMap::CT_upper, 'E', 0, 0x1e19, 0x1e18,
+    UnicodeLatinMap::AT_circumflex_below, 0 },
+  { 0x1e1b, UnicodeLatinMap::CT_lower, 'e', 0, 0x1e1b, 0x1e1a,
+    UnicodeLatinMap::AT_tilde_below, 0 },
+  { 0x1e1a, UnicodeLatinMap::CT_upper, 'E', 0, 0x1e1b, 0x1e1a,
+    UnicodeLatinMap::AT_tilde_below, 0 },
+  { 0x01dd, UnicodeLatinMap::CT_lower, 'e', 0, 0x01dd, 0x0045,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned },
+  { 0x018e, UnicodeLatinMap::CT_upper, 'E', 0, 0x0258, 0x018e,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_reversed },
+  { 0x0258, UnicodeLatinMap::CT_lower, 'e', 0, 0x0258, 0x018e,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_reversed },
+  { 0x0066, UnicodeLatinMap::CT_lower, 'f', 0, 0x0066, 0x0046,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0046, UnicodeLatinMap::CT_upper, 'F', 0, 0x0066, 0x0046,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x1e1f, UnicodeLatinMap::CT_lower, 'f', 0, 0x1e1f, 0x1e1e,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e1e, UnicodeLatinMap::CT_upper, 'F', 0, 0x1e1f, 0x1e1e,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x0192, UnicodeLatinMap::CT_lower, 'f', 0, 0x0192, 0x0191,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0191, UnicodeLatinMap::CT_upper, 'F', 0, 0x0192, 0x0191,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0067, UnicodeLatinMap::CT_lower, 'g', 0, 0x0067, 0x0047,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0047, UnicodeLatinMap::CT_upper, 'G', 0, 0x0067, 0x0047,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x01f5, UnicodeLatinMap::CT_lower, 'g', 0, 0x01f5, 0x01f4,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x01f4, UnicodeLatinMap::CT_upper, 'G', 0, 0x01f5, 0x01f4,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x011f, UnicodeLatinMap::CT_lower, 'g', 0, 0x011f, 0x011e,
+    UnicodeLatinMap::AT_breve, 0 },
+  { 0x011e, UnicodeLatinMap::CT_upper, 'G', 0, 0x011f, 0x011e,
+    UnicodeLatinMap::AT_breve, 0 },
+  { 0x011d, UnicodeLatinMap::CT_lower, 'g', 0, 0x011d, 0x011c,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x011c, UnicodeLatinMap::CT_upper, 'G', 0, 0x011d, 0x011c,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x01e7, UnicodeLatinMap::CT_lower, 'g', 0, 0x01e7, 0x01e6,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x01e6, UnicodeLatinMap::CT_upper, 'G', 0, 0x01e7, 0x01e6,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x0121, UnicodeLatinMap::CT_lower, 'g', 0, 0x0121, 0x0120,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x0120, UnicodeLatinMap::CT_upper, 'G', 0, 0x0121, 0x0120,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x0123, UnicodeLatinMap::CT_lower, 'g', 0, 0x0123, 0x0122,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x0122, UnicodeLatinMap::CT_upper, 'G', 0, 0x0123, 0x0122,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x1e21, UnicodeLatinMap::CT_lower, 'g', 0, 0x1e21, 0x1e20,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x1e20, UnicodeLatinMap::CT_upper, 'G', 0, 0x1e21, 0x1e20,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x0262, UnicodeLatinMap::CT_upper, 'G', 0, 0x0067, 0x0262,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_smallcap },
+  { 0x01e5, UnicodeLatinMap::CT_lower, 'g', 0, 0x01e5, 0x01e4,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x01e4, UnicodeLatinMap::CT_upper, 'G', 0, 0x01e5, 0x01e4,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x0260, UnicodeLatinMap::CT_lower, 'g', 0, 0x0260, 0x0193,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0193, UnicodeLatinMap::CT_upper, 'G', 0, 0x0260, 0x0193,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x029b, UnicodeLatinMap::CT_upper, 'G', 0, 0x0067, 0x029b,
+    UnicodeLatinMap::AT_hook, UnicodeLatinMap::AF_smallcap },
+  { 0x01a3, UnicodeLatinMap::CT_lower, 'o', 'i', 0x01a3, 0x01a2,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature },
+  { 0x01a2, UnicodeLatinMap::CT_upper, 'O', 'I', 0x01a3, 0x01a2,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature },
+  { 0x0068, UnicodeLatinMap::CT_lower, 'h', 0, 0x0068, 0x0048,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0048, UnicodeLatinMap::CT_upper, 'H', 0, 0x0068, 0x0048,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0125, UnicodeLatinMap::CT_lower, 'h', 0, 0x0125, 0x0124,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x0124, UnicodeLatinMap::CT_upper, 'H', 0, 0x0125, 0x0124,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x021f, UnicodeLatinMap::CT_lower, 'h', 0, 0x021f, 0x021e,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x021e, UnicodeLatinMap::CT_upper, 'H', 0, 0x021f, 0x021e,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x1e27, UnicodeLatinMap::CT_lower, 'h', 0, 0x1e27, 0x1e26,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x1e26, UnicodeLatinMap::CT_upper, 'H', 0, 0x1e27, 0x1e26,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x1e23, UnicodeLatinMap::CT_lower, 'h', 0, 0x1e23, 0x1e22,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e22, UnicodeLatinMap::CT_upper, 'H', 0, 0x1e23, 0x1e22,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e29, UnicodeLatinMap::CT_lower, 'h', 0, 0x1e29, 0x1e28,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x1e28, UnicodeLatinMap::CT_upper, 'H', 0, 0x1e29, 0x1e28,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x1e25, UnicodeLatinMap::CT_lower, 'h', 0, 0x1e25, 0x1e24,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e24, UnicodeLatinMap::CT_upper, 'H', 0, 0x1e25, 0x1e24,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e2b, UnicodeLatinMap::CT_lower, 'h', 0, 0x1e2b, 0x1e2a,
+    UnicodeLatinMap::AT_breve_below, 0 },
+  { 0x1e2a, UnicodeLatinMap::CT_upper, 'H', 0, 0x1e2b, 0x1e2a,
+    UnicodeLatinMap::AT_breve_below, 0 },
+  { 0x1e96, UnicodeLatinMap::CT_lower, 'h', 0, 0x1e96, 0x0048,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x029c, UnicodeLatinMap::CT_upper, 'H', 0, 0x0068, 0x029c,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_smallcap },
+  { 0x0195, UnicodeLatinMap::CT_lower, 'h', 'v', 0x0195, 0x195,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature },
+  { 0x0127, UnicodeLatinMap::CT_lower, 'h', 0, 0x0127, 0x0126,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x0126, UnicodeLatinMap::CT_upper, 'H', 0, 0x0127, 0x0126,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x0266, UnicodeLatinMap::CT_lower, 'h', 0, 0x0266, 0x0048,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0069, UnicodeLatinMap::CT_lower, 'i', 0, 0x0069, 0x0049,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0049, UnicodeLatinMap::CT_upper, 'I', 0, 0x0069, 0x0049,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x00ed, UnicodeLatinMap::CT_lower, 'i', 0, 0x00ed, 0x00cd,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x00cd, UnicodeLatinMap::CT_upper, 'I', 0, 0x00ed, 0x00cd,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x00ec, UnicodeLatinMap::CT_lower, 'i', 0, 0x00ec, 0x00cc,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x00cc, UnicodeLatinMap::CT_upper, 'I', 0, 0x00ec, 0x00cc,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x012d, UnicodeLatinMap::CT_lower, 'i', 0, 0x012d, 0x012c,
+    UnicodeLatinMap::AT_breve, 0 },
+  { 0x012c, UnicodeLatinMap::CT_upper, 'I', 0, 0x012d, 0x012c,
+    UnicodeLatinMap::AT_breve, 0 },
+  { 0x00ee, UnicodeLatinMap::CT_lower, 'i', 0, 0x00ee, 0x00ce,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x00ce, UnicodeLatinMap::CT_upper, 'I', 0, 0x00ee, 0x00ce,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x01d0, UnicodeLatinMap::CT_lower, 'i', 0, 0x01d0, 0x01cf,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x01cf, UnicodeLatinMap::CT_upper, 'I', 0, 0x01d0, 0x01cf,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x00ef, UnicodeLatinMap::CT_lower, 'i', 0, 0x00ef, 0x00cf,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x00cf, UnicodeLatinMap::CT_upper, 'I', 0, 0x00ef, 0x00cf,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x1e2f, UnicodeLatinMap::CT_lower, 'i', 0, 0x1e2f, 0x1e2e,
+    UnicodeLatinMap::AT_diaeresis_and_acute, 0 },
+  { 0x1e2e, UnicodeLatinMap::CT_upper, 'I', 0, 0x1e2f, 0x1e2e,
+    UnicodeLatinMap::AT_diaeresis_and_acute, 0 },
+  { 0x0129, UnicodeLatinMap::CT_lower, 'i', 0, 0x0129, 0x0128,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x0128, UnicodeLatinMap::CT_upper, 'I', 0, 0x0129, 0x0128,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x0130, UnicodeLatinMap::CT_upper, 'I', 0, 0x0069, 0x0130,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x012f, UnicodeLatinMap::CT_lower, 'i', 0, 0x012f, 0x012e,
+    UnicodeLatinMap::AT_ogonek, 0 },
+  { 0x012e, UnicodeLatinMap::CT_upper, 'I', 0, 0x012f, 0x012e,
+    UnicodeLatinMap::AT_ogonek, 0 },
+  { 0x012b, UnicodeLatinMap::CT_lower, 'i', 0, 0x012b, 0x012a,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x012a, UnicodeLatinMap::CT_upper, 'I', 0, 0x012b, 0x012a,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x1ec9, UnicodeLatinMap::CT_lower, 'i', 0, 0x1ec9, 0x1ec8,
+    UnicodeLatinMap::AT_hook_above, 0 },
+  { 0x1ec8, UnicodeLatinMap::CT_upper, 'I', 0, 0x1ec9, 0x1ec8,
+    UnicodeLatinMap::AT_hook_above, 0 },
+  { 0x0209, UnicodeLatinMap::CT_lower, 'i', 0, 0x0209, 0x0208,
+    UnicodeLatinMap::AT_double_grave, 0 },
+  { 0x0208, UnicodeLatinMap::CT_upper, 'I', 0, 0x0209, 0x0208,
+    UnicodeLatinMap::AT_double_grave, 0 },
+  { 0x020b, UnicodeLatinMap::CT_lower, 'i', 0, 0x020b, 0x020a,
+    UnicodeLatinMap::AT_inverted_breve, 0 },
+  { 0x020a, UnicodeLatinMap::CT_upper, 'I', 0, 0x020b, 0x020a,
+    UnicodeLatinMap::AT_inverted_breve, 0 },
+  { 0x1ecb, UnicodeLatinMap::CT_lower, 'i', 0, 0x1ecb, 0x1eca,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1eca, UnicodeLatinMap::CT_upper, 'I', 0, 0x1ecb, 0x1eca,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e2d, UnicodeLatinMap::CT_lower, 'i', 0, 0x1e2d, 0x1e2c,
+    UnicodeLatinMap::AT_tilde_below, 0 },
+  { 0x1e2c, UnicodeLatinMap::CT_upper, 'I', 0, 0x1e2d, 0x1e2c,
+    UnicodeLatinMap::AT_tilde_below, 0 },
+  { 0x0131, UnicodeLatinMap::CT_lower, 'i', 0, 0x0131, 0x0049,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_dotless },
+  { 0x026a, UnicodeLatinMap::CT_upper, 'I', 0, 0x0069, 0x026a,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_smallcap },
+  { 0x0268, UnicodeLatinMap::CT_lower, 'i', 0, 0x0268, 0x0197,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x0197, UnicodeLatinMap::CT_upper, 'I', 0, 0x0268, 0x0197,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x006a, UnicodeLatinMap::CT_lower, 'j', 0, 0x006a, 0x004a,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x004a, UnicodeLatinMap::CT_upper, 'J', 0, 0x006a, 0x004a,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0135, UnicodeLatinMap::CT_lower, 'j', 0, 0x0135, 0x0134,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x0134, UnicodeLatinMap::CT_upper, 'J', 0, 0x0135, 0x0134,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x01f0, UnicodeLatinMap::CT_lower, 'j', 0, 0x01f0, 0x004a,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x025f, UnicodeLatinMap::CT_lower, 'j', 0, 0x025f, 0x004a,
+    UnicodeLatinMap::AT_stroke, UnicodeLatinMap::AF_dotless },
+  { 0x0284, UnicodeLatinMap::CT_lower, 'j', 0, 0x0284, 0x004a,
+    UnicodeLatinMap::AT_stroke_and_hook, UnicodeLatinMap::AF_dotless },
+  { 0x006b, UnicodeLatinMap::CT_lower, 'k', 0, 0x006b, 0x004b,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x004b, UnicodeLatinMap::CT_upper, 'K', 0, 0x006b, 0x004b,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x1e31, UnicodeLatinMap::CT_lower, 'k', 0, 0x1e31, 0x1e30,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x1e30, UnicodeLatinMap::CT_upper, 'K', 0, 0x1e31, 0x1e30,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x01e9, UnicodeLatinMap::CT_lower, 'k', 0, 0x01e9, 0x01e8,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x01e8, UnicodeLatinMap::CT_upper, 'K', 0, 0x01e9, 0x01e8,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x0137, UnicodeLatinMap::CT_lower, 'k', 0, 0x0137, 0x0136,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x0136, UnicodeLatinMap::CT_upper, 'K', 0, 0x0137, 0x0136,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x1e33, UnicodeLatinMap::CT_lower, 'k', 0, 0x1e33, 0x1e32,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e32, UnicodeLatinMap::CT_upper, 'K', 0, 0x1e33, 0x1e32,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e35, UnicodeLatinMap::CT_lower, 'k', 0, 0x1e35, 0x1e34,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x1e34, UnicodeLatinMap::CT_upper, 'K', 0, 0x1e35, 0x1e34,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x0199, UnicodeLatinMap::CT_lower, 'k', 0, 0x0199, 0x0198,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0198, UnicodeLatinMap::CT_upper, 'K', 0, 0x0199, 0x0198,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x029e, UnicodeLatinMap::CT_lower, 'k', 0, 0x029e, 0x004b,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned },
+  { 0x006c, UnicodeLatinMap::CT_lower, 'l', 0, 0x006c, 0x004c,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x004c, UnicodeLatinMap::CT_upper, 'L', 0, 0x006c, 0x004c,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x013a, UnicodeLatinMap::CT_lower, 'l', 0, 0x013a, 0x0139,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x0139, UnicodeLatinMap::CT_upper, 'L', 0, 0x013a, 0x0139,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x013e, UnicodeLatinMap::CT_lower, 'l', 0, 0x013e, 0x013d,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x013d, UnicodeLatinMap::CT_upper, 'L', 0, 0x013e, 0x013d,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x013c, UnicodeLatinMap::CT_lower, 'l', 0, 0x013c, 0x013b,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x013b, UnicodeLatinMap::CT_upper, 'L', 0, 0x013c, 0x013b,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x1e37, UnicodeLatinMap::CT_lower, 'l', 0, 0x1e37, 0x1e36,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e36, UnicodeLatinMap::CT_upper, 'L', 0, 0x1e37, 0x1e36,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e39, UnicodeLatinMap::CT_lower, 'l', 0, 0x1e39, 0x1e38,
+    UnicodeLatinMap::AT_dot_below_and_macron, 0 },
+  { 0x1e38, UnicodeLatinMap::CT_upper, 'L', 0, 0x1e39, 0x1e38,
+    UnicodeLatinMap::AT_dot_below_and_macron, 0 },
+  { 0x1e3d, UnicodeLatinMap::CT_lower, 'l', 0, 0x1e3d, 0x1e3c,
+    UnicodeLatinMap::AT_circumflex_below, 0 },
+  { 0x1e3c, UnicodeLatinMap::CT_upper, 'L', 0, 0x1e3d, 0x1e3c,
+    UnicodeLatinMap::AT_circumflex_below, 0 },
+  { 0x1e3b, UnicodeLatinMap::CT_lower, 'l', 0, 0x1e3b, 0x1e3a,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x1e3a, UnicodeLatinMap::CT_upper, 'L', 0, 0x1e3b, 0x1e3a,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x01c9, UnicodeLatinMap::CT_lower, 'l', 'j', 0x01c9, 0x01c7,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature },
+  { 0x01c8, UnicodeLatinMap::CT_upper, 'L', 'j', 0x01c9, 0x01c7,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature },
+  { 0x01c7, UnicodeLatinMap::CT_upper, 'L', 'J', 0x01c9, 0x01c7,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature },
+  { 0x029f, UnicodeLatinMap::CT_upper, 'L', 0, 0x006c, 0x029f,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_smallcap },
+  { 0x0142, UnicodeLatinMap::CT_lower, 'l', 0, 0x0142, 0x0141,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x0141, UnicodeLatinMap::CT_upper, 'L', 0, 0x0142, 0x0141,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x028e, UnicodeLatinMap::CT_lower, 'y', 0, 0x028e, 0x0059,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned },
+  { 0x006d, UnicodeLatinMap::CT_lower, 'm', 0, 0x006d, 0x004d,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x004d, UnicodeLatinMap::CT_upper, 'M', 0, 0x006d, 0x004d,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x1e3f, UnicodeLatinMap::CT_lower, 'm', 0, 0x1e3f, 0x1e3e,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x1e3e, UnicodeLatinMap::CT_upper, 'M', 0, 0x1e3f, 0x1e3e,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x1e41, UnicodeLatinMap::CT_lower, 'm', 0, 0x1e41, 0x1e40,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e40, UnicodeLatinMap::CT_upper, 'M', 0, 0x1e41, 0x1e40,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e43, UnicodeLatinMap::CT_lower, 'm', 0, 0x1e43, 0x1e42,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e42, UnicodeLatinMap::CT_upper, 'M', 0, 0x1e43, 0x1e42,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x0271, UnicodeLatinMap::CT_lower, 'm', 0, 0x0271, 0x004d,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x006e, UnicodeLatinMap::CT_lower, 'n', 0, 0x006e, 0x004e,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x004e, UnicodeLatinMap::CT_upper, 'N', 0, 0x006e, 0x004e,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0144, UnicodeLatinMap::CT_lower, 'n', 0, 0x0144, 0x0143,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x0143, UnicodeLatinMap::CT_upper, 'N', 0, 0x0144, 0x0143,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x01f9, UnicodeLatinMap::CT_lower, 'n', 0, 0x01f9, 0x01f8,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x01f8, UnicodeLatinMap::CT_upper, 'N', 0, 0x01f9, 0x01f8,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x0148, UnicodeLatinMap::CT_lower, 'n', 0, 0x0148, 0x0147,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x0147, UnicodeLatinMap::CT_upper, 'N', 0, 0x0148, 0x0147,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x00f1, UnicodeLatinMap::CT_lower, 'n', 0, 0x00f1, 0x00d1,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x00d1, UnicodeLatinMap::CT_upper, 'N', 0, 0x00f1, 0x00d1,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x1e45, UnicodeLatinMap::CT_lower, 'n', 0, 0x1e45, 0x1e44,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e44, UnicodeLatinMap::CT_upper, 'N', 0, 0x1e45, 0x1e44,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x0146, UnicodeLatinMap::CT_lower, 'n', 0, 0x0146, 0x0145,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x0145, UnicodeLatinMap::CT_upper, 'N', 0, 0x0146, 0x0145,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x1e47, UnicodeLatinMap::CT_lower, 'n', 0, 0x1e47, 0x1e46,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e46, UnicodeLatinMap::CT_upper, 'N', 0, 0x1e47, 0x1e46,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e4b, UnicodeLatinMap::CT_lower, 'n', 0, 0x1e4b, 0x1e4a,
+    UnicodeLatinMap::AT_circumflex_below, 0 },
+  { 0x1e4a, UnicodeLatinMap::CT_upper, 'N', 0, 0x1e4b, 0x1e4a,
+    UnicodeLatinMap::AT_circumflex_below, 0 },
+  { 0x1e49, UnicodeLatinMap::CT_lower, 'n', 0, 0x1e49, 0x1e48,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x1e48, UnicodeLatinMap::CT_upper, 'N', 0, 0x1e49, 0x1e48,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x01cc, UnicodeLatinMap::CT_lower, 'n', 'j', 0x01cc, 0x01ca,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature },
+  { 0x01cb, UnicodeLatinMap::CT_upper, 'N', 'j', 0x01cc, 0x01ca,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature },
+  { 0x01ca, UnicodeLatinMap::CT_upper, 'N', 'J', 0x01cc, 0x01ca,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature },
+  { 0x0274, UnicodeLatinMap::CT_upper, 'N', 0, 0x006e, 0x0274,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_smallcap },
+  { 0x006f, UnicodeLatinMap::CT_lower, 'o', 0, 0x006f, 0x004f,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x004f, UnicodeLatinMap::CT_upper, 'O', 0, 0x006f, 0x004f,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x00f3, UnicodeLatinMap::CT_lower, 'o', 0, 0x00f3, 0x00d3,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x00d3, UnicodeLatinMap::CT_upper, 'O', 0, 0x00f3, 0x00d3,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x00f2, UnicodeLatinMap::CT_lower, 'o', 0, 0x00f2, 0x00d2,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x00d2, UnicodeLatinMap::CT_upper, 'O', 0, 0x00f2, 0x00d2,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x014f, UnicodeLatinMap::CT_lower, 'o', 0, 0x014f, 0x014e,
+    UnicodeLatinMap::AT_breve, 0 },
+  { 0x014e, UnicodeLatinMap::CT_upper, 'O', 0, 0x014f, 0x014e,
+    UnicodeLatinMap::AT_breve, 0 },
+  { 0x00f4, UnicodeLatinMap::CT_lower, 'o', 0, 0x00f4, 0x00d4,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x00d4, UnicodeLatinMap::CT_upper, 'O', 0, 0x00f4, 0x00d4,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x1ed1, UnicodeLatinMap::CT_lower, 'o', 0, 0x1ed1, 0x1ed0,
+    UnicodeLatinMap::AT_circumflex_and_acute, 0 },
+  { 0x1ed0, UnicodeLatinMap::CT_upper, 'O', 0, 0x1ed1, 0x1ed0,
+    UnicodeLatinMap::AT_circumflex_and_acute, 0 },
+  { 0x1ed3, UnicodeLatinMap::CT_lower, 'o', 0, 0x1ed3, 0x1ed2,
+    UnicodeLatinMap::AT_circumflex_and_grave, 0 },
+  { 0x1ed2, UnicodeLatinMap::CT_upper, 'O', 0, 0x1ed3, 0x1ed2,
+    UnicodeLatinMap::AT_circumflex_and_grave, 0 },
+  { 0x1ed7, UnicodeLatinMap::CT_lower, 'o', 0, 0x1ed7, 0x1ed6,
+    UnicodeLatinMap::AT_circumflex_and_tilde, 0 },
+  { 0x1ed6, UnicodeLatinMap::CT_upper, 'O', 0, 0x1ed7, 0x1ed6,
+    UnicodeLatinMap::AT_circumflex_and_tilde, 0 },
+  { 0x1ed5, UnicodeLatinMap::CT_lower, 'o', 0, 0x1ed5, 0x1ed4,
+    UnicodeLatinMap::AT_circumflex_and_hook_above, 0 },
+  { 0x1ed4, UnicodeLatinMap::CT_upper, 'O', 0, 0x1ed5, 0x1ed4,
+    UnicodeLatinMap::AT_circumflex_and_hook_above, 0 },
+  { 0x01d2, UnicodeLatinMap::CT_lower, 'o', 0, 0x01d2, 0x01d1,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x01d1, UnicodeLatinMap::CT_upper, 'O', 0, 0x01d2, 0x01d1,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x00f6, UnicodeLatinMap::CT_lower, 'o', 0, 0x00f6, 0x00d6,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x00d6, UnicodeLatinMap::CT_upper, 'O', 0, 0x00f6, 0x00d6,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x022b, UnicodeLatinMap::CT_lower, 'o', 0, 0x022b, 0x022a,
+    UnicodeLatinMap::AT_diaeresis_and_macron, 0 },
+  { 0x022a, UnicodeLatinMap::CT_upper, 'O', 0, 0x022b, 0x022a,
+    UnicodeLatinMap::AT_diaeresis_and_macron, 0 },
+  { 0x0151, UnicodeLatinMap::CT_lower, 'o', 0, 0x0151, 0x0150,
+    UnicodeLatinMap::AT_double_acute, 0 },
+  { 0x0150, UnicodeLatinMap::CT_upper, 'O', 0, 0x0151, 0x0150,
+    UnicodeLatinMap::AT_double_acute, 0 },
+  { 0x00f5, UnicodeLatinMap::CT_lower, 'o', 0, 0x00f5, 0x00d5,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x00d5, UnicodeLatinMap::CT_upper, 'O', 0, 0x00f5, 0x00d5,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x1e4d, UnicodeLatinMap::CT_lower, 'o', 0, 0x1e4d, 0x1e4c,
+    UnicodeLatinMap::AT_tilde_and_acute, 0 },
+  { 0x1e4c, UnicodeLatinMap::CT_upper, 'O', 0, 0x1e4d, 0x1e4c,
+    UnicodeLatinMap::AT_tilde_and_acute, 0 },
+  { 0x1e4f, UnicodeLatinMap::CT_lower, 'o', 0, 0x1e4f, 0x1e4e,
+    UnicodeLatinMap::AT_tilde_and_diaeresis, 0 },
+  { 0x1e4e, UnicodeLatinMap::CT_upper, 'O', 0, 0x1e4f, 0x1e4e,
+    UnicodeLatinMap::AT_tilde_and_diaeresis, 0 },
+  { 0x022d, UnicodeLatinMap::CT_lower, 'o', 0, 0x022d, 0x022c,
+    UnicodeLatinMap::AT_tilde_and_macron, 0 },
+  { 0x022c, UnicodeLatinMap::CT_upper, 'O', 0, 0x022d, 0x022c,
+    UnicodeLatinMap::AT_tilde_and_macron, 0 },
+  { 0x022f, UnicodeLatinMap::CT_lower, 'o', 0, 0x022f, 0x022e,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x022e, UnicodeLatinMap::CT_upper, 'O', 0, 0x022f, 0x022e,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x0231, UnicodeLatinMap::CT_lower, 'o', 0, 0x0231, 0x0230,
+    UnicodeLatinMap::AT_dot_above_and_macron, 0 },
+  { 0x0230, UnicodeLatinMap::CT_upper, 'O', 0, 0x0231, 0x0230,
+    UnicodeLatinMap::AT_dot_above_and_macron, 0 },
+  { 0x01eb, UnicodeLatinMap::CT_lower, 'o', 0, 0x01eb, 0x01ea,
+    UnicodeLatinMap::AT_ogonek, 0 },
+  { 0x01ea, UnicodeLatinMap::CT_upper, 'O', 0, 0x01eb, 0x01ea,
+    UnicodeLatinMap::AT_ogonek, 0 },
+  { 0x01ed, UnicodeLatinMap::CT_lower, 'o', 0, 0x01ed, 0x01ec,
+    UnicodeLatinMap::AT_ogonek_and_macron, 0 },
+  { 0x01ec, UnicodeLatinMap::CT_upper, 'O', 0, 0x01ed, 0x01ec,
+    UnicodeLatinMap::AT_ogonek_and_macron, 0 },
+  { 0x014d, UnicodeLatinMap::CT_lower, 'o', 0, 0x014d, 0x014c,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x014c, UnicodeLatinMap::CT_upper, 'O', 0, 0x014d, 0x014c,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x1e53, UnicodeLatinMap::CT_lower, 'o', 0, 0x1e53, 0x1e52,
+    UnicodeLatinMap::AT_macron_and_acute, 0 },
+  { 0x1e52, UnicodeLatinMap::CT_upper, 'O', 0, 0x1e53, 0x1e52,
+    UnicodeLatinMap::AT_macron_and_acute, 0 },
+  { 0x1e51, UnicodeLatinMap::CT_lower, 'o', 0, 0x1e51, 0x1e50,
+    UnicodeLatinMap::AT_macron_and_grave, 0 },
+  { 0x1e50, UnicodeLatinMap::CT_upper, 'O', 0, 0x1e51, 0x1e50,
+    UnicodeLatinMap::AT_macron_and_grave, 0 },
+  { 0x1ecf, UnicodeLatinMap::CT_lower, 'o', 0, 0x1ecf, 0x1ece,
+    UnicodeLatinMap::AT_hook_above, 0 },
+  { 0x1ece, UnicodeLatinMap::CT_upper, 'O', 0, 0x1ecf, 0x1ece,
+    UnicodeLatinMap::AT_hook_above, 0 },
+  { 0x020d, UnicodeLatinMap::CT_lower, 'o', 0, 0x020d, 0x020c,
+    UnicodeLatinMap::AT_double_grave, 0 },
+  { 0x020c, UnicodeLatinMap::CT_upper, 'O', 0, 0x020d, 0x020c,
+    UnicodeLatinMap::AT_double_grave, 0 },
+  { 0x020f, UnicodeLatinMap::CT_lower, 'o', 0, 0x020f, 0x020e,
+    UnicodeLatinMap::AT_inverted_breve, 0 },
+  { 0x020e, UnicodeLatinMap::CT_upper, 'O', 0, 0x020f, 0x020e,
+    UnicodeLatinMap::AT_inverted_breve, 0 },
+  { 0x01a1, UnicodeLatinMap::CT_lower, 'o', 0, 0x01a1, 0x01a0,
+    UnicodeLatinMap::AT_horn, 0 },
+  { 0x01a0, UnicodeLatinMap::CT_upper, 'O', 0, 0x01a1, 0x01a0,
+    UnicodeLatinMap::AT_horn, 0 },
+  { 0x1edb, UnicodeLatinMap::CT_lower, 'o', 0, 0x1edb, 0x1eda,
+    UnicodeLatinMap::AT_horn_and_acute, 0 },
+  { 0x1eda, UnicodeLatinMap::CT_upper, 'O', 0, 0x1edb, 0x1eda,
+    UnicodeLatinMap::AT_horn_and_acute, 0 },
+  { 0x1edd, UnicodeLatinMap::CT_lower, 'o', 0, 0x1edd, 0x1edc,
+    UnicodeLatinMap::AT_horn_and_grave, 0 },
+  { 0x1edc, UnicodeLatinMap::CT_upper, 'O', 0, 0x1edd, 0x1edc,
+    UnicodeLatinMap::AT_horn_and_grave, 0 },
+  { 0x1ee1, UnicodeLatinMap::CT_lower, 'o', 0, 0x1ee1, 0x1ee0,
+    UnicodeLatinMap::AT_horn_and_tilde, 0 },
+  { 0x1ee0, UnicodeLatinMap::CT_upper, 'O', 0, 0x1ee1, 0x1ee0,
+    UnicodeLatinMap::AT_horn_and_tilde, 0 },
+  { 0x1edf, UnicodeLatinMap::CT_lower, 'o', 0, 0x1edf, 0x1ede,
+    UnicodeLatinMap::AT_horn_and_hook_above, 0 },
+  { 0x1ede, UnicodeLatinMap::CT_upper, 'O', 0, 0x1edf, 0x1ede,
+    UnicodeLatinMap::AT_horn_and_hook_above, 0 },
+  { 0x1ee3, UnicodeLatinMap::CT_lower, 'o', 0, 0x1ee3, 0x1ee2,
+    UnicodeLatinMap::AT_horn_and_dot_below, 0 },
+  { 0x1ee2, UnicodeLatinMap::CT_upper, 'O', 0, 0x1ee3, 0x1ee2,
+    UnicodeLatinMap::AT_horn_and_dot_below, 0 },
+  { 0x1ecd, UnicodeLatinMap::CT_lower, 'o', 0, 0x1ecd, 0x1ecc,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1ecc, UnicodeLatinMap::CT_upper, 'O', 0, 0x1ecd, 0x1ecc,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1ed9, UnicodeLatinMap::CT_lower, 'o', 0, 0x1ed9, 0x1ed8,
+    UnicodeLatinMap::AT_circumflex_and_dot_below, 0 },
+  { 0x1ed8, UnicodeLatinMap::CT_upper, 'O', 0, 0x1ed9, 0x1ed8,
+    UnicodeLatinMap::AT_circumflex_and_dot_below, 0 },
+  { 0x0153, UnicodeLatinMap::CT_lower, 'o', 'e', 0x0153, 0x0152,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature },
+  { 0x0152, UnicodeLatinMap::CT_upper, 'O', 'E', 0x0153, 0x0152,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature },
+  { 0x0276, UnicodeLatinMap::CT_upper, 'O', 'E', 0x0153, 0x0276,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_smallcap | UnicodeLatinMap::AF_ligature },
+  { 0x00f8, UnicodeLatinMap::CT_lower, 'o', 0, 0x00f8, 0x00d8,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x00d8, UnicodeLatinMap::CT_upper, 'O', 0, 0x00f8, 0x00d8,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x01ff, UnicodeLatinMap::CT_lower, 'o', 0, 0x01ff, 0x01fe,
+    UnicodeLatinMap::AT_stroke_and_acute, 0 },
+  { 0x01fe, UnicodeLatinMap::CT_upper, 'O', 0, 0x01ff, 0x01fe,
+    UnicodeLatinMap::AT_stroke_and_acute, 0 },
+  { 0x0070, UnicodeLatinMap::CT_lower, 'p', 0, 0x0070, 0x0050,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0050, UnicodeLatinMap::CT_upper, 'P', 0, 0x0070, 0x0050,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x1e55, UnicodeLatinMap::CT_lower, 'p', 0, 0x1e55, 0x1e54,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x1e54, UnicodeLatinMap::CT_upper, 'P', 0, 0x1e55, 0x1e54,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x1e57, UnicodeLatinMap::CT_lower, 'p', 0, 0x1e57, 0x1e56,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e56, UnicodeLatinMap::CT_upper, 'P', 0, 0x1e57, 0x1e56,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x01a5, UnicodeLatinMap::CT_lower, 'p', 0, 0x01a5, 0x01a4,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x01a4, UnicodeLatinMap::CT_upper, 'P', 0, 0x01a5, 0x01a4,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0071, UnicodeLatinMap::CT_lower, 'q', 0, 0x0071, 0x0051,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0051, UnicodeLatinMap::CT_upper, 'Q', 0, 0x0071, 0x0051,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x02a0, UnicodeLatinMap::CT_lower, 'q', 0, 0x02a0, 0x0051,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0072, UnicodeLatinMap::CT_lower, 'r', 0, 0x0072, 0x0052,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0052, UnicodeLatinMap::CT_upper, 'R', 0, 0x0072, 0x0052,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0155, UnicodeLatinMap::CT_lower, 'r', 0, 0x0155, 0x0154,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x0154, UnicodeLatinMap::CT_upper, 'R', 0, 0x0155, 0x0154,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x0159, UnicodeLatinMap::CT_lower, 'r', 0, 0x0159, 0x0158,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x0158, UnicodeLatinMap::CT_upper, 'R', 0, 0x0159, 0x0158,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x1e59, UnicodeLatinMap::CT_lower, 'r', 0, 0x1e59, 0x1e58,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e58, UnicodeLatinMap::CT_upper, 'R', 0, 0x1e59, 0x1e58,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x0157, UnicodeLatinMap::CT_lower, 'r', 0, 0x0157, 0x0156,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x0156, UnicodeLatinMap::CT_upper, 'R', 0, 0x0157, 0x0156,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x0211, UnicodeLatinMap::CT_lower, 'r', 0, 0x0211, 0x0210,
+    UnicodeLatinMap::AT_double_grave, 0 },
+  { 0x0210, UnicodeLatinMap::CT_upper, 'R', 0, 0x0211, 0x0210,
+    UnicodeLatinMap::AT_double_grave, 0 },
+  { 0x0213, UnicodeLatinMap::CT_lower, 'r', 0, 0x0213, 0x0212,
+    UnicodeLatinMap::AT_inverted_breve, 0 },
+  { 0x0212, UnicodeLatinMap::CT_upper, 'R', 0, 0x0213, 0x0212,
+    UnicodeLatinMap::AT_inverted_breve, 0 },
+  { 0x1e5b, UnicodeLatinMap::CT_lower, 'r', 0, 0x1e5b, 0x1e5a,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e5a, UnicodeLatinMap::CT_upper, 'R', 0, 0x1e5b, 0x1e5a,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e5d, UnicodeLatinMap::CT_lower, 'r', 0, 0x1e5d, 0x1e5c,
+    UnicodeLatinMap::AT_dot_below_and_macron, 0 },
+  { 0x1e5c, UnicodeLatinMap::CT_upper, 'R', 0, 0x1e5d, 0x1e5c,
+    UnicodeLatinMap::AT_dot_below_and_macron, 0 },
+  { 0x1e5f, UnicodeLatinMap::CT_lower, 'r', 0, 0x1e5f, 0x1e5e,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x1e5e, UnicodeLatinMap::CT_upper, 'R', 0, 0x1e5f, 0x1e5e,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x0280, UnicodeLatinMap::CT_upper, 'R', 0, 0x0072, 0x0280,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_smallcap },
+  { 0x0279, UnicodeLatinMap::CT_lower, 'r', 0, 0x0279, 0x0052,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned },
+  { 0x027b, UnicodeLatinMap::CT_lower, 'r', 0, 0x027b, 0x0052,
+    UnicodeLatinMap::AT_hook, UnicodeLatinMap::AF_turned },
+  { 0x0281, UnicodeLatinMap::CT_upper, 'R', 0, 0x0072, 0x0281,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned | UnicodeLatinMap::AF_smallcap },
+  { 0x0073, UnicodeLatinMap::CT_lower, 's', 0, 0x0073, 0x0053,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0053, UnicodeLatinMap::CT_upper, 'S', 0, 0x0073, 0x0053,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x015b, UnicodeLatinMap::CT_lower, 's', 0, 0x015b, 0x015a,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x015a, UnicodeLatinMap::CT_upper, 'S', 0, 0x015b, 0x015a,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x1e65, UnicodeLatinMap::CT_lower, 's', 0, 0x1e65, 0x1e64,
+    UnicodeLatinMap::AT_acute_and_dot_above, 0 },
+  { 0x1e64, UnicodeLatinMap::CT_upper, 'S', 0, 0x1e65, 0x1e64,
+    UnicodeLatinMap::AT_acute_and_dot_above, 0 },
+  { 0x015d, UnicodeLatinMap::CT_lower, 's', 0, 0x015d, 0x015c,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x015c, UnicodeLatinMap::CT_upper, 'S', 0, 0x015d, 0x015c,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x0161, UnicodeLatinMap::CT_lower, 's', 0, 0x0161, 0x0160,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x0160, UnicodeLatinMap::CT_upper, 'S', 0, 0x0161, 0x0160,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x1e67, UnicodeLatinMap::CT_lower, 's', 0, 0x1e67, 0x1e66,
+    UnicodeLatinMap::AT_caron_and_dot_above, 0 },
+  { 0x1e66, UnicodeLatinMap::CT_upper, 'S', 0, 0x1e67, 0x1e66,
+    UnicodeLatinMap::AT_caron_and_dot_above, 0 },
+  { 0x1e61, UnicodeLatinMap::CT_lower, 's', 0, 0x1e61, 0x1e60,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e60, UnicodeLatinMap::CT_upper, 'S', 0, 0x1e61, 0x1e60,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x015f, UnicodeLatinMap::CT_lower, 's', 0, 0x015f, 0x015e,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x015e, UnicodeLatinMap::CT_upper, 'S', 0, 0x015f, 0x015e,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x1e63, UnicodeLatinMap::CT_lower, 's', 0, 0x1e63, 0x1e62,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e62, UnicodeLatinMap::CT_upper, 'S', 0, 0x1e63, 0x1e62,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e69, UnicodeLatinMap::CT_lower, 's', 0, 0x1e69, 0x1e68,
+    UnicodeLatinMap::AT_dot_below_and_dot_above, 0 },
+  { 0x1e68, UnicodeLatinMap::CT_upper, 'S', 0, 0x1e69, 0x1e68,
+    UnicodeLatinMap::AT_dot_below_and_dot_above, 0 },
+  { 0x0219, UnicodeLatinMap::CT_lower, 's', 0, 0x0219, 0x0218,
+    UnicodeLatinMap::AT_comma_below, 0 },
+  { 0x0218, UnicodeLatinMap::CT_upper, 'S', 0, 0x0219, 0x0218,
+    UnicodeLatinMap::AT_comma_below, 0 },
+  { 0x00df, UnicodeLatinMap::CT_lower, 's', 's', 0x00df, 0x00df,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0282, UnicodeLatinMap::CT_lower, 's', 0, 0x0282, 0x0053,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0074, UnicodeLatinMap::CT_lower, 't', 0, 0x0074, 0x0054,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0054, UnicodeLatinMap::CT_upper, 'T', 0, 0x0074, 0x0054,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0165, UnicodeLatinMap::CT_lower, 't', 0, 0x0165, 0x0164,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x0164, UnicodeLatinMap::CT_upper, 'T', 0, 0x0165, 0x0164,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x1e97, UnicodeLatinMap::CT_lower, 't', 0, 0x1e97, 0x0054,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x1e6b, UnicodeLatinMap::CT_lower, 't', 0, 0x1e6b, 0x1e6a,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e6a, UnicodeLatinMap::CT_upper, 'T', 0, 0x1e6b, 0x1e6a,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x0163, UnicodeLatinMap::CT_lower, 't', 0, 0x0163, 0x0162,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x0162, UnicodeLatinMap::CT_upper, 'T', 0, 0x0163, 0x0162,
+    UnicodeLatinMap::AT_cedilla, 0 },
+  { 0x1e6d, UnicodeLatinMap::CT_lower, 't', 0, 0x1e6d, 0x1e6c,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e6c, UnicodeLatinMap::CT_upper, 'T', 0, 0x1e6d, 0x1e6c,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x021b, UnicodeLatinMap::CT_lower, 't', 0, 0x021b, 0x021a,
+    UnicodeLatinMap::AT_comma_below, 0 },
+  { 0x021a, UnicodeLatinMap::CT_upper, 'T', 0, 0x021b, 0x021a,
+    UnicodeLatinMap::AT_comma_below, 0 },
+  { 0x1e71, UnicodeLatinMap::CT_lower, 't', 0, 0x1e71, 0x1e70,
+    UnicodeLatinMap::AT_circumflex_below, 0 },
+  { 0x1e70, UnicodeLatinMap::CT_upper, 'T', 0, 0x1e71, 0x1e70,
+    UnicodeLatinMap::AT_circumflex_below, 0 },
+  { 0x1e6f, UnicodeLatinMap::CT_lower, 't', 0, 0x1e6f, 0x1e6e,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x1e6e, UnicodeLatinMap::CT_upper, 'T', 0, 0x1e6f, 0x1e6e,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x0167, UnicodeLatinMap::CT_lower, 't', 0, 0x0167, 0x0166,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x0166, UnicodeLatinMap::CT_upper, 'T', 0, 0x0167, 0x0166,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x01ad, UnicodeLatinMap::CT_lower, 't', 0, 0x01ad, 0x01ac,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x01ac, UnicodeLatinMap::CT_upper, 'T', 0, 0x01ad, 0x01ac,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0287, UnicodeLatinMap::CT_lower, 't', 0, 0x0287, 0x0054,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned },
+  { 0x0075, UnicodeLatinMap::CT_lower, 'u', 0, 0x0075, 0x0055,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0055, UnicodeLatinMap::CT_upper, 'U', 0, 0x0075, 0x0055,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x00fa, UnicodeLatinMap::CT_lower, 'u', 0, 0x00fa, 0x00da,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x00da, UnicodeLatinMap::CT_upper, 'U', 0, 0x00fa, 0x00da,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x00f9, UnicodeLatinMap::CT_lower, 'u', 0, 0x00f9, 0x00d9,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x00d9, UnicodeLatinMap::CT_upper, 'U', 0, 0x00f9, 0x00d9,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x016d, UnicodeLatinMap::CT_lower, 'u', 0, 0x016d, 0x016c,
+    UnicodeLatinMap::AT_breve, 0 },
+  { 0x016c, UnicodeLatinMap::CT_upper, 'U', 0, 0x016d, 0x016c,
+    UnicodeLatinMap::AT_breve, 0 },
+  { 0x00fb, UnicodeLatinMap::CT_lower, 'u', 0, 0x00fb, 0x00db,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x00db, UnicodeLatinMap::CT_upper, 'U', 0, 0x00fb, 0x00db,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x01d4, UnicodeLatinMap::CT_lower, 'u', 0, 0x01d4, 0x01d3,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x01d3, UnicodeLatinMap::CT_upper, 'U', 0, 0x01d4, 0x01d3,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x016f, UnicodeLatinMap::CT_lower, 'u', 0, 0x016f, 0x016e,
+    UnicodeLatinMap::AT_ring_above, 0 },
+  { 0x016e, UnicodeLatinMap::CT_upper, 'U', 0, 0x016f, 0x016e,
+    UnicodeLatinMap::AT_ring_above, 0 },
+  { 0x00fc, UnicodeLatinMap::CT_lower, 'u', 0, 0x00fc, 0x00dc,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x00dc, UnicodeLatinMap::CT_upper, 'U', 0, 0x00fc, 0x00dc,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x01d8, UnicodeLatinMap::CT_lower, 'u', 0, 0x01d8, 0x01d7,
+    UnicodeLatinMap::AT_diaeresis_and_acute, 0 },
+  { 0x01d7, UnicodeLatinMap::CT_upper, 'U', 0, 0x01d8, 0x01d7,
+    UnicodeLatinMap::AT_diaeresis_and_acute, 0 },
+  { 0x01dc, UnicodeLatinMap::CT_lower, 'u', 0, 0x01dc, 0x01db,
+    UnicodeLatinMap::AT_diaeresis_and_grave, 0 },
+  { 0x01db, UnicodeLatinMap::CT_upper, 'U', 0, 0x01dc, 0x01db,
+    UnicodeLatinMap::AT_diaeresis_and_grave, 0 },
+  { 0x01da, UnicodeLatinMap::CT_lower, 'u', 0, 0x01da, 0x01d9,
+    UnicodeLatinMap::AT_diaeresis_and_caron, 0 },
+  { 0x01d9, UnicodeLatinMap::CT_upper, 'U', 0, 0x01da, 0x01d9,
+    UnicodeLatinMap::AT_diaeresis_and_caron, 0 },
+  { 0x01d6, UnicodeLatinMap::CT_lower, 'u', 0, 0x01d6, 0x01d5,
+    UnicodeLatinMap::AT_diaeresis_and_macron, 0 },
+  { 0x01d5, UnicodeLatinMap::CT_upper, 'U', 0, 0x01d6, 0x01d5,
+    UnicodeLatinMap::AT_diaeresis_and_macron, 0 },
+  { 0x0171, UnicodeLatinMap::CT_lower, 'u', 0, 0x0171, 0x0170,
+    UnicodeLatinMap::AT_double_acute, 0 },
+  { 0x0170, UnicodeLatinMap::CT_upper, 'U', 0, 0x0171, 0x0170,
+    UnicodeLatinMap::AT_double_acute, 0 },
+  { 0x0169, UnicodeLatinMap::CT_lower, 'u', 0, 0x0169, 0x0168,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x0168, UnicodeLatinMap::CT_upper, 'U', 0, 0x0169, 0x0168,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x1e79, UnicodeLatinMap::CT_lower, 'u', 0, 0x1e79, 0x1e78,
+    UnicodeLatinMap::AT_tilde_and_acute, 0 },
+  { 0x1e78, UnicodeLatinMap::CT_upper, 'U', 0, 0x1e79, 0x1e78,
+    UnicodeLatinMap::AT_tilde_and_acute, 0 },
+  { 0x0173, UnicodeLatinMap::CT_lower, 'u', 0, 0x0173, 0x0172,
+    UnicodeLatinMap::AT_ogonek, 0 },
+  { 0x0172, UnicodeLatinMap::CT_upper, 'U', 0, 0x0173, 0x0172,
+    UnicodeLatinMap::AT_ogonek, 0 },
+  { 0x016b, UnicodeLatinMap::CT_lower, 'u', 0, 0x016b, 0x016a,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x016a, UnicodeLatinMap::CT_upper, 'U', 0, 0x016b, 0x016a,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x1e7b, UnicodeLatinMap::CT_lower, 'u', 0, 0x1e7b, 0x1e7a,
+    UnicodeLatinMap::AT_macron_and_diaeresis, 0 },
+  { 0x1e7a, UnicodeLatinMap::CT_upper, 'U', 0, 0x1e7b, 0x1e7a,
+    UnicodeLatinMap::AT_macron_and_diaeresis, 0 },
+  { 0x1ee7, UnicodeLatinMap::CT_lower, 'u', 0, 0x1ee7, 0x1ee6,
+    UnicodeLatinMap::AT_hook_above, 0 },
+  { 0x1ee6, UnicodeLatinMap::CT_upper, 'U', 0, 0x1ee7, 0x1ee6,
+    UnicodeLatinMap::AT_hook_above, 0 },
+  { 0x0215, UnicodeLatinMap::CT_lower, 'u', 0, 0x0215, 0x0214,
+    UnicodeLatinMap::AT_double_grave, 0 },
+  { 0x0214, UnicodeLatinMap::CT_upper, 'U', 0, 0x0215, 0x0214,
+    UnicodeLatinMap::AT_double_grave, 0 },
+  { 0x0217, UnicodeLatinMap::CT_lower, 'u', 0, 0x0217, 0x0216,
+    UnicodeLatinMap::AT_inverted_breve, 0 },
+  { 0x0216, UnicodeLatinMap::CT_upper, 'U', 0, 0x0217, 0x0216,
+    UnicodeLatinMap::AT_inverted_breve, 0 },
+  { 0x01b0, UnicodeLatinMap::CT_lower, 'u', 0, 0x01b0, 0x01af,
+    UnicodeLatinMap::AT_horn, 0 },
+  { 0x01af, UnicodeLatinMap::CT_upper, 'U', 0, 0x01b0, 0x01af,
+    UnicodeLatinMap::AT_horn, 0 },
+  { 0x1ee9, UnicodeLatinMap::CT_lower, 'u', 0, 0x1ee9, 0x1ee8,
+    UnicodeLatinMap::AT_horn_and_acute, 0 },
+  { 0x1ee8, UnicodeLatinMap::CT_upper, 'U', 0, 0x1ee9, 0x1ee8,
+    UnicodeLatinMap::AT_horn_and_acute, 0 },
+  { 0x1eeb, UnicodeLatinMap::CT_lower, 'u', 0, 0x1eeb, 0x1eea,
+    UnicodeLatinMap::AT_horn_and_grave, 0 },
+  { 0x1eea, UnicodeLatinMap::CT_upper, 'U', 0, 0x1eeb, 0x1eea,
+    UnicodeLatinMap::AT_horn_and_grave, 0 },
+  { 0x1eef, UnicodeLatinMap::CT_lower, 'u', 0, 0x1eef, 0x1eee,
+    UnicodeLatinMap::AT_horn_and_tilde, 0 },
+  { 0x1eee, UnicodeLatinMap::CT_upper, 'U', 0, 0x1eef, 0x1eee,
+    UnicodeLatinMap::AT_horn_and_tilde, 0 },
+  { 0x1eed, UnicodeLatinMap::CT_lower, 'u', 0, 0x1eed, 0x1eec,
+    UnicodeLatinMap::AT_horn_and_hook_above, 0 },
+  { 0x1eec, UnicodeLatinMap::CT_upper, 'U', 0, 0x1eed, 0x1eec,
+    UnicodeLatinMap::AT_horn_and_hook_above, 0 },
+  { 0x1ef1, UnicodeLatinMap::CT_lower, 'u', 0, 0x1ef1, 0x1ef0,
+    UnicodeLatinMap::AT_horn_and_dot_below, 0 },
+  { 0x1ef0, UnicodeLatinMap::CT_upper, 'U', 0, 0x1ef1, 0x1ef0,
+    UnicodeLatinMap::AT_horn_and_dot_below, 0 },
+  { 0x1ee5, UnicodeLatinMap::CT_lower, 'u', 0, 0x1ee5, 0x1ee4,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1ee4, UnicodeLatinMap::CT_upper, 'U', 0, 0x1ee5, 0x1ee4,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e73, UnicodeLatinMap::CT_lower, 'u', 0, 0x1e73, 0x1e72,
+    UnicodeLatinMap::AT_diaeresis_below, 0 },
+  { 0x1e72, UnicodeLatinMap::CT_upper, 'U', 0, 0x1e73, 0x1e72,
+    UnicodeLatinMap::AT_diaeresis_below, 0 },
+  { 0x1e77, UnicodeLatinMap::CT_lower, 'u', 0, 0x1e77, 0x1e76,
+    UnicodeLatinMap::AT_circumflex_below, 0 },
+  { 0x1e76, UnicodeLatinMap::CT_upper, 'U', 0, 0x1e77, 0x1e76,
+    UnicodeLatinMap::AT_circumflex_below, 0 },
+  { 0x1e75, UnicodeLatinMap::CT_lower, 'u', 0, 0x1e75, 0x1e74,
+    UnicodeLatinMap::AT_tilde_below, 0 },
+  { 0x1e74, UnicodeLatinMap::CT_upper, 'U', 0, 0x1e75, 0x1e74,
+    UnicodeLatinMap::AT_tilde_below, 0 },
+  { 0x0265, UnicodeLatinMap::CT_lower, 'h', 0, 0x0265, 0x0048,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned },
+  { 0x026f, UnicodeLatinMap::CT_lower, 'm', 0, 0x026f, 0x019c,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned },
+  { 0x019c, UnicodeLatinMap::CT_upper, 'M', 0, 0x026f, 0x019c,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned },
+  { 0x0076, UnicodeLatinMap::CT_lower, 'v', 0, 0x0076, 0x0056,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0056, UnicodeLatinMap::CT_upper, 'V', 0, 0x0076, 0x0056,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x1e7d, UnicodeLatinMap::CT_lower, 'v', 0, 0x1e7d, 0x1e7c,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x1e7c, UnicodeLatinMap::CT_upper, 'V', 0, 0x1e7d, 0x1e7c,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x1e7f, UnicodeLatinMap::CT_lower, 'v', 0, 0x1e7f, 0x1e7e,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e7e, UnicodeLatinMap::CT_upper, 'V', 0, 0x1e7f, 0x1e7e,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x028b, UnicodeLatinMap::CT_lower, 'v', 0, 0x028b, 0x01b2,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x01b2, UnicodeLatinMap::CT_upper, 'V', 0, 0x028b, 0x01b2,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x028c, UnicodeLatinMap::CT_lower, 'v', 0, 0x028c, 0x0056,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned },
+  { 0x0077, UnicodeLatinMap::CT_lower, 'w', 0, 0x0077, 0x0057,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0057, UnicodeLatinMap::CT_upper, 'W', 0, 0x0077, 0x0057,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x1e83, UnicodeLatinMap::CT_lower, 'w', 0, 0x1e83, 0x1e82,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x1e82, UnicodeLatinMap::CT_upper, 'W', 0, 0x1e83, 0x1e82,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x1e81, UnicodeLatinMap::CT_lower, 'w', 0, 0x1e81, 0x1e80,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x1e80, UnicodeLatinMap::CT_upper, 'W', 0, 0x1e81, 0x1e80,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x0175, UnicodeLatinMap::CT_lower, 'w', 0, 0x0175, 0x0174,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x0174, UnicodeLatinMap::CT_upper, 'W', 0, 0x0175, 0x0174,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x1e98, UnicodeLatinMap::CT_lower, 'w', 0, 0x1e98, 0x0057,
+    UnicodeLatinMap::AT_ring_above, 0 },
+  { 0x1e85, UnicodeLatinMap::CT_lower, 'w', 0, 0x1e85, 0x1e84,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x1e84, UnicodeLatinMap::CT_upper, 'W', 0, 0x1e85, 0x1e84,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x1e87, UnicodeLatinMap::CT_lower, 'w', 0, 0x1e87, 0x1e86,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e86, UnicodeLatinMap::CT_upper, 'W', 0, 0x1e87, 0x1e86,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e89, UnicodeLatinMap::CT_lower, 'w', 0, 0x1e89, 0x1e88,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e88, UnicodeLatinMap::CT_upper, 'W', 0, 0x1e89, 0x1e88,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x028d, UnicodeLatinMap::CT_lower, 'w', 0, 0x028d, 0x0057,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned },
+  { 0x0078, UnicodeLatinMap::CT_lower, 'x', 0, 0x0078, 0x0058,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0058, UnicodeLatinMap::CT_upper, 'X', 0, 0x0078, 0x0058,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x1e8d, UnicodeLatinMap::CT_lower, 'x', 0, 0x1e8d, 0x1e8c,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x1e8c, UnicodeLatinMap::CT_upper, 'X', 0, 0x1e8d, 0x1e8c,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x1e8b, UnicodeLatinMap::CT_lower, 'x', 0, 0x1e8b, 0x1e8a,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e8a, UnicodeLatinMap::CT_upper, 'X', 0, 0x1e8b, 0x1e8a,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x0079, UnicodeLatinMap::CT_lower, 'y', 0, 0x0079, 0x0059,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x0059, UnicodeLatinMap::CT_upper, 'Y', 0, 0x0079, 0x0059,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x00fd, UnicodeLatinMap::CT_lower, 'y', 0, 0x00fd, 0x00dd,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x00dd, UnicodeLatinMap::CT_upper, 'Y', 0, 0x00fd, 0x00dd,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x1ef3, UnicodeLatinMap::CT_lower, 'y', 0, 0x1ef3, 0x1ef2,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x1ef2, UnicodeLatinMap::CT_upper, 'Y', 0, 0x1ef3, 0x1ef2,
+    UnicodeLatinMap::AT_grave, 0 },
+  { 0x0177, UnicodeLatinMap::CT_lower, 'y', 0, 0x0177, 0x0176,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x0176, UnicodeLatinMap::CT_upper, 'Y', 0, 0x0177, 0x0176,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x1e99, UnicodeLatinMap::CT_lower, 'y', 0, 0x1e99, 0x0059,
+    UnicodeLatinMap::AT_ring_above, 0 },
+  { 0x00ff, UnicodeLatinMap::CT_lower, 'y', 0, 0x00ff, 0x0178,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x0178, UnicodeLatinMap::CT_upper, 'Y', 0, 0x00ff, 0x0178,
+    UnicodeLatinMap::AT_diaeresis, 0 },
+  { 0x1ef9, UnicodeLatinMap::CT_lower, 'y', 0, 0x1ef9, 0x1ef8,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x1ef8, UnicodeLatinMap::CT_upper, 'Y', 0, 0x1ef9, 0x1ef8,
+    UnicodeLatinMap::AT_tilde, 0 },
+  { 0x1e8f, UnicodeLatinMap::CT_lower, 'y', 0, 0x1e8f, 0x1e8e,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e8e, UnicodeLatinMap::CT_upper, 'Y', 0, 0x1e8f, 0x1e8e,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x0233, UnicodeLatinMap::CT_lower, 'y', 0, 0x0233, 0x0232,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x0232, UnicodeLatinMap::CT_upper, 'Y', 0, 0x0233, 0x0232,
+    UnicodeLatinMap::AT_macron, 0 },
+  { 0x1ef7, UnicodeLatinMap::CT_lower, 'y', 0, 0x1ef7, 0x1ef6,
+    UnicodeLatinMap::AT_hook_above, 0 },
+  { 0x1ef6, UnicodeLatinMap::CT_upper, 'Y', 0, 0x1ef7, 0x1ef6,
+    UnicodeLatinMap::AT_hook_above, 0 },
+  { 0x1ef5, UnicodeLatinMap::CT_lower, 'y', 0, 0x1ef5, 0x1ef4,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1ef4, UnicodeLatinMap::CT_upper, 'Y', 0, 0x1ef5, 0x1ef4,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x028f, UnicodeLatinMap::CT_upper, 'Y', 0, 0x0079, 0x028f,
+    UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_smallcap },
+  { 0x01b4, UnicodeLatinMap::CT_lower, 'y', 0, 0x01b4, 0x01b3,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x01b3, UnicodeLatinMap::CT_upper, 'Y', 0, 0x01b4, 0x01b3,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x007a, UnicodeLatinMap::CT_lower, 'z', 0, 0x007a, 0x005a,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x005a, UnicodeLatinMap::CT_upper, 'Z', 0, 0x007a, 0x005a,
+    UnicodeLatinMap::AT_none, 0 },
+  { 0x017a, UnicodeLatinMap::CT_lower, 'z', 0, 0x017a, 0x0179,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x0179, UnicodeLatinMap::CT_upper, 'Z', 0, 0x017a, 0x0179,
+    UnicodeLatinMap::AT_acute, 0 },
+  { 0x1e91, UnicodeLatinMap::CT_lower, 'z', 0, 0x1e91, 0x1e90,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x1e90, UnicodeLatinMap::CT_upper, 'Z', 0, 0x1e91, 0x1e90,
+    UnicodeLatinMap::AT_circumflex, 0 },
+  { 0x017e, UnicodeLatinMap::CT_lower, 'z', 0, 0x017e, 0x017d,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x017d, UnicodeLatinMap::CT_upper, 'Z', 0, 0x017e, 0x017d,
+    UnicodeLatinMap::AT_caron, 0 },
+  { 0x017c, UnicodeLatinMap::CT_lower, 'z', 0, 0x017c, 0x017b,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x017b, UnicodeLatinMap::CT_upper, 'Z', 0, 0x017c, 0x017b,
+    UnicodeLatinMap::AT_dot_above, 0 },
+  { 0x1e93, UnicodeLatinMap::CT_lower, 'z', 0, 0x1e93, 0x1e92,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e92, UnicodeLatinMap::CT_upper, 'Z', 0, 0x1e93, 0x1e92,
+    UnicodeLatinMap::AT_dot_below, 0 },
+  { 0x1e95, UnicodeLatinMap::CT_lower, 'z', 0, 0x1e95, 0x1e94,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x1e94, UnicodeLatinMap::CT_upper, 'Z', 0, 0x1e95, 0x1e94,
+    UnicodeLatinMap::AT_line_below, 0 },
+  { 0x01b6, UnicodeLatinMap::CT_lower, 'z', 0, 0x01b6, 0x01b5,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x01b5, UnicodeLatinMap::CT_upper, 'Z', 0, 0x01b6, 0x01b5,
+    UnicodeLatinMap::AT_stroke, 0 },
+  { 0x0225, UnicodeLatinMap::CT_lower, 'z', 0, 0x0225, 0x0224,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0224, UnicodeLatinMap::CT_upper, 'Z', 0, 0x0225, 0x0224,
+    UnicodeLatinMap::AT_hook, 0 },
+  { 0x0291, UnicodeLatinMap::CT_lower, 'z', 0, 0x0291, 0x005a,
+    UnicodeLatinMap::AT_curl, 0 },
+};
+#ifndef CPPPARSER
+static const int latin_map_length = sizeof(latin_map) / sizeof(UnicodeLatinMap::Entry);
+#endif
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: UnicodeLatinMap::look_up
+//       Access: Public, Static
+//  Description: Returns the Entry associated with the indicated
+//               character, if there is one.
+////////////////////////////////////////////////////////////////////
+const UnicodeLatinMap::Entry *UnicodeLatinMap::
+look_up(wchar_t character) {
+  if (!_initialized) {
+    init();
+  }
+
+  if (character < max_direct_chars) {
+    return _direct_chars[character];
+
+  } else {
+    ByCharacter::const_iterator ci;
+    ci = _by_character.find(character);
+    if (ci != _by_character.end()) {
+      return (*ci).second;
+    }
+    return NULL;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: UnicodeLatinMap::init
+//       Access: Private, Static
+//  Description: Initializes the map, if it has not already been
+//               initialized.
+////////////////////////////////////////////////////////////////////
+void UnicodeLatinMap::
+init() {
+  if (!_initialized) {
+    for (int i = 0; i < latin_map_length; i++) {
+      const UnicodeLatinMap::Entry *entry = &latin_map[i];
+
+      // The first 256 characters are very common in Latin-alphabet
+      // languages, so index those in an array for superfast lookup.
+      // Everything else goes into the map.
+      if (entry->_character < max_direct_chars) {
+        _direct_chars[entry->_character] = entry;
+      } else {
+        _by_character[entry->_character] = entry;
+      }
+    }
+    _initialized = true;
+  }
+}
+

+ 146 - 0
panda/src/text/unicodeLatinMap.h

@@ -0,0 +1,146 @@
+// Filename: unicodeLatinMap.h
+// Created by:  drose (01Feb03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef UNICODELATINMAP_H
+#define UNICODELATINMAP_H
+
+#include "pandabase.h"
+#include "pmap.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : UnicodeLatinMap
+// Description : This class mainly serves as a container for a largish
+//               table of the subset of the Unicode character set that
+//               corresponds to the Latin alphabet, with its various
+//               accent marks and so on.  Specifically, this table
+//               indicates how to map between the Unicode accented
+//               character and the corresponding ASCII equivalent
+//               without the accent mark; as well as how to switch
+//               case from upper to lower while retaining the Unicode
+//               accent marks.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA UnicodeLatinMap {
+public:
+  enum AccentType {
+    AT_none,
+    AT_acute,
+    AT_acute_and_dot_above,
+    AT_breve,
+    AT_breve_and_acute,
+    AT_breve_and_dot_below,
+    AT_breve_and_grave,
+    AT_breve_and_hook_above,
+    AT_breve_and_tilde,
+    AT_breve_below,
+    AT_caron,
+    AT_caron_and_dot_above,
+    AT_cedilla,
+    AT_cedilla_and_acute,
+    AT_cedilla_and_breve,
+    AT_circumflex,
+    AT_circumflex_and_acute,
+    AT_circumflex_and_dot_below,
+    AT_circumflex_and_grave,
+    AT_circumflex_and_hook_above,
+    AT_circumflex_and_tilde,
+    AT_circumflex_below,
+    AT_comma_below,
+    AT_curl,
+    AT_diaeresis,
+    AT_diaeresis_and_acute,
+    AT_diaeresis_and_caron,
+    AT_diaeresis_and_grave,
+    AT_diaeresis_and_macron,
+    AT_diaeresis_below,
+    AT_dot_above,
+    AT_dot_above_and_macron,
+    AT_dot_below,
+    AT_dot_below_and_dot_above,
+    AT_dot_below_and_macron,
+    AT_double_acute,
+    AT_double_grave,
+    AT_grave,
+    AT_hook,
+    AT_hook_above,
+    AT_horn,
+    AT_horn_and_acute,
+    AT_horn_and_dot_below,
+    AT_horn_and_grave,
+    AT_horn_and_hook_above,
+    AT_horn_and_tilde,
+    AT_inverted_breve,
+    AT_line_below,
+    AT_macron,
+    AT_macron_and_acute,
+    AT_macron_and_diaeresis,
+    AT_macron_and_grave,
+    AT_ogonek,
+    AT_ogonek_and_macron,
+    AT_ring_above,
+    AT_ring_above_and_acute,
+    AT_ring_below,
+    AT_stroke,
+    AT_stroke_and_acute,
+    AT_stroke_and_hook,
+    AT_tilde,
+    AT_tilde_and_acute,
+    AT_tilde_and_diaeresis,
+    AT_tilde_and_macron,
+    AT_tilde_below,
+    AT_topbar,
+  };
+
+  enum AdditionalFlags {
+    AF_ligature   = 0x0001,
+    AF_turned     = 0x0002,
+    AF_reversed   = 0x0004,
+    AF_smallcap   = 0x0008,
+    AF_dotless    = 0x0010,
+  };
+  
+  enum CharType {
+    CT_upper,
+    CT_lower,
+    CT_punct,
+  };
+
+  class Entry {
+  public:
+    wchar_t _character;
+    CharType _char_type;
+    char _ascii_equiv;
+    char _ascii_additional;
+    wchar_t _tolower_character;
+    wchar_t _toupper_character;
+    AccentType _accent_type;
+    int _additional_flags;
+  };
+
+  static const Entry *look_up(wchar_t character);
+
+private:
+  static void init();
+  static bool _initialized;
+
+  typedef pmap<wchar_t, const Entry *> ByCharacter;
+  static ByCharacter _by_character;
+  enum { max_direct_chars = 256 };
+  static const Entry *_direct_chars[max_direct_chars];
+};
+
+#endif