harfbuzz/src/hb-ot-cmap-table.hh

1243 lines
36 KiB
C++
Raw Normal View History

2014-05-09 21:35:56 +02:00
/*
* Copyright © 2014 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Behdad Esfahbod
*/
#ifndef HB_OT_CMAP_TABLE_HH
#define HB_OT_CMAP_TABLE_HH
#include "hb-open-type.hh"
#include "hb-set.hh"
2014-05-09 21:35:56 +02:00
/*
* cmap -- Character to Glyph Index Mapping
* https://docs.microsoft.com/en-us/typography/opentype/spec/cmap
2014-05-09 21:35:56 +02:00
*/
#define HB_OT_TAG_cmap HB_TAG('c','m','a','p')
namespace OT {
2014-05-14 03:47:51 +02:00
struct CmapSubtableFormat0
{
bool get_glyph (hb_codepoint_t codepoint, hb_codepoint_t *glyph) const
2014-05-14 03:47:51 +02:00
{
hb_codepoint_t gid = codepoint < 256 ? glyphIdArray[codepoint] : 0;
if (!gid)
return false;
*glyph = gid;
return true;
2014-05-14 03:47:51 +02:00
}
void collect_unicodes (hb_set_t *out) const
{
for (unsigned int i = 0; i < 256; i++)
if (glyphIdArray[i])
2018-11-15 20:40:56 +01:00
out->add (i);
}
2014-05-14 03:47:51 +02:00
bool sanitize (hb_sanitize_context_t *c) const
{
2014-05-14 03:47:51 +02:00
TRACE_SANITIZE (this);
return_trace (c->check_struct (this));
2014-05-14 03:47:51 +02:00
}
protected:
HBUINT16 format; /* Format number is set to 0. */
HBUINT16 length; /* Byte length of this subtable. */
HBUINT16 language; /* Ignore. */
2018-02-12 02:00:42 +01:00
HBUINT8 glyphIdArray[256];/* An array that maps character
2014-05-14 03:47:51 +02:00
* code to glyph index values. */
public:
DEFINE_SIZE_STATIC (6 + 256);
};
2014-05-09 21:35:56 +02:00
struct CmapSubtableFormat4
{
2018-05-03 01:12:04 +02:00
struct segment_plan
{
HBUINT16 start_code;
HBUINT16 end_code;
bool use_delta;
};
bool serialize (hb_serialize_context_t *c,
2018-11-15 20:40:56 +01:00
const hb_subset_plan_t *plan,
const hb_sorted_vector_t<segment_plan> &segments)
2018-05-03 01:12:04 +02:00
{
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (*this))) return_trace (false);
this->format = 4;
this->length = get_sub_table_size (segments);
this->segCountX2 = segments.length * 2;
this->entrySelector = hb_max (1u, hb_bit_storage (segments.length)) - 1;
this->searchRange = 2 * (1u << this->entrySelector);
this->rangeShift = segments.length * 2 > this->searchRange
? 2 * segments.length - this->searchRange
: 0;
2018-12-22 00:46:51 +01:00
HBUINT16 *end_count = c->allocate_size<HBUINT16> (HBUINT16::static_size * segments.length);
c->allocate_size<HBUINT16> (HBUINT16::static_size); // 2 bytes of padding.
2018-12-22 00:46:51 +01:00
HBUINT16 *start_count = c->allocate_size<HBUINT16> (HBUINT16::static_size * segments.length);
HBINT16 *id_delta = c->allocate_size<HBINT16> (HBUINT16::static_size * segments.length);
HBUINT16 *id_range_offset = c->allocate_size<HBUINT16> (HBUINT16::static_size * segments.length);
if (id_range_offset == nullptr)
return_trace (false);
2018-12-22 00:46:51 +01:00
for (unsigned int i = 0; i < segments.length; i++)
{
end_count[i] = segments[i].end_code;
start_count[i] = segments[i].start_code;
if (segments[i].use_delta)
{
2018-11-15 20:40:56 +01:00
hb_codepoint_t cp = segments[i].start_code;
hb_codepoint_t start_gid = 0;
if (unlikely (!plan->new_gid_for_codepoint (cp, &start_gid) && cp != 0xFFFF))
return_trace (false);
id_delta[i] = start_gid - segments[i].start_code;
} else {
id_delta[i] = 0;
2018-11-15 20:40:56 +01:00
unsigned int num_codepoints = segments[i].end_code - segments[i].start_code + 1;
HBUINT16 *glyph_id_array = c->allocate_size<HBUINT16> (HBUINT16::static_size * num_codepoints);
if (glyph_id_array == nullptr)
return_trace (false);
// From the cmap spec:
//
// id_range_offset[i]/2
// + (cp - segments[i].start_code)
// + (id_range_offset + i)
// =
// glyph_id_array + (cp - segments[i].start_code)
//
// So, solve for id_range_offset[i]:
//
// id_range_offset[i]
// =
// 2 * (glyph_id_array - id_range_offset - i)
id_range_offset[i] = 2 * (glyph_id_array - id_range_offset - i);
2018-11-15 20:40:56 +01:00
for (unsigned int j = 0; j < num_codepoints; j++)
{
hb_codepoint_t cp = segments[i].start_code + j;
2019-05-08 21:01:55 +02:00
hb_codepoint_t new_gid = 0;
2018-11-15 20:40:56 +01:00
if (unlikely (!plan->new_gid_for_codepoint (cp, &new_gid)))
return_trace (false);
glyph_id_array[j] = new_gid;
2018-11-15 20:40:56 +01:00
}
}
}
return_trace (true);
2018-05-03 01:12:04 +02:00
}
static size_t get_sub_table_size (const hb_sorted_vector_t<segment_plan> &segments)
2018-05-03 01:12:04 +02:00
{
size_t segment_size = 0;
2018-12-22 00:46:51 +01:00
for (unsigned int i = 0; i < segments.length; i++)
2018-05-03 01:12:04 +02:00
{
// Parallel array entries
segment_size +=
2018-11-15 20:40:56 +01:00
2 // end count
+ 2 // start count
+ 2 // delta
+ 2; // range offset
2018-05-03 01:12:04 +02:00
if (!segments[i].use_delta)
2018-11-15 20:40:56 +01:00
// Add bytes for the glyph index array entries for this segment.
segment_size += (segments[i].end_code - segments[i].start_code + 1) * 2;
2018-05-03 01:12:04 +02:00
}
return min_size
2018-11-15 20:40:56 +01:00
+ 2 // Padding
+ segment_size;
2018-05-03 01:12:04 +02:00
}
static bool create_sub_table_plan (const hb_subset_plan_t *plan,
hb_sorted_vector_t<segment_plan> *segments)
2018-05-03 01:12:04 +02:00
{
segment_plan *segment = nullptr;
hb_codepoint_t last_gid = 0;
hb_codepoint_t cp = HB_SET_VALUE_INVALID;
while (plan->unicodes->next (&cp)) {
2019-05-08 21:01:55 +02:00
hb_codepoint_t new_gid = 0;
if (unlikely (!plan->new_gid_for_codepoint (cp, &new_gid)))
{
DEBUG_MSG(SUBSET, nullptr, "Unable to find new gid for %04x", cp);
return false;
}
2018-11-15 20:40:56 +01:00
/* Stop adding to cmap if we are now outside of unicode BMP. */
if (cp > 0xFFFF) break;
2018-11-15 20:40:56 +01:00
if (!segment ||
cp != segment->end_code + 1u)
{
2018-11-15 20:40:56 +01:00
segment = segments->push ();
segment->start_code = cp;
segment->end_code = cp;
2018-11-15 20:40:56 +01:00
segment->use_delta = true;
} else {
segment->end_code = cp;
2018-11-15 20:40:56 +01:00
if (last_gid + 1u != new_gid)
// gid's are not consecutive in this segment so delta
// cannot be used.
segment->use_delta = false;
}
last_gid = new_gid;
}
// There must be a final entry with end_code == 0xFFFF. Check if we need to add one.
if (segment == nullptr || segment->end_code != 0xFFFF)
{
segment = segments->push ();
segment->start_code = 0xFFFF;
segment->end_code = 0xFFFF;
segment->use_delta = true;
}
return true;
2018-05-03 01:12:04 +02:00
}
struct accelerator_t
2014-05-09 21:35:56 +02:00
{
accelerator_t () {}
accelerator_t (const CmapSubtableFormat4 *subtable) { init (subtable); }
~accelerator_t () { fini (); }
2018-10-30 06:35:44 +01:00
void init (const CmapSubtableFormat4 *subtable)
{
segCount = subtable->segCountX2 / 2;
2018-09-10 23:29:26 +02:00
endCount = subtable->values.arrayZ;
startCount = endCount + segCount + 1;
idDelta = startCount + segCount;
idRangeOffset = idDelta + segCount;
glyphIdArray = idRangeOffset + segCount;
glyphIdArrayLength = (subtable->length - 16 - 8 * segCount) / 2;
}
void fini () {}
2014-05-10 01:55:51 +02:00
bool get_glyph (hb_codepoint_t codepoint, hb_codepoint_t *glyph) const
2014-05-10 01:55:51 +02:00
{
/* Custom two-array bsearch. */
2018-08-26 00:25:03 +02:00
int min = 0, max = (int) this->segCount - 1;
const HBUINT16 *startCount = this->startCount;
const HBUINT16 *endCount = this->endCount;
unsigned int i;
while (min <= max)
{
2018-11-15 20:40:56 +01:00
int mid = ((unsigned int) min + (unsigned int) max) / 2;
if (codepoint < startCount[mid])
max = mid - 1;
else if (codepoint > endCount[mid])
min = mid + 1;
else
{
i = mid;
goto found;
}
}
return false;
found:
hb_codepoint_t gid;
2018-08-26 00:25:03 +02:00
unsigned int rangeOffset = this->idRangeOffset[i];
if (rangeOffset == 0)
2018-08-26 00:25:03 +02:00
gid = codepoint + this->idDelta[i];
2014-05-10 01:55:51 +02:00
else
{
/* Somebody has been smoking... */
2018-08-26 00:25:03 +02:00
unsigned int index = rangeOffset / 2 + (codepoint - this->startCount[i]) + i - this->segCount;
if (unlikely (index >= this->glyphIdArrayLength))
return false;
2018-08-26 00:25:03 +02:00
gid = this->glyphIdArray[index];
if (unlikely (!gid))
return false;
2018-08-26 00:25:03 +02:00
gid += this->idDelta[i];
2014-05-10 01:55:51 +02:00
}
gid &= 0xFFFFu;
if (!gid)
return false;
*glyph = gid;
return true;
2014-05-10 01:55:51 +02:00
}
HB_INTERNAL static bool get_glyph_func (const void *obj, hb_codepoint_t codepoint, hb_codepoint_t *glyph)
2018-08-26 00:25:03 +02:00
{
return ((const accelerator_t *) obj)->get_glyph (codepoint, glyph);
}
void collect_unicodes (hb_set_t *out) const
2018-08-26 00:25:03 +02:00
{
2018-08-26 01:14:32 +02:00
unsigned int count = this->segCount;
if (count && this->startCount[count - 1] == 0xFFFFu)
2018-11-15 20:40:56 +01:00
count--; /* Skip sentinel segment. */
2018-08-26 01:14:32 +02:00
for (unsigned int i = 0; i < count; i++)
2018-08-26 00:25:03 +02:00
{
unsigned int rangeOffset = this->idRangeOffset[i];
if (rangeOffset == 0)
out->add_range (this->startCount[i], this->endCount[i]);
else
{
for (hb_codepoint_t codepoint = this->startCount[i];
codepoint <= this->endCount[i];
codepoint++)
{
unsigned int index = rangeOffset / 2 + (codepoint - this->startCount[i]) + i - this->segCount;
if (unlikely (index >= this->glyphIdArrayLength))
break;
hb_codepoint_t gid = this->glyphIdArray[index];
if (unlikely (!gid))
continue;
out->add (codepoint);
}
}
2018-08-26 00:25:03 +02:00
}
}
2014-05-10 01:55:51 +02:00
const HBUINT16 *endCount;
const HBUINT16 *startCount;
const HBUINT16 *idDelta;
const HBUINT16 *idRangeOffset;
const HBUINT16 *glyphIdArray;
unsigned int segCount;
unsigned int glyphIdArrayLength;
};
bool get_glyph (hb_codepoint_t codepoint, hb_codepoint_t *glyph) const
{
2018-10-30 06:35:44 +01:00
accelerator_t accel (this);
return accel.get_glyph_func (&accel, codepoint, glyph);
2014-05-09 21:35:56 +02:00
}
void collect_unicodes (hb_set_t *out) const
{
2018-10-30 06:35:44 +01:00
accelerator_t accel (this);
2018-08-26 00:25:03 +02:00
accel.collect_unicodes (out);
}
2014-05-09 21:35:56 +02:00
bool sanitize (hb_sanitize_context_t *c) const
{
2014-05-09 21:35:56 +02:00
TRACE_SANITIZE (this);
if (unlikely (!c->check_struct (this)))
return_trace (false);
if (unlikely (!c->check_range (this, length)))
{
/* Some broken fonts have too long of a "length" value.
* If that is the case, just change the value to truncate
* the subtable at the end of the blob. */
uint16_t new_length = (uint16_t) hb_min ((uintptr_t) 65535,
(uintptr_t) (c->end -
(char *) this));
if (!c->try_set (&length, new_length))
return_trace (false);
}
return_trace (16 + 4 * (unsigned int) segCountX2 <= length);
2014-05-09 21:35:56 +02:00
}
2018-05-03 01:12:04 +02:00
2014-05-09 21:35:56 +02:00
protected:
HBUINT16 format; /* Format number is set to 4. */
HBUINT16 length; /* This is the length in bytes of the
2014-05-09 21:35:56 +02:00
* subtable. */
HBUINT16 language; /* Ignore. */
HBUINT16 segCountX2; /* 2 x segCount. */
HBUINT16 searchRange; /* 2 * (2**floor(log2(segCount))) */
HBUINT16 entrySelector; /* log2(searchRange/2) */
HBUINT16 rangeShift; /* 2 x segCount - searchRange */
2014-05-09 21:35:56 +02:00
2018-09-10 23:29:26 +02:00
UnsizedArrayOf<HBUINT16>
values;
2014-05-09 21:35:56 +02:00
#if 0
HBUINT16 endCount[segCount]; /* End characterCode for each segment,
* last=0xFFFFu. */
HBUINT16 reservedPad; /* Set to 0. */
HBUINT16 startCount[segCount]; /* Start character code for each segment. */
HBINT16 idDelta[segCount]; /* Delta for all character codes in segment. */
HBUINT16 idRangeOffset[segCount];/* Offsets into glyphIdArray or 0 */
2018-09-10 23:29:26 +02:00
UnsizedArrayOf<HBUINT16>
glyphIdArray; /* Glyph index array (arbitrary length) */
2014-05-09 21:35:56 +02:00
#endif
public:
DEFINE_SIZE_ARRAY (14, values);
};
2014-05-12 23:58:31 +02:00
struct CmapSubtableLongGroup
2014-05-12 23:51:15 +02:00
{
friend struct CmapSubtableFormat12;
2014-05-12 23:58:31 +02:00
friend struct CmapSubtableFormat13;
template<typename U>
friend struct CmapSubtableLongSegmented;
friend struct cmap;
2014-05-12 23:51:15 +02:00
int cmp (hb_codepoint_t codepoint) const
{
if (codepoint < startCharCode) return -1;
if (codepoint > endCharCode) return +1;
return 0;
}
bool sanitize (hb_sanitize_context_t *c) const
{
2014-05-12 23:51:15 +02:00
TRACE_SANITIZE (this);
return_trace (c->check_struct (this));
2014-05-12 23:51:15 +02:00
}
private:
HBUINT32 startCharCode; /* First character code in this group. */
HBUINT32 endCharCode; /* Last character code in this group. */
HBUINT32 glyphID; /* Glyph index; interpretation depends on
* subtable format. */
2014-05-12 23:51:15 +02:00
public:
DEFINE_SIZE_STATIC (12);
};
DECLARE_NULL_NAMESPACE_BYTES (OT, CmapSubtableLongGroup);
2014-05-12 23:51:15 +02:00
template <typename UINT>
struct CmapSubtableTrimmed
{
bool get_glyph (hb_codepoint_t codepoint, hb_codepoint_t *glyph) const
{
/* Rely on our implicit array bound-checking. */
hb_codepoint_t gid = glyphIdArray[codepoint - startCharCode];
if (!gid)
return false;
*glyph = gid;
return true;
}
void collect_unicodes (hb_set_t *out) const
{
hb_codepoint_t start = startCharCode;
unsigned int count = glyphIdArray.len;
for (unsigned int i = 0; i < count; i++)
if (glyphIdArray[i])
2018-11-15 20:40:56 +01:00
out->add (start + i);
}
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) && glyphIdArray.sanitize (c));
}
protected:
UINT formatReserved; /* Subtable format and (maybe) padding. */
UINT length; /* Byte length of this subtable. */
UINT language; /* Ignore. */
UINT startCharCode; /* First character code covered. */
ArrayOf<GlyphID, UINT>
glyphIdArray; /* Array of glyph index values for character
* codes in the range. */
public:
DEFINE_SIZE_ARRAY (5 * sizeof (UINT), glyphIdArray);
};
struct CmapSubtableFormat6 : CmapSubtableTrimmed<HBUINT16> {};
struct CmapSubtableFormat10 : CmapSubtableTrimmed<HBUINT32 > {};
template <typename T>
struct CmapSubtableLongSegmented
2014-05-12 23:51:15 +02:00
{
2018-02-10 02:33:34 +01:00
friend struct cmap;
bool get_glyph (hb_codepoint_t codepoint, hb_codepoint_t *glyph) const
2014-05-12 23:51:15 +02:00
{
hb_codepoint_t gid = T::group_get_glyph (groups.bsearch (codepoint), codepoint);
if (!gid)
return false;
*glyph = gid;
return true;
2014-05-12 23:51:15 +02:00
}
void collect_unicodes (hb_set_t *out) const
{
for (unsigned int i = 0; i < this->groups.len; i++) {
out->add_range (this->groups[i].startCharCode,
hb_min ((hb_codepoint_t) this->groups[i].endCharCode,
(hb_codepoint_t) HB_UNICODE_MAX));
}
}
bool sanitize (hb_sanitize_context_t *c) const
{
2014-05-12 23:51:15 +02:00
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) && groups.sanitize (c));
2014-05-12 23:51:15 +02:00
}
bool serialize (hb_serialize_context_t *c,
const hb_sorted_vector_t<CmapSubtableLongGroup> &group_data)
2018-02-10 02:33:34 +01:00
{
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (*this))) return_trace (false);
2018-12-18 22:49:08 +01:00
if (unlikely (!groups.serialize (c, group_data.as_array ()))) return_trace (false);
2019-04-02 01:50:28 +02:00
return_trace (true);
2018-02-10 02:33:34 +01:00
}
2014-05-12 23:51:15 +02:00
protected:
HBUINT16 format; /* Subtable format; set to 12. */
HBUINT16 reserved; /* Reserved; set to 0. */
HBUINT32 length; /* Byte length of this subtable. */
HBUINT32 language; /* Ignore. */
SortedArrayOf<CmapSubtableLongGroup, HBUINT32>
2014-05-12 23:58:31 +02:00
groups; /* Groupings. */
public:
DEFINE_SIZE_ARRAY (16, groups);
};
struct CmapSubtableFormat12 : CmapSubtableLongSegmented<CmapSubtableFormat12>
2014-05-12 23:58:31 +02:00
{
static hb_codepoint_t group_get_glyph (const CmapSubtableLongGroup &group,
hb_codepoint_t u)
{ return likely (group.startCharCode <= group.endCharCode) ?
group.glyphID + (u - group.startCharCode) : 0; }
bool serialize (hb_serialize_context_t *c,
const hb_sorted_vector_t<CmapSubtableLongGroup> &groups_data)
{
2019-04-02 01:50:28 +02:00
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (*this))) return_trace (false);
this->format = 12;
this->reserved = 0;
this->length = get_sub_table_size (groups_data);
return_trace (CmapSubtableLongSegmented<CmapSubtableFormat12>::serialize (c, groups_data));
}
static size_t get_sub_table_size (const hb_sorted_vector_t<CmapSubtableLongGroup> &groups_data)
{
return 16 + 12 * groups_data.length;
}
static bool create_sub_table_plan (const hb_subset_plan_t *plan,
hb_sorted_vector_t<CmapSubtableLongGroup> *groups_out)
{
CmapSubtableLongGroup *group = nullptr;
hb_codepoint_t cp = HB_SET_VALUE_INVALID;
while (plan->unicodes->next (&cp)) {
2019-05-08 21:01:55 +02:00
hb_codepoint_t new_gid = 0;
if (unlikely (!plan->new_gid_for_codepoint (cp, &new_gid)))
{
DEBUG_MSG(SUBSET, nullptr, "Unable to find new gid for %04x", cp);
return false;
}
if (!group || !_is_gid_consecutive (group, cp, new_gid))
{
group = groups_out->push ();
group->startCharCode = cp;
group->endCharCode = cp;
group->glyphID = new_gid;
}
else group->endCharCode = cp;
}
DEBUG_MSG(SUBSET, nullptr, "cmap");
for (unsigned int i = 0; i < groups_out->length; i++) {
CmapSubtableLongGroup& group = (*groups_out)[i];
DEBUG_MSG(SUBSET, nullptr, " %d: U+%04X-U+%04X, gid %d-%d", i, (uint32_t) group.startCharCode, (uint32_t) group.endCharCode, (uint32_t) group.glyphID, (uint32_t) group.glyphID + ((uint32_t) group.endCharCode - (uint32_t) group.startCharCode));
}
return true;
}
private:
static bool _is_gid_consecutive (CmapSubtableLongGroup *group,
hb_codepoint_t cp,
hb_codepoint_t new_gid)
{
return (cp - 1 == group->endCharCode) &&
new_gid == group->glyphID + (cp - group->startCharCode);
}
};
2014-05-12 23:58:31 +02:00
struct CmapSubtableFormat13 : CmapSubtableLongSegmented<CmapSubtableFormat13>
{
static hb_codepoint_t group_get_glyph (const CmapSubtableLongGroup &group,
hb_codepoint_t u HB_UNUSED)
{ return group.glyphID; }
2014-05-12 23:51:15 +02:00
};
2014-06-27 23:03:22 +02:00
typedef enum
{
GLYPH_VARIANT_NOT_FOUND = 0,
GLYPH_VARIANT_FOUND = 1,
GLYPH_VARIANT_USE_DEFAULT = 2
} glyph_variant_t;
struct UnicodeValueRange
{
int cmp (const hb_codepoint_t &codepoint) const
2014-06-27 23:03:22 +02:00
{
if (codepoint < startUnicodeValue) return -1;
if (codepoint > startUnicodeValue + additionalCount) return +1;
return 0;
}
bool sanitize (hb_sanitize_context_t *c) const
{
2014-06-27 23:03:22 +02:00
TRACE_SANITIZE (this);
return_trace (c->check_struct (this));
2014-06-27 23:03:22 +02:00
}
HBUINT24 startUnicodeValue; /* First value in this range. */
HBUINT8 additionalCount; /* Number of additional values in this
2014-06-27 23:03:22 +02:00
* range. */
public:
DEFINE_SIZE_STATIC (4);
};
struct DefaultUVS : SortedArrayOf<UnicodeValueRange, HBUINT32>
{
void collect_unicodes (hb_set_t *out) const
{
unsigned int count = len;
for (unsigned int i = 0; i < count; i++)
{
hb_codepoint_t first = arrayZ[i].startUnicodeValue;
hb_codepoint_t last = hb_min ((hb_codepoint_t) (first + arrayZ[i].additionalCount),
(hb_codepoint_t) HB_UNICODE_MAX);
out->add_range (first, last);
}
}
public:
DEFINE_SIZE_ARRAY (4, *this);
};
2014-06-27 23:03:22 +02:00
struct UVSMapping
{
int cmp (const hb_codepoint_t &codepoint) const
2014-06-27 23:03:22 +02:00
{
return unicodeValue.cmp (codepoint);
}
bool sanitize (hb_sanitize_context_t *c) const
{
2014-06-27 23:03:22 +02:00
TRACE_SANITIZE (this);
return_trace (c->check_struct (this));
2014-06-27 23:03:22 +02:00
}
HBUINT24 unicodeValue; /* Base Unicode value of the UVS */
2014-06-27 23:03:22 +02:00
GlyphID glyphID; /* Glyph ID of the UVS */
public:
DEFINE_SIZE_STATIC (5);
};
struct NonDefaultUVS : SortedArrayOf<UVSMapping, HBUINT32>
{
void collect_unicodes (hb_set_t *out) const
{
unsigned int count = len;
for (unsigned int i = 0; i < count; i++)
out->add (arrayZ[i].glyphID);
}
public:
DEFINE_SIZE_ARRAY (4, *this);
};
2014-06-27 23:03:22 +02:00
struct VariationSelectorRecord
{
glyph_variant_t get_glyph (hb_codepoint_t codepoint,
hb_codepoint_t *glyph,
const void *base) const
2014-06-27 23:03:22 +02:00
{
if ((base+defaultUVS).bfind (codepoint))
2014-06-27 23:03:22 +02:00
return GLYPH_VARIANT_USE_DEFAULT;
const UVSMapping &nonDefault = (base+nonDefaultUVS).bsearch (codepoint);
if (nonDefault.glyphID)
2014-06-27 23:03:22 +02:00
{
*glyph = nonDefault.glyphID;
2014-06-27 23:03:22 +02:00
return GLYPH_VARIANT_FOUND;
}
return GLYPH_VARIANT_NOT_FOUND;
}
void collect_unicodes (hb_set_t *out, const void *base) const
{
(base+defaultUVS).collect_unicodes (out);
(base+nonDefaultUVS).collect_unicodes (out);
}
int cmp (const hb_codepoint_t &variation_selector) const
2014-06-27 23:03:22 +02:00
{
return varSelector.cmp (variation_selector);
}
bool sanitize (hb_sanitize_context_t *c, const void *base) const
{
2014-06-27 23:03:22 +02:00
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
defaultUVS.sanitize (c, base) &&
nonDefaultUVS.sanitize (c, base));
2014-06-27 23:03:22 +02:00
}
HBUINT24 varSelector; /* Variation selector. */
2017-01-23 05:28:56 +01:00
LOffsetTo<DefaultUVS>
2018-10-13 12:30:05 +02:00
defaultUVS; /* Offset to Default UVS Table. May be 0. */
2017-01-23 05:28:56 +01:00
LOffsetTo<NonDefaultUVS>
2018-10-13 12:30:05 +02:00
nonDefaultUVS; /* Offset to Non-Default UVS Table. May be 0. */
2014-06-27 23:03:22 +02:00
public:
DEFINE_SIZE_STATIC (11);
};
struct CmapSubtableFormat14
{
glyph_variant_t get_glyph_variant (hb_codepoint_t codepoint,
hb_codepoint_t variation_selector,
hb_codepoint_t *glyph) const
2014-06-27 23:03:22 +02:00
{
return record.bsearch (variation_selector).get_glyph (codepoint, glyph, this);
2014-06-27 23:03:22 +02:00
}
void collect_variation_selectors (hb_set_t *out) const
{
unsigned int count = record.len;
for (unsigned int i = 0; i < count; i++)
out->add (record.arrayZ[i].varSelector);
}
void collect_variation_unicodes (hb_codepoint_t variation_selector,
hb_set_t *out) const
{
record.bsearch (variation_selector).collect_unicodes (out, this);
}
bool sanitize (hb_sanitize_context_t *c) const
{
2014-06-27 23:03:22 +02:00
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
record.sanitize (c, this));
2014-06-27 23:03:22 +02:00
}
protected:
HBUINT16 format; /* Format number is set to 14. */
HBUINT32 length; /* Byte length of this subtable. */
SortedArrayOf<VariationSelectorRecord, HBUINT32>
2014-06-27 23:03:22 +02:00
record; /* Variation selector records; sorted
* in increasing order of `varSelector'. */
public:
DEFINE_SIZE_ARRAY (10, record);
};
2014-05-09 21:35:56 +02:00
struct CmapSubtable
{
/* Note: We intentionally do NOT implement subtable formats 2 and 8. */
bool get_glyph (hb_codepoint_t codepoint,
hb_codepoint_t *glyph) const
2014-05-09 21:35:56 +02:00
{
switch (u.format) {
2018-02-15 04:36:33 +01:00
case 0: return u.format0 .get_glyph (codepoint, glyph);
case 4: return u.format4 .get_glyph (codepoint, glyph);
case 6: return u.format6 .get_glyph (codepoint, glyph);
case 10: return u.format10.get_glyph (codepoint, glyph);
case 12: return u.format12.get_glyph (codepoint, glyph);
case 13: return u.format13.get_glyph (codepoint, glyph);
2014-06-27 23:03:22 +02:00
case 14:
default: return false;
}
}
void collect_unicodes (hb_set_t *out) const
{
switch (u.format) {
case 0: u.format0 .collect_unicodes (out); return;
case 4: u.format4 .collect_unicodes (out); return;
case 6: u.format6 .collect_unicodes (out); return;
case 10: u.format10.collect_unicodes (out); return;
case 12: u.format12.collect_unicodes (out); return;
case 13: u.format13.collect_unicodes (out); return;
case 14:
default: return;
}
}
2014-06-27 23:03:22 +02:00
bool sanitize (hb_sanitize_context_t *c) const
{
2014-05-09 21:35:56 +02:00
TRACE_SANITIZE (this);
if (!u.format.sanitize (c)) return_trace (false);
2014-05-09 21:35:56 +02:00
switch (u.format) {
case 0: return_trace (u.format0 .sanitize (c));
case 4: return_trace (u.format4 .sanitize (c));
case 6: return_trace (u.format6 .sanitize (c));
case 10: return_trace (u.format10.sanitize (c));
case 12: return_trace (u.format12.sanitize (c));
case 13: return_trace (u.format13.sanitize (c));
case 14: return_trace (u.format14.sanitize (c));
default:return_trace (true);
2014-05-09 21:35:56 +02:00
}
}
public:
2014-05-09 21:35:56 +02:00
union {
HBUINT16 format; /* Format identifier */
2014-05-14 03:47:51 +02:00
CmapSubtableFormat0 format0;
2014-05-09 21:35:56 +02:00
CmapSubtableFormat4 format4;
CmapSubtableFormat6 format6;
CmapSubtableFormat10 format10;
2014-05-12 23:51:15 +02:00
CmapSubtableFormat12 format12;
2014-05-12 23:58:31 +02:00
CmapSubtableFormat13 format13;
2014-06-27 23:03:22 +02:00
CmapSubtableFormat14 format14;
2014-05-09 21:35:56 +02:00
} u;
public:
DEFINE_SIZE_UNION (2, format);
};
struct EncodingRecord
{
int cmp (const EncodingRecord &other) const
2014-05-09 21:35:56 +02:00
{
int ret;
ret = platformID.cmp (other.platformID);
2014-05-09 21:35:56 +02:00
if (ret) return ret;
ret = encodingID.cmp (other.encodingID);
2014-05-09 21:35:56 +02:00
if (ret) return ret;
return 0;
}
bool sanitize (hb_sanitize_context_t *c, const void *base) const
{
2014-05-09 21:35:56 +02:00
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
subtable.sanitize (c, base));
2014-05-09 21:35:56 +02:00
}
HBUINT16 platformID; /* Platform ID. */
HBUINT16 encodingID; /* Platform-specific encoding ID. */
2017-01-23 05:28:56 +01:00
LOffsetTo<CmapSubtable>
2014-05-09 21:35:56 +02:00
subtable; /* Byte offset from beginning of table to the subtable for this encoding. */
public:
DEFINE_SIZE_STATIC (8);
};
struct cmap
{
static constexpr hb_tag_t tableTag = HB_OT_TAG_cmap;
2014-05-09 21:35:56 +02:00
struct subset_plan
{
size_t final_size () const
{
return 4 // header
+ 8 * num_enc_records
2018-11-15 20:40:56 +01:00
+ CmapSubtableFormat4::get_sub_table_size (this->format4_segments)
+ CmapSubtableFormat12::get_sub_table_size (this->format12_groups);
}
unsigned int num_enc_records;
bool has_unicode_bmp;
bool has_unicode_ucs4;
bool has_ms_bmp;
bool has_ms_ucs4;
hb_sorted_vector_t<CmapSubtableFormat4::segment_plan> format4_segments;
hb_sorted_vector_t<CmapSubtableLongGroup> format12_groups;
};
bool _create_plan (const hb_subset_plan_t *plan,
subset_plan *cmap_plan) const
{
cmap_plan->has_unicode_bmp = find_subtable (0, 3);
cmap_plan->has_unicode_ucs4 = find_subtable (0, 4);
cmap_plan->has_ms_bmp = find_subtable (3, 1);
cmap_plan->has_ms_ucs4 = find_subtable (3, 10);
cmap_plan->num_enc_records = cmap_plan->has_unicode_bmp + cmap_plan->has_unicode_ucs4 + cmap_plan->has_ms_bmp + cmap_plan->has_ms_ucs4;
2018-11-15 20:40:56 +01:00
if (unlikely (!CmapSubtableFormat4::create_sub_table_plan (plan, &cmap_plan->format4_segments)))
2018-05-03 01:12:04 +02:00
return false;
if (!find_subtable (12)) return true;
2018-05-03 01:12:04 +02:00
return CmapSubtableFormat12::create_sub_table_plan (plan, &cmap_plan->format12_groups);
2018-02-10 02:33:34 +01:00
}
bool _subset (const hb_subset_plan_t *plan,
const subset_plan &cmap_subset_plan,
size_t dest_sz,
void *dest) const
2018-02-10 02:33:34 +01:00
{
hb_serialize_context_t c (dest, dest_sz);
2018-02-10 02:33:34 +01:00
2018-07-23 21:00:02 +02:00
cmap *table = c.start_serialize<cmap> ();
if (unlikely (!c.extend_min (*table)))
2018-02-10 02:33:34 +01:00
{
return false;
}
table->version = 0;
2018-02-10 02:33:34 +01:00
if (unlikely (!table->encodingRecord.serialize (&c, cmap_subset_plan.num_enc_records))) return false;
2018-05-03 01:12:04 +02:00
// TODO(grieger): Convert the below to a for loop
int enc_index = 0;
int unicode_bmp_index = 0;
int unicode_ucs4_index = 0;
int ms_bmp_index = 0;
int ms_ucs4_index = 0;
2018-02-10 02:33:34 +01:00
// Format 4, Plat 0 Encoding Record
if (cmap_subset_plan.has_unicode_bmp)
{
unicode_bmp_index = enc_index;
EncodingRecord &format4_plat0_rec = table->encodingRecord[enc_index++];
format4_plat0_rec.platformID = 0; // Unicode
format4_plat0_rec.encodingID = 3;
}
// Format 12, Plat 0 Encoding Record
if (cmap_subset_plan.has_unicode_ucs4)
{
unicode_ucs4_index = enc_index;
EncodingRecord &format12_rec = table->encodingRecord[enc_index++];
format12_rec.platformID = 0; // Unicode
format12_rec.encodingID = 4; // Unicode UCS-4
}
2018-02-10 02:33:34 +01:00
// Format 4, Plat 3 Encoding Record
if (cmap_subset_plan.has_ms_bmp)
{
ms_bmp_index = enc_index;
EncodingRecord &format4_plat3_rec = table->encodingRecord[enc_index++];
format4_plat3_rec.platformID = 3; // Windows
format4_plat3_rec.encodingID = 1; // Unicode BMP
}
2018-02-12 19:12:11 +01:00
// Format 12, Plat 3 Encoding Record
if (cmap_subset_plan.has_ms_ucs4)
{
ms_ucs4_index = enc_index;
EncodingRecord &format12_rec = table->encodingRecord[enc_index++];
format12_rec.platformID = 3; // Windows
format12_rec.encodingID = 10; // Unicode UCS-4
}
2018-02-10 02:33:34 +01:00
// Write out format 4 sub table
2018-05-03 01:12:04 +02:00
{
if (unlikely (!cmap_subset_plan.has_unicode_bmp && !cmap_subset_plan.has_ms_bmp)) return false;
EncodingRecord &format4_rec = cmap_subset_plan.has_unicode_bmp?
table->encodingRecord[unicode_bmp_index]:
table->encodingRecord[ms_bmp_index];
CmapSubtable &subtable = format4_rec.subtable.serialize (&c, table);
if (cmap_subset_plan.has_unicode_bmp && cmap_subset_plan.has_ms_bmp)
table->encodingRecord[ms_bmp_index].subtable = (unsigned int) format4_rec.subtable;
subtable.u.format = 4;
2018-05-03 01:12:04 +02:00
CmapSubtableFormat4 &format4 = subtable.u.format4;
if (unlikely (!format4.serialize (&c, plan, cmap_subset_plan.format4_segments)))
2018-11-15 20:40:56 +01:00
return false;
2018-05-03 01:12:04 +02:00
}
2018-02-12 19:12:11 +01:00
// Write out format 12 sub table.
if (cmap_subset_plan.format12_groups)
2018-05-03 01:12:04 +02:00
{
if (unlikely (!cmap_subset_plan.has_unicode_ucs4 && !cmap_subset_plan.has_ms_ucs4)) return false;
EncodingRecord &format12_rec = cmap_subset_plan.has_unicode_ucs4?
table->encodingRecord[unicode_ucs4_index]:
table->encodingRecord[ms_ucs4_index];
2018-07-23 21:00:02 +02:00
CmapSubtable &subtable = format12_rec.subtable.serialize (&c, table);
if (cmap_subset_plan.has_unicode_ucs4 && cmap_subset_plan.has_ms_ucs4)
table->encodingRecord[ms_ucs4_index].subtable = (unsigned int) format12_rec.subtable;
subtable.u.format = 12;
2018-02-10 02:33:34 +01:00
2018-05-03 01:12:04 +02:00
CmapSubtableFormat12 &format12 = subtable.u.format12;
if (unlikely (!format12.serialize (&c, cmap_subset_plan.format12_groups)))
2018-11-15 20:40:56 +01:00
return false;
2018-05-03 01:12:04 +02:00
}
2018-02-10 02:33:34 +01:00
c.end_serialize ();
2018-02-12 19:12:11 +01:00
return true;
}
bool subset (hb_subset_plan_t *plan) const
2018-02-10 02:33:34 +01:00
{
subset_plan cmap_subset_plan;
2018-02-10 02:33:34 +01:00
if (unlikely (!_create_plan (plan, &cmap_subset_plan)))
{
DEBUG_MSG(SUBSET, nullptr, "Failed to generate a cmap subsetting plan.");
return false;
}
2018-02-10 02:33:34 +01:00
// We now know how big our blob needs to be
2018-11-15 20:40:56 +01:00
size_t dest_sz = cmap_subset_plan.final_size ();
void *dest = malloc (dest_sz);
2018-02-15 04:36:33 +01:00
if (unlikely (!dest)) {
DEBUG_MSG(SUBSET, nullptr, "Unable to alloc %lu for cmap subset output", (unsigned long) dest_sz);
2018-02-14 23:16:25 +01:00
return false;
2018-02-10 02:33:34 +01:00
}
2018-05-03 01:12:04 +02:00
if (unlikely (!_subset (plan, cmap_subset_plan, dest_sz, dest)))
2018-02-10 02:33:34 +01:00
{
DEBUG_MSG(SUBSET, nullptr, "Failed to perform subsetting of cmap.");
2018-02-15 04:36:33 +01:00
free (dest);
2018-02-14 23:16:25 +01:00
return false;
2018-02-10 02:33:34 +01:00
}
// all done, write the blob into dest
2018-11-15 20:40:56 +01:00
hb_blob_t *cmap_prime = hb_blob_create ((const char *) dest,
dest_sz,
HB_MEMORY_MODE_READONLY,
dest,
free);
bool result = plan->add_table (HB_OT_TAG_cmap, cmap_prime);
hb_blob_destroy (cmap_prime);
return result;
2018-02-10 02:33:34 +01:00
}
2018-08-26 00:33:05 +02:00
const CmapSubtable *find_best_subtable (bool *symbol = nullptr) const
{
if (symbol) *symbol = false;
const CmapSubtable *subtable;
/* 32-bit subtables. */
if ((subtable = this->find_subtable (3, 10))) return subtable;
if ((subtable = this->find_subtable (0, 6))) return subtable;
if ((subtable = this->find_subtable (0, 4))) return subtable;
/* 16-bit subtables. */
if ((subtable = this->find_subtable (3, 1))) return subtable;
if ((subtable = this->find_subtable (0, 3))) return subtable;
if ((subtable = this->find_subtable (0, 2))) return subtable;
if ((subtable = this->find_subtable (0, 1))) return subtable;
if ((subtable = this->find_subtable (0, 0))) return subtable;
/* Symbol subtable. */
if ((subtable = this->find_subtable (3, 0)))
{
if (symbol) *symbol = true;
return subtable;
}
/* Meh. */
2018-11-15 20:40:56 +01:00
return &Null (CmapSubtable);
2018-08-26 00:33:05 +02:00
}
struct accelerator_t
{
void init (hb_face_t *face)
{
2018-11-15 20:40:56 +01:00
this->table = hb_sanitize_context_t ().reference_table<cmap> (face);
2018-08-26 00:33:05 +02:00
bool symbol;
2018-11-06 01:46:29 +01:00
this->subtable = table->find_best_subtable (&symbol);
2018-11-15 20:40:56 +01:00
this->subtable_uvs = &Null (CmapSubtableFormat14);
{
2018-07-23 21:00:02 +02:00
const CmapSubtable *st = table->find_subtable (0, 5);
if (st && st->u.format == 14)
2018-11-06 01:46:29 +01:00
subtable_uvs = &st->u.format14;
}
2018-11-06 01:46:29 +01:00
this->get_glyph_data = subtable;
if (unlikely (symbol))
{
this->get_glyph_funcZ = get_glyph_from_symbol<CmapSubtable>;
} else {
2018-11-06 01:46:29 +01:00
switch (subtable->u.format) {
/* Accelerate format 4 and format 12. */
default:
this->get_glyph_funcZ = get_glyph_from<CmapSubtable>;
break;
case 12:
this->get_glyph_funcZ = get_glyph_from<CmapSubtableFormat12>;
break;
case 4:
{
2018-11-06 01:46:29 +01:00
this->format4_accel.init (&subtable->u.format4);
this->get_glyph_data = &this->format4_accel;
this->get_glyph_funcZ = this->format4_accel.get_glyph_func;
}
break;
}
}
}
void fini () { this->table.destroy (); }
bool get_nominal_glyph (hb_codepoint_t unicode,
hb_codepoint_t *glyph) const
{
if (unlikely (!this->get_glyph_funcZ)) return false;
return this->get_glyph_funcZ (this->get_glyph_data, unicode, glyph);
}
unsigned int get_nominal_glyphs (unsigned int count,
const hb_codepoint_t *first_unicode,
unsigned int unicode_stride,
hb_codepoint_t *first_glyph,
unsigned int glyph_stride) const
{
if (unlikely (!this->get_glyph_funcZ)) return 0;
hb_cmap_get_glyph_func_t get_glyph_funcZ = this->get_glyph_funcZ;
const void *get_glyph_data = this->get_glyph_data;
unsigned int done;
for (done = 0;
done < count && get_glyph_funcZ (get_glyph_data, *first_unicode, first_glyph);
done++)
{
2019-01-22 12:45:40 +01:00
first_unicode = &StructAtOffsetUnaligned<hb_codepoint_t> (first_unicode, unicode_stride);
first_glyph = &StructAtOffsetUnaligned<hb_codepoint_t> (first_glyph, glyph_stride);
}
return done;
}
bool get_variation_glyph (hb_codepoint_t unicode,
hb_codepoint_t variation_selector,
hb_codepoint_t *glyph) const
{
2018-11-06 01:46:29 +01:00
switch (this->subtable_uvs->get_glyph_variant (unicode,
variation_selector,
glyph))
{
case GLYPH_VARIANT_NOT_FOUND: return false;
case GLYPH_VARIANT_FOUND: return true;
case GLYPH_VARIANT_USE_DEFAULT: break;
}
return get_nominal_glyph (unicode, glyph);
}
void collect_unicodes (hb_set_t *out) const
{
2018-11-06 01:46:29 +01:00
subtable->collect_unicodes (out);
}
void collect_variation_selectors (hb_set_t *out) const
{
2018-11-06 01:46:29 +01:00
subtable_uvs->collect_variation_selectors (out);
}
void collect_variation_unicodes (hb_codepoint_t variation_selector,
hb_set_t *out) const
{
2018-11-06 01:46:29 +01:00
subtable_uvs->collect_variation_unicodes (variation_selector, out);
}
protected:
typedef bool (*hb_cmap_get_glyph_func_t) (const void *obj,
hb_codepoint_t codepoint,
hb_codepoint_t *glyph);
template <typename Type>
HB_INTERNAL static bool get_glyph_from (const void *obj,
hb_codepoint_t codepoint,
hb_codepoint_t *glyph)
{
const Type *typed_obj = (const Type *) obj;
return typed_obj->get_glyph (codepoint, glyph);
}
template <typename Type>
HB_INTERNAL static bool get_glyph_from_symbol (const void *obj,
hb_codepoint_t codepoint,
hb_codepoint_t *glyph)
{
const Type *typed_obj = (const Type *) obj;
if (likely (typed_obj->get_glyph (codepoint, glyph)))
return true;
if (codepoint <= 0x00FFu)
{
/* For symbol-encoded OpenType fonts, we duplicate the
* U+F000..F0FF range at U+0000..U+00FF. That's what
* Windows seems to do, and that's hinted about at:
* https://docs.microsoft.com/en-us/typography/opentype/spec/recom
* under "Non-Standard (Symbol) Fonts". */
return typed_obj->get_glyph (0xF000u + codepoint, glyph);
}
return false;
}
private:
2018-11-06 01:46:29 +01:00
hb_nonnull_ptr_t<const CmapSubtable> subtable;
hb_nonnull_ptr_t<const CmapSubtableFormat14> subtable_uvs;
hb_cmap_get_glyph_func_t get_glyph_funcZ;
const void *get_glyph_data;
CmapSubtableFormat4::accelerator_t format4_accel;
hb_blob_ptr_t<cmap> table;
};
protected:
const CmapSubtable *find_subtable (unsigned int platform_id,
unsigned int encoding_id) const
2014-05-09 21:35:56 +02:00
{
EncodingRecord key;
key.platformID = platform_id;
key.encodingID = encoding_id;
2014-05-09 21:35:56 +02:00
const EncodingRecord &result = encodingRecord.bsearch (key);
if (!result.subtable)
2017-10-15 12:11:08 +02:00
return nullptr;
return &(this+result.subtable);
2014-05-09 21:35:56 +02:00
}
bool find_subtable (unsigned format) const
{
auto it =
+ hb_iter (encodingRecord)
| hb_map (&EncodingRecord::subtable)
| hb_map (hb_add (this))
| hb_filter ([&] (const CmapSubtable& _) { return _.u.format == format; })
;
return it.len ();
}
2018-11-23 04:17:49 +01:00
public:
bool sanitize (hb_sanitize_context_t *c) const
2018-11-23 04:17:49 +01:00
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
likely (version == 0) &&
encodingRecord.sanitize (c, this));
}
protected:
HBUINT16 version; /* Table version number (0). */
SortedArrayOf<EncodingRecord>
encodingRecord; /* Encoding tables. */
2014-05-09 21:35:56 +02:00
public:
DEFINE_SIZE_ARRAY (4, encodingRecord);
};
struct cmap_accelerator_t : cmap::accelerator_t {};
2014-05-09 21:35:56 +02:00
} /* namespace OT */
#endif /* HB_OT_CMAP_TABLE_HH */