File indexing completed on 2025-04-27 06:42:45
0001 /* 0002 SPDX-FileCopyrightText: 2007 James B. Bowlin <bowlin@mindspring.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "skylabeler.h" 0008 0009 #include <cstdio> 0010 0011 #include <QPainter> 0012 #include <QPixmap> 0013 0014 #include "Options.h" 0015 #include "kstarsdata.h" // MINZOOM 0016 #include "skymap.h" 0017 #include "projections/projector.h" 0018 0019 //---------------------------------------------------------------------------// 0020 // A Little data container class 0021 //---------------------------------------------------------------------------// 0022 0023 typedef struct LabelRun 0024 { 0025 LabelRun(int s, int e) : start(s), end(e) {} 0026 int start; 0027 int end; 0028 0029 } LabelRun; 0030 0031 //----- Now for the main event ----------------------------------------------// 0032 0033 //----- Static Methods ------------------------------------------------------// 0034 0035 SkyLabeler *SkyLabeler::pinstance = nullptr; 0036 0037 SkyLabeler *SkyLabeler::Instance() 0038 { 0039 if (!pinstance) 0040 pinstance = new SkyLabeler(); 0041 return pinstance; 0042 } 0043 0044 void SkyLabeler::setZoomFont() 0045 { 0046 #ifndef KSTARS_LITE 0047 QFont font(m_p.font()); 0048 #else 0049 QFont font(m_stdFont); 0050 #endif 0051 int deltaSize = 0; 0052 if (Options::zoomFactor() < 2.0 * MINZOOM) 0053 deltaSize = 2; 0054 else if (Options::zoomFactor() < 10.0 * MINZOOM) 0055 deltaSize = 1; 0056 0057 #ifndef KSTARS_LITE 0058 if (deltaSize) 0059 { 0060 font.setPointSize(font.pointSize() - deltaSize); 0061 m_p.setFont(font); 0062 } 0063 #else 0064 if (deltaSize) 0065 { 0066 font.setPointSize(font.pointSize() - deltaSize); 0067 } 0068 if (m_drawFont.pointSize() != font.pointSize()) 0069 { 0070 m_drawFont = font; 0071 } 0072 #endif 0073 } 0074 0075 double SkyLabeler::ZoomOffset() 0076 { 0077 double offset = dms::PI * Options::zoomFactor() / 10800.0 / 3600.0; 0078 return 4.0 + offset * 0.5; 0079 } 0080 0081 //----- Constructor ---------------------------------------------------------// 0082 0083 SkyLabeler::SkyLabeler() 0084 : m_fontMetrics(QFont()), m_picture(-1), labelList(NUM_LABEL_TYPES) 0085 { 0086 #ifdef KSTARS_LITE 0087 //Painter is needed to get default font and we use it only once to have only one warning 0088 m_stdFont = QFont(); 0089 0090 //For some reason there is no point size in default font on Android 0091 #ifdef ANDROID 0092 m_stdFont.setPointSize(16); 0093 #else 0094 m_stdFont.setPointSize(m_stdFont.pointSize()+2); 0095 #endif 0096 0097 #endif 0098 } 0099 0100 SkyLabeler::~SkyLabeler() 0101 { 0102 for (auto &row : screenRows) 0103 { 0104 for (auto &item : *row) 0105 { 0106 delete item; 0107 } 0108 delete row; 0109 } 0110 } 0111 0112 bool SkyLabeler::drawGuideLabel(QPointF &o, const QString &text, double angle) 0113 { 0114 // Create bounding rectangle by rotating the (height x width) rectangle 0115 qreal h = m_fontMetrics.height(); 0116 qreal w = m_fontMetrics.width(text); 0117 qreal s = sin(angle * dms::PI / 180.0); 0118 qreal c = cos(angle * dms::PI / 180.0); 0119 0120 qreal w2 = w / 2.0; 0121 0122 qreal top, bot, left, right; 0123 0124 // These numbers really do depend on the sign of the angle like this 0125 if (angle >= 0.0) 0126 { 0127 top = o.y() - s * w2; 0128 bot = o.y() + c * h + s * w2; 0129 left = o.x() - c * w2 - s * h; 0130 right = o.x() + c * w2; 0131 } 0132 else 0133 { 0134 top = o.y() + s * w2; 0135 bot = o.y() + c * h - s * w2; 0136 left = o.x() - c * w2; 0137 right = o.x() + c * w2 - s * h; 0138 } 0139 0140 // return false if label would overlap existing label 0141 if (!markRegion(left, right, top, bot)) 0142 return false; 0143 0144 // for debugging the bounding rectangle: 0145 //psky.drawLine( QPointF( left, top ), QPointF( right, top ) ); 0146 //psky.drawLine( QPointF( right, top ), QPointF( right, bot ) ); 0147 //psky.drawLine( QPointF( right, bot ), QPointF( left, bot ) ); 0148 //psky.drawLine( QPointF( left, bot ), QPointF( left, top ) ); 0149 0150 // otherwise draw the label and return true 0151 m_p.save(); 0152 m_p.translate(o); 0153 0154 m_p.rotate(angle); //rotate the coordinate system 0155 m_p.drawText(QPointF(-w2, h), text); 0156 m_p.restore(); //reset coordinate system 0157 0158 return true; 0159 } 0160 0161 bool SkyLabeler::drawNameLabel(SkyObject *obj, const QPointF &_p, 0162 const qreal padding_factor) 0163 { 0164 QString sLabel = obj->labelString(); 0165 if (sLabel.isEmpty()) 0166 return false; 0167 0168 double offset = obj->labelOffset(); 0169 QPointF p(_p.x() + offset, _p.y() + offset); 0170 0171 if (!markText(p, sLabel, padding_factor)) 0172 { 0173 return false; 0174 } 0175 else 0176 { 0177 double factor = log(Options::zoomFactor() / 750.0); 0178 double newPointSize = qBound(12.0, factor * m_stdFont.pointSizeF(), 18.0) * (1.0 + 0.7 * Options::labelFontScaling()/100.0); 0179 QFont zoomFont(m_p.font()); 0180 zoomFont.setPointSizeF(newPointSize); 0181 m_p.setFont(zoomFont); 0182 m_p.drawText(p, sLabel); 0183 return true; 0184 } 0185 } 0186 0187 void SkyLabeler::setFont(const QFont &font) 0188 { 0189 #ifndef KSTARS_LITE 0190 m_p.setFont(font); 0191 #else 0192 m_drawFont = font; 0193 #endif 0194 m_fontMetrics = QFontMetrics(font); 0195 } 0196 0197 void SkyLabeler::setPen(const QPen &pen) 0198 { 0199 #ifdef KSTARS_LITE 0200 Q_UNUSED(pen); 0201 #else 0202 m_p.setPen(pen); 0203 #endif 0204 } 0205 0206 void SkyLabeler::shrinkFont(int delta) 0207 { 0208 #ifndef KSTARS_LITE 0209 QFont font(m_p.font()); 0210 #else 0211 QFont font(m_drawFont); 0212 #endif 0213 font.setPointSize(font.pointSize() - delta); 0214 setFont(font); 0215 } 0216 0217 void SkyLabeler::useStdFont() 0218 { 0219 setFont(m_stdFont); 0220 } 0221 0222 void SkyLabeler::resetFont() 0223 { 0224 setFont(m_skyFont); 0225 } 0226 0227 void SkyLabeler::getMargins(const QString &text, float *left, float *right, float *top, float *bot) 0228 { 0229 float height = m_fontMetrics.height(); 0230 float width = m_fontMetrics.width(text); 0231 float sideMargin = m_fontMetrics.width("MM") + width / 2.0; 0232 0233 // Create the margins within which it is okay to draw the label 0234 double winHeight; 0235 double winWidth; 0236 #ifdef KSTARS_LITE 0237 winHeight = SkyMapLite::Instance()->height(); 0238 winWidth = SkyMapLite::Instance()->width(); 0239 #else 0240 winHeight = m_p.window().height(); 0241 winWidth = m_p.window().width(); 0242 #endif 0243 0244 *right = winWidth - sideMargin; 0245 *left = sideMargin; 0246 *top = height; 0247 *bot = winHeight - 2.0 * height; 0248 } 0249 0250 void SkyLabeler::reset(SkyMap *skyMap) 0251 { 0252 // ----- Set up Projector --- 0253 m_proj = skyMap->projector(); 0254 // ----- Set up Painter ----- 0255 if (m_p.isActive()) 0256 m_p.end(); 0257 m_picture = QPicture(); 0258 m_p.begin(&m_picture); 0259 //This works around BUG 10496 in Qt 0260 m_p.drawPoint(0, 0); 0261 m_p.drawPoint(skyMap->width() + 1, skyMap->height() + 1); 0262 // ----- Set up Zoom Dependent Font ----- 0263 0264 m_stdFont = QFont(m_p.font()); 0265 setZoomFont(); 0266 m_skyFont = m_p.font(); 0267 m_fontMetrics = QFontMetrics(m_skyFont); 0268 m_minDeltaX = (int)m_fontMetrics.width("MMMMM"); 0269 0270 // ----- Set up Zoom Dependent Offset ----- 0271 m_offset = SkyLabeler::ZoomOffset(); 0272 0273 // ----- Prepare Virtual Screen ----- 0274 m_yScale = (m_fontMetrics.height() + 1.0); 0275 0276 int maxY = int(skyMap->height() / m_yScale); 0277 if (maxY < 1) 0278 maxY = 1; // prevents a crash below? 0279 0280 int m_maxX = skyMap->width(); 0281 m_size = (maxY + 1) * m_maxX; 0282 0283 // Resize if needed: 0284 if (maxY > m_maxY) 0285 { 0286 screenRows.resize(m_maxY); 0287 for (int y = m_maxY; y <= maxY; y++) 0288 { 0289 screenRows.append(new LabelRow()); 0290 } 0291 //printf("resize: %d -> %d, size:%d\n", m_maxY, maxY, screenRows.size()); 0292 } 0293 0294 // Clear all pre-existing rows as needed 0295 0296 int minMaxY = (maxY < m_maxY) ? maxY : m_maxY; 0297 0298 for (int y = 0; y <= minMaxY; y++) 0299 { 0300 LabelRow *row = screenRows[y]; 0301 0302 for (auto &item : *row) 0303 { 0304 delete item; 0305 } 0306 row->clear(); 0307 } 0308 0309 // never decrease m_maxY: 0310 if (m_maxY < maxY) 0311 m_maxY = maxY; 0312 0313 // reset the counters 0314 m_marks = m_hits = m_misses = m_elements = 0; 0315 0316 //----- Clear out labelList ----- 0317 for (auto &item : labelList) 0318 { 0319 item.clear(); 0320 } 0321 } 0322 0323 #ifdef KSTARS_LITE 0324 void SkyLabeler::reset() 0325 { 0326 SkyMapLite *skyMap = SkyMapLite::Instance(); 0327 // ----- Set up Projector --- 0328 m_proj = skyMap->projector(); 0329 0330 //m_stdFont was moved to constructor 0331 setZoomFont(); 0332 m_skyFont = m_drawFont; 0333 m_fontMetrics = QFontMetrics(m_skyFont); 0334 m_minDeltaX = (int)m_fontMetrics.width("MMMMM"); 0335 // ----- Set up Zoom Dependent Offset ----- 0336 m_offset = ZoomOffset(); 0337 0338 // ----- Prepare Virtual Screen ----- 0339 m_yScale = (m_fontMetrics.height() + 1.0); 0340 0341 int maxY = int(skyMap->height() / m_yScale); 0342 if (maxY < 1) 0343 maxY = 1; // prevents a crash below? 0344 0345 int m_maxX = skyMap->width(); 0346 m_size = (maxY + 1) * m_maxX; 0347 0348 // Resize if needed: 0349 if (maxY > m_maxY) 0350 { 0351 screenRows.resize(m_maxY); 0352 for (int y = m_maxY; y <= maxY; y++) 0353 { 0354 screenRows.append(new LabelRow()); 0355 } 0356 //printf("resize: %d -> %d, size:%d\n", m_maxY, maxY, screenRows.size()); 0357 } 0358 0359 // Clear all pre-existing rows as needed 0360 0361 int minMaxY = (maxY < m_maxY) ? maxY : m_maxY; 0362 0363 for (int y = 0; y <= minMaxY; y++) 0364 { 0365 LabelRow *row = screenRows[y]; 0366 for (int i = 0; i < row->size(); i++) 0367 { 0368 delete row->at(i); 0369 } 0370 row->clear(); 0371 } 0372 0373 // never decrease m_maxY: 0374 if (m_maxY < maxY) 0375 m_maxY = maxY; 0376 0377 // reset the counters 0378 m_marks = m_hits = m_misses = m_elements = 0; 0379 0380 //----- Clear out labelList ----- 0381 for (int i = 0; i < labelList.size(); i++) 0382 { 0383 labelList[i].clear(); 0384 } 0385 } 0386 #endif 0387 0388 void SkyLabeler::draw(QPainter &p) 0389 { 0390 //FIXME: need a better soln. Apparently starting a painter 0391 //clears the picture. 0392 // But it's not like that's something that should be in the docs, right? 0393 // No, that's definitely better to leave to people to figure out on their own. 0394 if (m_p.isActive()) 0395 { 0396 m_p.end(); 0397 } 0398 m_picture.play(&p); //can't replay while it's being painted on 0399 //this is also undocumented btw. 0400 //m_p.begin(&m_picture); 0401 } 0402 0403 // We use Run Length Encoding to hold the information instead of an array of 0404 // chars. This is both faster and smaller but the code is more complicated. 0405 // 0406 // This code is easy to break and hard to fix. 0407 0408 bool SkyLabeler::markText(const QPointF &p, const QString &text, qreal padding_factor) 0409 { 0410 static const auto ramp_zoom = log10(MAXZOOM) + log10(0.3); 0411 0412 if (padding_factor != 1) 0413 { 0414 padding_factor = 0415 (1 - ((std::min(log10(Options::zoomFactor()), ramp_zoom)) / ramp_zoom)) * 0416 padding_factor + 0417 1; 0418 } 0419 0420 const qreal maxX = p.x() + m_fontMetrics.width(text) * padding_factor; 0421 const qreal minY = p.y() - m_fontMetrics.height() * padding_factor; 0422 return markRegion(p.x(), maxX, p.y(), minY); 0423 } 0424 0425 bool SkyLabeler::markRegion(qreal left, qreal right, qreal top, qreal bot) 0426 { 0427 if (m_maxY < 1) 0428 { 0429 if (!m_errors++) 0430 qDebug() << Q_FUNC_INFO << QString("Someone forgot to reset the SkyLabeler!"); 0431 return true; 0432 } 0433 0434 // setup x coordinates of rectangular region 0435 int minX = int(left); 0436 int maxX = int(right); 0437 if (maxX < minX) 0438 { 0439 maxX = minX; 0440 minX = int(right); 0441 } 0442 0443 // setup y coordinates 0444 int maxY = int(bot / m_yScale); 0445 int minY = int(top / m_yScale); 0446 0447 if (maxY < 0) 0448 maxY = 0; 0449 if (maxY > m_maxY) 0450 maxY = m_maxY; 0451 if (minY < 0) 0452 minY = 0; 0453 if (minY > m_maxY) 0454 minY = m_maxY; 0455 0456 if (maxY < minY) 0457 { 0458 int temp = maxY; 0459 maxY = minY; 0460 minY = temp; 0461 } 0462 0463 // check to see if we overlap any existing label 0464 // We must check all rows before we start marking 0465 for (int y = minY; y <= maxY; y++) 0466 { 0467 LabelRow *row = screenRows[y]; 0468 int i; 0469 for (i = 0; i < row->size(); i++) 0470 { 0471 if (row->at(i)->end < minX) 0472 continue; // skip past these 0473 if (row->at(i)->start > maxX) 0474 break; 0475 m_misses++; 0476 return false; 0477 } 0478 } 0479 0480 m_hits++; 0481 m_marks += (maxX - minX + 1) * (maxY - minY + 1); 0482 0483 // Okay, there was no overlap so let's insert the current rectangle into 0484 // screenRows. 0485 0486 for (int y = minY; y <= maxY; y++) 0487 { 0488 LabelRow *row = screenRows[y]; 0489 0490 // Simplest case: an empty row 0491 if (row->size() < 1) 0492 { 0493 row->append(new LabelRun(minX, maxX)); 0494 m_elements++; 0495 continue; 0496 } 0497 0498 // Find out our place in the universe (or row). 0499 // H'mm. Maybe we could cache these numbers above. 0500 int i; 0501 for (i = 0; i < row->size(); i++) 0502 { 0503 if (row->at(i)->end >= minX) 0504 break; 0505 } 0506 0507 // i now points to first label PAST ours 0508 0509 // if we are first, append or merge at start of list 0510 if (i == 0) 0511 { 0512 if (row->at(0)->start - maxX < m_minDeltaX) 0513 { 0514 row->at(0)->start = minX; 0515 } 0516 else 0517 { 0518 row->insert(0, new LabelRun(minX, maxX)); 0519 m_elements++; 0520 } 0521 continue; 0522 } 0523 0524 // if we are past the last label, merge or append at end 0525 else if (i == row->size()) 0526 { 0527 if (minX - row->at(i - 1)->end < m_minDeltaX) 0528 { 0529 row->at(i - 1)->end = maxX; 0530 } 0531 else 0532 { 0533 row->append(new LabelRun(minX, maxX)); 0534 m_elements++; 0535 } 0536 continue; 0537 } 0538 0539 // if we got here, we must insert or merge the new label 0540 // between [i-1] and [i] 0541 0542 bool mergeHead = (minX - row->at(i - 1)->end < m_minDeltaX); 0543 bool mergeTail = (row->at(i)->start - maxX < m_minDeltaX); 0544 0545 // double merge => combine all 3 into one 0546 if (mergeHead && mergeTail) 0547 { 0548 row->at(i - 1)->end = row->at(i)->end; 0549 delete row->at(i); 0550 row->removeAt(i); 0551 m_elements--; 0552 } 0553 0554 // Merge label with [i-1] 0555 else if (mergeHead) 0556 { 0557 row->at(i - 1)->end = maxX; 0558 } 0559 0560 // Merge label with [i] 0561 else if (mergeTail) 0562 { 0563 row->at(i)->start = minX; 0564 } 0565 0566 // insert between the two 0567 else 0568 { 0569 row->insert(i, new LabelRun(minX, maxX)); 0570 m_elements++; 0571 } 0572 } 0573 0574 return true; 0575 } 0576 0577 void SkyLabeler::addLabel(SkyObject *obj, SkyLabeler::label_t type) 0578 { 0579 bool visible = false; 0580 QPointF p = m_proj->toScreen(obj, true, &visible); 0581 if (!visible || !m_proj->onScreen(p) || obj->translatedName().isEmpty()) 0582 return; 0583 labelList[(int)type].append(SkyLabel(p, obj)); 0584 } 0585 0586 #ifdef KSTARS_LITE 0587 void SkyLabeler::addLabel(SkyObject *obj, QPointF pos, label_t type) 0588 { 0589 labelList[(int)type].append(SkyLabel(pos, obj)); 0590 } 0591 #endif 0592 0593 void SkyLabeler::drawQueuedLabels() 0594 { 0595 KStarsData *data = KStarsData::Instance(); 0596 0597 resetFont(); 0598 m_p.setPen(QColor(data->colorScheme()->colorNamed("PNameColor"))); 0599 drawQueuedLabelsType(PLANET_LABEL); 0600 0601 if (labelList[SATURN_MOON_LABEL].size() > 0) 0602 { 0603 shrinkFont(2); 0604 drawQueuedLabelsType(SATURN_MOON_LABEL); 0605 resetFont(); 0606 } 0607 0608 if (labelList[JUPITER_MOON_LABEL].size() > 0) 0609 { 0610 shrinkFont(2); 0611 drawQueuedLabelsType(JUPITER_MOON_LABEL); 0612 resetFont(); 0613 } 0614 0615 // No colors for these fellas? Just following planets along? 0616 drawQueuedLabelsType(ASTEROID_LABEL); 0617 drawQueuedLabelsType(COMET_LABEL); 0618 0619 m_p.setPen(QColor(data->colorScheme()->colorNamed("SatLabelColor"))); 0620 drawQueuedLabelsType(SATELLITE_LABEL); 0621 0622 // Whelp we're here and we don't have a Rude Label color? 0623 // Will just set it to Planet color since this is how it used to be!! 0624 m_p.setPen(QColor(data->colorScheme()->colorNamed("PNameColor"))); 0625 LabelList list = labelList[RUDE_LABEL]; 0626 0627 for (const auto &item : list) 0628 { 0629 drawRudeNameLabel(item.obj, item.o); 0630 } 0631 } 0632 0633 void SkyLabeler::drawQueuedLabelsType(SkyLabeler::label_t type) 0634 { 0635 LabelList list = labelList[type]; 0636 0637 for (const auto &item : list) 0638 { 0639 drawNameLabel(item.obj, item.o); 0640 } 0641 } 0642 0643 //Rude name labels don't check for collisions with other labels, 0644 //these get drawn no matter what. Transient labels are rude labels. 0645 //To mitigate confusion from possibly "underlapping" labels, paint a 0646 //semi-transparent background. 0647 void SkyLabeler::drawRudeNameLabel(SkyObject *obj, const QPointF &p) 0648 { 0649 QString sLabel = obj->labelString(); 0650 double offset = obj->labelOffset(); 0651 QRectF rect = m_p.fontMetrics().boundingRect(sLabel); 0652 rect.moveTo(p.x() + offset, p.y() + offset); 0653 0654 //Interestingly, the fontMetric boundingRect isn't where you might think... 0655 //We need to tweak rect to get the BG rectangle rect2 0656 QRectF rect2 = rect; 0657 rect2.moveTop(rect.top() - 0.6 * rect.height()); 0658 rect2.setHeight(0.8 * rect.height()); 0659 0660 //FIXME: Implement label background options 0661 QColor color(KStarsData::Instance()->colorScheme()->colorNamed("SkyColor")); 0662 color.setAlpha(m_p.pen().color().alpha()); //same transparency for the text and the background 0663 m_p.fillRect(rect2, QBrush(color)); 0664 m_p.drawText(rect.topLeft(), sLabel); 0665 } 0666 0667 //----- Diagnostic and information routines ----- 0668 0669 float SkyLabeler::fillRatio() 0670 { 0671 if (m_size == 0) 0672 return 0.0; 0673 return 100.0 * float(m_marks) / float(m_size); 0674 } 0675 0676 float SkyLabeler::hitRatio() 0677 { 0678 if (m_hits == 0) 0679 return 0.0; 0680 return 100.0 * float(m_hits) / (float(m_hits + m_misses)); 0681 } 0682 0683 void SkyLabeler::printInfo() 0684 { 0685 printf("SkyLabeler:\n"); 0686 printf(" fillRatio=%.1f%%\n", fillRatio()); 0687 printf(" hits=%d misses=%d ratio=%.1f%%\n", m_hits, m_misses, hitRatio()); 0688 printf(" yScale=%.1f maxY=%d\n", m_yScale, m_maxY); 0689 0690 printf(" screenRows=%d elements=%d virtualSize=%.1f Kbytes\n", screenRows.size(), m_elements, 0691 float(m_size) / 1024.0); 0692 0693 // static const char *labelName[NUM_LABEL_TYPES]; 0694 // 0695 // labelName[STAR_LABEL] = "Star"; 0696 // labelName[ASTEROID_LABEL] = "Asteroid"; 0697 // labelName[COMET_LABEL] = "Comet"; 0698 // labelName[PLANET_LABEL] = "Planet"; 0699 // labelName[JUPITER_MOON_LABEL] = "Jupiter Moon"; 0700 // labelName[SATURN_MOON_LABEL] = "Saturn Moon"; 0701 // labelName[DEEP_SKY_LABEL] = "Deep Sky Object"; 0702 // labelName[CONSTEL_NAME_LABEL] = "Constellation Name"; 0703 // 0704 // for (int i = 0; i < NUM_LABEL_TYPES; i++) 0705 // { 0706 // printf(" %20ss: %d\n", labelName[i], labelList[i].size()); 0707 // } 0708 // 0709 // // Check for errors in the data structure 0710 // for (int y = 0; y <= m_maxY; y++) 0711 // { 0712 // LabelRow *row = screenRows[y]; 0713 // int size = row->size(); 0714 // if (size < 2) 0715 // continue; 0716 // 0717 // bool error = false; 0718 // for (int i = 1; i < size; i++) 0719 // { 0720 // if (row->at(i - 1)->end > row->at(i)->start) 0721 // error = true; 0722 // } 0723 // if (!error) 0724 // continue; 0725 // 0726 // printf("ERROR: %3d: ", y); 0727 // for (int i = 0; i < row->size(); i++) 0728 // { 0729 // printf("(%d, %d) ", row->at(i)->start, row->at(i)->end); 0730 // } 0731 // printf("\n"); 0732 // } 0733 }