void LineNumberWidget::paintEvent(QPaintEvent *){ int contentsY = editor->verticalScrollBar()->value(); qreal pageBottom = contentsY + editor->viewport()->height(); int lineNumber = 1; const QFontMetrics fm = fontMetrics(); const int ascent = fontMetrics().ascent() + 1; // height = ascent + descent + 1 QPainter p(this); for (QTextBlock block = editor->document()->begin();block.isValid();block = block.next(), ++lineNumber) { QTextLayout *layout = block.layout(); const QRectF boundingRect = layout->boundingRect(); QPointF position = layout->position(); if (position.y() + boundingRect.height() < contentsY) continue; if (position.y() > pageBottom) break; const QString txt = QString::number(lineNumber); p.drawText(width() - fm.width(txt), qRound(position.y()) - contentsY + ascent, txt); } }
void BoxBuilder::build(const QTextDocument* doc,const QSize& pixmapSize, const QColor& glyphColor) { pixmap_ = QPixmap(pixmapSize); pixmap_.fill(Qt::white); glyphPen_ = QPen(glyphColor); boxes_.clear(); for(QTextBlock block = doc->begin();block!=doc->end();block=block.next()) { //log_ << "BLOCK has started" << std::endl; QTextLayout* tl = block.layout(); QString blockText = block.text(); QList<QGlyphRun> glyphs = tl->glyphRuns(); int glyphsNum = 0; std::for_each(glyphs.begin(),glyphs.end(),[&glyphsNum](QGlyphRun grun) { glyphsNum += grun.glyphIndexes().size(); }); if(glyphsNum != blockText.length()) log_ << " the number of glyphs in the layout differs with text length, the numbers are: " << glyphsNum << " and " << blockText.length() << std::endl; glyphPosition_ = tl->position(); blockText_ = blockText; std::for_each( glyphs.begin(), glyphs.end(), [this](const QGlyphRun& grun) {handleGlyphRun_(grun);}); } }
void TextLineNumbers::paintEvent( QPaintEvent* ) { int contentsY = m_textEdit->verticalScrollBar()->value(); qreal pageBottom = contentsY + m_textEdit->viewport()->height(); int m_lineNumber = 1; const QFontMetrics fm = fontMetrics(); const int ascent = fontMetrics().ascent() +1; QPainter p( this ); for ( QTextBlock block = m_textEdit->document()->begin(); block.isValid(); block = block.next(), m_lineNumber++ ) { QTextLayout* layout = block.layout(); const QRectF boundingRect = layout->boundingRect(); QPointF position = layout->position(); if ( position.y() +boundingRect.height() < contentsY ) continue; if ( position.y() > pageBottom ) break; const QString txt = QString::number( m_lineNumber - 1 ); p.drawText( width() -fm.width( txt ) - 2, qRound( position.y() ) -contentsY +ascent, txt ); // -fm.width( "0" ) is an ampty place/indent } p.end(); }
void TextLayer::recreateTexture(VidgfxContext *gfx) { if(!m_isTexDirty) return; // Don't waste any time if it hasn't changed m_isTexDirty = false; // Delete existing texture if one exists if(m_texture != NULL) vidgfx_context_destroy_tex(gfx, m_texture); m_texture = NULL; // Determine texture size. We need to keep in mind that the text in the // document might extend outside of the layer's bounds. m_document.setTextWidth(m_rect.width()); QSize size( (int)ceilf(m_document.size().width()), (int)ceilf(m_document.size().height())); if(m_document.isEmpty() || size.isEmpty()) { // Nothing to display return; } // Create temporary canvas. We need to be careful here as text is rendered // differently on premultiplied vs non-premultiplied pixel formats. On a // premultiplied format text is rendered with subpixel rendering enabled // while on a non-premultiplied format it is not. As we don't want subpixel // rendering we use the standard ARGB32 format. QSize imgSize( size.width() + m_strokeSize * 2, size.height() + m_strokeSize * 2); QImage img(imgSize, QImage::Format_ARGB32); img.fill(Qt::transparent); QPainter p(&img); p.setRenderHint(QPainter::Antialiasing, true); // Render text //m_document.drawContents(&p); // Render stroke if(m_strokeSize > 0) { #define STROKE_TECHNIQUE 0 #if STROKE_TECHNIQUE == 0 // Technique 0: Use QTextDocument's built-in text outliner //quint64 timeStart = App->getUsecSinceExec(); QTextDocument *outlineDoc = m_document.clone(this); QTextCharFormat format; QPen pen(m_strokeColor, (double)(m_strokeSize * 2)); pen.setJoinStyle(Qt::RoundJoin); format.setTextOutline(pen); QTextCursor cursor(outlineDoc); cursor.select(QTextCursor::Document); cursor.mergeCharFormat(format); // Take into account the stroke offset p.translate(m_strokeSize, m_strokeSize); //quint64 timePath = App->getUsecSinceExec(); outlineDoc->drawContents(&p); delete outlineDoc; //quint64 timeEnd = App->getUsecSinceExec(); //appLog() << "Path time = " << (timePath - timeStart) << " usec"; //appLog() << "Render time = " << (timeEnd - timePath) << " usec"; //appLog() << "Full time = " << (timeEnd - timeStart) << " usec"; #elif STROKE_TECHNIQUE == 1 // Technique 1: Create a text QPainterPath and stroke it quint64 timeStart = App->getUsecSinceExec(); // Create the path for the text's stroke QPainterPath path; QTextBlock &block = m_document.firstBlock(); int numBlocks = m_document.blockCount(); for(int i = 0; i < numBlocks; i++) { QTextLayout *layout = block.layout(); for(int j = 0; j < layout->lineCount(); j++) { QTextLine &line = layout->lineAt(j); const QString text = block.text().mid( line.textStart(), line.textLength()); QPointF pos = layout->position() + line.position(); pos.ry() += line.ascent(); //appLog() << pos << ": " << text; path.addText(pos, block.charFormat().font(), text); } block = block.next(); } quint64 timePath = App->getUsecSinceExec(); path = path.simplified(); // Fixes gaps with large stroke sizes quint64 timeSimplify = App->getUsecSinceExec(); // Render the path //p.strokePath(path, QPen(m_strokeColor, m_strokeSize)); // Convert it to a stroke QPainterPathStroker stroker; stroker.setWidth(m_strokeSize); //stroker.setCurveThreshold(2.0); stroker.setJoinStyle(Qt::RoundJoin); path = stroker.createStroke(path); // Render the path p.fillPath(path, m_strokeColor); quint64 timeEnd = App->getUsecSinceExec(); appLog() << "Path time = " << (timePath - timeStart) << " usec"; appLog() << "Simplify time = " << (timeSimplify - timePath) << " usec"; appLog() << "Render time = " << (timeEnd - timeSimplify) << " usec"; appLog() << "Full time = " << (timeEnd - timeStart) << " usec"; #elif STROKE_TECHNIQUE == 2 // Technique 2: Similar to technique 1 but do each block separately quint64 timeStart = App->getUsecSinceExec(); quint64 timeTotalSimplify = 0; quint64 timeTotalRender = 0; // Create the path for the text's stroke QTextBlock &block = m_document.firstBlock(); int numBlocks = m_document.blockCount(); for(int i = 0; i < numBlocks; i++) { // Convert this block to a painter path QPainterPath path; QTextLayout *layout = block.layout(); for(int j = 0; j < layout->lineCount(); j++) { QTextLine &line = layout->lineAt(j); const QString text = block.text().mid( line.textStart(), line.textLength()); QPointF pos = layout->position() + line.position() + QPointF(m_strokeSize, m_strokeSize); pos.ry() += line.ascent(); //appLog() << pos << ": " << text; path.addText(pos, block.charFormat().font(), text); } // Prevent gaps appearing at larger stroke sizes quint64 timeA = App->getUsecSinceExec(); path = path.simplified(); quint64 timeB = App->getUsecSinceExec(); timeTotalSimplify += timeB - timeA; // Render the path QPen pen(m_strokeColor, m_strokeSize * 2); pen.setJoinStyle(Qt::RoundJoin); p.strokePath(path, pen); timeA = App->getUsecSinceExec(); timeTotalRender += timeA - timeB; // Iterate block = block.next(); } // Make the final draw take into account the stroke offset p.translate(m_strokeSize, m_strokeSize); quint64 timeEnd = App->getUsecSinceExec(); appLog() << "Simplify time = " << timeTotalSimplify << " usec"; appLog() << "Render time = " << timeTotalRender << " usec"; appLog() << "Full time = " << (timeEnd - timeStart) << " usec"; #elif STROKE_TECHNIQUE == 3 // Technique 3: Raster brute-force where for each destination pixel // we measure the distance to the closest opaque source pixel quint64 timeStart = App->getUsecSinceExec(); // Get bounding region based on text line bounding rects QRegion region; QTextBlock &block = m_document.firstBlock(); int numBlocks = m_document.blockCount(); for(int i = 0; i < numBlocks; i++) { QTextLayout *layout = block.layout(); for(int j = 0; j < layout->lineCount(); j++) { QTextLine &line = layout->lineAt(j); const QString text = block.text().mid( line.textStart(), line.textLength()); QRect rect = line.naturalTextRect() .translated(layout->position()).toAlignedRect(); if(rect.isEmpty()) continue; // Don't add empty rectangles rect.adjust(0, 0, 1, 0); // QTextLine is incorrect? rect.adjust( -m_strokeSize, -m_strokeSize, m_strokeSize, m_strokeSize); //appLog() << rect; region += rect; } // Iterate block = block.next(); } quint64 timeRegion = App->getUsecSinceExec(); #if 0 // Debug bounding region QPainterPath regionPath; regionPath.addRegion(region); regionPath.setFillRule(Qt::WindingFill); p.fillPath(regionPath, QColor(255, 0, 0, 128)); #endif // 0 // We cannot read and write to the same image at the same time so // create a second one. Note that this is not premultiplied. QImage imgOut(size, QImage::Format_ARGB32); imgOut.fill(Qt::transparent); // Do distance calculation. We assume that non-fully transparent // pixels are always next to a fully opaque one so if the closest // "covered" pixel is not fully opaque then we can use that pixel's // opacity to determine the distance to the shape's edge. for(int y = 0; y < img.height(); y++) { for(int x = 0; x < img.width(); x++) { if(!region.contains(QPoint(x, y))) continue; float dist = getDistance(img, x, y, m_strokeSize); // We fake antialiasing by blurring the edge by 1px float outEdge = (float)m_strokeSize; if(dist >= outEdge) continue; // Outside stroke completely float opacity = qMin(1.0f, outEdge - dist); QColor col = m_strokeColor; col.setAlphaF(col.alphaF() * opacity); // Blend the stroke so that it appears under the existing // pixel data QRgb origRgb = img.pixel(x, y); QColor origCol(origRgb); origCol.setAlpha(qAlpha(origRgb)); col = blendColors(col, origCol, 1.0f); imgOut.setPixel(x, y, col.rgba()); } } quint64 timeRender = App->getUsecSinceExec(); // Swap image data p.end(); img = imgOut; p.begin(&img); quint64 timeEnd = App->getUsecSinceExec(); appLog() << "Region time = " << (timeRegion - timeStart) << " usec"; appLog() << "Render time = " << (timeRender - timeRegion) << " usec"; appLog() << "Swap time = " << (timeEnd - timeRender) << " usec"; appLog() << "Full time = " << (timeEnd - timeStart) << " usec"; #endif // STROKE_TECHNIQUE } // Render text m_document.drawContents(&p); // Convert the image to a GPU texture m_texture = vidgfx_context_new_tex(gfx, img); // Preview texture for debugging //img.save(App->getDataDirectory().filePath("Preview.png")); }
void Text::layout() { #if 0 QSizeF pageSize(-1.0, 1000000); setPos(0.0, 0.0); if (parent() && _layoutToParentWidth) { pageSize.setWidth(parent()->width()); if (parent()->type() == HBOX || parent()->type() == VBOX || parent()->type() == TBOX) { Box* box = static_cast<Box*>(parent()); rxpos() += box->leftMargin() * DPMM; rypos() += box->topMargin() * DPMM; // pageSize.setHeight(box->height() - (box->topMargin() + box->bottomMargin()) * DPMM); pageSize.setWidth(box->width() - (box->leftMargin() + box->rightMargin()) * DPMM); } } QTextOption to = _doc->defaultTextOption(); to.setUseDesignMetrics(true); to.setWrapMode(pageSize.width() <= 0.0 ? QTextOption::NoWrap : QTextOption::WrapAtWordBoundaryOrAnywhere); _doc->setDefaultTextOption(to); if (pageSize.width() <= 0.0) _doc->setTextWidth(_doc->idealWidth()); else _doc->setPageSize(pageSize); if (hasFrame()) { frame = QRectF(); for (QTextBlock tb = _doc->begin(); tb.isValid(); tb = tb.next()) { QTextLayout* tl = tb.layout(); int n = tl->lineCount(); for (int i = 0; i < n; ++i) // frame |= tl->lineAt(0).naturalTextRect().translated(tl->position()); frame |= tl->lineAt(0).rect().translated(tl->position()); } if (circle()) { if (frame.width() > frame.height()) { frame.setY(frame.y() + (frame.width() - frame.height()) * -.5); frame.setHeight(frame.width()); } else { frame.setX(frame.x() + (frame.height() - frame.width()) * -.5); frame.setWidth(frame.height()); } } qreal w = (paddingWidth() + frameWidth() * .5) * DPMM; frame.adjust(-w, -w, w, w); w = frameWidth() * DPMM; _bbox = frame.adjusted(-w, -w, w, w); } else { _bbox = QRectF(QPointF(0.0, 0.0), _doc->size()); //_doc->documentLayout()->frameBoundingRect(_doc->rootFrame()); } _doc->setModified(false); style().layout(this); // process alignment if ((style().align() & ALIGN_VCENTER) && (subtype() == TEXT_TEXTLINE)) { // special case: vertically centered text with TextLine needs to // take into account the line width TextLineSegment* tls = (TextLineSegment*)parent(); TextLine* tl = (TextLine*)(tls->line()); qreal textlineLineWidth = point(tl->lineWidth()); rypos() -= textlineLineWidth * .5; } if (parent() == 0) return; if (parent()->type() == SEGMENT) { Segment* s = static_cast<Segment*>(parent()); rypos() += s ? s->measure()->system()->staff(staffIdx())->y() : 0.0; } #endif Font f = style().font(spatium()); qreal asc, desc, leading; qreal w = textMetrics(f.family(), _text, f.size(), &asc, &desc, &leading); // printf("text(%s) asc %f desc %f leading %f w %f\n", qPrintable(_text), asc, desc, leading, w); _lineHeight = asc + desc; _lineSpacing = _lineHeight + leading; _baseLine = asc; setbbox(QRectF(0.0, -asc, w, _lineHeight)); #if 0 if (parent() && _layoutToParentWidth) { qreal wi = parent()->width(); qreal ph = parent()->height(); qreal x; qreal y = pos.y(); if (align() & ALIGN_HCENTER) x = (wi - w) * .5; else if (align() & ALIGN_RIGHT) x = wi - w; else x = 0.0; if (align() & ALIGN_VCENTER) y = (ph - asc) * .5; else if (align() & ALIGN_BOTTOM) y = ph - asc; else y = asc; setPos(x, y + asc); } #endif style().layout(this); // process alignment rypos() += asc; if (parent() && _layoutToParentWidth) { qreal wi = parent()->width(); if (align() & ALIGN_HCENTER) rxpos() = (wi - w) * .5; else if (align() & ALIGN_RIGHT) rxpos() = wi - w; } if ((style().align() & ALIGN_VCENTER) && (subtype() == TEXT_TEXTLINE)) { // special case: vertically centered text with TextLine needs to // take into account the line width TextLineSegment* tls = (TextLineSegment*)parent(); TextLine* tl = (TextLine*)(tls->line()); qreal textlineLineWidth = point(tl->lineWidth()); rypos() -= textlineLineWidth * .5; } if (parent() == 0) return; if (parent()->type() == SEGMENT) { Segment* s = static_cast<Segment*>(parent()); rypos() += s ? s->measure()->system()->staff(staffIdx())->y() : 0.0; } }