/** * Renders the text data into painter paths. */ void RTextRenderer::render() { boundingBox = RBox(); painterPaths.clear(); richText = ""; QString text = textData.getEscapedText(); //RVector position = textData.getPosition(); RVector position = textData.getAlignmentPoint(); double textHeight = textData.getTextHeight(); RS::VAlign verticalAlignment = textData.getVAlign(); RS::HAlign horizontalAlignment = textData.getHAlign(); double lineSpacingFactor = textData.getLineSpacingFactor(); QString fontName = textData.getFontName(); bool bold = textData.isBold(); bool italic = textData.isItalic(); double angle = textData.getAngle(); //RColor color = textData.getColor(true); // split up text where separation is required due to line feed, // or any type of height change (\P, \H[0-9]*.?[0-9]+;, \S*/*;) // split up text at every formatting change: QRegExp regExpAll(rxAll); QStringList literals = text.split(regExpAll); // collect formatting change information for every literal: QStringList formattings; int pos = 0; while ((pos = regExpAll.indexIn(text, pos)) != -1) { formattings << regExpAll.cap(1); pos += regExpAll.matchedLength(); } // x position in real units: double xCursor = 0.0; // y position for next text line: double yCursor = 0.0; // painter paths for current text line: QList<RPainterPath> linePaths; // max ascent of current text line: double maxAscent = 0.0; // max descent of current line: double minDescent = 0.0; // max descent of _previous_ line: double minDescentPrevious = 0.0; // true for the first block of the current line: bool firstBlockInLine = true; // current line has leading spaces: bool leadingSpaces = false; // current line has trailing spaces: bool trailingSpaces = false; // text has leading empty lines: bool leadingEmptyLines = false; // text has trailing empty lines: bool trailingEmptyLines = false; int lineCounter = 0; QString textBlock; QList<QTextLayout::FormatRange> formats; bool blockChangedHeightOrFont = false; // implicit top level format block: QTextCharFormat f; f.setForeground(QBrush(QColor())); currentFormat.push(f); QTextLayout::FormatRange fr; fr.start = 0; fr.length = 0; fr.format = currentFormat.top(); formats.append(fr); blockHeight.push(textHeight); blockFont.push(fontName); blockBold.push(bold); blockItalic.push(italic); useCadFont.push(RFontList::isCadFont(fontName)); openTags.push(QStringList()); width = 0.0; height = 0.0; // iterate through all text blocks: for (int i=0; i<literals.size(); ++i) { // the literal text, e.g. "ABC": QString literal = literals.at(i); // the formatting _after_ the text, e.g. "\C1;" QString formatting; if (i<formattings.size()) { formatting = formattings.at(i); } // qDebug() << "block: " << i; // qDebug() << " literal: " << literal; // qDebug() << " literal length: " << literal.length(); // qDebug() << " formatting: " << formatting; // qDebug() << " font: " << blockFont.top(); // qDebug() << " height: " << blockHeight.top(); // qDebug() << " cad font: " << useCadFont.top(); // qDebug() << " xCursor: " << xCursor; // handle literal: textBlock.append(literal); if (firstBlockInLine) { if (!literal.isEmpty()) { if (literal.at(0).isSpace()) { leadingSpaces = true; } } else { if (formatting=="\\~") { leadingSpaces = true; } } firstBlockInLine = false; } bool lineFeed = false; bool paragraphFeed = false; bool heightChange = false; bool stackedText = false; bool fontChange = false; bool colorChange = false; bool blockEnd = false; // detect formatting that ends the current text block: if (!formatting.isEmpty()) { fontChange = QRegExp(rxFontChangeTtf).exactMatch(formatting) || QRegExp(rxFontChangeCad).exactMatch(formatting); colorChange = QRegExp(rxColorChangeCustom).exactMatch(formatting) || QRegExp(rxColorChangeIndex).exactMatch(formatting); heightChange = QRegExp(rxHeightChange).exactMatch(formatting); stackedText = QRegExp(rxStackedText).exactMatch(formatting); lineFeed = QRegExp(rxLineFeed).exactMatch(formatting); paragraphFeed = QRegExp(rxParagraphFeed).exactMatch(formatting); blockEnd = QRegExp(rxEndBlock).exactMatch(formatting); } bool start = (i==0); bool end = (i==literals.size()-1); // first line is empty: if (textBlock.trimmed().isEmpty() && (lineFeed || paragraphFeed || end)) { if (start) { leadingEmptyLines = true; } else if (end) { trailingEmptyLines = true; } } // reached a new text block that needs to be rendered separately // due to line feed, height change, font change, ...: if (target==RichText || lineFeed || paragraphFeed || heightChange || stackedText || fontChange || colorChange || end || (blockEnd && blockChangedHeightOrFont)) { // render block _before_ format change that requires new block: if (textBlock!="") { // fix format lengths to match up. each format stops where // the next one starts. formatting that is carried over is set // again for the new format. for (int i=0; i<formats.size(); i++) { int nextStart; if (i<formats.size()-1) { nextStart = formats[i+1].start; } else { nextStart = textBlock.length(); } formats[i].length = nextStart - formats[i].start; } if (target==RichText) { richText += getRichTextForBlock( textBlock, formats); } if (target==PainterPaths) { double horizontalAdvance = 0.0; double horizontalAdvanceNoSpacing = 0.0; double ascent = 0.0; double descent = 0.0; QList<RPainterPath> paths; // get painter paths for current text block at height 1.0: paths = getPainterPathsForBlock( textBlock, formats, horizontalAdvance, horizontalAdvanceNoSpacing, ascent, descent); // transform to scale text from 1.0 to current text height: QTransform sizeTransform; sizeTransform.scale(blockHeight.top(), blockHeight.top()); // transform for current block due to xCursor position: QTransform blockTransform; blockTransform.translate(xCursor, 0); // combine transforms for current text block: QTransform allTransforms = sizeTransform; allTransforms *= blockTransform; maxAscent = qMax(maxAscent, ascent * blockHeight.top()); minDescent = qMin(minDescent, descent * blockHeight.top()); // transform paths of current block and append to paths // of current text line: for (int i=0; i<paths.size(); ++i) { RPainterPath p = paths.at(i); p.transform(allTransforms); linePaths.append(p); } xCursor += horizontalAdvance * blockHeight.top(); } } // empty text, might be line feed, we need ascent, descent anyway: else if (lineFeed || paragraphFeed || end) { if (target==PainterPaths) { double horizontalAdvance = 0.0; double horizontalAdvanceNoSpacing = 0.0; double ascent = 0.0; double descent = 0.0; // get painter paths for current text block at height 1.0: getPainterPathsForBlock( "A", QList<QTextLayout::FormatRange>(), horizontalAdvance, horizontalAdvanceNoSpacing, ascent, descent); maxAscent = qMax(maxAscent, ascent * blockHeight.top()); minDescent = qMin(minDescent, descent * blockHeight.top()); } } // handle stacked text: if (stackedText) { QRegExp reg; reg.setPattern(rxStackedText); reg.exactMatch(formatting); if (target==PainterPaths) { double horizontalAdvance[2]; horizontalAdvance[0] = 0.0; horizontalAdvance[1] = 0.0; double horizontalAdvanceNoSpacing[2]; horizontalAdvanceNoSpacing[0] = 0.0; horizontalAdvanceNoSpacing[1] = 0.0; double heightFactor = 0.4; // s==0: superscript, s==1: subscript: for (int s=0; s<=1; ++s) { QString script = reg.cap(s+1); QList<RPainterPath> paths; double ascent = 0.0; double descent = 0.0; QList<QTextLayout::FormatRange> localFormats; QTextLayout::FormatRange fr; fr.start = 0; fr.length = script.length(); fr.format = currentFormat.top(); localFormats.append(fr); // get painter paths for superscript at height 1.0: paths = getPainterPathsForBlock( script, localFormats, horizontalAdvance[s], horizontalAdvanceNoSpacing[s], ascent, descent); if (s==0) { maxAscent = qMax(maxAscent, ascent * blockHeight.top() * heightFactor + blockHeight.top()*(1.0-heightFactor)); } else { minDescent = qMin(minDescent, descent * blockHeight.top() * heightFactor); } // transform to scale text from 1.0 to current text height * 0.4: QTransform sizeTransform; sizeTransform.scale(blockHeight.top()*heightFactor, blockHeight.top()*heightFactor); // move top text more to the right for italic texts: double xOffset = 0.0; if (s==0 && blockItalic.top()==true) { double y = blockHeight.top()*(1.0-heightFactor); // assume italic means roughly 12 degrees: xOffset = tan(RMath::deg2rad(12)) * y; } // transform for current block due to xCursor position // and top or bottom text position: QTransform blockTransform; blockTransform.translate(xCursor + xOffset, s==0 ? blockHeight.top()*(1.0-heightFactor) : 0.0); horizontalAdvance[s] += xOffset / (blockHeight.top() * heightFactor); // combine transforms for current text block: QTransform allTransforms = sizeTransform; allTransforms *= blockTransform; // transform paths of current block and append to paths // of current text line: for (int i=0; i<paths.size(); ++i) { RPainterPath p = paths.at(i); p.transform(allTransforms); linePaths.append(p); } } xCursor += qMax(horizontalAdvance[0], horizontalAdvance[1]) * blockHeight.top() * heightFactor; } if (target==RichText) { QString super = reg.cap(1); QString sub = reg.cap(2); if (!super.isEmpty()) { richText += QString("<span style=\"vertical-align:super;\">%1</span>").arg(super); } if (!sub.isEmpty()) { richText += QString("<span style=\"vertical-align:sub;\">%1</span>").arg(sub); } } } // prepare for next text block: if (lineFeed || paragraphFeed || end) { if (!textBlock.isEmpty()) { if (textBlock.at(textBlock.length()-1).isSpace()) { trailingSpaces = true; } } // else { // if (formatting=="\\~") { // trailingSpaces = true; // } // } } textBlock = ""; formats.clear(); QTextLayout::FormatRange fr; fr.start = 0; fr.length = 0; fr.format = currentFormat.top(); formats.append(fr); // handle text line. // add all painter paths of the current line to result set of // painter paths. apply line feed transformations. if (lineFeed || paragraphFeed || end) { // qDebug() << "lineFeed: adding text line:"; // qDebug() << " maxAscent: " << maxAscent; // qDebug() << " minDescent: " << minDescent; // qDebug() << " minDescentPrevious: " << minDescentPrevious; // qDebug() << "got line feed or end"; // qDebug() << " trailingSpaces: " << trailingSpaces; if (target==RichText) { if (lineFeed || paragraphFeed) { richText += "<br/>"; } } if (lineCounter!=0) { yCursor += (minDescentPrevious - maxAscent) * lineSpacingFactor; } // calculate line bounding box for alignment without spaces at borders: RBox lineBoundingBox; for (int i=0; i<linePaths.size(); ++i) { RPainterPath p = linePaths.at(i); lineBoundingBox.growToInclude(p.getBoundingBox()); } double featureSize = lineBoundingBox.getHeight(); QTransform lineTransform; switch (horizontalAlignment) { case RS::HAlignAlign: case RS::HAlignFit: case RS::HAlignLeft: if (!leadingSpaces) { // move completely to the left (left border is 0.0): lineTransform.translate( -lineBoundingBox.getMinimum().x, yCursor); } else { lineTransform.translate(0, yCursor); } break; case RS::HAlignMid: case RS::HAlignCenter: if (!leadingSpaces && !trailingSpaces) { lineTransform.translate( -(lineBoundingBox.getMinimum().x + lineBoundingBox.getMaximum().x)/2.0, yCursor); } else { lineTransform.translate(-xCursor/2.0, yCursor); } break; case RS::HAlignRight: if (!trailingSpaces) { lineTransform.translate(-lineBoundingBox.getMaximum().x, yCursor); } else { lineTransform.translate(-xCursor, yCursor); } break; } width = qMax(width, lineBoundingBox.getMaximum().x - qMin(0.0, lineBoundingBox.getMinimum().x)); // add current text line to result set: QPen pen; for (int i=0; i<linePaths.size(); ++i) { RPainterPath p = linePaths[i]; if (i==0) { pen = p.getPen(); if (pen.style()==Qt::NoPen) { pen = QPen(p.getBrush().color()); } } p.transform(lineTransform); p.setFeatureSize(featureSize); painterPaths.append(p); boundingBox.growToInclude(p.getBoundingBox()); } RPainterPath bbPath; bbPath.addBox(lineBoundingBox); bbPath.transform(lineTransform); bbPath.setFeatureSize(-featureSize); bbPath.setPen(pen); painterPaths.append(bbPath); lineCounter++; xCursor = 0.0; maxAscent = 0.0; minDescentPrevious = minDescent; minDescent = 0.0; linePaths.clear(); firstBlockInLine = true; leadingSpaces = false; trailingSpaces = false; } } // handle formatting after text block, that applies either // to the next text block(s) or the rest of the current text block: if (formatting.isEmpty()) { continue; } QRegExp reg; // unicode: reg.setPattern(rxUnicode); if (reg.exactMatch(formatting)) { textBlock += QChar(reg.cap(1).toInt(0, 16)); continue; } // curly braket open: reg.setPattern(rxCurlyOpen); if (reg.exactMatch(formatting)) { textBlock += "{"; continue; } // curly braket close: reg.setPattern(rxCurlyClose); if (reg.exactMatch(formatting)) { textBlock += "}"; continue; } // degree: reg.setPattern(rxDegree); if (reg.exactMatch(formatting)) { textBlock += chDegree; continue; } // plus/minus: reg.setPattern(rxPlusMinus); if (reg.exactMatch(formatting)) { textBlock += chPlusMinus; continue; } // diameter: reg.setPattern(rxDiameter); if (reg.exactMatch(formatting)) { textBlock += chDiameter; continue; } // backslash: reg.setPattern(rxBackslash); if (reg.exactMatch(formatting)) { textBlock += "\\"; continue; } // non-breaking space: reg.setPattern(rxNonBreakingSpace); if (reg.exactMatch(formatting)) { if (target==PainterPaths) { textBlock += QChar(Qt::Key_nobreakspace); } if (target==RichText) { textBlock += " "; } continue; } // font change (TTF): reg.setPattern(rxFontChangeTtf); if (reg.exactMatch(formatting)) { blockFont.top() = reg.cap(1); blockBold.top() = (reg.cap(2).toInt()!=0); blockItalic.top() = (reg.cap(3).toInt()!=0); useCadFont.top() = false; blockChangedHeightOrFont = true; if (target==RichText) { QString style; style += QString("font-family:%1;").arg(blockFont.top()); style += QString("font-weight:%1;").arg(blockBold.top() ? "bold" : "normal"); style += QString("font-style:%1;").arg(blockItalic.top() ? "italic" : "normal"); richText += QString("<span style=\"%1\">").arg(style); openTags.top().append("span"); } continue; } // font change (CAD): reg.setPattern(rxFontChangeCad); if (reg.exactMatch(formatting)) { blockFont.top() = reg.cap(1); useCadFont.top() = true; if (xCursor>RS::PointTolerance) { RFont* f = RFontList::get(blockFont.top()); if (f!=NULL && f->isValid()) { xCursor += f->getLetterSpacing() / 9.0; } } blockChangedHeightOrFont = true; if (target==RichText) { QString style; style += QString("font-family:%1;").arg(blockFont.top()); richText += QString("<span style=\"%1\">").arg(style); openTags.top().append("span"); } continue; } // height change: reg.setPattern(rxHeightChange); if (reg.exactMatch(formatting)) { bool factor = reg.cap(2)=="x"; if (factor) { blockHeight.top() *= reg.cap(1).toDouble(); } else { blockHeight.top() = reg.cap(1).toDouble(); } blockChangedHeightOrFont = true; if (target==RichText) { QString style; style += QString("font-size:%1pt;").arg(blockHeight.top() * fontHeightFactor); richText += QString("<span style=\"%1\">").arg(style); openTags.top().append("span"); } continue; } QTextLayout::FormatRange fr; fr.start = textBlock.length(); fr.length = 0; // start format block: reg.setPattern(rxBeginBlock); if (reg.exactMatch(formatting)) { currentFormat.push(currentFormat.top()); blockFont.push(blockFont.top()); blockBold.push(blockBold.top()); blockItalic.push(blockItalic.top()); blockHeight.push(blockHeight.top()); useCadFont.push(useCadFont.top()); if (target==RichText) { openTags.push(QStringList()); } blockChangedHeightOrFont = false; continue; } // end format block: reg.setPattern(rxEndBlock); if (reg.exactMatch(formatting)) { currentFormat.pop(); fr.format = currentFormat.top(); formats.append(fr); blockFont.pop(); blockBold.pop(); blockItalic.pop(); blockHeight.pop(); useCadFont.pop(); blockChangedHeightOrFont = false; if (target==RichText) { // close all tags that were opened in this block: for (int i=openTags.top().size()-1; i>=0; --i) { QString tag = openTags.top().at(i); richText += QString("</%1>").arg(tag); } openTags.pop(); } continue; } // color change (indexed): reg.setPattern(rxColorChangeIndex); if (reg.exactMatch(formatting)) { RColor blockColor = RColor::createFromCadIndex(reg.cap(1)); if (blockColor.isByLayer()) { currentFormat.top().setForeground(RColor::CompatByLayer); } else if (blockColor.isByBlock()) { currentFormat.top().setForeground(RColor::CompatByBlock); } else { currentFormat.top().setForeground(blockColor); } fr.format = currentFormat.top(); formats.append(fr); if (target==RichText) { QString style; style += QString("color:%1;").arg(blockColor.name()); richText += QString("<span style=\"%1\">").arg(style); openTags.top().append("span"); } continue; } // color change (custom): reg.setPattern(rxColorChangeCustom); if (reg.exactMatch(formatting)) { RColor blockColor = RColor::createFromCadCustom(reg.cap(1)); currentFormat.top().setForeground(blockColor); fr.format = currentFormat.top(); formats.append(fr); if (target==RichText) { QString style; style += QString("color:%1;").arg(blockColor.name()); richText += QString("<span style=\"%1\">").arg(style); openTags.top().append("span"); } continue; } } if (target==PainterPaths) { // at this point, the text is at 0/0 with the base line of the // first text line at y==0 // vertical alignment: double topLine = qMax(textHeight, boundingBox.getMaximum().y); double bottomLine = qMin(0.0, boundingBox.getMinimum().y); QTransform globalTransform; globalTransform.translate(position.x, position.y); globalTransform.rotate(RMath::rad2deg(angle)); switch (verticalAlignment) { case RS::VAlignTop: //if (!leadingEmptyLines) { globalTransform.translate(0.0, -topLine); //} break; case RS::VAlignMiddle: if (leadingEmptyLines || trailingEmptyLines) { globalTransform.translate(0.0, -(yCursor+textHeight)/2.0); } else { globalTransform.translate(0.0, -(bottomLine + topLine) / 2.0); } break; case RS::VAlignBottom: case RS::VAlignBase: if (trailingEmptyLines) { globalTransform.translate(0.0, -yCursor); } else { globalTransform.translate(0.0, -bottomLine); } break; } height = boundingBox.getHeight(); // apply global transform for position, angle and vertical alignment: boundingBox = RBox(); for (int i=0; i<painterPaths.size(); ++i) { painterPaths[i].transform(globalTransform); boundingBox.growToInclude(painterPaths[i].getBoundingBox()); } } if (target==RichText) { // close all tags that were opened: for (int i=openTags.top().size()-1; i>=0; --i) { QString tag = openTags.top().at(i); richText += QString("</%1>").arg(tag); } } }
/** * Renders the text data into painter paths. */ void RTextRenderer::renderSimple() { boundingBox = RBox(); painterPaths.clear(); richText = ""; RVector pos = textData.getAlignmentPoint(); QString text = textData.getEscapedText(); double textHeight = textData.getTextHeight(); RS::VAlign verticalAlignment = textData.getVAlign(); RS::HAlign horizontalAlignment = textData.getHAlign(); QString fontName = textData.getFontName(); bool bold = textData.isBold(); bool italic = textData.isItalic(); double angle = textData.getAngle(); // degree: text.replace(QRegExp(RTextRenderer::rxDegree), RTextRenderer::chDegree); // plus minus: text.replace(QRegExp(RTextRenderer::rxPlusMinus), RTextRenderer::chPlusMinus); // diameter: text.replace(QRegExp(RTextRenderer::rxDiameter), RTextRenderer::chDiameter); bool leadingSpaces = false; bool trailingSpaces = false; if (!text.isEmpty()) { if (text.at(0).isSpace()) { leadingSpaces = true; } if (text.at(text.length()-1).isSpace()) { trailingSpaces = true; } } // implicit top level format block: QTextCharFormat f; f.setForeground(QBrush(QColor())); currentFormat.push(f); QTextLayout::FormatRange fr; fr.start = 0; fr.length = text.length(); fr.format = currentFormat.top(); QList<QTextLayout::FormatRange> formats; formats.append(fr); blockHeight.push(textHeight); blockFont.push(fontName); blockBold.push(bold); blockItalic.push(italic); useCadFont.push(RFontList::isCadFont(blockFont.top())); openTags.push(QStringList()); double horizontalAdvance = 0.0; double horizontalAdvanceNoSpacing = 0.0; double ascent = 0.0; double descent = 0.0; // get painter paths for text at height 1.0: painterPaths = getPainterPathsForBlock( text, formats, horizontalAdvance, horizontalAdvanceNoSpacing, ascent, descent); width = horizontalAdvanceNoSpacing * textHeight; // transform to scale text from 1.0 to current text height: QTransform sizeTransform; sizeTransform.scale(textHeight, textHeight); // transform paths of text: boundingBox = RBox(); for (int i=0; i<painterPaths.size(); ++i) { painterPaths[i].transform(sizeTransform); boundingBox.growToInclude(painterPaths[i].getBoundingBox()); } // feature size of a text is its height // determines if text is displayed or only bounding box double featureSize = boundingBox.getHeight(); QPen pen; for (int i=0; i<painterPaths.size(); ++i) { if (i==0) { pen = painterPaths[i].getPen(); if (pen.style()==Qt::NoPen) { pen = QPen(painterPaths[i].getBrush().color()); } } painterPaths[i].setFeatureSize(featureSize); } RPainterPath bbPath; bbPath.addBox(boundingBox); bbPath.setFeatureSize(-featureSize); bbPath.setPen(pen); painterPaths.append(bbPath); //height = boundingBox.getHeight(); double topLine = textHeight; double baseLine = 0.0; double bottomLine = descent * textHeight; // at this point, the text is at 0/0 with the base line of the // first text line at y==0 double xOffset = 0.0; double yOffset = 0.0; switch (verticalAlignment) { case RS::VAlignTop: yOffset = -topLine; break; case RS::VAlignMiddle: yOffset = -textHeight/2.0; break; case RS::VAlignBase: yOffset = -baseLine; break; case RS::VAlignBottom: yOffset = -bottomLine; break; } switch (horizontalAlignment) { default: case RS::HAlignAlign: case RS::HAlignFit: case RS::HAlignLeft: if (!leadingSpaces) { // move completely to the left (left border is 0.0): xOffset = -boundingBox.getMinimum().x; } else { xOffset = 0.0; } break; case RS::HAlignMid: case RS::HAlignCenter: if (!leadingSpaces && !trailingSpaces) { xOffset = -(boundingBox.getMinimum().x + boundingBox.getMaximum().x)/2.0; } else { xOffset = -horizontalAdvance/2.0*textHeight; } break; case RS::HAlignRight: if (!trailingSpaces) { xOffset = -boundingBox.getMaximum().x; } else { xOffset = -horizontalAdvance*textHeight; } break; } height = boundingBox.getHeight(); QTransform globalTransform; globalTransform.translate(pos.x, pos.y); globalTransform.rotate(RMath::rad2deg(angle)); globalTransform.translate(xOffset, yOffset); // apply global transform for position, angle and vertical alignment: boundingBox = RBox(); for (int i=0; i<painterPaths.size(); ++i) { painterPaths[i].transform(globalTransform); boundingBox.growToInclude(painterPaths[i].getBoundingBox()); } }