How to convert an existing Google Slides/Keynote/PowerPoint theme to Slideas Markdown Presentation template?


In this tutorial, we are going to see the various aspects of the migration from a template in Powerpoint format (.pptx) to Slideas.

Note: You can also use this technique to convert a Keynote template by exporting it first to Powerpoint (File -> Export to -> Powerpoint)

There are several ways to create themes for Slideas: 1. Duplicate an existing one and make modifications. 2. Use the theme framework and build on top 3. Create a theme from scratch

We are going to use the second option which does already a lot of works while letting us enough freedom.

The up to date and full source code for the theme made in this tutorial can be found in the Github Themes Repository

Preparation

Finding a theme candidate

For this tutorial, I am going to use the Quickly Theme from Slidescarnival

Slidescarnival provides a wide set of multi-purposes themes under the Creative Commons License Attribution 4.0.

This template is an interesting one because it will involve different concepts:

  • Custom fonts
  • Multiple palettes
  • Vector images

The original template, let’s try to be as close as possible in our Slideas version.

Required tools

  • lesscss It is a CSS preprocessor that will make our life much easier when writing CSS code.
  • Powerpoint, 1-month trial version: only required to export vector assets.

Preparing Assets

We are lucky, there is one slide providing directly information about colours and fonts.

Fonts

We are going to get the 2 following fonts from Google Fonts:

It is recommended to use the .woff2 font format which is well supported by Slideas and offers an interesting compression ratio.

We will need to download our copy of the fonts and bundle them with the theme. You should avoid using online versions because Slideas can be used offline.

To facilitate the download of the fonts, you can use google-webfonts-helper.

Graphics

Bitmap Images

We do not need this operation in this example but it is interesting to know how to do it. .pptx is actually a .zip file. To get included bitmap images, you need to follow these steps:

  1. Rename the Quickly.pptx file to Quickly.zip.
  2. Extract the content to a Quickly folder.
  3. Go to Quickly\ppt\media folder.

You will see the following images:

Vector Graphics

This is one of the most complex parts because it involves several tools and steps. Again, it is just documented in case you have an existing template having such vectors. In most of the cases, you can skip this step.

Extracting the Vector Shape in EMF format

For this step, we will need to use PowerPoint. If you know an alternative way to extract shapes from a .pptx without using PowerPoint, let me know.

Screenshots are from the Windows version of Powerpoint but Mac version is similar.

  1. Open the Quickly.pptx file in PowerPoint.
  2. Go to View -> Slide Master
  3. Select the layout containing the shape you want to export.
  4. Select the shape, right click and select the Save as picture option
  5. Save to .emf format

Converting EMF to SVG

Now that we have the shape in .emf, we need to convert it to a more common format: SVG.

There are plenty of converters available, most of them are free. I can suggest https://inkscape.org/ if you already have it installed or https://cloudconvert.com/ that we are going to use.

This step is pretty simple, upload your .emf, click on convert and download the result.

Optimising the SVG

By default, the converted SVG is not optimized. Even if this is something optional, it is better to optimize your SVGs. They will load faster and will be smaller.

For this exercise, SVGOMG is my preferred tool. As you can see in the screenshot below

Defining Theme Structure

Before starting to “develop” the theme itself, it is important to think about how we will structure the theme. Mainly, it is to define palettes, their colors, and layouts.

Palettes

We are going to create only 3 palettes but once you created one, creating other is relatively simple:

Aqua
Salmon
Sky

For each palette, we need to define a set of colors. These are lessCss variables. For aqua, let’s name it aqua-colours.less

@accent1: #added4;
@accent2: #fbcdbe;
@accent3: #cdbcd5;
@accent4: #c9e4b4;
@accent5: #c3ced9;
@accent6: #dbbde5;
@light: #fff;
@dark: #7c7f91;
@light2: #EEE;
@dark2: #c3ced9;

Layouts

To keep things simple, we are only going to have 3 layouts:

  • A default one
  • 1 for titles/section
  • 1 supporting a title and multiple columns

Developing the theme

File structure

File Description
*.woff2 Font files for Muli & Amatic
*.svg SVG files generated as shown previously
theme.json Theme properties. Format is detailed here.
theme.jpg Theme thumbnail preview
default-palet.less Less CSS code common to all palettes
aqua-colors.less Colors for the Aqua palette
aqua-palet.less Aqua Palette
salmon-colors.less Colors for the Salmon palette
salmon-palet.less Salmon palette
sky-colors.less Colors for the Sky palette
sky-pale.less Sky palette
quickly-theme.less Main Less CSS theme file

The quickly-theme.less file

Adding the Fonts

/********************************************/
/* Fonts   */

  /* latin-ext */
  @font-face {
    font-family: 'Muli';
    font-style: italic;
    font-weight: 400;
    src:  url(Muli-Italic-Ext.woff2) format('woff2');
    unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  /* latin */
  @font-face {
    font-family: 'Muli';
    font-style: italic;
    font-weight: 400;
    src:  url(Muli-Italic.woff2) format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }

...

Depending on the font and the range of languages you want to support, you may have to add additional font files (Muli-Italic-Ext.woff2 here).

General CSS

Here we mainly set fonts, and style the various types of content (blockquote, img,…)

/***************************************************************************/
/* Master: common to all layouts   */
.reveal {
  line-height: 1;
  /*Sometimes fonts don't display optimally on all devices*/
  /*-moz-osx-font-smoothing: grayscale;*/
  -webkit-font-smoothing: antialiased;
  font-family: "Muli";
  text-rendering: optimizeLegibility;
  font-weight: 400;
  font-size: 28px;
  line-height: 1.4;
}

/*********************************************
 * HEADERS */
.reveal h1,
.reveal h2,
.reveal h3,
.reveal h4,
.reveal h5,
.reveal h6 {
  font-family: "Amatic";
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: normal;
  text-shadow: none;
  word-wrap: break-word;
}

.reveal h1 {
  font-size: 2.4em;
}

.reveal h2 {
  font-size: 2em;
}

.reveal h3 {
  font-size: 1.6em;
}

.reveal h4 {
  font-size: 1.3em;
}

h1,
h2 {
  Text-align: center;
}

.reveal figure img {
  border-radius: 50%
}

.reveal pre {
  display: block;
  position: relative;
  width: 94%;
  margin: 20px auto;
  text-align: left;
  font-size: 0.55em;
  font-family: monospace;
  line-height: 1.2em;
  word-wrap: break-word;
  border-radius: 8px;
  background-image: url('data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 60 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><circle cx="14.028" cy="12.645" r="6.465" style="fill:#ff5f56;"/><circle cx="33.542" cy="12.645" r="6.465" style="fill:#ffbd2e;"/><circle cx="52.982" cy="12.645" r="6.465" style="fill:#27c93f;"/></svg>');
  padding: 33px 5px 5px 5px;
  background-size: 100px 30px;
  background-repeat: no-repeat;
}

.reveal code {
  font-family: monospace;
}

.reveal pre code {
  display: block;
  padding: 35px 5px 5px 5px;

}

table {
  margin-top: 0.5 em;
}

/* blockquote */
.reveal blockquote {
  display: block;
  padding: 15px 20px 15px 75px;
  margin: 0 0 20px;
  position: relative;
  font-style: italic;
  border-left-style: solid;
  border-left-width: 10px;
}

.reveal blockquote::before {
  content: "\201C";
  /*Unicode for Left Double Quote*/

  /*Font*/
  font-family: Georgia, serif;
  font-size: 60px;
  font-weight: bold;
  color: #999;

  /*Positioning*/
  position: absolute;
  left: 10px;
  top: 5px;

}

.reveal blockquote::after {
  /*Reset to make sure*/
  content: "";
}

.reveal blockquote cite {
  display: block;
  font-weight: 700;
  font-style: normal;
}

Layouts

We now add the code specific to the 3 layouts. As you can see, thanks to the Slideas theme’s framework, the code is relatively small.

@import "../Shared/theme-shared.less";

.reveal .backgrounds .title,
.reveal .backgrounds .default {
  background-repeat: no-repeat;
}

.reveal .slide-background {
  background-size: 100% 100%;
}

.reveal .innercontent {
  position: absolute;
  height: auto;
  bottom: 0;
  top: 0;
  left: 0;
  right: 0;
  margin-top: 50px;
  margin-bottom: 70px;
  margin-right: 50px;
  margin-left: 70px;
  display: flex;
  flex-direction: column;
}

.reveal .title .innercontent {
  margin-top: 100px;
  margin-bottom: 100px;
  margin-right: 25%;
  margin-left: 25%;
  display: flex;
  flex-direction: column;
  justify-items: center;
  justify-content: center;
}

.reveal .title .innercontent {
  padding: 50px;
}

.reveal .title .innercontent {
  margin: 50px;
  display: flex;
  flex-direction: column;
  justify-items: center;
  justify-content: center;
}

.reveal .title h1,
.reveal .title h2,
.reveal .title .innercontent h1,
.reveal .title .innercontent h2 {
  text-align: center;
}

.reveal p img {
  border-radius: 50%
}

.slidenumber {
  width: 100%;
  text-align: center;
  bottom: 35px;
  position: absolute;
}

default-palet.less

It contains the code which is common to all palettes. Note: using variables you can also define CSS based on the colors of the palettes.

Base styling

Here, we import the shared palet and define colors for background and foreground.

@import "../Shared/shared-default-palet.less";

.reveal .backgrounds {
    background-color: @light;
  }

  .reveal{
      color: @dark;
  }

Lists

This is only needed because we want to have some fancy bullets in the accent color.

  /* Lists */
  .reveal ul{
    list-style-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" width="10px" height="14px"><title>Bullet</title><rect fill="@{accent1}"  width="10" height="10" rx="5" ry="5"/></svg>');
  }

  .reveal ul ul {
    list-style-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" width="10px" height="14px"><title>Bullet</title><rect fill="@{accent1}"  width="10" height="10" rx="0" ry="0"/></svg>');
  }

  .reveal ul ul ul {
    list-style-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" width="11px" height="14px"><title>Bullet</title><rect stroke="@{accent1}" fill-opacity="0.0" width="10" height="10" rx="5" ry="5"/></svg>');
  }


  .reveal .darkbg ul{
    list-style-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" width="10px" height="14px"><title>Bullet</title><rect fill="@{light}"  width="10" height="10" rx="5" ry="5"/></svg>');
  }

  .reveal  .darkbg ul ul {
    list-style-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" width="10px" height="14px"><title>Bullet</title><rect fill="@{light}"  width="10" height="10" rx="0" ry="0"/></svg>');
  }

  .reveal .darkbg ul ul ul {
    list-style-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" width="11px" height="14px"><title>Bullet</title><rect stroke="@{light}" fill-opacity="0.0" width="10" height="10" rx="5" ry="5"/></svg>');
  }

  .reveal ol li::before {
      color: @accent1;
  }

  .reveal .darkbg ol li::before {
      color: @light;
  }

Other styling

  a{
    color:@dark;
  }

  .reveal blockquote:before{
      color: @accent1;
  }

   .reveal .svgicon{
      stroke: @accent1;
   }

   .reveal pre{
    border: 1px solid #CCC;
  }

  table td,
  th,
  thead {
    border: 1px solid @accent1 !important;
    color: @dark;
  }
  thead {
    background-color: rgba(0, 0, 0, 0.3);
  }
  tr:nth-child(even) > td {
    background: rgba(0, 0, 0, 0.1);
  }
  tr > td {
    border-top: 1px solid rgba(0, 0, 0, 0.5);
  }
  th {
    background-color: @accent1;
  }

aqua-palet.less

The code is similar for all palettes so we only detail the aqua one.

We define here the different SVG backgrounds because depending on the palette, the pattern is different. In the case where only the color is different, we can put the code in default-palet , using the technique used for bullets.

@import "aqua-colors.less";
@import "default-palet.less";

.reveal .backgrounds .title{
    background-color: @accent1;
    background-image: url(frame-crosses-white.svg) ;
  }

  .reveal .backgrounds .default{
    background-color: @light;
    background-image: url(frame-crosses-aqua.svg) ;
   }

  .reveal .default h1, .reveal .default h1{
    color: @accent1;
  }

The theme.json

This file provides general information on the theme and also specifies palettes and layouts. The format of this file is describe here.

{
    "Name": "Notes",
    "Version": 1.0,
    "Author": "Slideas",
    "Description": "Notes ",
    "Css": "notes-theme.css",
    "Palets": [
        {
            "Name": "Green",
            "Css": "green-palet.css",
            "Dark1": "#000000",
            "Light1": "#ffffff",
            "Dark2": "#666666",
            "Light2": "#dddddd",
            "Accent1": "#91BEAA",
            "Accent2": "#B7D968",
            "Accent3": "#B576AD",
            "Accent4": "#E04644",
            "Accent5": "#FDE47F",
            "Accent6": "#7CCCE5"
        },
        {
            "Name": "Violet",
            "Css": "violet-palet.css",
            "Dark1": "#000000",
            "Light1": "#ffffff",
            "Dark2": "#666666",
            "Light2": "#dddddd",
            "Accent1": "#B576AD",
            "Accent2": "#B7D968",
            "Accent3": "#91BEAA",
            "Accent4": "#E04644",
            "Accent5": "#FDE47F",
            "Accent6": "#7CCCE5"
        },
        {
            "Name": "Blue",
            "Css": "blue-palet.css",
            "Dark1": "#000000",
            "Light1": "#ffffff",
            "Dark2": "#666666",
            "Light2": "#dddddd",
            "Accent1": "#7CCCE5",
            "Accent2": "#B7D968",
            "Accent3": "#B576AD",
            "Accent4": "#E04644",
            "Accent5": "#FDE47F",
            "Accent6": "#91BEAA"
        },
        {
            "Name": "Yellow",
            "Css": "yellow-palet.css",
            "Dark1": "#000000",
            "Light1": "#ffffff",
            "Dark2": "#666666",
            "Light2": "#dddddd",
            "Accent1": "#FDE47F",
            "Accent2": "#B7D968",
            "Accent3": "#91BEAA",
            "Accent4": "#E04644",
            "Accent5": "#B576AD",
            "Accent6": "#7CCCE5"
        },
        {
            "Name": "Red",
            "Css": "red-palet.css",
            "Dark1": "#000000",
            "Light1": "#ffffff",
            "Dark2": "#666666",
            "Light2": "#dddddd",
            "Accent1": "#E04644",
            "Accent2": "#B7D968",
            "Accent3": "#91beaa",
            "Accent4": "#B576AD",
            "Accent5": "#FDE47F",
            "Accent6": "#7CCCE5"
        }
    ],
    "Layouts": [
        {
            "Name": "Default",
            "Group": "Basic",
            "Classes": "default",
            "Dynamic": false,
            "DynamicPart": "",
            "FixedParts": 1,
            "Example": "Layout: Default\n\n# Title\n\nWith some **content**\n\n",
            "Template": "<div class = \"innercontent\" ><div class=\"grid\"><div data-slide-fit=\"fit\" class=\"column\"> {@{content}@} </div></div><div class=\"footnotes\">{@{footnotes}@}</div></div><div class=\"oblique\"></div><div class=\"slidenumber\">{@{FOOTER}@}</div>",
            "ThumbnailTemplate": "<div class = \"innercontent\" data-slide-fit=\"fit\"><h1>Title</h1> And content...</div><div class=\"slidenumber\">1</div>   >",
        },
        {
            "Name": "Title",
            "Group": "Basic",
            "Classes": "title aligncenter",
            "Dynamic": false,
            "Example": "Layout: Title\n\n# Title\n## Level 2 title\n\n",
            "DynamicPart": "",
            "FixedParts": 1,
            "Template": " <div class = \"innercontent inverted\" data-slide-fit=\"fit\">{@{content}@}</div> ",
            "ThumbnailTemplate": " <div class = \"innercontent inverted\" data-slide-fit=\"fit\"><h1>Title</h1><h2>SubTitle</h2></div>     ",
        },
        {
            "Name": "HeaderAndColumns",
            "Group": "Basic",
            "Classes": "titleandcolumns default",
            "Dynamic": true,
            "FixedParts": 1,
            "Example": "Layout: HeaderAndColumns\n\n# Title\n+++\n### Column1\n+++\n### Column2\n+++\n### Column3\n\n",
            "DynamicPart": "<div class=\"column txtfit\"  data-slide-fit=\"fit\"> {@{content}@} </div>",
            "Template": "<div class = \"innercontent\"><div class=\"titleframe\"> {@{content}@} </div> <div class=\"grid\">{@{dynamic}@}</div><div class=\"footnotes\">{@{footnotes}@}</div></div><div class=\"slidenumber\">{@{FOOTER}@}</div>",
            "ThumbnailTemplate": "<div class = \"innercontent\"><div class=\"titleframe\"> <h1>Title</h1 </div> <div class=\"grid\"><div class=\"column txtfit\"  data-slide-fit=\"fit\"> <h2>Column 1</h2> </div><div class=\"column txtfit\"  data-slide-fit=\"fit\"> <h2>Column 2</h2> </div></div></div><div class=\"slidenumber\">1</div>",
        },
        {
            "Name": "SectionTitle",
            "Group": "Basic",
            "Classes": "sectiontitle",
            "Dynamic": false,
            "Example": "Layout: SectionTitle\n\n# Title\n## Level 2 title\n\n",
            "DynamicPart": "",
            "FixedParts": 1,
            "Template": " <div class = \"innercontent inverted\" data-slide-fit=\"fit\">{@{content}@}</div> ",
            "ThumbnailTemplate": " <div class = \"innercontent inverted\" data-slide-fit=\"fit\"><h1>Title</h1><h2>SubTitle</h2></div>     ",
        },
        {
            "Name": "Split",
            "Group": "Basic",
            "Classes": "split",
            "Dynamic": false,
            "DynamicPart": "",
            "FixedParts": 1,
            "Example": "Layout: Split\n\n![](https://slideas.app/img/photos-background.jpg)\n+++\n# Right part\nWith some **content**\n\n",
            "Template": "<div class = \"innercontent\"><div class=\"grid\"><div class=\"column txtfit\"  data-slide-fit=\"fit\"> {@{content}@} </div><div class=\"column txtfit inverted colored\"  data-slide-fit=\"fit\">{@{content}@} </div></div></div><div class=\"slidenumber\">{@{FOOTER}@}</div>",
            "ThumbnailTemplate": "<div class = \"innercontent\"><div class=\"grid\"><div class=\"column txtfit\"  data-slide-fit=\"fit\"> <h1>Column 1</h1>  </div><div class=\"column txtfit inverted colored\"  data-slide-fit=\"fit\"><h1>Column 2</h1>  </div></div></div><div class=\"slidenumber\">1</div>",
        }
    ]
}

Compiling the less stylesheets

lessc quickly-theme.less dist/quickly-theme.css
lessc salmon-palet.less dist/salmon-palet.css
lessc sky-palet.less dist/sky-palet.css
lessc aqua-palet.less dist/aqua-palet.css

Conclusion

This can appear to be a lengthy process but we have here a complex scenario and we started from scratch. It is recommended to start from an existing theme and bring the modifications you wish. You can download the “compiled” theme here or directly get the sources from GitHub.

Agaric

Agaric is fan of Design, Markdown Minimalism and Efficiency. He makes Slideas.
@AgaricPerdereau