Ceci est une ancienne révision du document !
Over the previous few instalments, I’ve been looking at Inkscape’s new multi-page feature, culminating in last month’s analysis of how it’s implemented within the SVG file. What I found was that the details of each page are stored in a proprietary ‘page’ element in the Inkscape namespace – appearing in the XML editor as <inkscape:page> – rather than in a standard SVG <view> element. This is a strange choice, in my opinion, as using the latter would have made the extra pages more accessible via a web browser.
Last month, I described a couple of methods by which a browser can be coaxed into showing the extra pages. The first used a little-known version of the URL fragment identifier to target each page by its viewBox values. This doesn’t require the file to be modified, but does need you to dig into the XML to extract the relevant numbers. The second approach modifies the file to manually add <view> elements, which is more work but does allow you to use a more semantic URL. If you haven’t read the previous article, I strongly advise doing so before proceeding, as this one directly builds on that information.
Our aim here is to make it easier to access all the pages of an Inkscape document via a web browser. One thing browsers do, which Inkscape doesn’t, is to run JavaScript. So, by adding a small JS function that will be executed when the file is opened in a browser, we can automatically create <view> elements from each of the <inkscape:page> elements, without the need to manually copy-and-paste coordinates. This is obviously useful when there are a lot of pages to consider, but even for files with just a couple of pages, the ability to just paste the same snippet of code into any file without having to modify it each time makes this approach arguably simpler than manually creating the <view> elements yourself.
We’ll be using the same multi-page SVG file from last month, consisting of four pages arranged horizontally, with a different background color and content in each.
Having created a multi-page Inkscape file, the first thing to do is to save it to disk and open it inside a web browser. This will display only the first page, as expected. As we progress through this code, we’ll log some data to the developer console, so open the dev tools in the browser (typically by pressing F12), and switch to the ‘Console’ tab. Below is how it looks in Firefox on my Ubuntu Mate machine.
I’ve talked about adding JavaScript code to Inkscape files in the past, so I won’t go into depth about the options here. For what we’re trying to achieve, we just want a simple chunk of script embedded directly into the file which will run at load time. The easiest way to create this within Inkscape is as follows: • Open the File > Document Properties… dialog • Switch to the ‘Scripting’ tab. • Within that tab, choose the ‘Embedded scripts’ tab. • Click the ‘+’ button below the (empty) list of embedded scripts. • You should see a new entry appear in the list with an arbitrary ID. • Ensure that entry is selected. • Write your code in the ‘Content’ box below. • You can save (Ctrl-S) as you develop, without having to close the dialog. • Whenever you save the file, manually reload in the browser (F5) to see the effect.
Let’s begin by logging out the existing <inkscape:page> elements to the console using this code (top right).
It’s not essential to fully understand the workings of this code in order to use it, but I’ll describe it anyway for those who are interested. The first two lines simply set up variables for the namespaces we’ll need. We won’t be using the SVG namespace just yet, but we will require it soon, so this is a good time to introduce it. The third line uses one of the browser’s built-in functions to find all the ‘page’ elements in the Inkscape namespace, and assign them to a variable as a ‘collection’.
For historical and technical reasons, a ‘collection’ is very similar to a JS array, but not quite actually the same thing. These days, JavaScript’s array functions are pretty powerful, so we would really like to create an array from the content of our collection so we can use those functions. The ‘array.from(pages)’ part does exactly that, iterating over each entry in the collection to build up a temporary array. We can then use the ‘forEach’ array method to execute a block of code for each entry in the array.
The forEach() method expects to have one parameter, and that parameter should be a function. We could build a function elsewhere and pass its name in here, but it’s more common in JS to see ‘anonymous’ functions used for small tasks like this one. In this case, the anonymous function is called once for each element in the array, and, each time it’s called, it’s passed the current element (in a variable we’ve called ‘page’) and the index of that element in the array (‘idx’). The ‘⇒’ syntax is used for so-called arrow functions, and can be thought of largely as an alternative to the ‘function’ keyword you might be more familiar with if you haven’t gone near JS for a while.
Inside curly braces (‘{…}’) we have the body of the function – just a single line that uses the console.log() function to print the idx and pages values to the developer console. Finally, the last line closes not only the function body, but also the end of the forEach() method.
If you’re not very familiar with JavaScript, then do take a few minutes to try to understand the code above. It’s particularly useful to examine where each pair of brackets (‘(…)’) and braces (‘{…}’) start and end, and what content is inside each one.
With that code in place, save the file and reload it in the web browser, and you should see something in the console looking similar to this:
The green numbers are the idx values, starting at zero because… well, there are good reasons, but this is not the place to go into them. Suffice to say that most programming languages use zero-based indexing for things like arrays, and JavaScript is no exception.
Following each green number, you can see an XML representation of each SVG element in the array – the four <inkscape:page> nodes we’re interested in. Each node also has a whole load of other baggage attached to it in the JS world, and you can see much of that by expanding the small triangle next to each one. In practice, we don’t need any of that for our task, so feel free to leave that triangle unexpanded, or to collapse it back down again if curiosity does get the better of you.
So far, our code hasn’t really achieved very much – just printing the index, and the same nodes we can see in Inkscape’s XML editor. But now that we’ve got a way to grab a handle to each <page> element, we can start to pull them apart to get to the individual details we’ll need. We’re going to want to extract the x, y, width, and height values. These are stored as ‘attributes’ on the element, and can be retrieved using the getAttribute() method. Let’s add four lines after the console.log() to retrieve these values, and assign each to a JS variable (x, y, w, h).
const x = page.getAttribute('x');
const y = page.getAttribute('y');
const w = page.getAttribute('width');
const h = page.getAttribute('height');
We could log them out at this point, but ultimately we’re going to want these formatted into a space-separated string for use in a viewBox attribute. One additional line of code will do this for us, and then we’ll log the result:
const viewBox = `${x} ${y} ${w} ${h}`;
console.log(viewBox);
Be very careful! The ‘const viewBox’ line uses a feature of JS called ‘template strings’. These are delimited by backticks (`) rather than normal quotes or apostrophes. Using this method lets us put our variables directly into the string using the ${} notation, and the JS engine will swap them out for the variables’ values when the code runs. If your log ends up containing the actual ${} string, then you’ve used the wrong type of quotes, and will need to search your keyboard again for the easily-overlooked backtick character.
All those previous 6 lines should have been added after the existing console.log() and before the closing brace. If you’ve done it correctly, saving the file and reloading it in the browser should show something like this – similar to the previous output, but with the viewBox values displayed after each XML node (see image below).
Now we’ve got all the details we need, it’s time to create a new <view> element for each page. For this, we need to use the document.createElementNS() function – the NS on the end referring to the fact that this lets us specify a namespace for our new element. This is where our earlier assignment of the svgNS variable will be used, ensuring that we end up with what is effectively an <svg:view> element, rather than an <inkscape:view> element, or anything else.
Each <view> element will also require two attributes. One is the ‘viewBox’ for which we’ve already prepared the value. The other is an ‘id’ attribute which will define the string we have to append to our URL to view this page. For the sake of simplicity, we’re just going to name the pages ‘page-1’, ‘page-2’, and so on, using another JS template string in which we’ll also add 1 to the value to rid ourselves of those pesky zero-indexed numbers. Therefore, to create our new element, and set both attributes, we’ll need these three lines of code:
const view = document.createElementNS(svgNS, 'view');
view.setAttribute('id', `page-${idx + 1}`);
view.setAttribute('viewBox', viewBox);
There’s just one thing left to do. Although we’ve created our new <view> element, it currently just lives as an object in the JS world, and needs to be inserted into the browser’s internal model of the document. We’ll insert each <view> as a child of the corresponding <inkscape:page> element in order to keep things neatly together. This last line will do the job:
page.appendChild(view);
With that, the final code should look like this – albeit that you can’t actually see all the lines at once in Inkscape’s unfortunately inflexible editor field (top right).
You can optionally remove the console.log() lines if you wish, as they’re purely there for educational and debugging purposes, and have no effect on the actual operation of the code. Now that the code is done, how do you actually use it? Simply load the SVG file directly into your browser, and append ‘#page-2’ to the end of the URL to view the second page. I’m sure you can work out the syntax for the other pages. Entering an invalid ID (e.g. ‘#page-22’) simply causes the browser to show the first page.
There we have it: a small chunk of JS that you can add to any multi-page Inkscape file to make the additional pages available via a web browser. What more could you possibly want?
Quite a bit as it happens. This code is good, but it suffers from a significant limitation: in order for the browser to execute it, the SVG file has to be loaded directly (or within an <object> tag). Most common ways of including SVG files in a web page – via an <img> tag or a CSS url() function – are deliberately prevented from executing JavaScript. This significantly limits the usefulness of this code – at least as it stands.
Another issue is that we’ve just given each page a rather generic ID. Within Inkscape, it’s possible to name each page – wouldn’t it be nicer if we could use those names when referring to each page, rather than just page-1 and page-2?
Next month, I’ll extend this code a little further to address both these issues. It still won’t give the simple, seamless experience that we could have had if Inkscape natively created named views, but it’s better than being stuck with multi-page files that can display only the first page!