Click here to Skip to main content
15,867,488 members
Articles / Desktop Programming / WPF

SharpVectors - SVG# Reloaded: An Introduction

Rate me:
Please Sign up or sign in to vote.
4.98/5 (33 votes)
17 Nov 2010BSD10 min read 203.3K   21.6K   101   53
A C# library for converting SVG to WPF and viewing SVG files in WPF Applications

SharpVectors 1.0 Beta

Introduction

The SVG# Reloaded project aims to provide libraries and tools to parse, convert and view the SVG on Windows, especially on the Windows Presentation Foundation platform.

Background

A few months ago, I was looking for a library to convert SVG diagrams to XAML for use in an WPF application. I initially thought some of the available open source projects could easily handle the project, but all failed. The next step was to look for a commercial library, the only one found also failed to handle the project so I have no choice but to create one since I have already told my boss it should not be a problem.

I started with the SharpVectorGraphics (aka SVG#), and hence the name SVG# Reloaded.

Project Information

This project is currently hosted on Codeplex.

The SharpVectors 1.0 is currently placed in the Beta stage to allow for more time to work on the documentation, fix bugs and accept suggestions. The final release is schedule for January, 2011.

The SVG to XAML/WPF conversion is complete enough and I have used it in the real world application.

Quick Start

The SVG# Reloaded is packaged in several .NET assemblies. The library itself is divided into two parts; the data handling (parsing and modeling) and the rendering. This makes it possible to have different rendering engines and currently we have the WPF based rendering engine and the GDI+ based rendering engine (an improvement over the original SVG# rendering but less complete compared with the WPF rendering engine).

WPF Package

For the WPF package, this is the dependency diagram:

SharpVectors.Converters.png

  • SharpVectors.Core: This is the core library defining all the required interfaces as defined by the W3C SVG specifications, including the events and the style sheets interfaces.
  • SharpVectors.Dom: This is an extension to the .NET Framework implementation of the XML DOM (XmlDocument) to support the SVG.
  • SharpVectors.Css: This is an extension to the .NET Framework DOM to support style sheets interfaces.
  • SharpVectors.Model: This is the main implementation of the SVG DOM and interfaces. This is the parser of the SVG documents, reducing the SVG file to memory model of .NET objects. This and the above assemblies do not depend on GDI+ or the WPF libraries.
  • SharpVectors.Runtime: This is an optional WPF library providing SVG object specific information at the runtime. This includes conversion classes to handle GlyphTypeface.FontUri, which will otherwise be hard-coded with the full path information that may not work on the user's machine, classes to handle embedded images etc.
  • SharpVectors.Rendering.Wpf: This is WPF library, which handles the rendering of the SVG object to the WPF drawing objects.
  • SharpVectors.Converters: This is WPF library, which uses the SharpVectors.Rendering.Wpf library to perform actual conversion for viewing.

NOTE: The current WPF package will only generate low level objects; Geometry and Drawing. Higher level objects like Shape are not generated, so there is no support for Silverlight.

GDI+ Package

The dependency diagram is shown below:

SharpVectorsGdi.png
  • SharpVectors.Rendering.Gdi: This is GDI+ library, which handles the rendering of the SVG object to the System.Drawing objects.

Using the Code

In this section, we will provide a number of illustrative examples to demonstrate the use of this library.

These sample codes are divided into two parts; converters and controls (including markup extensions). Complete VS.NET solution for these parts are provided for download, and here is a little guide to using these projects/solutions:

Converter Samples: SharpVectorsSamples

  1. These are the SVG to WPF converter samples used for the tutorials, a total of 8 simple projects (in C# and VB.NET)
  2. For the current assembly references in the projects to be automatically resolved, extract these samples to the following directory:
    SharpVectorsReloaded\Samples\SharpVectorsSamples

Markup Extension and Controls Samples: SharpVectorsControlSamples

  1. These are the markup extensions and controls samples used for the tutorials, a total of 6 projects.
  2. For the current assembly references in the projects to be automatically resolved, extract these samples to the following directory:
    SharpVectorsReloaded\Samples\SharpVectorsControlSamples

Converters: Overview

The SVG to WPF conversion is the main use of this SVG# Reloaded library currently. The other uses will be improved with time.

The following is a diagram showing all the available converters.

Converters.png
  • FileSvgConverter: This converts the SVG file to the corresponding XAML file, which can be viewed in WPF application. The root object in the converted file is DrawingGroup.
  • FileSvgReader: This converts the SVG file to DrawingGroup and can optionally save the result to a file as XAML.
  • ImageSvgConverter: This converts the SVG file to static or bitmap image, which can be saved to a file.
  • DirectorySvgConverter: This converts a directory (and optionally the sub-directories) of SVG files to XAML files in a specified directory, maintaining the original directory structure.

Now, the base class SvgConverter defines the following common properties:

SvgConverter.png
  • DrawingSettings:This is defined by a class, WpfDrawingSettings, in the SharpVectors.Rendering.Wpf assembly and provides the available drawing/rendering options for the conversion.

    DrawingSettings.png

    All the properties of this class are well documented. The most important properties are:

    • CultureInfo: This is the culture information used for the text rendering, and it is passed to the FormattedText class. The default is the English culture.
    • IncludeRuntime: This determines whether the application using the output of the conversion will link to the SharpVectors.Runtime.dll. The default is true, set it to false if you do not intend to use the runtime assembly.
    • TextAsGeometry: This determines whether the texts are rendered as path geometry. The default is false. The vertical text does not currently support this option. Set this to true if you do not want to use the runtime assembly, so that font path will not be included in the output.
  • SaveXaml: Determines whether to save conversion output to XAML format.
  • SaveZaml: Determines whether to save conversion output to ZAML format, which is a G-zip compression of the XAML format, and similar to the SVGZ (for SVG).
  • UseFrameXamlWriter: Determines whether the use the .NET Framework version of the XAML writer when saving the output to the XAML format. The default is false, and a customized XAML writer is used.

Converters: Illustrative Example

We will create a simple console application for illustration, using the following sample SVG file (named, Test.svg):

XML
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="6cm" height="5cm" viewBox="0 0 600 500"
xmlns="http://www.w3.org/2000/svg" version="1.1">
    <!-- Outline the drawing area with a blue line -->
    <rect x="1" y="1" width="598" height="498" 
          fill="none" stroke="blue"/>
    <circle cx="300" cy="225" r="100" fill="red"/>
    <text x="300" y="480" font-family="Verdana" 
          font-size="35" text-anchor="middle">
        Sample Text 1
    </text>
</svg>
  1. Create a console .NET 3.5 framework application, name it FileSvgConverterSample
  2. Add the following WPF framework assemblies:
    • WindowBase.dll
    • PresentationCore.dll
    • PresentationFramework.dll
  3. Add the following SVG# Reloaded assemblies
    • SharpVectors.Converters.dll
    • SharpVectors.Core.dll
    • SharpVectors.Css.dll
    • SharpVectors.Dom.dll
    • SharpVectors.Model.dll
    • SharpVectors.Rendering.Wpf.dll
    • SharpVectors.Runtime.dll
  4. Modify the generated code to the following:

    For the C# Application:

    C#
    using System;
    
    using SharpVectors.Converters;
    using SharpVectors.Renderers.Wpf;
    
    namespace FileSvgConverterSample
    {
        class Program
        {
            static void Main(string[] args)
            {
                // 1. Create conversion options
                WpfDrawingSettings settings = new WpfDrawingSettings();
                settings.IncludeRuntime = false;
                settings.TextAsGeometry = true;
    
                // 2. Select a file to be converted
                string svgTestFile = "Test.svg";
    
                // 3. Create a file converter
                FileSvgConverter converter = new FileSvgConverter(settings);
                // 4. Perform the conversion to XAML
                converter.Convert(svgTestFile);
            }
        }
    }

    For the VB.NET Application:

    VB.NET
    Imports SharpVectors.Converters
    Imports SharpVectors.Renderers.Wpf
    
    Module MainModule
    
        Sub Main()
            ' 1. Create conversion options
            Dim settings As WpfDrawingSettings = New WpfDrawingSettings()
            settings.IncludeRuntime = False
            settings.TextAsGeometry = True
    
            ' 2. Select a file to be converted
            Dim svgTestFile As String = "Test.svg"
    
            ' 3. Create a file converter
            Dim converter As FileSvgConverter = New FileSvgConverter(settings)
            ' 4. Perform the conversion to XAML
            converter.Convert(svgTestFile)
        End Sub
    
    End Module
  5. Compile and run the program. An XAML file, Test.xaml, will be generated in the working directory. The output will look like this when viewed (this is the illustrative sample for FileSvgReader):

    SvgReader.png

Markup Extensions: Overview

These are WPF markup extensions or type converters for handling the SVG files in WPF applications.

Currently, the SVG# Reloaded provides one markup extension, SvgImageExtension, which converts an SVG source file to a DrawingImage.

SvgImageExtension.png
  • As shown in the diagram above, all the rendering settings are available on this markup extension as properties.
  • The main property here is the SvgImageExtension.Source, which is the path to the SVG file, and the file itself can be located in the following:
    • Web/Internet: The path in this case is the HTTP, FTP, etc. scheme URI of the file.
    • Local Computer Disk: The path is the absolute or the relative URI to the SVG file.
    • Resources: The path is the Microsoft Pack URI of the SVG resource file.

Markup Extensions: Illustrative Example

For the illustration, we will create a simple WPF Application shown below, each image displayed is an SVG file in the WPF Image control:

SvgImageSample.png
  1. Create a .NET 3.5 WPF Application in C# or VB.NET, we will name it SvgImageSample and rename the main window, MainWindow.
  2. As above, add the WPF package of the SVG# Reloaded assemblies.
  3. Modify the generated XAML code to the following (the C# or VB.NET codes are not modified):
    XML
    <Window x:Class="SvgImageSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="SvgImage Sample" Height="346" Width="409" Background="White">
        <Window.Resources>
            <ResourceDictionary> 
                <sys:String x:Key="WebFile">
                    http://upload.wikimedia.org/wikipedia/commons/c/c7/SVG.svg
                </sys:String>
            </ResourceDictionary>
        </Window.Resources>
        <DockPanel>
            <TabControl SelectedIndex="0" OverridesDefaultStyle="False">
                <TabItem>
                    <TabItem.Header>By Local File</TabItem.Header>
                    <TabItem.Content>  
                        <!-- 1. Load local SVG file, using the constructor -->
                        <Image Source="{svgc:SvgImage ../Test1.svg}"/>        
                    </TabItem.Content>
                </TabItem>
                <TabItem>
                    <TabItem.Header>By Web File</TabItem.Header>
                    <TabItem.Content>    
                        <!-- 2. Load Web SVG file, using the constructor. -->
                        <Image Source="{svgc:SvgImage {StaticResource WebFile}}"/>
                    </TabItem.Content>
                </TabItem>
                <TabItem>
                    <TabItem.Header>By Local/Resource File</TabItem.Header>
                    <TabItem.Content>
                        <!-- 3. Load local, using the constructor and a property. -->
                        <Image Source="{svgc:SvgImage Test2.svg, TextAsGeometry=True}"/>
                    </TabItem.Content>
                </TabItem>
                <TabItem>
                    <TabItem.Header>By Sub-Folder File</TabItem.Header>
                    <TabItem.Content>
                        <!-- 4. Load local, using constructor - works, but not 
                         recommended syntax, simply use /SubFolder/Test3.svg  -->
                        <Image Source="{svgc:SvgImage \\SubFolder\\Test3.svg}"/>
                    </TabItem.Content>
                </TabItem>
                <TabItem>
                    <TabItem.Header>By Local/Resource File</TabItem.Header>
                    <TabItem.Content>
                        <!-- 5. Load resource, using the constructor. -->
                        <Image Source="{svgc:SvgImage /Resources/Test.svg}"/>
                    </TabItem.Content>
                </TabItem>
                <TabItem>
                    <TabItem.Header>By Properties</TabItem.Header>
                    <TabItem.Content>
                        <!-- 6. Load resource, using property. -->
                        <Image Source="{svgc:SvgImage Source=/Resources/Test.svg}"/>
                    </TabItem.Content>
                </TabItem>
            </TabControl>
        </DockPanel>
    </Window>

    NOTE: As shown above, the local relative path and resource path are similar, and in this case, the local directory is searched at runtime, and if no such file is found, it is assumed to be in the resource.

  4. Compile and run the program.

Viewbox Control: Overview

SvgViewbox control is a WPF Viewbox derived control for viewing the SVG files in WPF applications, and allowing you to use all the Viewbox decorator properties.

SvgViewbox.png

It wraps a drawing canvas instead of image, so will support interactivity when added to future release of the drawing canvas.

The main property is the SvgViewbox.Source, which is an System.Uri specifying the path to the SVG file.

Viewbox Control: Illustrative Example

For the illustration, we will create the following WPF sample application:

SvgViewboxSample.png
  1. Create a WPF application project, named SvgViewboxSample, similar to the steps in previous sections.
  2. Modify the XAML of the main window to the following:
    XML
    <Window x:Class="SvgViewboxSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
        Title="SvgViewbox Sample" Height="346" Width="430" Background="White">
        <DockPanel>
            <TabControl SelectedIndex="0" OverridesDefaultStyle="False">
                <TabItem>
                    <TabItem.Header>Web File</TabItem.Header>
                    <TabItem.Content>
                        <!-- 1. Load Web SVG file -->
                        <svgc:SvgViewbox Source=
                "http://croczilla.com/bits_and_pieces/svg/samples/tiger/tiger.svg"/>
                    </TabItem.Content>
                </TabItem>
                <TabItem>
                    <TabItem.Header>Local File 1</TabItem.Header>
                    <TabItem.Content>
                        <!-- 2. Load local SVG file -->
                        <svgc:SvgViewbox Source="../Test1.svg"/>
                    </TabItem.Content>
                </TabItem>
                <TabItem>
                    <TabItem.Header>Local File 2</TabItem.Header>
                    <TabItem.Content>
                        <!-- 3. Load local SVG file -->
                        <svgc:SvgViewbox Source="Test2.svg" TextAsGeometry="True"/>
                    </TabItem.Content>
                </TabItem>
                <TabItem>
                    <TabItem.Header>Sub-Folder File</TabItem.Header>
                    <TabItem.Content>
                        <!-- 4. Load local sub-folder SVG file  -->
                        <svgc:SvgViewbox Source="\SubFolder\Test3.svg"/>
                    </TabItem.Content>
                </TabItem>
                <TabItem>
                    <TabItem.Header>Resource File</TabItem.Header>
                    <TabItem.Content>
                        <!-- 5. Load Resource SVG file -->
                        <svgc:SvgViewbox Source="/Resources/Test.svg" 
    					Stretch="Uniform"/>
                    </TabItem.Content>
                </TabItem>
            </TabControl>
        </DockPanel>
    </Window> 
  3. Compile and run the program.

Canvas Control: Overview

SvgCanvas control is a WPF Canvas derived control for viewing the SVG files in WPF applications, and allowing you to use all the canvas properties.

SvgCanvas.png
  • It derives from a drawing canvas instead of the generic canvas control, so will support interactivity when added to future release of the drawing canvas.
  • The main property is the SvgCanvas.Source, which is an System.Uri specifying the path to the SVG file.

Canvas Control: Illustrative Example

For the illustration, we will create the following WPF sample application:

SvgCanvasSample.png
  1. Create a WPF application project, named SvgCanvasSample, similar to the steps above.
  2. Modify the XAML of the main window to the following:
    XML
    <Window x:Class="SvgCanvasSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
        Title="SvgCanvas Sample" Height="332" Width="413" Background="White">
        <DockPanel>
            <TabControl SelectedIndex="0" OverridesDefaultStyle="False">
                <TabItem>
                    <TabItem.Header>Web File</TabItem.Header>
                    <TabItem.Content>
                        <ScrollViewer CanContentScroll="False" 
                           VerticalScrollBarVisibility="Auto" 
                           HorizontalScrollBarVisibility="Auto">                        
                            <!-- 1. Load Web SVG file -->
                            <svgc:SvgCanvas Source=
           "http://croczilla.com/bits_and_pieces/svg/samples/butterfly/butterfly.svg"/>
                        </ScrollViewer>
                    </TabItem.Content>
                </TabItem>
                <TabItem>
                    <TabItem.Header>Local File 1</TabItem.Header>
                    <TabItem.Content>
                        <ScrollViewer CanContentScroll="False" 
                           VerticalScrollBarVisibility="Auto" 
                           HorizontalScrollBarVisibility="Auto">                        
                            <!-- 2. Load local SVG file -->
                            <svgc:SvgCanvas Source="../Test1.svg"/>
                        </ScrollViewer>
                    </TabItem.Content>
                </TabItem>
                <TabItem>
                    <TabItem.Header>Local File 2</TabItem.Header>
                    <TabItem.Content>
                        <ScrollViewer CanContentScroll="False" 
                           VerticalScrollBarVisibility="Auto" 
                           HorizontalScrollBarVisibility="Auto">
                            <!-- 3. Load local SVG file -->
                            <svgc:SvgCanvas Source="Test2.svg"/>
                        </ScrollViewer>
                    </TabItem.Content>
                </TabItem>
                <TabItem>
                    <TabItem.Header>Sub-Folder File</TabItem.Header>
                    <TabItem.Content>
                        <ScrollViewer CanContentScroll="False" 
                           VerticalScrollBarVisibility="Auto" 
                           HorizontalScrollBarVisibility="Auto">
                            <!-- 4. Load local sub-folder SVG file  -->
                            <svgc:SvgCanvas Source="\SubFolder\Test3.svg"/>
                        </ScrollViewer>
                    </TabItem.Content>
                </TabItem>
                <TabItem>
                    <TabItem.Header>Resource File</TabItem.Header>
                    <TabItem.Content>
                        <ScrollViewer CanContentScroll="False" 
                           VerticalScrollBarVisibility="Auto" 
                           HorizontalScrollBarVisibility="Auto">
                            <!-- 5. Load Resource SVG file -->
                            <svgc:SvgCanvas Source="/Resources/Test.svg"/>
                        </ScrollViewer>
                    </TabItem.Content>
                </TabItem>
            </TabControl>
        </DockPanel>
    </Window>  
  3. Compile and run the program.

Sample Applications

The SVG# Reloaded comes with some applications. The following two may be of interest to you.

WpfTestSvgSample

This is an application for browsing directory (recursively) of SVG files.

TestApplication.png

WpfW3CSvgTestSuite

This is an application for viewing the W3C Test Suite compliant results. It has two panes: top and bottom. The top pane is the generated WPF output, the bottom pane is the W3C expected output image.

By the test results, SVG# Reloaded is the most complete SVG reader for WPF!

You can download the strip-down test suite from the project site, since the size is still large for CodeProject file upload limit.

TestSuiteApplication.png

Points of Interest

Many parts of WPF are still work in progress. For instance, vertical texts (as in Japanese), which are supported well in GDI+ are still missing. Previously, the team claimed it was due to lack of time, but with WPF 4.0 released without it, one cannot tell their next excuse!

Credits

The SVG# Reloaded uses source codes from articles and other open source projects without which this might not be possible. We wish to acknowledge and thank the authors of these great articles and projects:

Conclusion

This is the most complete library available to handle the conversion of SVG files to WPF, that I know of. However, SVG itself is a large and complex specification, and even though one will expect the most modern framework, WPF, to easily handle this decade old specification, it is not able.

In this release, the conversion to WPF is complete enough, however, the controls will need some improvements before the final release.

In future version releases, I will work further on the support of the specifications. Any help in this direction is highly welcomed.

History

  • November 16, 2010: Initial release of SharpVectors 1.0 Beta
  • November 17, 2010: Improved formatting and added credits section

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Engineer
Japan Japan
Systems Engineer

Comments and Discussions

 
BugSharpVectors and Visio export Pin
HHGM5-Mar-18 3:05
HHGM5-Mar-18 3:05 
BugProblem about the specific region of Svg on SvgCanvas which is not clickable Pin
Member 1295903128-Aug-17 3:40
Member 1295903128-Aug-17 3:40 
QuestionConvert SVG to MemoryStream ? Pin
anemos_782-Mar-17 12:17
anemos_782-Mar-17 12:17 
AnswerRe: Convert SVG to MemoryStream ? Pin
Garth J Lancaster2-Mar-17 17:11
professionalGarth J Lancaster2-Mar-17 17:11 
QuestionBug found (or may be my mistake) Pin
cob_25812-Mar-16 23:07
cob_25812-Mar-16 23:07 
QuestionThe proper way to use wpf binding Pin
zork7627-Jan-16 1:01
zork7627-Jan-16 1:01 
QuestionPrint Vector not raster Pin
blastnsmash23-Sep-15 12:06
blastnsmash23-Sep-15 12:06 
Is there any way to use SharpVector library to print vector graphics? For example, take a drawinggroup in WPF and print it? Sending this to a xpsdocument always rasterizes the print.
QuestionHow to change Color and text font properties of a SVG file while loading svg file Pin
Sandesh Nayak22-Jul-15 1:18
Sandesh Nayak22-Jul-15 1:18 
BugGetting exception from WpfTextRendering class Pin
Sandesh Nayak22-Jul-15 0:08
Sandesh Nayak22-Jul-15 0:08 
QuestionRadial Gradients Pin
John Trinder26-Aug-14 16:04
professionalJohn Trinder26-Aug-14 16:04 
AnswerRe: Radial Gradients Pin
Paul Selormey28-Mar-15 21:25
Paul Selormey28-Mar-15 21:25 
GeneralMy vote of 1 Pin
mahessel20-Jul-13 2:59
mahessel20-Jul-13 2:59 
GeneralExcellent Pin
Al Moje26-Jan-12 15:54
Al Moje26-Jan-12 15:54 
BugGreat code, but.... Pin
zfeld15-Dec-11 5:19
zfeld15-Dec-11 5:19 
QuestionFantastic Pin
Dave Kerr30-Nov-11 3:46
mentorDave Kerr30-Nov-11 3:46 
AnswerRe: Fantastic Pin
Paul Selormey30-Nov-11 14:06
Paul Selormey30-Nov-11 14:06 
QuestionClarification regarding the licensing [modified] Pin
Bharat Mane11-Jul-11 21:14
Bharat Mane11-Jul-11 21:14 
AnswerRe: Clarification regarding the licensing Pin
Paul Selormey11-Jul-11 21:19
Paul Selormey11-Jul-11 21:19 
GeneralRe: Clarification regarding the licensing Pin
Bharat Mane12-Jul-11 1:15
Bharat Mane12-Jul-11 1:15 
GeneralAn Error occurred while rebuilding library in .NET 4.0 Pin
Bharat Mane26-May-11 21:02
Bharat Mane26-May-11 21:02 
GeneralRe: An Error occurred while rebuilding library in .NET 4.0 Pin
Paul Selormey11-Jul-11 21:22
Paul Selormey11-Jul-11 21:22 
GeneralRe: An Error occurred while rebuilding library in .NET 4.0 Pin
Paul Selormey9-Mar-13 3:59
Paul Selormey9-Mar-13 3:59 
GeneralRe: An Error occurred while rebuilding library in .NET 4.0 Pin
Bharat Mane2-Apr-13 22:49
Bharat Mane2-Apr-13 22:49 
GeneralIs there any way to copy part of SVG? Pin
Bharat Mane17-May-11 19:27
Bharat Mane17-May-11 19:27 
GeneralRe: Is there any way to copy part of SVG? Pin
Paul Selormey17-May-11 19:38
Paul Selormey17-May-11 19:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.