Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save asizer/dc0279aa47abbc3a19a70f514dab765b to your computer and use it in GitHub Desktop.
Save asizer/dc0279aa47abbc3a19a70f514dab765b to your computer and use it in GitHub Desktop.
Dividing a Large SVG for Printing

Dividing a Large SVG for Printing

There is no native way to define page breaks within a large SVG for printing—it either gets scaled down to fit on a single page or gets cut off. There are many situations where pagination would be useful, such as maps, flow charts, or org diagrams. Really, any large, complicated graphic that becomes incomprehensible if scaled too small.

This pen demonstrates a way to generate custom pagination by re-using the same graphic within multiple SVG elements, using the viewBox attribute to define which part of the graphic should be visible each time. Media queries control which SVGs are visible on screen or when printed.

Print preview or print to file to see the results.

(Getting the browser to print a single iframe within Codepen is a little tricky. For Firefox, use "This Frame > Print" (no preview option, you'll have to print to file). For IE, select the entire content of the results frame, use "Print Preview", and then choose "As selected on screen". For Chrome, I've only been able to get it to work with Debug mode (i.e., without iframes). Opera's the worst: no print preview, and no way to print a single frame.)

Benefits:

  • You can explicitly define the breaks, including whether you want overlap in coverage between pages (standard for maps, and demonstrated here).

  • You can add extra graphics or labels (with some work to get them positioned correctly), such as the map continuation arrows I've added here.

Downsides:

  • You have to explicitly define the breaks, for each print medium you want to support, and create separate markup for each page in each layout. Here, I've defined print and landscape layouts for standard letter/A4 page sizes, but if you were to print on larger paper you would get sub-optimal breaks.

  • Browser's CSS print layout algorithms are not as advanced as screen layout. Chrome doesn't support viewport units in print (they should represent the size of the print region on each page) and was collapsing the SVGs down to zero if I used them, so instead I've defined the print size in inches, which further limits the adaptability to other page formats.

  • Even then, Chrome and IE's efforts to break large graphics across pages causes problems when I try to scale down the graphics slightly for portrait mode. Using defined width and height and the viewBox attribute wasn't enough, I had to specifically set page-break-inside: avoid.

P.S. The layout of the text labels are a little wonky in FF and IE because these browsers don't support alignment-baseline: central. You could achieve the same effect with dx/dy attributes on the text elements, but the markup is complex enough for an example.

A Pen by Amelia Bellamy-Royds on CodePen.

License.

<!DOCTYPE html>
<html>
<head>
<script src="./script.js"></script>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<figure class="svg-container">
<!-- The actual graphic is a 3:2 image
which will be wrapped in a scrolling
container on screen. -->
<svg class="screen" width="18in" height="12in"
viewBox="0 0 1800 1200">
<g id="graphic"><!--
Actual graphic goes here. I'm using a script to generate it.
--></g>
</svg>
<!-- For printing in landscape mode,
the graphic is divided into four
overlapping quadrants which will
each fit on a letter/A4 page
without scaling. The 1000*700 viewBox
is equivalent to 10in*7in of the
onscreen dimensions. -->
<svg class="print landscape" viewBox="0 0 1000 700">
<use xlink:href="#graphic" />
<defs>
<symbol id="arrow-left" overflow="visible"
viewBox="0 0 12 12"
preserveAspectRatio="xMinYMid meet">
<!-- left-pointing block arrow pointing to 0,0 point -->
<path class="arrow" d="M0 0L5 -5, 5 -3, 12 -3,
12 3, 5 3, 5 5Z" />
</symbol>
<symbol id="arrow-right" overflow="visible"
viewBox="0 0 12 12"
preserveAspectRatio="xMinYMid meet">
<!-- right-pointing block arrow pointing to 0,0 point -->
<path class="arrow" transform="rotate(180)"
d="M0 0L5 -5, 5 -3, 12 -3,
12 3, 5 3, 5 5Z" />
</symbol>
<symbol id="arrow-top" overflow="visible"
viewBox="0 0 12 12"
preserveAspectRatio="xMinYMid meet">
<!-- top-pointing block arrow pointing to 0,0 point -->
<path class="arrow" transform="rotate(90)"
d="M0 0L5 -5, 5 -3, 12 -3,
12 3, 5 3, 5 5Z" />
</symbol>
<symbol id="arrow-bottom" overflow="visible"
viewBox="0 0 12 12"
preserveAspectRatio="xMinYMid meet">
<!-- bottom-pointing block arrow pointing to 0,0 point -->
<path class="arrow" transform="rotate(-90)"
d="M0 0L5 -5, 5 -3, 12 -3,
12 3, 5 3, 5 5Z" />
</symbol>
</defs>
<g class="labels">
<text class="label" x="50%" y="50%">A</text>
<use xlink:href="#arrow-right" x="100%" y="50%"
width="100" height="100" />
<text class="pointer right" x="100%" y="50%" dx="-50">B</text>
<use xlink:href="#arrow-bottom" x="50%" y="100%"
width="100" height="100" />
<text class="pointer bottom" x="50%" y="100%" dy="-50">C</text>
</g>
</svg>
<svg class="print landscape" viewBox="800 0 1000 700">
<use xlink:href="#graphic" />
<g class="labels" transform="translate(800,0)">
<text class="label" x="50%" y="50%">B</text>
<use xlink:href="#arrow-left" x="0%" y="50%"
width="100" height="100" />
<text class="pointer left" x="0%" y="50%" dx="50">A</text>
<use xlink:href="#arrow-bottom" x="50%" y="100%"
width="100" height="100" />
<text class="pointer bottom" x="50%" y="100%" dy="-50">D</text>
</g>
</svg>
<svg class="print landscape" viewBox="0 500 1000 700">
<use xlink:href="#graphic" />
<g class="labels" transform="translate(0,500)">
<text class="label" x="50%" y="50%">C</text>
<use xlink:href="#arrow-right" x="100%" y="50%"
width="100" height="100" />
<text class="pointer right" x="100%" y="50%" dx="-50">D</text>
<use xlink:href="#arrow-top" x="50%" y="0%"
width="100" height="100" />
<text class="pointer top" x="50%" y="0%" dy="50">A</text>
</g>
</svg>
<svg class="print landscape" viewBox="800 500 1000 700">
<use xlink:href="#graphic" />
<g class="labels" transform="translate(800,500)">
<text class="label" x="50%" y="50%">D</text>
<use xlink:href="#arrow-left" x="0%" y="50%"
width="100" height="100" />
<text class="pointer left" x="0%" y="50%" dx="50">C</text>
<use xlink:href="#arrow-top" x="50%" y="0%"
width="100" height="100" />
<text class="pointer top" x="50%" y="0%" dy="50">B</text>
</g>
</svg>
<!-- For printing in portrait mode,
the graphic is scaled down slightly
to fit on two pages. Again,
the content of each page will
overlap slightly. -->
<svg class="print portrait" viewBox="0 0 1000 1200">
<use xlink:href="#graphic" />
<g class="labels">
<text class="label" x="50%" y="50%">A</text>
<use xlink:href="#arrow-right" x="100%" y="50%"
width="100" height="100" />
<text class="pointer right" x="100%" y="50%" dx="-50">B</text>
</g>
</svg>
<svg class="print portrait" viewBox="800 0 1000 1200">
<use xlink:href="#graphic" />
<g class="labels" transform="translate(800,0)">
<text class="label" x="50%" y="50%">B</text>
<use xlink:href="#arrow-left" x="0%" y="50%"
width="100" height="100" />
<text class="pointer left" x="0%" y="50%" dx="50">A</text>
</g>
</svg>
</figure>
</body>
var doc = document;
var g = doc.getElementById("graphic");
var svgNS = g.namespaceURI;
var r, t;
for (var i=0; i<18; i++){
for (var j=0; j<12; j++) {
r = doc.createElementNS(svgNS, "rect");
r.setAttribute("width", "80");
r.setAttribute("height", "80");
r.setAttribute("x", (i*100 + 10));
r.setAttribute("y", (j*100 + 10));
r.style.setProperty("fill-opacity", ((i*j + 1)%20)/20, null);
g.insertBefore(r, null);
t = doc.createElementNS(svgNS, "text");
t.setAttribute("x", (i*100 + 50));
t.setAttribute("y", (j*100 + 50));
t.setAttribute("class", "diagram")
t.textContent = [i,j];
g.insertBefore(t, null);
}
}
symbol, use, svg {
overflow: visible;
}
rect {
stroke: navy;
}
text.diagram {
stroke: white;
font-weight: bold;
fill: black;
text-anchor: middle;
alignment-baseline: central;
}
/* Screen styles */
figure.svg-container {
display: block;
overflow: scroll;
max-width: 90vw;
max-height: 90vh;
border:gray solid thin;
}
svg.print {
display: none;
}
@media print{
figure.svg-container {
display: inline;
overflow: auto;
border: none;
}
svg.screen {
display: none;
}
svg.print {
overflow: hidden;
border: thin lightgray solid;
padding: 0.5em;
-moz-box-sizing: border-box;
box-sizing: border-box;
page-break-inside: avoid;
break-inside: avoid;
}
}
@media print and (orientation: landscape){
svg.print.landscape {
display: block;
height: 7in;
width: 10in;
/* It would be better to set dimensions
using viewport units, but those don't
seem to work well with print layout */
}
}
@media print and (orientation: portrait){
svg.print.portrait {
display: block;
height: 9in;
width: 7.5in;
}
}
/* sheet labels and arrows */
g.labels {
opacity: 0.3;
font-family: sans-serif;
}
text.label {
alignment-baseline: central;
text-anchor: middle;
font-size: 3in;
fill: navy;
}
path.arrow {
fill: navy;
}
text.pointer {
font-size: 50px;
fill: lightseagreen;
}
text.pointer.top {
alignment-baseline: top;
text-anchor: middle;
}
text.pointer.bottom {
alignment-baseline: bottom;
text-anchor: middle;
}
text.pointer.left {
alignment-baseline: central;
text-anchor: start;
}
text.pointer.right {
alignment-baseline: central;
text-anchor: end;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment