diff --git a/resources/glyph2font.html b/resources/glyph2font.html
new file mode 100644
index 0000000000000000000000000000000000000000..b1d63d20934e7033c3e9f068d4c9e721ad5ae6dc
--- /dev/null
+++ b/resources/glyph2font.html
@@ -0,0 +1,225 @@
+<html>
+  <head>
+    <style>
+      #form {
+        user-select: none; 
+      }
+      table {
+        margin-top: 5px;
+      }
+      table, tr, td, th {
+        user-select: none; 
+        border-collapse: collapse;
+      }
+      td, th {
+        border: 1px solid #CCC;
+        width: 12px;
+        height: 15px;
+      }
+      td.on {
+        background-color: #CCC;
+      }
+      
+      #addChar, #generate {
+        display: none;
+      }
+      body.started #addChar, body.started #generate {
+        display: inline;
+      }
+      body.started #create {
+        display: none;
+      }
+      body.started input {
+        pointer-events:none;
+      }
+      #page {
+        position:relative;
+      }
+      #page>div {
+        position: relative;
+        float: left;
+      }
+
+      #output {
+        margin-left: 20px;
+        margin-top: 12px;
+        font-size:12px;
+        user-select: all;
+      }
+      
+    </style>
+  </head>
+  <body>
+    <div id="form">
+      <span>Font array name:</span> <input placeholder="Font array name" type="text" id="name" value="My_Font"/><br/>
+      <span>Height:</span> <input placeholder="Font height" type="number" id="height" value="13" max="64"/><br/>
+      <span>Width:</span> <input placeholder="Font width" type="number" id="width" value="10"/><br/>
+      <span>First char code:</span> <input placeholder="First char code" type="number" id="code" value="1" min="1"/><br/>
+      <br/>
+      <button id="create">Create</button>
+      <button id="addChar">Add a character</button>
+      <button id="generate">Generate</button>
+    </div>
+    <div id="page">
+    <div id="chars" ondragstart="return false;"></div>
+    <div id="output">
+      <div class="output" id="header"> </div>
+      <div class="output" id="jump"> </div>
+      <div class="output" id="data"> </div>
+    </div>
+    </div>
+    <script language="JavaScript1.5">
+    (function() {
+      // 100% vanilla ECS6, no framework or library was injured for this project.
+      // This page allows drawing icons in grids and generating the map with the format required by 
+      // library for OLED SD1306 screen: https://github.com/squix78/esp8266-oled-ssd1306
+      // TODO: 
+      // Implement data parsing to edit existing font
+      // Implement methods to move glyphs, symetrical input, ...
+      // Implement char insertion, non displayable char, ...
+
+      let font;
+      class Font {
+        constructor(height, width, code) {
+          this.height = height;
+          this.width = width;
+          this.firstCharCode = code;
+          this.currentCharCode = code;
+          this.chars = [];
+          this.jumpMap = [];
+        }
+      
+        addChar() {
+          let fontContainer = document.getElementById('chars');
+          let charContainer = fontContainer.appendChild(document.createElement("table"));
+          charContainer.setAttribute("code", this.currentCharCode);
+          let header = charContainer.appendChild(document.createElement("th"));
+          header.setAttribute("colspan", this.width-1);
+          header.textContent = `Character ${this.currentCharCode}`;
+          let removeCharHeader = charContainer.appendChild(document.createElement("th"));
+          removeCharHeader.setAttribute("action", "remove");
+          removeCharHeader.textContent = "x";
+          for(let r = 0; r < this.height; r++) {
+          	let rowContainer = charContainer.appendChild(document.createElement("tr"));
+            for(let c = 0; c < this.width; c++) {
+          		let pixContainer = rowContainer.appendChild(document.createElement("td"));
+            }
+          }
+          this.currentCharCode++;
+        }
+        
+        togglePixel(pixel) {
+          pixel.className = pixel.className == 'on' ? '': 'on';
+        }
+        
+        generate() {
+          document.getElementById('header').textContent = '';
+          document.getElementById('jump').textContent = '';
+          document.getElementById('data').textContent = '';
+          let name = document.getElementById('name').value.replace(/[-.*+= ]/g, '_');
+          let bits2add = (1 + ((this.height - 1) >> 3))*8 - this.height;  // number of missing bits to fill leftmost byte
+          let chars = document.getElementsByTagName('table');
+          let charCount = chars.length;
+          let charAddr = 0;
+          output('jump', '  // Jump table:');
+          output('data', '  // Data:');
+          for(let ch = 0; ch < charCount; ch++) {
+            let charBytes = [];
+            let charBits = [];
+            let charWidth = 1; // always add one row of off pixels to the right ?
+            let rows = chars[ch].getElementsByTagName('tr');
+            
+            for(let col = 0; col < this.width ; col++) {
+              var bits = ""; // using string because js uses 32b ints when performing bit operations
+              //for(let r = 0; r < rows.length; r++) {
+              for(let r = rows.length-1; r >=0 ; r--) {
+                let pixelState = rows[r].children[col].className;
+                bits += (pixelState == 'on' ? '1': '0');           
+              }
+              // Need to complete missing bits to have an odd number of bytes
+              for(let b = 0; b < bits2add; b++) {
+                bits +=  '0';
+              }
+              // output('data', `  // ${bits}`);  // Debugging help: rotated bitmap
+              for(let b = 0 ; b < bits.length/16; b+=16) {
+                let word = parseInt(bits.substring(b, b+15), 2);
+                charBytes.push(getLsB(word));
+                charBytes.push(getMsB(word));
+                
+              } 
+            }
+            // Remove bytes with value 0 at the end of the array.
+            while(charBytes[charBytes.length-1] == '0x0') {
+              charBytes.pop();
+            }
+            
+            output('data', `  ${charBytes.join(',')},`);
+            output('jump', `  ${getMsB(charAddr)}, ${getLsB(charAddr)}, ${toHexString(charBytes.length)}, ${toHexString(this.width)}, `);
+            charAddr += charBytes.length;                                           
+          }
+          output('data', '};');
+
+          
+          
+          output('header', `const char ${name}[] PROGMEM = {`);
+          output('header', `  ${toHexString(this.width)}, // Width: ${this.width}`);
+          output('header', `  ${toHexString(this.height)}, // Height: ${this.height}`);          
+          output('header', `  ${toHexString(this.firstCharCode)}, // First char: ${this.firstCharCode}`);
+          output('header', `  ${toHexString(charCount)}, // Number of chars: ${charCount}`);
+          
+          
+        }
+      }
+      
+      // Return anInt as hex string
+      toHexString = function(anInt) {
+        return `0x${anInt.toString(16)}`
+      }
+      
+      // Return least significant byte as hex string
+      getLsB = function(anInt) {
+        return toHexString(anInt & 0xFF);      
+      }
+      // Return most significant byte as hex string
+      getMsB = function(anInt) {
+        return toHexString(anInt>>>8);      
+      }
+     
+      output = function(targetId, msg) {
+        var output = document.getElementById(targetId);
+        var line = output.appendChild(document.createElement('div'));
+        line.textContent = msg;
+        
+      }
+      
+      document.getElementById('generate').addEventListener('click', function() {
+        font.generate();
+      });
+      
+      document.getElementById('addChar').addEventListener('click', function() {
+        font.addChar();
+      });
+      document.getElementById('create').addEventListener('click', function(button) {
+        document.body.className = "started";      
+      	font = new Font(parseInt(document.getElementById('height').value), parseInt(document.getElementById('width').value), parseInt(document.getElementById('code').value));
+        font.addChar();
+      });
+      document.getElementById('chars').addEventListener('mousedown', function(e) {
+        let target = e.target;
+        if (target.nodeName != 'TD') return;
+        font.togglePixel(target);
+      });
+      document.getElementById('chars').addEventListener('mouseover', function(e) {
+        let target = e.target;
+        if (target.nodeName != 'TD' || e.buttons != 1) return;
+        font.togglePixel(target);
+      });
+      document.getElementById('chars').addEventListener('dragstart', function(e) {
+        return;
+      });
+        
+      })();
+    </script>  
+  </body>
+</html>
+