void StelQGLRenderer::drawTextGravityHelper (const TextParams& params, QPainter& painter, const int baseX, const int baseY, StelProjectorP projector) { const Vec2f viewportCenter = projector->getViewportCenterAbsolute(); const float dx = baseX - viewportCenter[0]; const float dy = baseY - viewportCenter[1]; const float d = std::sqrt(dx * dx + dy * dy); // Cull the text if it's too far away to be visible in the screen. if (d > qMax(projector->getViewportXywh()[3], projector->getViewportXywh()[2]) * 2) { return; } const QString string = params.string_; const int charCount = string.length(); const float charWidth = painter.fontMetrics().width(string) / charCount; float theta = std::atan2(dy - 1, dx); const float psi = qMin(5.0, std::atan2(charWidth, d + 1) * 180.0f / M_PI); const float xVc = viewportCenter[0] + params.xShift_; const float yVc = viewportCenter[1] + params.yShift_; const QString lang = StelApp::getInstance().getLocaleMgr().getAppLanguage(); const bool leftToRight = !QString("ar fa ur he yi ckb").contains(lang); // Draw each character separately for (int i = 0; i < charCount; ++i) { const int charIndex = leftToRight ? i : charCount - 1 - i; const float x = d * std::cos(theta) + xVc; const float y = d * std::sin(theta) + yVc; const float angle = 90.0f + theta * 180.0f / M_PI; drawText(TextParams(x, y, string[charIndex]).angleDegrees(angle)); // Compute how much the character contributes to the angle const float width = painter.fontMetrics().width(string[charIndex]); theta += psi * M_PI / 180.0 * (1 + (width - charWidth) / charWidth); } }
void StelQGLRenderer::drawText(const TextParams& params) { statistics[TEXT_DRAWS] += 1.0; StelQGLTextureBackend* currentTexture = currentlyBoundTextures[0]; if(params.string_.length() == 0) { return; } viewport.enablePainting(); if(currentFontSet) { viewport.setFont(currentFont); } QPainter* painter = viewport.getPainter(); Q_ASSERT_X(NULL != painter, Q_FUNC_INFO, "Trying to draw text but painting is disabled"); QFontMetrics fontMetrics = painter->fontMetrics(); StelProjectorP projector = NULL == params.projector_ ? StelApp::getInstance().getCore()->getProjection2d() : params.projector_; Vec3f win; if(params.doNotProject_) { win = params.position_; } else if(!projector->project(params.position_, win)) { viewport.disablePainting(); return; } const int x = win[0]; const int y = win[1]; // Avoid drawing if outside viewport. // We do a worst-case approximation as getting exact text dimensions is expensive. // We also account for rotation by assuming the worst case in bot X and Y // (culling with a rotating rectangle would be expensive) const int cullDistance = std::max(fontMetrics.height(), params.string_.size() * fontMetrics.maxWidth()); const Vec4i viewXywh = projector->getViewportXywh(); const int viewMinX = viewXywh[0]; const int viewMinY = viewXywh[1]; const int viewMaxX = viewMinX + viewXywh[2]; const int viewMaxY = viewMinY + viewXywh[3]; if(y + cullDistance < viewMinY || y - cullDistance > viewMaxY || x + cullDistance < viewMinX || x - cullDistance > viewMaxX) { viewport.disablePainting(); return; } if(projector->useGravityLabels() && !params.noGravity_) { drawTextGravityHelper(params, *painter, x, y, projector); return; } const int pixelSize = painter->font().pixelSize(); // Strings drawn by drawText() can differ by text, font size, or the font itself. const QByteArray hash = params.string_.toUtf8() + QByteArray::number(pixelSize) + painter->font().family().toUtf8(); StelQGLTextureBackend* textTexture = textTextureCache.object(hash); // No texture in cache for this string, need to draw it. if (NULL == textTexture) { const QRect extents = fontMetrics.boundingRect(params.string_); // Width and height of the text. // Texture width/height is required to be at least equal to this. // // Both X and Y need to be at least 1 so we don't create an empty image // (doesn't work with textures) const int requiredWidth = std::max(1, extents.width() + 1 + static_cast<int>(0.02f * extents.width())); const int requiredHeight = std::max(1, extents.height()); // Create temporary image and render text into it // QImage is used solely to reuse existing QGLTextureBackend constructor // function. QPixmap could be used as well (not sure which is faster, // needs profiling) QImage image = areNonPowerOfTwoTexturesSupported() ? QImage(requiredWidth, requiredHeight, QImage::Format_ARGB32_Premultiplied) : QImage(StelUtils::smallestPowerOfTwoGreaterOrEqualTo(requiredWidth), StelUtils::smallestPowerOfTwoGreaterOrEqualTo(requiredHeight), QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter fontPainter(&image); fontPainter.setFont(painter->font()); fontPainter.setRenderHints(QPainter::TextAntialiasing, true); fontPainter.setPen(Qt::white); // The second argument ensures the text is positioned correctly even if // the image is enlarged to power-of-two. fontPainter.drawText(-extents.x(), image.height() - requiredHeight - extents.y(), params.string_); textTexture = StelQGLTextureBackend::constructFromImage (this, QString(), TextureParams().filtering(TextureFiltering_Linear), image); const QSize size = textTexture->getDimensions(); if(!textTexture->getStatus() == TextureStatus_Loaded) { qWarning() << "Texture error: " << textTexture->getErrorMessage(); Q_ASSERT_X(false, Q_FUNC_INFO, "Failed to construct a text texture"); } textTextureCache.insert(hash, textTexture, 4 * size.width() * size.height()); } // Even if NPOT textures are not supported, we always draw the full rectangle // of the texture. The extra space is fully transparent, so it's not an issue. // Shortcut variables to calculate the rectangle. const QSize size = textTexture->getDimensions(); const float w = size.width(); const float h = size.height(); const float xShift = params.xShift_; const float yShift = params.yShift_; const float angleDegrees = params.angleDegrees_ + (params.noGravity_ ? 0.0f : projector->getDefaultAngleForGravityText()); // Zero out very small angles. // // (this could also be used to optimize the case with zero angled // to avoid sin/cos if needed) const bool angled = std::fabs(angleDegrees) >= 1.0f * M_PI / 180.f; const float cosr = angled ? std::cos(angleDegrees * M_PI / 180.0) : 1.0f; const float sinr = angled ? std::sin(angleDegrees * M_PI / 180.0) : 0.0f; // Corners of the (possibly rotated) texture rectangle. const Vec2f ne(round(x + cosr * xShift - sinr * yShift), round(y + sinr * xShift + cosr * yShift)); const Vec2f nw(round(x + cosr * (w + xShift) - sinr * yShift), round(y + sinr * (w + xShift) + cosr * yShift)); const Vec2f se(round(x + cosr * xShift - sinr * (h + yShift)), round(y + sinr * xShift + cosr * (h + yShift))); const Vec2f sw(round(x + cosr * (w + xShift) - sinr * (h + yShift)), round(y + sinr * (w + xShift) + cosr * (h + yShift))); // Construct the text vertex buffer if it doesn't exist yet, otherwise clear it. if(NULL == textBuffer) { textBuffer = createVertexBuffer<TexturedVertex>(PrimitiveType_TriangleStrip); } else { textBuffer->unlock(); textBuffer->clear(); } textBuffer->addVertex(TexturedVertex(ne, Vec2f(0.0f, 0.0f))); textBuffer->addVertex(TexturedVertex(nw, Vec2f(1.0f, 0.0f))); textBuffer->addVertex(TexturedVertex(se, Vec2f(0.0f, 1.0f))); textBuffer->addVertex(TexturedVertex(sw, Vec2f(1.0f, 1.0f))); textBuffer->lock(); // Draw. const BlendMode oldBlendMode = blendMode; setBlendMode(BlendMode_Alpha); textTexture->bind(0); drawVertexBuffer(textBuffer); setBlendMode(oldBlendMode); // Reset user-bound texture. if(NULL != currentTexture) { currentTexture->bind(0); } viewport.disablePainting(); }