30 #include "config_auto.h" 
   45 static BOOL_VAR(equationdetect_save_bi_image, 
false, 
"Save input bi image");
 
   46 static BOOL_VAR(equationdetect_save_spt_image, 
false, 
"Save special character image");
 
   47 static BOOL_VAR(equationdetect_save_seed_image, 
false, 
"Save the seed image");
 
   48 static BOOL_VAR(equationdetect_save_merged_image, 
false, 
"Save the merged image");
 
   55 static int SortCPByTopReverse(
const void* p1, 
const void* p2) {
 
   56   const ColPartition* cp1 = *static_cast<ColPartition* const*>(p1);
 
   57   const ColPartition* cp2 = *static_cast<ColPartition* const*>(p2);
 
   59   const TBOX &box1(cp1->bounding_box()), &box2(cp2->bounding_box());
 
   60   return box2.
top() - box1.top();
 
   63 static int SortCPByBottom(
const void* p1, 
const void* p2) {
 
   64   const ColPartition* cp1 = *static_cast<ColPartition* const*>(p1);
 
   65   const ColPartition* cp2 = *static_cast<ColPartition* const*>(p2);
 
   67   const TBOX &box1(cp1->bounding_box()), &box2(cp2->bounding_box());
 
   68   return box1.
bottom() - box2.bottom();
 
   71 static int SortCPByHeight(
const void* p1, 
const void* p2) {
 
   72   const ColPartition* cp1 = *static_cast<ColPartition* const*>(p1);
 
   73   const ColPartition* cp2 = *static_cast<ColPartition* const*>(p2);
 
   75   const TBOX &box1(cp1->bounding_box()), &box2(cp2->bounding_box());
 
   76   return box1.
height() - box2.height();
 
  103                                const char* equ_name) {
 
  104   const char* default_name = 
"equ";
 
  105   if (equ_name == 
nullptr) {
 
  106     equ_name = default_name;
 
  114     tprintf(
"Warning: equation region detection requested," 
  115             " but %s failed to load from %s\n", equ_name, equ_datapath);
 
  132   if (to_block == 
nullptr) {
 
  133     tprintf(
"Warning: input to_block is nullptr!\n");
 
  140   for (
int i = 0; i < blob_lists.
size(); ++i) {
 
  141     BLOBNBOX_IT bbox_it(blob_lists[i]);
 
  142     for (bbox_it.mark_cycle_pt (); !bbox_it.cycled_list();
 
  144       bbox_it.data()->set_special_text_type(
BSTT_NONE);
 
  152     BLOBNBOX *blobnbox, 
const int height_th) {
 
  160   BLOB_CHOICE_LIST ratings_equ, ratings_lang;
 
  170   const float x_orig = (box.
left() + box.
right()) / 2.0f, y_orig = box.
bottom();
 
  171   std::unique_ptr<TBLOB> normed_blob(
new TBLOB(*tblob));
 
  172   normed_blob->
Normalize(
nullptr, 
nullptr, 
nullptr, x_orig, y_orig, scaling, scaling,
 
  182   BLOB_CHOICE *lang_choice = 
nullptr, *equ_choice = 
nullptr;
 
  183   if (ratings_lang.length() > 0) {
 
  184     BLOB_CHOICE_IT choice_it(&ratings_lang);
 
  185     lang_choice = choice_it.data();
 
  187   if (ratings_equ.length() > 0) {
 
  188     BLOB_CHOICE_IT choice_it(&ratings_equ);
 
  189     equ_choice = choice_it.data();
 
  192   const float lang_score = lang_choice ? lang_choice->
certainty() : -FLT_MAX;
 
  193   const float equ_score = equ_choice ? equ_choice->certainty() : -FLT_MAX;
 
  195   const float kConfScoreTh = -5.0f, kConfDiffTh = 1.8;
 
  198   const float diff = fabs(lang_score - equ_score);
 
  202   if (fmax(lang_score, equ_score) < kConfScoreTh) {
 
  205   } 
else if (diff > kConfDiffTh && equ_score > lang_score) {
 
  209   } 
else if (lang_choice) {
 
  234     if (ids_to_exclude.
empty()) {
 
  235       static const STRING kCharsToEx[] = {
"'", 
"`", 
"\"", 
"\\", 
",", 
".",
 
  236           "〈", 
"〉", 
"《", 
"》", 
"」", 
"「", 
""};
 
  238       while (kCharsToEx[i] != 
"") {
 
  242       ids_to_exclude.
sort();
 
  249   static const STRING kDigitsChars = 
"|";
 
  265   const int classify_integer_matcher =
 
  278     BLOBNBOX_C_IT bbox_it(part->
boxes());
 
  281     for (bbox_it.mark_cycle_pt (); !bbox_it.cycled_list();
 
  283       if (bbox_it.data()->special_text_type() != 
BSTT_SKIP) {
 
  284         blob_heights.
push_back(bbox_it.data()->bounding_box().height());
 
  288     const int height_th =  blob_heights[blob_heights.size() / 2] / 3 * 2;
 
  289     for (bbox_it.mark_cycle_pt (); !bbox_it.cycled_list();
 
  291       if (bbox_it.data()->special_text_type() != 
BSTT_SKIP) {
 
  299       classify_class_pruner);
 
  301       classify_integer_matcher);
 
  303   if (equationdetect_save_spt_image) {  
 
  312   BLOBNBOX_C_IT blob_it(part->
boxes());
 
  314   for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
 
  318   for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
 
  327     BLOBNBOX_C_IT blob_it2 = blob_it;
 
  329     while (!blob_it2.at_last()) {
 
  330       BLOBNBOX* nextblob = blob_it2.forward();
 
  332       if (nextblob_box.
left() >= blob_box.
right()) {
 
  335       const float kWidthR = 0.4, kHeightR = 0.3;
 
  337           yoverlap = blob_box.
y_overlap(nextblob_box);
 
  338       const float widthR = static_cast<float>(
 
  339           std::min(nextblob_box.
width(), blob_box.
width())) /
 
  340           std::max(nextblob_box.
width(), blob_box.
width());
 
  341       const float heightR = static_cast<float>(
 
  345       if (xoverlap && yoverlap && widthR > kWidthR && heightR > kHeightR) {
 
  349         blob_box += nextblob_box;
 
  361     tprintf(
"Warning: lang_tesseract_ is nullptr!\n");
 
  364   if (!part_grid || !best_columns) {
 
  365     tprintf(
"part_grid/best_columns is nullptr!!\n");
 
  375   if (equationdetect_save_bi_image) {
 
  391   if (equationdetect_save_seed_image) {
 
  399     for (
int i = 0; i < 
cp_seeds_.size(); ++i) {
 
  407     for (
int i = 0; i < seeds_expanded.
size(); ++i) {
 
  416   if (equationdetect_save_merged_image) {  
 
  437       if (parts_to_merge.
empty()) {
 
  443       for (
int i = 0; i < parts_to_merge.
size(); ++i) {
 
  444         ASSERT_HOST(parts_to_merge[i] != 
nullptr && parts_to_merge[i] != part);
 
  445         part->
Absorb(parts_to_merge[i], 
nullptr);
 
  452     if (parts_updated.
empty()) {  
 
  457     for (
int i = 0; i < parts_updated.
size(); ++i) {
 
  466   ASSERT_HOST(seed != 
nullptr && parts_overlap != 
nullptr);
 
  472   const int kRadNeighborCells = 30;
 
  473   search.StartRadSearch((seed_box.left() + seed_box.right()) / 2,
 
  474                         (seed_box.top() + seed_box.bottom()) / 2,
 
  476   search.SetUniqueMode(
true);
 
  481   const float kLargeOverlapTh = 0.95;
 
  482   const float kEquXOverlap = 0.4, kEquYOverlap = 0.5;
 
  483   while ((part = 
search.NextRadSearch()) != 
nullptr) {
 
  491         y_overlap_fraction = part_box.y_overlap_fraction(seed_box);
 
  494     if (x_overlap_fraction >= kLargeOverlapTh &&
 
  495         y_overlap_fraction >= kLargeOverlapTh) {
 
  499       if ((x_overlap_fraction > kEquXOverlap && y_overlap_fraction > 0.0) ||
 
  500           (x_overlap_fraction > 0.0 && y_overlap_fraction > kEquYOverlap)) {
 
  526       part_box.left(), part_box.bottom(), &grid_x, &grid_y);
 
  555     const int kTextBlobsTh = 20;
 
  580   indented_texts_left.
sort();
 
  581   texts_foreground_density.
sort();
 
  582   float foreground_density_th = 0.15;  
 
  583   if (!texts_foreground_density.
empty()) {
 
  585     foreground_density_th = 0.8 * texts_foreground_density[
 
  586         texts_foreground_density.
size() / 2];
 
  589   for (
int i = 0; i < seeds1.
size(); ++i) {
 
  590     const TBOX& box = seeds1[i]->bounding_box();
 
  603   for (
int i = 0; i < seeds2.
size(); ++i) {
 
  604     if (
CheckForSeed2(indented_texts_left, foreground_density_th, seeds2[i])) {
 
  613   const int pix_height = pixGetHeight(pix_bi);
 
  614   Box* box = boxCreate(tbox.
left(), pix_height - tbox.
top(),
 
  616   Pix *pix_sub = pixClipRectangle(pix_bi, box, 
nullptr);
 
  618   pixForegroundFraction(pix_sub, &fract);
 
  619   pixDestroy(&pix_sub);
 
  632   float parts_passed = 0.0;
 
  633   for (
int i = 0; i < sub_boxes.
size(); ++i) {
 
  635     if (density < density_th) {
 
  641   const float kSeedPartRatioTh = 0.3;
 
  642   bool retval = (parts_passed / sub_boxes.
size() >= kSeedPartRatioTh);
 
  657   parts_splitted->
clear();
 
  660   bool found_split = 
true;
 
  661   while (found_split) {
 
  663     BLOBNBOX_C_IT box_it(right_part->
boxes());
 
  668     int previous_right = INT32_MIN;
 
  671     for (box_it.mark_cycle_pt(); !box_it.cycled_list(); box_it.forward()) {
 
  672       const TBOX& box = box_it.data()->bounding_box();
 
  673       if (previous_right != INT32_MIN &&
 
  674           box.
left() - previous_right > kThreshold) {
 
  677         const int mid_x = (box.
left() + previous_right) / 2;
 
  679         right_part = left_part->
SplitAt(mid_x);
 
  688       previous_right = std::max(previous_right, static_cast<int>(box.
right()));
 
  700   splitted_boxes->
clear();
 
  712   int previous_right = INT32_MIN;
 
  713   BLOBNBOX_C_IT box_it(part->
boxes());
 
  714   for (box_it.mark_cycle_pt(); !box_it.cycled_list(); box_it.forward()) {
 
  715     const TBOX& box = box_it.data()->bounding_box();
 
  716     if (previous_right != INT32_MIN &&
 
  717         box.
left() - previous_right > kThreshold) {
 
  720       previous_right = INT32_MIN;
 
  722     if (previous_right == INT32_MIN) {
 
  728     previous_right = std::max(previous_right, static_cast<int>(box.
right()));
 
  732   if (previous_right != INT32_MIN) {
 
  739     const float foreground_density_th,
 
  745   if (!indented_texts_left.
empty() &&
 
  761   if (sorted_vec.
empty()) {
 
  764   const int kDistTh = static_cast<int>(roundf(0.03 * 
resolution_));
 
  770   while (index >= 0 && abs(val - sorted_vec[index--]) < kDistTh) {
 
  776   while (index < sorted_vec.
size() && sorted_vec[index++] - val < kDistTh) {
 
  807   const int kGapTh = static_cast<int>(roundf(
 
  810   search.SetUniqueMode(
true);
 
  813   for (
int i = 0; i < 
cp_seeds_.size(); ++i) {
 
  819     if (left_margin + kMarginDiffTh < right_margin &&
 
  820         left_margin < kMarginDiffTh) {
 
  823           part_box.right(), part_box.top(), part_box.bottom());
 
  824       right_to_left = 
false;
 
  825     } 
else if (left_margin > cps_cx) {
 
  829           part_box.left(), part_box.top(), part_box.bottom());
 
  830       right_to_left = 
true;
 
  836     bool side_neighbor_found = 
false;
 
  837     while ((neighbor = 
search.NextSideSearch(right_to_left)) != 
nullptr) {
 
  840           part_box.x_gap(neighbor_box) > kGapTh ||
 
  841           !part_box.major_y_overlap(neighbor_box) ||
 
  842           part_box.major_x_overlap(neighbor_box)) {
 
  846       side_neighbor_found = 
true;
 
  849     if (!side_neighbor_found) {  
 
  854       if (neighbor_box.width() > part_box.width() &&
 
  878     if (prev != 
nullptr) {
 
  880       const TBOX &prev_box = prev->bounding_box();
 
  884         int gap = current_box.
y_gap(prev_box);
 
  885         if (gap < std::min(current_box.
height(), prev_box.
height())) {
 
  894   if (ygaps.
size() < 8) {  
 
  900   int spacing = 0, 
count;
 
  902     spacing += ygaps[
count];
 
  904   return spacing / 
count;
 
  908     const bool top_to_bottom, 
const int textparts_linespacing) {
 
  921   for (
int i = 0; i < 
cp_seeds_.size(); ++i) {
 
  927     if (
IsInline(!top_to_bottom, textparts_linespacing, part)) {
 
  937                               const int textparts_linespacing,
 
  945   const float kYGapRatioTh = 1.0;
 
  948     search.StartVerticalSearch(part_box.left(), part_box.right(),
 
  951     search.StartVerticalSearch(part_box.left(), part_box.right(),
 
  954   search.SetUniqueMode(
true);
 
  955   while ((neighbor = 
search.NextVerticalSearch(search_bottom)) != 
nullptr) {
 
  957     if (part_box.y_gap(neighbor_box) > kYGapRatioTh *
 
  958              std::min(part_box.height(), neighbor_box.height())) {
 
  967     const float kHeightRatioTh = 0.5;
 
  968     const int kYGapTh = textparts_linespacing > 0 ?
 
  969         textparts_linespacing + static_cast<int>(roundf(0.02 * 
resolution_)):
 
  971     if (part_box.x_overlap(neighbor_box) &&  
 
  972         part_box.y_gap(neighbor_box) <= kYGapTh &&  
 
  974         static_cast<float>(std::min(part_box.height(), neighbor_box.height())) /
 
  975         std::max(part_box.height(), neighbor_box.height()) > kHeightRatioTh) {
 
  987   const int kSeedMathBlobsCount = 2;
 
  988   const int kSeedMathDigitBlobsCount = 5;
 
  994       math_blobs + digit_blobs <= kSeedMathDigitBlobsCount) {
 
 1002     const float math_density_high,
 
 1003     const float math_density_low,
 
 1009   if (math_digit_density > math_density_high) {
 
 1013       math_digit_density > math_density_low) {
 
 1026   const int kXGapTh = static_cast<int>(roundf(0.5 * 
resolution_));
 
 1027   const int kRadiusTh = static_cast<int>(roundf(3.0 * 
resolution_));
 
 1028   const int kYGapTh = static_cast<int>(roundf(0.5 * 
resolution_));
 
 1033   search.StartRadSearch((part_box.left() + part_box.right()) / 2,
 
 1034       (part_box.top() + part_box.bottom()) / 2, kRadiusTh);
 
 1035   search.SetUniqueMode(
true);
 
 1036   bool left_indented = 
false, right_indented = 
false;
 
 1037   while ((neighbor = 
search.NextRadSearch()) != 
nullptr &&
 
 1038          (!left_indented || !right_indented)) {
 
 1039     if (neighbor == part) {
 
 1044     if (part_box.major_y_overlap(neighbor_box) &&
 
 1045         part_box.x_gap(neighbor_box) < kXGapTh) {
 
 1056     if (!part_box.x_overlap(neighbor_box) || part_box.y_overlap(neighbor_box)) {
 
 1060     if (part_box.y_gap(neighbor_box) < kYGapTh) {
 
 1061       const int left_gap = part_box.left() - neighbor_box.left();
 
 1062       const int right_gap = neighbor_box.right() - part_box.right();
 
 1063       if (left_gap > kXGapTh) {
 
 1064         left_indented = 
true;
 
 1066       if (right_gap > kXGapTh) {
 
 1067         right_indented = 
true;
 
 1072   if (left_indented && right_indented) {
 
 1075   if (left_indented) {
 
 1078   if (right_indented) {
 
 1085   if (seed == 
nullptr ||  
 
 1098   if (parts_to_merge.
empty()) {  
 
 1106   for (
int i = 0; i < parts_to_merge.
size(); ++i) {
 
 1111       for (
int j = 0; j < 
cp_seeds_.size(); ++j) {
 
 1121     seed->
Absorb(part, 
nullptr);
 
 1128     const bool search_left,
 
 1131   ASSERT_HOST(seed != 
nullptr && parts_to_merge != 
nullptr);
 
 1132   const float kYOverlapTh = 0.6;
 
 1133   const int kXGapTh = static_cast<int>(roundf(0.2 * 
resolution_));
 
 1137   const int x = search_left ? seed_box.
left() : seed_box.right();
 
 1138   search.StartSideSearch(x, seed_box.bottom(), seed_box.top());
 
 1139   search.SetUniqueMode(
true);
 
 1143   while ((part = 
search.NextSideSearch(search_left)) != 
nullptr) {
 
 1148     if (part_box.x_gap(seed_box) > kXGapTh) {  
 
 1153     if ((part_box.left() >= seed_box.left() && search_left) ||
 
 1154         (part_box.right() <= seed_box.right() && !search_left)) {
 
 1171       if (part_box.y_overlap_fraction(seed_box) < kYOverlapTh &&
 
 1172           seed_box.y_overlap_fraction(part_box) < kYOverlapTh) {
 
 1184     const bool search_bottom,
 
 1187   ASSERT_HOST(seed != 
nullptr && parts_to_merge != 
nullptr &&
 
 1189   const float kXOverlapTh = 0.4;
 
 1190   const int kYGapTh = static_cast<int>(roundf(0.2 * 
resolution_));
 
 1194   const int y = search_bottom ? seed_box.
bottom() : seed_box.top();
 
 1195   search.StartVerticalSearch(
 
 1197   search.SetUniqueMode(
true);
 
 1202   int skipped_min_top = std::numeric_limits<int>::max(), skipped_max_bottom = -1;
 
 1203   while ((part = 
search.NextVerticalSearch(search_bottom)) != 
nullptr) {
 
 1209     if (part_box.y_gap(seed_box) > kYGapTh) {  
 
 1214     if ((part_box.bottom() >= seed_box.bottom() && search_bottom) ||
 
 1215         (part_box.top() <= seed_box.top() && !search_bottom)) {
 
 1219     bool skip_part = 
false;
 
 1232       if (part_box.x_overlap_fraction(seed_box) < kXOverlapTh &&
 
 1233           seed_box.x_overlap_fraction(part_box) < kXOverlapTh) {
 
 1239         if (skipped_min_top > part_box.top()) {
 
 1240           skipped_min_top = part_box.
top();
 
 1242         if (skipped_max_bottom < part_box.bottom()) {
 
 1243           skipped_max_bottom = part_box.bottom();
 
 1258   for (
int i = 0; i < parts.
size(); i++) {
 
 1259     const TBOX& part_box(parts[i]->bounding_box());
 
 1260     if ((search_bottom && part_box.
top() <= skipped_max_bottom) ||
 
 1261         (!search_bottom && part_box.
bottom() >= skipped_min_top)) {
 
 1271                                          const TBOX& part_box)
 const {
 
 1272   const int kXGapTh = static_cast<int>(roundf(0.25 * 
resolution_));
 
 1273   const int kYGapTh = static_cast<int>(roundf(0.05 * 
resolution_));
 
 1283        part_box.
y_gap(seed_box) > kYGapTh) &&
 
 1285        part_box.
x_gap(seed_box) > kXGapTh)) {
 
 1321   if (text_parts.
empty()) {
 
 1326   text_parts.
sort(&SortCPByHeight);
 
 1327   const TBOX& text_box = text_parts[text_parts.
size() / 2]->bounding_box();
 
 1328   int med_height = text_box.
height();
 
 1329   if (text_parts.
size() % 2 == 0 && text_parts.
size() > 1) {
 
 1330     const TBOX& text_box =
 
 1331         text_parts[text_parts.
size() / 2 - 1]->bounding_box();
 
 1332     med_height = static_cast<int>(roundf(
 
 1333         0.5 * (text_box.
height() + med_height)));
 
 1337   for (
int i = 0; i < text_parts.
size(); ++i) {
 
 1338     const TBOX& text_box(text_parts[i]->bounding_box());
 
 1339     if (text_box.
height() > med_height) {
 
 1350     for (
int j = 0; j < math_blocks.
size(); ++j) {
 
 1352       text_parts[i]->Absorb(math_blocks[j], 
nullptr);
 
 1360   ASSERT_HOST(part != 
nullptr && math_blocks != 
nullptr);
 
 1361   math_blocks->
clear();
 
 1365   int y_gaps[2] = {std::numeric_limits<int>::max(), std::numeric_limits<int>::max()};
 
 1367   int neighbors_left = std::numeric_limits<int>::max(), neighbors_right = 0;
 
 1368   for (
int i = 0; i < 2; ++i) {
 
 1371       const TBOX& neighbor_box = neighbors[i]->bounding_box();
 
 1372       y_gaps[i] = neighbor_box.
y_gap(part_box);
 
 1373       if (neighbor_box.
left() < neighbors_left) {
 
 1374         neighbors_left = neighbor_box.
left();
 
 1376       if (neighbor_box.
right() > neighbors_right) {
 
 1377         neighbors_right = neighbor_box.
right();
 
 1381   if (neighbors[0] == neighbors[1]) {
 
 1383     neighbors[1] = 
nullptr;
 
 1384     y_gaps[1] = std::numeric_limits<int>::max();
 
 1388   if (part_box.left() < neighbors_left || part_box.right() > neighbors_right) {
 
 1393   int index = y_gaps[0] < y_gaps[1] ? 0 : 1;
 
 1397     math_blocks->
push_back(neighbors[index]);
 
 1406     math_blocks->
push_back(neighbors[index]);
 
 1415   ColPartition *nearest_neighbor = 
nullptr, *neighbor = 
nullptr;
 
 1416   const int kYGapTh = static_cast<int>(roundf(
resolution_ * 0.5));
 
 1419   search.SetUniqueMode(
true);
 
 1421   int y = search_bottom ? part_box.
bottom() : part_box.top();
 
 1422   search.StartVerticalSearch(part_box.left(), part_box.right(), y);
 
 1423   int min_y_gap = std::numeric_limits<int>::max();
 
 1424   while ((neighbor = 
search.NextVerticalSearch(search_bottom)) != 
nullptr) {
 
 1428     const TBOX& neighbor_box(neighbor->bounding_box());
 
 1429     int y_gap = neighbor_box.
y_gap(part_box);
 
 1430     if (y_gap > kYGapTh) {  
 
 1433     if (!neighbor_box.major_x_overlap(part_box) ||
 
 1434         (search_bottom && neighbor_box.bottom() > part_box.bottom()) ||
 
 1435         (!search_bottom && neighbor_box.top() < part_box.top())) {
 
 1438     if (y_gap < min_y_gap) {
 
 1440       nearest_neighbor = neighbor;
 
 1444   return nearest_neighbor;
 
 1452   const int kYGapTh = static_cast<int>(roundf(
resolution_ * 0.1));
 
 1457                                        STRING* image_name)
 const {
 
 1460   snprintf(page, 
sizeof(page), 
"%04d", 
page_count_);
 
 1466   pix = pixConvertTo32(pixBi);
 
 1471     BLOBNBOX_C_IT blob_it(part->
boxes());
 
 1472     for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
 
 1477   pixWrite(outfile.
c_str(), pix, IFF_TIFF_LZW);
 
 1484   gsearch.StartFullSearch();
 
 1486   while ((part = gsearch.NextFullSearch()) != 
nullptr) {
 
 1488     Box *box = boxCreate(tbox.
left(), pixGetHeight(pix) - tbox.
top(),
 
 1491       pixRenderBoxArb(pix, box, 5, 255, 0, 0);
 
 1493       pixRenderBoxArb(pix, box, 5, 0, 255, 0);
 
 1495       pixRenderBoxArb(pix, box, 5, 0, 0, 255);
 
 1500   pixWrite(outfile.
c_str(), pix, IFF_TIFF_LZW);
 
 1508   tprintf(
"Printing special blobs density values for ColParition (t=%d,b=%d) ",
 
 1509           h - box.top(), h - box.bottom());
 
 1513     auto type = static_cast<BlobSpecialTextType>(i);