Hassaan
Hassaan

Reputation: 37

SVG created using Qt QPainter and QSvgGenerator clipped to viewport size

For a project, I am using QPainter with QSvgGenerator to create SVG as an output. The project basically produces basic class diagrams from C++ code. However, when I open the SVG using Google Chrome or any other web-browser, it is clipped according to the size of browser window without any scrollbars. On resizing window, further clipping occurs.

Related Code

QSvgGenerator temp_img; 
//Save file as image
QString path = QFileDialog::getSaveFileName(w, ("Save as image"), "", 
("SVG file (*.svg)"));

if (path.isEmpty())
    return;

temp_img.setFileName(path);

QPainter painter;
painter.begin(&temp_img);
painter.setFont(QFont("Arial",12));
.
.
.
painter.end();

I have tried using setViewBox() but no effect. I am using Qt for the first time so please try to be as elaborate as possible. I preferred SVG over Bitmaps because of Quality issues.

EDIT: Zooming out reveals the hidden parts.

Upvotes: 1

Views: 1958

Answers (2)

Scheff's Cat
Scheff's Cat

Reputation: 20141

The specification of either size or view box is essential for a stand-alone SVG.

This is described in the SVG Specification by the W3C:

7.2 The initial viewport

The SVG user agent negotiates with its parent user agent to determine the viewport into which the SVG user agent can render the document. In some circumstances, SVG content will be embedded (by reference or inline) within a containing document. This containing document might include attributes, properties and/or other parameters (explicit or implicit) which specify or provide hints about the dimensions of the viewport for the SVG content. SVG content itself optionally can provide information about the appropriate viewport region for the content via the 'width' and 'height' XML attributes on the 'svg' element. The negotiation process uses any information provided by the containing document and the SVG content itself to choose the viewport location and size.

If the parent document format defines rules for referenced or embedded graphics content, then the negotiation process is determined by the parent document format specification. If the parent document is styled with CSS, then the negotiation process must follow the CSS rules for replaced elements. If there are CSS width and height properties (or corresponding XSL properties) on the referencing element (or rootmost 'svg' element for inline SVG content) that are sufficient to establish the width and height of the viewport, then these positioning properties establish the viewport's width, height, and aspect ratio.

If there is no parent document, the SVG user agent must use the 'width' and 'height' attributes on the rootmost 'svg' element element as the width and height for the viewport.

Note that the time at which the viewport size negotiation is finalized is implementation-specific. Authors who need to be sure of the dimensions of the viewport should do so with load-event or resize-event handlers.

I made some examples to illustrate this.

A test file without width, height, and viewBoxtest.svg:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  version="1.2" baseProfile="tiny">
  <title>SVG Test</title>
  <desc>A test to illustrate the role of width and height</desc>
  <rect x="0" y="0" width="100" height="100"
   style="stroke-width:1;stroke:#888;fill:none"/>
  <circle cx="50" cy="50" r="30"
   style="stroke-width:1;stroke:#000;fill:none"/>
</svg>

Output in Google Chrome:

[![Snapshot of test.svg in Google Chrome][1]][1]

[![Snapshot of test.svg in Google Chrome (after resize)][2]][2]

Output in Firefox:

[![Snapshot of test.svg in Firefox][3]][3]

Although, the browser seems to render correctly, the graph size isn't considered correctly. (No vertical scrollbar appears although the SVG contents doesn't fit into view.)

Providing a view port (width and height) – test.100x100.svg:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100" height="100"
 viewBox="0 0 100 100"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  version="1.2" baseProfile="tiny">
  <title>SVG Test</title>
  <desc>A test to illustrate the role of width and height</desc>
  <rect x="0" y="0" width="100" height="100"
   style="stroke-width:1;stroke:#888;fill:none"/>
  <circle cx="50" cy="50" r="30"
   style="stroke-width:1;stroke:#000;fill:none"/>
</svg>

Output in Google Chrome:

[![Snapshot of test.100x100.svg in Google Chrome][4]][4]

[![Snapshot of test.100x100.svg in Google Chrome (after resize)][5]][5]

The same file with reduced width and heighttest.50x50.svg:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="50" height="50"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  version="1.2" baseProfile="tiny">
  <title>SVG Test</title>
  <desc>A test to illustrate the role of width and height</desc>
  <rect x="0" y="0" width="100" height="100"
   style="stroke-width:1;stroke:#888;fill:none"/>
  <circle cx="50" cy="50" r="30"
   style="stroke-width:1;stroke:#000;fill:none"/>
</svg>

Output in Google Chrome:

[![Snapshot of test.50x50.svg in Google Chrome][6]][6]

  1. The initial view size is reduced.
  2. The graphic output is clipped against width and height.

Next, I added a viewBox attribute – test.50x50.view100x100.svg:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="50" height="50"
 viewBox="0 0 100 100"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  version="1.2" baseProfile="tiny">
  <title>SVG Test</title>
  <desc>A test to illustrate the role of width and height</desc>
  <rect x="0" y="0" width="100" height="100"
   style="stroke-width:1;stroke:#888;fill:none"/>
  <circle cx="50" cy="50" r="30"
   style="stroke-width:1;stroke:#000;fill:none"/>
</svg>

Output in Google Chrome:

[![Snapshot of test.50x50.view100x100.svg in Google Chrome][7]][7]

The view is still reduced but the graphics contents is scaled to fit into view.

What happens when width and height are missing? – test.view100x100.svg:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
 viewBox="0 0 100 100"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  version="1.2" baseProfile="tiny">
  <title>SVG Test</title>
  <desc>A test to illustrate the role of width and height</desc>
  <rect x="0" y="0" width="100" height="100"
   style="stroke-width:1;stroke:#888;fill:none"/>
  <circle cx="50" cy="50" r="30"
   style="stroke-width:1;stroke:#000;fill:none"/>
</svg>

Output in Google Chrome:

[![Snapshot of test.view100x100.svg in Google Chrome][8]][8]

[![Snapshot of test.view100x100.svg in Google Chrome (after resize)][9]][9]

The output is scaled to fit in the browser view.

This can be compiled to the following rule of thumbs:

  1. The viewBox defines something like a clip space for the graphical contents.

  2. The width and height provide a hint for the output size the renderer should use.

  3. If viewBox is missing then it is set to viewBox="0 0 width height".

  4. If width and height are missing the renderer is free to chose it on its own.

  5. At least, either width and height, or viewBox should be provided.


The QSvgGenerator provides the following properties to control width, height, and viewBox:

size : QSize

This property holds the size of the generated SVG drawing

By default this property is set to QSize(-1, -1), which indicates that the generator should not output the width and height attributes of the element.

Note: It is not possible to change this property while a QPainter is active on the generator.

viewBox : QRectF

This property holds the viewBox of the generated SVG drawing

By default this property is set to QRect(0, 0, -1, -1), which indicates that the generator should not output the viewBox attribute of the element.

Note: It is not possible to change this property while a QPainter is active on the generator.

Upvotes: 0

Scheff&#39;s Cat
Scheff&#39;s Cat

Reputation: 20141

As the OP didn't provide a MCVE, I prepared one on my own:

#include <QtWidgets>
#include <QtSvg/QSvgGenerator>

const int w = 100, h = 100;

void renderTest(QPainter &qPainter, double s)
{
  qPainter.setTransform(QTransform().scale(s, s));
  qPainter.setFont(QFont("Arial", 12));
  qPainter.setPen(Qt::gray);
  qPainter.drawRect(0, 0, w, h);
  qPainter.setPen(Qt::black);
  qPainter.drawLine(0.1 * w, 0.5 * h, 0.9 * w, 0.5 * h);
  qPainter.drawLine(0.5 * w, 0.1 * h, 0.5 * w, 0.9 * h);
  qPainter.drawLine(0.45 * w, 0.2 * h, 0.55 * w, 0.2 * h);
  qPainter.drawLine(0.45 * w, 0.8 * h, 0.55 * w, 0.8 * h);
  qPainter.drawLine(0.2 * w, 0.45 * h, 0.2 * w, 0.55 * h);
  qPainter.drawLine(0.8 * w, 0.45 * h, 0.8 * w, 0.55 * h);
  qPainter.drawText(QPointF(0.51 * w, 0.49 * h), "0");
  qPainter.drawText(QPointF(0.51 * w, 0.79 * h), "-1");
  qPainter.drawText(QPointF(0.51 * w, 0.19 * h), "+1");
  qPainter.drawText(QPointF(0.21 * w, 0.49 * h), "-1");
  qPainter.drawText(QPointF(0.81 * w, 0.49 * h), "+1");
  qPainter.setPen(Qt::blue);
  qPainter.drawEllipse(QPointF(0.5 * w, 0.5 * h), 0.3 * w, 0.3 * h);
}

void renderSvgFile(const QString &qFilePath, double s)
{
  QSvgGenerator qSvgGen;
  qSvgGen.setFileName(qFilePath);
  qSvgGen.setSize(QSize(s * w, s * h));
  renderTest(QPainter(&qSvgGen), s);
}

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // render tests
  for (int i = 1; i <= 100; i *= 10) {
    const QString qFilePath = QString("testQSvgGen.%1%2.svg").arg(i).arg("0%");
    qDebug() << "Render" << qFilePath;
    renderSvgFile(qFilePath, i * 0.1);
  }
  // done
  return 0;
}

It generates three files:

  • testQSvgGen.10%.svg
  • testQSvgGen.100%.svg
  • testQSvgGen.1000%.svg

Preview in Windows Explorer (with SVG-Preview plug-in)

Although the images are written with different sizes, there is no noticable difference in the preview. The reason is that the preview scales the result to its own required resolution to fit the output into current Explorer icon size. (The same applies to the preview size on right size.)

View in Google Chrome Browser

In opposition to this, a Web Browser (Google Chrome in the above snapshot) considers the size setting of SVG.

These settings are

  • in testQSvgGen.10%.svg:
<svg width="3.52778mm" height="3.52778mm"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
 version="1.2" baseProfile="tiny">
  • in testQSvgGen.100%.svg
<svg width="35.2778mm" height="35.2778mm"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
 version="1.2" baseProfile="tiny">
  • in testQSvgGen.100%.svg
<svg width="352.778mm" height="352.778mm"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
 version="1.2" baseProfile="tiny">

A closer look into the generated SVG-Code uncovered that the 3 files look very similar in general. The scaling which I forced by

  qPainter.setTransform(QTransform().scale(s, s));

to adjust the graphics output to the intended image size is simply translated into a transform="matrix()" attribute with the scaling for each group (<g>).


So, I cannot confirm what OP complained:

The size set in QSvgGenerator::setSize() is considered in generated SVG files, and the browsers respect this setting (as expected).


Source code of generated testQSvgGen.10%.svg:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="352.778mm" height="352.778mm"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  version="1.2" baseProfile="tiny">
<title>Qt SVG Document</title>
<desc>Generated with Qt</desc>
<defs>
</defs>
<g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" >

<g fill="none" stroke="#a0a0a4" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(10,0,0,10,0,0)"
font-family="Arial" font-size="12" font-weight="400" font-style="normal" 
>
<rect x="0" y="0" width="100" height="100"/>
</g>

<g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(10,0,0,10,0,0)"
font-family="Arial" font-size="12" font-weight="400" font-style="normal" 
>
<polyline fill="none" vector-effect="none" points="10,50 90,50 " />
<polyline fill="none" vector-effect="none" points="50,10 50,90 " />
<polyline fill="none" vector-effect="none" points="45,20 55,20 " />
<polyline fill="none" vector-effect="none" points="45,80 55,80 " />
<polyline fill="none" vector-effect="none" points="20,45 20,55 " />
<polyline fill="none" vector-effect="none" points="80,45 80,55 " />
</g>

<g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(10,0,0,10,0,0)"
font-family="Arial" font-size="12" font-weight="400" font-style="normal" 
>
<text fill="#000000" fill-opacity="1" stroke="none" xml:space="preserve" x="51" y="49" font-family="Arial" font-size="12" font-weight="400" font-style="normal" 
 >0</text>
</g>

<g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(10,0,0,10,0,0)"
font-family="Arial" font-size="12" font-weight="400" font-style="normal" 
>
<text fill="#000000" fill-opacity="1" stroke="none" xml:space="preserve" x="51" y="79" font-family="Arial" font-size="12" font-weight="400" font-style="normal" 
 >-1</text>
</g>

<g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(10,0,0,10,0,0)"
font-family="Arial" font-size="12" font-weight="400" font-style="normal" 
>
<text fill="#000000" fill-opacity="1" stroke="none" xml:space="preserve" x="51" y="19" font-family="Arial" font-size="12" font-weight="400" font-style="normal" 
 >+1</text>
</g>

<g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(10,0,0,10,0,0)"
font-family="Arial" font-size="12" font-weight="400" font-style="normal" 
>
<text fill="#000000" fill-opacity="1" stroke="none" xml:space="preserve" x="21" y="49" font-family="Arial" font-size="12" font-weight="400" font-style="normal" 
 >-1</text>
</g>

<g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(10,0,0,10,0,0)"
font-family="Arial" font-size="12" font-weight="400" font-style="normal" 
>
<text fill="#000000" fill-opacity="1" stroke="none" xml:space="preserve" x="81" y="49" font-family="Arial" font-size="12" font-weight="400" font-style="normal" 
 >+1</text>
</g>

<g fill="none" stroke="#0000ff" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(10,0,0,10,0,0)"
font-family="Arial" font-size="12" font-weight="400" font-style="normal" 
>
<circle cx="50" cy="50" r="30"/>
</g>
</g>
</svg>

Upvotes: 1

Related Questions