File indexing completed on 2024-07-14 07:17:23

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 }