593 lines
21 KiB
C++
593 lines
21 KiB
C++
/*
|
|
* Copyright © 2022 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): Garret Rieger
|
|
*/
|
|
|
|
#ifndef GRAPH_PAIRPOS_GRAPH_HH
|
|
#define GRAPH_PAIRPOS_GRAPH_HH
|
|
|
|
#include "coverage-graph.hh"
|
|
#include "classdef-graph.hh"
|
|
#include "../OT/Layout/GPOS/PairPos.hh"
|
|
#include "../OT/Layout/GPOS/PosLookupSubTable.hh"
|
|
|
|
namespace graph {
|
|
|
|
struct PairPosFormat1 : public OT::Layout::GPOS_impl::PairPosFormat1_3<SmallTypes>
|
|
{
|
|
bool sanitize (graph_t::vertex_t& vertex) const
|
|
{
|
|
int64_t vertex_len = vertex.obj.tail - vertex.obj.head;
|
|
unsigned min_size = OT::Layout::GPOS_impl::PairPosFormat1_3<SmallTypes>::min_size;
|
|
if (vertex_len < min_size) return false;
|
|
|
|
return vertex_len >=
|
|
min_size + pairSet.get_size () - pairSet.len.get_size();
|
|
}
|
|
|
|
hb_vector_t<unsigned> split_subtables (gsubgpos_graph_context_t& c, unsigned this_index)
|
|
{
|
|
hb_set_t visited;
|
|
|
|
const unsigned coverage_id = c.graph.index_for_offset (this_index, &coverage);
|
|
const unsigned coverage_size = c.graph.vertices_[coverage_id].table_size ();
|
|
const unsigned base_size = OT::Layout::GPOS_impl::PairPosFormat1_3<SmallTypes>::min_size
|
|
+ coverage_size;
|
|
|
|
unsigned accumulated = base_size;
|
|
hb_vector_t<unsigned> split_points;
|
|
for (unsigned i = 0; i < pairSet.len; i++)
|
|
{
|
|
unsigned pair_set_index = pair_set_graph_index (c, this_index, i);
|
|
accumulated += c.graph.find_subgraph_size (pair_set_index, visited);
|
|
accumulated += SmallTypes::size; // for PairSet offset.
|
|
|
|
// TODO(garretrieger): don't count the size of the largest pairset against the limit, since
|
|
// it will be packed last in the order and does not contribute to
|
|
// the 64kb limit.
|
|
|
|
if (accumulated > (1 << 16))
|
|
{
|
|
split_points.push (i);
|
|
accumulated = base_size;
|
|
visited.clear (); // node sharing isn't allowed between splits.
|
|
}
|
|
}
|
|
|
|
return do_split (c, this_index, split_points);
|
|
}
|
|
|
|
private:
|
|
|
|
// Split this PairPos into two or more PairPos's. split_points defines
|
|
// the indices (first index to include in the new table) to split at.
|
|
// Returns the object id's of the newly created PairPos subtables.
|
|
hb_vector_t<unsigned> do_split (gsubgpos_graph_context_t& c,
|
|
unsigned this_index,
|
|
const hb_vector_t<unsigned> split_points)
|
|
{
|
|
hb_vector_t<unsigned> new_objects;
|
|
if (!split_points)
|
|
return new_objects;
|
|
|
|
for (unsigned i = 0; i < split_points.length; i++)
|
|
{
|
|
unsigned start = split_points[i];
|
|
unsigned end = (i < split_points.length - 1) ? split_points[i + 1] : pairSet.len;
|
|
unsigned id = clone_range (c, this_index, start, end);
|
|
|
|
if (id == (unsigned) -1)
|
|
{
|
|
new_objects.reset ();
|
|
new_objects.allocated = -1; // mark error
|
|
return new_objects;
|
|
}
|
|
new_objects.push (id);
|
|
}
|
|
|
|
if (!shrink (c, this_index, split_points[0]))
|
|
{
|
|
new_objects.reset ();
|
|
new_objects.allocated = -1; // mark error
|
|
}
|
|
|
|
return new_objects;
|
|
}
|
|
|
|
bool shrink (gsubgpos_graph_context_t& c,
|
|
unsigned this_index,
|
|
unsigned count)
|
|
{
|
|
DEBUG_MSG (SUBSET_REPACK, nullptr,
|
|
" Shrinking PairPosFormat1 (%u) to [0, %u).",
|
|
this_index,
|
|
count);
|
|
unsigned old_count = pairSet.len;
|
|
if (count >= old_count)
|
|
return true;
|
|
|
|
pairSet.len = count;
|
|
c.graph.vertices_[this_index].obj.tail -= (old_count - count) * SmallTypes::size;
|
|
|
|
unsigned coverage_id = c.graph.index_for_offset (this_index, &coverage);
|
|
unsigned coverage_size = c.graph.vertices_[coverage_id].table_size ();
|
|
auto& coverage_v = c.graph.vertices_[coverage_id];
|
|
Coverage* coverage_table = (Coverage*) coverage_v.obj.head;
|
|
if (!coverage_table->sanitize (coverage_v))
|
|
return false;
|
|
|
|
auto new_coverage =
|
|
+ hb_zip (coverage_table->iter (), hb_range ())
|
|
| hb_filter ([&] (hb_pair_t<unsigned, unsigned> p) {
|
|
return p.second < count;
|
|
})
|
|
| hb_map_retains_sorting (hb_first)
|
|
;
|
|
|
|
return Coverage::make_coverage (c, new_coverage, coverage_id, coverage_size);
|
|
}
|
|
|
|
// Create a new PairPos including PairSet's from start (inclusive) to end (exclusive).
|
|
// Returns object id of the new object.
|
|
unsigned clone_range (gsubgpos_graph_context_t& c,
|
|
unsigned this_index,
|
|
unsigned start, unsigned end) const
|
|
{
|
|
DEBUG_MSG (SUBSET_REPACK, nullptr,
|
|
" Cloning PairPosFormat1 (%u) range [%u, %u).", this_index, start, end);
|
|
|
|
unsigned num_pair_sets = end - start;
|
|
unsigned prime_size = OT::Layout::GPOS_impl::PairPosFormat1_3<SmallTypes>::min_size
|
|
+ num_pair_sets * SmallTypes::size;
|
|
|
|
unsigned pair_pos_prime_id = c.create_node (prime_size);
|
|
if (pair_pos_prime_id == (unsigned) -1) return -1;
|
|
|
|
PairPosFormat1* pair_pos_prime = (PairPosFormat1*) c.graph.object (pair_pos_prime_id).head;
|
|
pair_pos_prime->format = this->format;
|
|
pair_pos_prime->valueFormat[0] = this->valueFormat[0];
|
|
pair_pos_prime->valueFormat[1] = this->valueFormat[1];
|
|
pair_pos_prime->pairSet.len = num_pair_sets;
|
|
|
|
for (unsigned i = start; i < end; i++)
|
|
{
|
|
c.graph.move_child<> (this_index,
|
|
&pairSet[i],
|
|
pair_pos_prime_id,
|
|
&pair_pos_prime->pairSet[i - start]);
|
|
}
|
|
|
|
unsigned coverage_id = c.graph.index_for_offset (this_index, &coverage);
|
|
if (!Coverage::clone_coverage (c,
|
|
coverage_id,
|
|
pair_pos_prime_id,
|
|
2,
|
|
start, end))
|
|
return -1;
|
|
|
|
return pair_pos_prime_id;
|
|
}
|
|
|
|
|
|
|
|
unsigned pair_set_graph_index (gsubgpos_graph_context_t& c, unsigned this_index, unsigned i) const
|
|
{
|
|
return c.graph.index_for_offset (this_index, &pairSet[i]);
|
|
}
|
|
};
|
|
|
|
struct PairPosFormat2 : public OT::Layout::GPOS_impl::PairPosFormat2_4<SmallTypes>
|
|
{
|
|
bool sanitize (graph_t::vertex_t& vertex) const
|
|
{
|
|
// TODO(garretrieger): implement me!
|
|
return true;
|
|
}
|
|
|
|
hb_vector_t<unsigned> split_subtables (gsubgpos_graph_context_t& c, unsigned this_index)
|
|
{
|
|
// TODO(garretrieger): sanitization.
|
|
const unsigned base_size = OT::Layout::GPOS_impl::PairPosFormat2_4<SmallTypes>::min_size
|
|
+ size_of (c, this_index, &coverage)
|
|
+ size_of (c, this_index, &classDef1)
|
|
+ size_of (c, this_index, &classDef2);
|
|
|
|
const unsigned class1_count = class1Count;
|
|
const unsigned class2_count = class2Count;
|
|
const unsigned class1_record_size =
|
|
class2_count * (valueFormat1.get_size () + valueFormat2.get_size ());
|
|
|
|
const unsigned value_1_len = valueFormat1.get_len ();
|
|
const unsigned value_2_len = valueFormat2.get_len ();
|
|
const unsigned total_value_len = value_1_len + value_2_len;
|
|
|
|
unsigned accumulated = base_size;
|
|
hb_vector_t<unsigned> split_points;
|
|
|
|
hb_hashmap_t<void*, unsigned> device_tables = get_all_device_tables (c, this_index);
|
|
hb_vector_t<unsigned> format1_device_table_indices = valueFormat1.get_device_table_indices ();
|
|
hb_vector_t<unsigned> format2_device_table_indices = valueFormat2.get_device_table_indices ();
|
|
bool has_device_tables = bool(format1_device_table_indices) || bool(format2_device_table_indices);
|
|
|
|
hb_set_t visited;
|
|
for (unsigned i = 0; i < class1_count; i++)
|
|
{
|
|
accumulated += class1_record_size;
|
|
if (has_device_tables) {
|
|
for (unsigned j = 0; j < class2_count; j++)
|
|
{
|
|
unsigned value1_index = total_value_len * (class2_count * i + j);
|
|
unsigned value2_index = value1_index + value_1_len;
|
|
accumulated += size_of_value_record_children (c,
|
|
device_tables,
|
|
format1_device_table_indices,
|
|
value1_index,
|
|
visited);
|
|
accumulated += size_of_value_record_children (c,
|
|
device_tables,
|
|
format2_device_table_indices,
|
|
value2_index,
|
|
visited);
|
|
}
|
|
}
|
|
|
|
// TODO(garretrieger): don't count the size of the largest pairset against the limit, since
|
|
// it will be packed last in the order and does not contribute to
|
|
// the 64kb limit.
|
|
|
|
if (accumulated > (1 << 16))
|
|
{
|
|
split_points.push (i);
|
|
accumulated = base_size;
|
|
visited.clear (); // node sharing isn't allowed between splits.
|
|
}
|
|
}
|
|
|
|
split_context split_context {
|
|
c,
|
|
this_index,
|
|
class1_record_size,
|
|
total_value_len,
|
|
value_1_len,
|
|
value_2_len,
|
|
device_tables,
|
|
format1_device_table_indices,
|
|
format2_device_table_indices
|
|
};
|
|
|
|
return do_split (split_context, split_points);
|
|
}
|
|
private:
|
|
|
|
struct split_context
|
|
{
|
|
gsubgpos_graph_context_t& c;
|
|
unsigned this_index;
|
|
unsigned class1_record_size;
|
|
unsigned value_record_len;
|
|
unsigned value1_record_len;
|
|
unsigned value2_record_len;
|
|
|
|
const hb_hashmap_t<void*, unsigned>& device_tables;
|
|
const hb_vector_t<unsigned>& format1_device_table_indices;
|
|
const hb_vector_t<unsigned>& format2_device_table_indices;
|
|
};
|
|
|
|
hb_vector_t<unsigned> do_split (split_context& split_context,
|
|
const hb_vector_t<unsigned>& split_points)
|
|
{
|
|
// TODO(garretrieger): refactor into a common method shared between subtables.
|
|
// template on context which could provide the clone and shrink methods.
|
|
hb_vector_t<unsigned> new_objects;
|
|
if (!split_points)
|
|
return new_objects;
|
|
|
|
for (unsigned i = 0; i < split_points.length; i++)
|
|
{
|
|
unsigned start = split_points[i];
|
|
unsigned end = (i < split_points.length - 1) ? split_points[i + 1] : class1Count;
|
|
unsigned id = clone_range (split_context, start, end);
|
|
|
|
if (id == (unsigned) -1)
|
|
{
|
|
new_objects.reset ();
|
|
new_objects.allocated = -1; // mark error
|
|
return new_objects;
|
|
}
|
|
new_objects.push (id);
|
|
}
|
|
|
|
if (!shrink (split_context, split_points[0]))
|
|
{
|
|
new_objects.reset ();
|
|
new_objects.allocated = -1; // mark error
|
|
}
|
|
|
|
return new_objects;
|
|
}
|
|
|
|
unsigned clone_range (split_context& split_context,
|
|
unsigned start, unsigned end) const
|
|
{
|
|
DEBUG_MSG (SUBSET_REPACK, nullptr,
|
|
" Cloning PairPosFormat2 (%u) range [%u, %u).", split_context.this_index, start, end);
|
|
|
|
unsigned num_records = end - start;
|
|
unsigned prime_size = OT::Layout::GPOS_impl::PairPosFormat2_4<SmallTypes>::min_size
|
|
+ num_records * split_context.class1_record_size;
|
|
|
|
unsigned pair_pos_prime_id = split_context.c.create_node (prime_size);
|
|
if (pair_pos_prime_id == (unsigned) -1) return -1;
|
|
|
|
PairPosFormat2* pair_pos_prime =
|
|
(PairPosFormat2*) split_context.c.graph.object (pair_pos_prime_id).head;
|
|
pair_pos_prime->format = this->format;
|
|
pair_pos_prime->valueFormat1 = this->valueFormat1;
|
|
pair_pos_prime->valueFormat2 = this->valueFormat2;
|
|
pair_pos_prime->class1Count = num_records;
|
|
pair_pos_prime->class2Count = this->class2Count;
|
|
clone_class1_records (split_context,
|
|
pair_pos_prime_id,
|
|
start,
|
|
end);
|
|
|
|
unsigned coverage_id =
|
|
split_context.c.graph.index_for_offset (split_context.this_index, &coverage);
|
|
|
|
Coverage* new_coverage = Coverage::clone_coverage (split_context.c,
|
|
coverage_id,
|
|
pair_pos_prime_id,
|
|
2,
|
|
start, end);
|
|
if (!new_coverage)
|
|
return -1;
|
|
|
|
// classDef1
|
|
unsigned class_def_1_id =
|
|
split_context.c.graph.index_for_offset (split_context.this_index, &classDef1);
|
|
if (!ClassDef::clone_class_def (split_context.c,
|
|
class_def_1_id,
|
|
pair_pos_prime_id,
|
|
8,
|
|
new_coverage->iter ()))
|
|
return -1;
|
|
|
|
// classDef2
|
|
unsigned class_def_2_id =
|
|
split_context.c.graph.index_for_offset (split_context.this_index, &classDef2);
|
|
auto* class_def_link = split_context.c.graph.vertices_[pair_pos_prime_id].obj.real_links.push ();
|
|
class_def_link->width = SmallTypes::size;
|
|
class_def_link->objidx = class_def_2_id;
|
|
class_def_link->position = 10;
|
|
split_context.c.graph.vertices_[class_def_2_id].parents.push (pair_pos_prime_id);
|
|
split_context.c.graph.duplicate (pair_pos_prime_id, class_def_2_id);
|
|
|
|
return pair_pos_prime_id;
|
|
}
|
|
|
|
void clone_class1_records (split_context& split_context,
|
|
unsigned pair_pos_prime_id,
|
|
unsigned start, unsigned end) const
|
|
{
|
|
PairPosFormat2* pair_pos_prime =
|
|
(PairPosFormat2*) split_context.c.graph.object (pair_pos_prime_id).head;
|
|
|
|
char* start_addr = ((char*)&values[0]) + start * split_context.class1_record_size;
|
|
unsigned num_records = end - start;
|
|
memcpy (&pair_pos_prime->values[0],
|
|
start_addr,
|
|
num_records * split_context.class1_record_size);
|
|
|
|
if (!split_context.format1_device_table_indices
|
|
&& !split_context.format2_device_table_indices)
|
|
// No device tables to move over.
|
|
return;
|
|
|
|
unsigned class2_count = class2Count;
|
|
for (unsigned i = start; i < end; i++)
|
|
{
|
|
for (unsigned j = 0; j < class2_count; j++)
|
|
{
|
|
unsigned value1_index = split_context.value_record_len * (class2_count * i + j);
|
|
unsigned value2_index = value1_index + split_context.value1_record_len;
|
|
|
|
unsigned new_value1_index = split_context.value_record_len * (class2_count * (i - start) + j);
|
|
unsigned new_value2_index = new_value1_index + split_context.value1_record_len;
|
|
|
|
transfer_device_tables (split_context,
|
|
pair_pos_prime_id,
|
|
split_context.format1_device_table_indices,
|
|
value1_index,
|
|
new_value1_index);
|
|
|
|
transfer_device_tables (split_context,
|
|
pair_pos_prime_id,
|
|
split_context.format2_device_table_indices,
|
|
value2_index,
|
|
new_value2_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
void transfer_device_tables (split_context& split_context,
|
|
unsigned pair_pos_prime_id,
|
|
const hb_vector_t<unsigned>& device_table_indices,
|
|
unsigned old_value_record_index,
|
|
unsigned new_value_record_index) const
|
|
{
|
|
PairPosFormat2* pair_pos_prime =
|
|
(PairPosFormat2*) split_context.c.graph.object (pair_pos_prime_id).head;
|
|
|
|
for (unsigned i : device_table_indices)
|
|
{
|
|
OT::Offset16* record = (OT::Offset16*) &values[old_value_record_index + i];
|
|
if (!split_context.device_tables.has ((void*) record)) continue;
|
|
|
|
split_context.c.graph.move_child (
|
|
split_context.this_index,
|
|
record,
|
|
pair_pos_prime_id,
|
|
(OT::Offset16*) &pair_pos_prime->values[new_value_record_index + i]);
|
|
}
|
|
}
|
|
|
|
bool shrink (split_context& split_context,
|
|
unsigned count)
|
|
{
|
|
DEBUG_MSG (SUBSET_REPACK, nullptr,
|
|
" Shrinking PairPosFormat2 (%u) to [0, %u).",
|
|
split_context.this_index,
|
|
count);
|
|
unsigned old_count = class1Count;
|
|
if (count >= old_count)
|
|
return true;
|
|
|
|
class1Count = count;
|
|
split_context.c.graph.vertices_[split_context.this_index].obj.tail -=
|
|
(old_count - count) * split_context.class1_record_size;
|
|
|
|
unsigned coverage_id =
|
|
split_context.c.graph.index_for_offset (split_context.this_index, &coverage);
|
|
auto& coverage_v = split_context.c.graph.vertices_[coverage_id];
|
|
Coverage* coverage_table = (Coverage*) coverage_v.obj.head;
|
|
if (!coverage_table->sanitize (coverage_v))
|
|
return false;
|
|
|
|
auto new_coverage =
|
|
+ hb_zip (coverage_table->iter (), hb_range ())
|
|
| hb_filter ([&] (hb_pair_t<unsigned, unsigned> p) {
|
|
return p.second < count;
|
|
})
|
|
| hb_map_retains_sorting (hb_first)
|
|
;
|
|
|
|
if (!Coverage::make_coverage (split_context.c, new_coverage, coverage_id, coverage_v.table_size ()))
|
|
return false;
|
|
|
|
// classDef1
|
|
coverage_table = (Coverage*) coverage_v.obj.head; // get the new table
|
|
unsigned class_def_id = split_context.c.graph.index_for_offset (split_context.this_index,
|
|
&classDef1);
|
|
auto& class_def_v = split_context.c.graph.vertices_[class_def_id];
|
|
ClassDef* class_def_table = (ClassDef*) class_def_v.obj.head;
|
|
if (!class_def_table->sanitize (class_def_v))
|
|
return false;
|
|
|
|
auto new_class_def =
|
|
+ coverage_table->iter ()
|
|
| hb_map_retains_sorting ([&] (hb_codepoint_t gid) {
|
|
return hb_pair (gid, class_def_table->get_class (gid));
|
|
})
|
|
;
|
|
|
|
return ClassDef::make_class_def (split_context.c,
|
|
new_class_def,
|
|
class_def_id,
|
|
class_def_v.table_size ());
|
|
}
|
|
|
|
hb_hashmap_t<void*, unsigned>
|
|
get_all_device_tables (gsubgpos_graph_context_t& c,
|
|
unsigned this_index) const
|
|
{
|
|
hb_hashmap_t<void*, unsigned> result;
|
|
|
|
const auto& o = c.graph.object (this_index);
|
|
for (const auto& l : o.real_links) {
|
|
result.set ((void*) (((uint8_t*)this) + l.position), l.objidx);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
unsigned size_of_value_record_children (gsubgpos_graph_context_t& c,
|
|
const hb_hashmap_t<void*, unsigned>& device_tables,
|
|
const hb_vector_t<unsigned> device_table_indices,
|
|
unsigned value_record_index,
|
|
hb_set_t& visited)
|
|
{
|
|
unsigned size = 0;
|
|
for (unsigned i : device_table_indices)
|
|
{
|
|
OT::Layout::GPOS_impl::Value* record = &values[value_record_index + i];
|
|
unsigned* obj_idx;
|
|
if (!device_tables.has ((void*) record, &obj_idx)) continue;
|
|
size += c.graph.find_subgraph_size (*obj_idx, visited);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
unsigned size_of (gsubgpos_graph_context_t& c,
|
|
unsigned this_index,
|
|
const void* offset) const
|
|
{
|
|
const unsigned id = c.graph.index_for_offset (this_index, offset);
|
|
return c.graph.vertices_[id].table_size ();
|
|
}
|
|
};
|
|
|
|
struct PairPos : public OT::Layout::GPOS_impl::PairPos
|
|
{
|
|
hb_vector_t<unsigned> split_subtables (gsubgpos_graph_context_t& c, unsigned this_index)
|
|
{
|
|
switch (u.format) {
|
|
case 1:
|
|
return ((PairPosFormat1*)(&u.format1))->split_subtables (c, this_index);
|
|
case 2:
|
|
return ((PairPosFormat2*)(&u.format2))->split_subtables (c, this_index);
|
|
#ifndef HB_NO_BORING_EXPANSION
|
|
case 3: HB_FALLTHROUGH;
|
|
case 4: HB_FALLTHROUGH;
|
|
// Don't split 24bit PairPos's.
|
|
#endif
|
|
default:
|
|
return hb_vector_t<unsigned> ();
|
|
}
|
|
}
|
|
|
|
bool sanitize (graph_t::vertex_t& vertex) const
|
|
{
|
|
int64_t vertex_len = vertex.obj.tail - vertex.obj.head;
|
|
if (vertex_len < u.format.get_size ()) return false;
|
|
|
|
switch (u.format) {
|
|
case 1:
|
|
return ((PairPosFormat1*)(&u.format1))->sanitize (vertex);
|
|
case 2:
|
|
return ((PairPosFormat2*)(&u.format2))->sanitize (vertex);
|
|
#ifndef HB_NO_BORING_EXPANSION
|
|
case 3: HB_FALLTHROUGH;
|
|
case 4: HB_FALLTHROUGH;
|
|
#endif
|
|
default:
|
|
// We don't handle format 3 and 4 here.
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
#endif // GRAPH_PAIRPOS_GRAPH_HH
|