/** * Zooms the view in a way that the given window is visible and fills the view. * The view is updated. */ void RGraphicsView::zoomTo(const RBox& window, int margin) { if (!window.isValid()) { return; } saveViewport(); RVector f(RMAXDOUBLE, RMAXDOUBLE); double w = window.getWidth(); double h = window.getHeight(); if (w<1.0e-6 && h<1.0e-6) { return; } if (w>1.0e-6) { f.x = (getWidth() - 2 * margin) / w; } if (h>1.0e-6) { f.y = (getHeight() - 2 * margin) / h; } f.x = f.y = qMin(f.x, f.y); if (RSettings::getLimitZoomAndScroll() && f.x < 1.0e-9) { //f.x = f.y = 1.0; return; } setFactor(f.x); /* RBox viewWindow = mapToView(window); qDebug() << "viewWindow: " << viewWindow; RVector size = viewWindow.getSize(); RVector f; if (size.x > 1.0e-6) { f.x = (getWidth() - 2 * margin) / size.x; } else { f.x = RMAXDOUBLE; } if (size.y > 1.0e-6) { f.y = (getHeight() - 2 * margin) / size.y; } else { f.y = RMAXDOUBLE; } f.x = f.y = qMin(f.x, f.y); if (f.x < 1.0e-6 || f.x == RMAXDOUBLE) { f.x = f.y = 1.0; } setFactor(factor * f.x); */ centerToBox(window); }
/** * \return True if this vector is inside the given box, false otherwise. */ bool RVector::isInside(const RBox& b) const { RVector bMin = b.getMinimum(); RVector bMax = b.getMaximum(); return (x >= bMin.x && x <= bMax.x && y >= bMin.y && y <= bMax.y && z >= bMin.z && z <= bMax.z); }
void RGraphicsSceneQt::exportRay(const RRay& ray) { bool created = beginPath(); Q_ASSERT(currentPainterPath.isValid()); // find largest view box over all attached views: RBox box; QList<RGraphicsView*>::iterator it; for (it=views.begin(); it!=views.end(); it++) { RBox b = (*it)->getBox(); box.growToIncludeBox(b); } // trim line to view box: RLine clippedLine = ray.getClippedLine(box); double offs = clippedLine.getStartPoint().getDistanceTo(ray.getBasePoint()); if (RMath::isSameDirection(ray.getBasePoint().getAngleTo(clippedLine.getStartPoint()), ray.getDirection1())) { offs *= -1; } exportLine(clippedLine, offs); currentPainterPath.setAlwaysRegen(true); if (created) { endPath(); } }
RPolyline RPasteOperation::getBoundary(double unitFactor) { RBox box = sourceDocument.getBoundingBox(); RPolyline polyline = box.getPolyline2d(); polyline.scale(scale * unitFactor); polyline.rotate(rotation); polyline.move(offset); return polyline; }
void ROrthoGrid::paintGridLines(const RVector& space, const RBox& box, bool meta) { if (!space.isValid()) { return; } // updates cache if necessary: getProjection(); isIsometric(); RVector min = box.getCorner1(); RVector max = box.getCorner2(); double deltaX = max.x - min.x; double deltaY = max.y - min.y; if (deltaX / space.x > 1e3 || deltaY / space.y > 1e3) { return; } double dx = deltaY / tan(M_PI/6); if (isometric) { min.x -= dx; max.x += dx; } int c; double x; for (x=min.x, c=0; x<max.x; x+=space.x, c++) { //int x2 = RMath::mround(x/space.x); //if (!isometric || c%2==0) { if (isometric) { if (projection==RS::IsoTop || projection==RS::IsoRight) { view.paintGridLine(RLine(RVector(x, min.y), RVector(x+dx, max.y))); } if (projection==RS::IsoTop || projection==RS::IsoLeft) { view.paintGridLine(RLine(RVector(x, min.y), RVector(x-dx, max.y))); } // vertical grid lines: if (projection==RS::IsoRight || projection==RS::IsoLeft) { view.paintGridLine(RLine(RVector(x, min.y), RVector(x, max.y))); view.paintGridLine(RLine(RVector(x-space.x/2, min.y), RVector(x-space.x/2, max.y))); } } else { view.paintGridLine(RLine(RVector(x, min.y), RVector(x, max.y))); } //} } // horizontal lines: if (!isometric) { for (double y=min.y; y<max.y; y+=space.y) { view.paintGridLine(RLine(RVector(min.x, y), RVector(max.x, y))); } } }
RBox RImageData::getBoundingBox(bool ignoreEmpty) const { Q_UNUSED(ignoreEmpty) RBox ret; QList<RLine> edges = getEdges(); for (int i=0; i<edges.size(); i++) { ret.growToInclude(edges.at(i).getBoundingBox()); } return ret; }
/** * \return The bounding box that contains this entity. */ RBox REntityData::getBoundingBox(bool ignoreEmpty) const { Q_UNUSED(ignoreEmpty) RBox ret; QList<QSharedPointer<RShape> > shapes = getShapes(); for (int i=0; i<shapes.size(); i++) { RBox bb = shapes.at(i)->getBoundingBox(); ret.growToInclude(bb); } return ret; }
bool RGraphicsView::zoomToSelection() { RDocument* document = getDocument(); if (document == NULL) { return false; } RBox selectionBox = document->getSelectionBox(); if (selectionBox.isValid() && (selectionBox.getWidth()>RS::PointTolerance || selectionBox.getHeight()>RS::PointTolerance)) { zoomTo(selectionBox, getMargin()); return true; } return false; }
/** * \return The bounding box that contains this entity. */ RBox REntityData::getBoundingBox() const { RBox bb; QList<QSharedPointer<RShape> > shapes = getShapes(); for (int i=0; i<shapes.size(); i++) { if (!bb.isValid()) { bb = shapes.at(i)->getBoundingBox(); } else { bb.growToInclude(shapes.at(i)->getBoundingBox()); } } return bb; }
RPolyline RPasteOperation::getBoundary(double unitFactor) { RBox box = sourceDocument.getBoundingBox(); RPolyline polyline = box.getPolyline2d(); if (flipHorizontal) { polyline.flipHorizontal(); } if (flipVertical) { polyline.flipVertical(); } polyline.scale(scale * unitFactor); polyline.rotate(getRotation()); polyline.move(getOffset()); return polyline; }
RBox RMemoryStorage::getSelectionBox() const { RBlock::Id currentBlock = getCurrentBlockId(); RBox ret; QHash<RObject::Id, QSharedPointer<REntity> >::const_iterator it; for (it = entityMap.constBegin(); it != entityMap.constEnd(); ++it) { QSharedPointer<REntity> e = *it; if (!e.isNull() && !e->isUndone() && e->isSelected() && e->getBlockId() == currentBlock) { ret.growToInclude(e->getBoundingBox()); } } return ret; }
/** * Centers the view on the given \c box. The view is updated. * * \param box Box to move into the center of the drawing in model coordinates. */ void RGraphicsView::centerToBox(const RBox& box) { RVector center = box.getCenter(); RVector boxCenter = mapFromView(RVector(getWidth(), getHeight())/2.0); setOffset(offset - (center - boxCenter)); //regenerate(); //viewportChangeEvent(); }
RBox RPolyline::getBoundingBox() const { RBox ret; if (countVertices()==1) { ret = RBox(vertices.at(0), vertices.at(0)); } QList<QSharedPointer<RShape> > sub = getExploded(); QList<QSharedPointer<RShape> >::iterator it; for (it=sub.begin(); it!=sub.end(); ++it) { RBox bb = (*it)->getBoundingBox(); ret.growToInclude(bb); } return ret; }
/** * Exports the surfaces of the given box. * This is a convenience function that exports 12 triangles but may * also be re-implemented to do something else. */ void RExporter::exportBox(const RBox& box) { QList<RTriangle> triangles = box.getTriangles(); QList<RTriangle>::iterator it; for (it=triangles.begin(); it!=triangles.end(); ++it) { exportTriangle(*it); } }
void RGraphicsView::autoZoom(int margin, bool ignoreEmpty) { RDocument* document = getDocument(); if (document == NULL) { return; } RBox bb = document->getBoundingBox(true, ignoreEmpty); // TODO: optional: bb.growXY( RUnit::convert( document->getMaxLineweight()/100.0/2, RS::Millimeter, document->getUnit() ) ); zoomTo(bb, (margin!=-1 ? margin : getMargin())); }
/** * Called whenever the current viewport changed through panning or * zooming. Updates the scroll bars, rulers, etc. */ void REventHandler::viewportChanged() { if (hsb==NULL || vsb==NULL) { return; } hsb->blockSignals(true); vsb->blockSignals(true); RDocument& document = documentInterface->getDocument(); RBox box = document.getBoundingBox(true, true); double min = box.getMinimum().x * graphicsView->getFactor() - 800; double max = box.getMaximum().x * graphicsView->getFactor() - graphicsView->getWidth() + 800; hsb->setRange(min, max); hsb->setPageStep(graphicsView->getWidth()); hsb->setValue(-graphicsView->getOffset().x * graphicsView->getFactor()); min = graphicsView->getHeight() - box.getMaximum().y * graphicsView->getFactor() - 800; max = 800 - box.getMinimum().y * graphicsView->getFactor(); vsb->setRange(min, max); vsb->setPageStep(graphicsView->getHeight()); vsb->setValue(graphicsView->getOffset().y * graphicsView->getFactor()); hsb->blockSignals(false); vsb->blockSignals(false); // rulers if (hruler!=NULL) { hruler->updateViewport(); } if (vruler!=NULL) { vruler->updateViewport(); } // grid info label QLabel* infoLabel = widget->findChild<QLabel*>("InfoLabel"); RGrid* grid = graphicsView->getGrid(); if (grid!=NULL) { infoLabel->setText(grid->getInfoText()); } }
/** * \return List of bezier spline segments which together represent this curve. */ QList<RSpline> RSpline::getBezierSegments(const RBox& queryBox) const { // spline is a single bezier segment: if (countControlPoints()==getDegree()+1) { return QList<RSpline>() << *this; } updateInternal(); QList<RSpline> ret; #ifndef R_NO_OPENNURBS ON_NurbsCurve* dup = dynamic_cast<ON_NurbsCurve*>(curve.DuplicateCurve()); if (dup==NULL) { return ret; } dup->MakePiecewiseBezier(); for (int i=0; i<=dup->CVCount() - dup->Order(); ++i) { ON_BezierCurve bc; if (!dup->ConvertSpanToBezier(i, bc)) { continue; } QList<RVector> ctrlPts; for (int cpi=0; cpi<bc.CVCount(); cpi++) { ON_3dPoint onp; bc.GetCV(cpi, onp); ctrlPts.append(RVector(onp.x, onp.y, onp.z)); } RSpline bezierSegment(ctrlPts, degree); if (!queryBox.isValid() || queryBox.intersects(bezierSegment.getBoundingBox())) { ret.append(bezierSegment); } } delete dup; #endif return ret; }
/** * Maps the given \c box (e.g. a 3d bounding box) to a 2d box * in view coordinates (pixels). */ RBox RGraphicsView::mapToView(const RBox& box) const { QList<RVector> boxCorners = box.getCorners(); RVector minView(RMAXDOUBLE, RMAXDOUBLE, RMAXDOUBLE); RVector maxView(RMINDOUBLE, RMINDOUBLE, RMINDOUBLE); RVector corner; for (int i=0; i<8; i++) { corner = mapToView(boxCorners[i]); minView = RVector::getMinimum(corner, minView); maxView = RVector::getMaximum(corner, maxView); } return RBox(minView, maxView); }
void ROrthoGrid::paintGridPoints(const RVector& space, const RBox& box) { if (!space.isValid()) { return; } RVector min = box.getCorner1(); RVector max = box.getCorner2(); if ((max.x - min.x) / space.x > 1e3 || (max.y - min.y) / space.y > 1e3) { return; } RVector gridPointUcs; int x, y; for (gridPointUcs.x = min.x; gridPointUcs.x < max.x; gridPointUcs.x += space.x) { x = RMath::mround(gridPointUcs.x/space.x); for (gridPointUcs.y = min.y; gridPointUcs.y < max.y; gridPointUcs.y += space.y) { y = RMath::mround(gridPointUcs.y/space.y); if (!isometric || (x+y)%2==0) { view.paintGridPoint(gridPointUcs); } } } }
QList<RVector> RSnapTangential::snapEntity( QSharedPointer<REntity> entity, const RVector& point, const RBox& queryBox, RGraphicsView& view) { QList<RVector> ret; RDocumentInterface* di = view.getDocumentInterface(); if (di==NULL) { return ret; } QSharedPointer<RShape> shape = entity->getClosestShape(point, queryBox.getWidth()/2, true); if (shape.isNull()) { return ret; } QSharedPointer<RCircle> circle = shape.dynamicCast<RCircle>(); if (!circle.isNull()) { QList<RLine> lines = circle->getTangents(di->getLastPosition()); for (int i=0; i<lines.length(); i++) { ret.append(lines[i].getEndPoint()); } } QSharedPointer<RArc> arc = shape.dynamicCast<RArc>(); if (!arc.isNull()) { QList<RLine> lines = arc->getTangents(di->getLastPosition()); for (int i=0; i<lines.length(); i++) { ret.append(lines[i].getEndPoint()); } } QSharedPointer<REllipse> ellipse = shape.dynamicCast<REllipse>(); if (!ellipse.isNull()) { QList<RLine> lines = ellipse->getTangents(di->getLastPosition()); for (int i=0; i<lines.length(); i++) { ret.append(lines[i].getEndPoint()); } } return ret; }
QList<RVector> RSnapPerpendicular::snapEntity( QSharedPointer<REntity> entity, const RVector& point, const RBox& queryBox, RGraphicsView& view) { QList<RVector> ret; RDocumentInterface* di = view.getDocumentInterface(); if (di==NULL) { return ret; } QSharedPointer<RShape> shape = entity->getClosestShape(point, queryBox.getWidth()/2, true); if (shape.isNull()) { return ret; } ret.append(shape->getClosestPointOnShape(di->getLastPosition(), false)); return ret; }
/** * \return A polygon that contains this entity. * Default implementation returns a polygon around the bounding box. */ RPolyline REntityData::getHull(double offset) const { RBox bb = getBoundingBox(); bb.grow(offset); return bb.getPolyline2d(); }
bool RShape::stretch(const RBox& area, const RVector& offset) { return stretch(area.getPolyline2d(), offset); }
/** * 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); } } }
RBox RMemoryStorage::getBoundingBox(bool ignoreHiddenLayers, bool ignoreEmpty) const { if (!boundingBoxChanged) { return boundingBox[ignoreHiddenLayers][ignoreEmpty]; } RBlock::Id currentBlockId = getCurrentBlockId(); boundingBox[0][0] = RBox(); boundingBox[0][1] = RBox(); boundingBox[1][0] = RBox(); boundingBox[1][1] = RBox(); maxLineweight = RLineweight::Weight000; QHash<RObject::Id, QSharedPointer<REntity> >::const_iterator it; for (it = entityMap.constBegin(); it != entityMap.constEnd(); ++it) { QSharedPointer<REntity> e = *it; if (e.isNull() || e->isUndone()) { continue; } //if (ignoreHiddenLayers) { bool layerHidden = false; QSharedPointer<RLayer> layer = queryLayerDirect(e->getLayerId()); if (layer.isNull() || layer->isFrozen()) { layerHidden = true; } //} if (e->getBlockId() == currentBlockId) { //bb.growToInclude(e->getBoundingBox(ignoreEmpty)); RBox bb = e->getBoundingBox(false); RBox bbIgnoreEmpty = e->getBoundingBox(true); if (!bb.isSane()) { continue; } boundingBox[0][0].growToInclude(bb); boundingBox[0][1].growToInclude(bbIgnoreEmpty); if (!layerHidden) { boundingBox[1][0].growToInclude(bb); boundingBox[1][1].growToInclude(bbIgnoreEmpty); } } // don't resolve block references, if line weight is ByBlock, // the maxLinewidth will be adjusted when the block reference // is encountered: QStack<REntity*> blockRefStack; RLineweight::Lineweight lw = e->getLineweight(true, blockRefStack); maxLineweight = qMax(lw, maxLineweight); } boundingBoxChanged = false; // qDebug() << "\n\nbb: " << boundingBox[0][0]; // qDebug() << "bb ignoreEmpty: " << boundingBox[0][1]; // qDebug() << "bb ignoreHiddenLayers: " << boundingBox[1][0]; // qDebug() << "bb ignoreHiddenLayers, ignoreEmpty: " << boundingBox[1][1]; return boundingBox[ignoreHiddenLayers][ignoreEmpty]; }